Bearbeiten von „OS2.jugend

Zur Navigation springen Zur Suche springen
Warnung: Du bist nicht angemeldet. Deine IP-Adresse wird bei Bearbeitungen öffentlich sichtbar. Melde dich an oder erstelle ein Benutzerkonto, damit Bearbeitungen deinem Benutzernamen zugeordnet werden. Ein eigenes Benutzerkonto hat eine ganze Reihe von Vorteilen.

Die Bearbeitung kann rückgängig gemacht werden. Bitte prüfe den Vergleich unten, um sicherzustellen, dass du dies tun möchtest, und veröffentliche dann unten deine Änderungen, um die Bearbeitung rückgängig zu machen.

Aktuelle Version Dein Text
Zeile 1: Zeile 1:
[[Kategorie:Greasemonkey]]
[[Kategorie:Greasemonkey]]
[[Kategorie:Greasemonkey WE]]
{| style="background-color:white; font-size:11px; float: right; margin:3px 3px 3px 10px; border:1px solid #999; border-color: #9C1818; border-collapse:collapse;" width=500 cellpadding=3 cellspacing=0
{| style="background-color:white; font-size:11px; float: right; margin:3px 3px 3px 10px; border:1px solid #999; border-color: #9C1818; border-collapse:collapse;" width=500 cellpadding=3 cellspacing=0
| colspan="2" style="padding:0.3em; background-color:#9C1818; font-size: 18px; color:#FFFFFF" align=center| '''OS2.jugend'''
| colspan="2" style="padding:0.3em; background-color:#9C1818; font-size: 18px; color:#FFFFFF" align=center| '''OS2.jugend'''
Zeile 8: Zeile 7:
|- bgcolor="#FFCC00"
|- bgcolor="#FFCC00"
| '''Version'''
| '''Version'''
| '''0.73 (WebExtensions)'''
| '''0.48'''
|- bgcolor="#FFCC00"
|- bgcolor="#FFCC00"
| '''Autor'''
| '''Autor'''
Zeile 27: Zeile 26:
| '''ju.php?page=2'''
| '''ju.php?page=2'''
| '''Jugendteam-Spielereinzelwerte'''
| '''Jugendteam-Spielereinzelwerte'''
|-
| '''ju.php?page=3'''
| '''Jugendteam-Opt. Skill'''
|-
| '''ju.php?page=4'''
| '''Jugendteam-Optionen'''
|}
|}
|- bgcolor="#FFCC00"
|- bgcolor="#FFCC00"
| '''Funktionalität'''
| '''Funktionalität'''
| '''Trennstriche zwischen den Jahrgängen'''<br> '''Aktueller Skill, Opti und MW'''<br> '''Prognose von Opti und MW für Ende Jahrgang 18'''<br> '''Optionen und Menu'''<br> '''Neue Marktwertformel'''<br> '''Automatische Ermittlung des ZATs'''<br> '''Hidden-Optionen und Datenspeicher'''<br> '''Geburtstage und dezimales Alter'''<br> '''Erweiterte Optionen auch auf der Seite'''<br> '''Zusatzspalten Talent/Quote/Aufw./Geb./Alter'''<br> '''Zusatzspalten Quote/Alter/Pos in der Übersicht'''<br> '''Zusatzspalten Alter ersetzen/Aufwertungen kurz+TOR'''<br> '''Zusatzspalten fix/tr./%H/%N/Prios jetzt und Ende'''<br> '''Interaktive Menü-Optionen'''<br> '''Gemeinsame Code- und Datenbasis'''<br> '''Qualitätsbalken'''<br> '''Markierung der Primärskills bei Einzelwerten und Aufwertungen'''<br> '''Beachtung von Jugendförderung und Doppelpositionen'''<br> '''Warnung vor Ende 18 in letzter Periode und mehr am letzten ZAT'''<br> '''Reguläre Ausdrücke im @include'''<br> '''Neues Design und Seite "Opt. Skill"'''<br> '''Gruppierung nach Jahrgängen U13 bis U18 (per Option)'''<br> '''Warnung vor Ende 18 auch im Managerbüro (per Option)'''<br> '''Warnung vor Sperre des Ziehens im Falle eines Aufstiegs'''<br> '''Neuer Jahrgang U19'''
| '''Trennstriche zwischen den Jahrgängen'''<br> '''Aktueller Skill, Opti und MW'''<br> '''Prognose von Opti und MW für Ende Jahrgang 18'''<br> '''Optionen und Menu'''<br> '''Neue Marktwertformel'''<br> '''Automatische Ermittlung des ZATs'''<br> '''Hidden-Optionen und Datenspeicher'''<br> '''Geburtstage und dezimales Alter'''<br> '''Erweiterte Optionen auch auf der Seite'''<br> '''Zusatzspalten Talent/Quote/Aufw./Geb./Alter'''<br> '''Zusatzspalten Quote/Alter/Pos in der Übersicht'''
 
|- bgcolor="#FFCC00"
|- bgcolor="#FFCC00"
| '''Letzte Änderung'''
| '''Letzte Änderung'''
| '''{{REVISIONDAY}}.{{REVISIONMONTH}}.{{REVISIONYEAR}}'''
| '''{{REVISIONDAY}}.{{REVISIONMONTH}}.{{REVISIONYEAR}}'''
|}
|}
== [https://github.com/Eselce/OS2.scripts/blob/master/OS2.jugend.user.js Quellcode] [https://eselce.github.io/OS2.scripts/OS2.jugend.user.js INSTALLATION] ==
<pre>
<pre>
// ==UserScript==
// ==UserScript==
// @name        OS2.jugend
// @name        OS2.jugend
// @namespace    http://os.ongapo.com/
// @namespace    http://os.ongapo.com/
// @version      0.73
// @version      0.48
// @copyright    2013+
// @copyright    2013+
// @author      Sven Loges (SLC) / Andreas Eckes (Strindheim BK)
// @author      Andreas Eckes (Strindheim BK)
// @author      Sven Loges (SLC)
// @description  Jugendteam-Script fuer Online Soccer 2.0
// @description  Jugendteam-Script fuer Online Soccer 2.0
// @include      /^https?://(www\.)?(os\.ongapo\.com|online-soccer\.eu|os-zeitungen\.com)/haupt\.php(\?changetosecond=\w+(&\S+)*)?$/
// @include      http*://os.ongapo.com/haupt.php
// @include      /^https?://(www\.)?(os\.ongapo\.com|online-soccer\.eu|os-zeitungen\.com)/ju\.php(\?page=\d+(&\S+)*)?$/
// @include      http*://os.ongapo.com/haupt.php?changetosecond=*
// @grant        GM.getValue
// @include      http*://os.ongapo.com/ju.php
// @grant        GM.setValue
// @include      http*://os.ongapo.com/ju.php?page=*
// @grant        GM.deleteValue
// @include      http*://www.os.ongapo.com/haupt.php
// @grant        GM.registerMenuCommand
// @include      http*://www.os.ongapo.com/haupt.php?changetosecond=*
// @grant        GM.info
// @include      http*://www.os.ongapo.com/ju.php
// @require     https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @include      http*://www.os.ongapo.com/ju.php?page=*
// @include      http*://online-soccer.eu/haupt.php
// @include      http*://online-soccer.eu/haupt.php?changetosecond=*
// @include      http*://online-soccer.eu/ju.php
// @include      http*://online-soccer.eu/ju.php?page=*
// @include      http*://www.online-soccer.eu/haupt.php
// @include      http*://www.online-soccer.eu/haupt.php?changetosecond=*
// @include     http*://www.online-soccer.eu/ju.php
// @include      http*://www.online-soccer.eu/ju.php?page=*
// @grant        GM_getValue
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_setValue
Zeile 67: Zeile 66:
// ==/UserScript==
// ==/UserScript==


// ECMAScript 6:
// ECMAScript 6: Erlaubt 'const', 'let', ...
/* jshint esnext: true */
/* jshint esnext: true */
/* jshint moz: true */
/* jshint moz: true */


// ==================== Konfigurations-Abschnitt fuer Optionen ====================
// ==================== Konfigurations-Abschnitt fuer Optionen ====================
const __LOGLEVEL = 3;


// Options-Typen
// Options-Typen
Zeile 93: Zeile 90:
const __OPTMEM = {
const __OPTMEM = {
     'normal' : {
     'normal' : {
                  'Name'      : "Session",
                  'Value'    : sessionStorage,
                  'Display'  : "sessionStorage",
                  'Prefix'    : 'run'
              },
    'unbegrenzt' : {
                   'Name'      : "Browser",
                   'Name'      : "Browser",
                   'Value'    : localStorage,
                   'Value'    : localStorage,
                   'Display'  : "localStorage",
                   'Display'  : "localStorage",
                  'Prefix'    : 'run'
              },
    'begrenzt' : {
                  'Name'      : "Session",
                  'Value'    : sessionStorage,
                  'Display'  : "sessionStorage",
                   'Prefix'    : 'run'
                   'Prefix'    : 'run'
               },
               },
Zeile 114: Zeile 111:
// Moegliche Optionen (hier die Standardwerte editieren oder ueber das Benutzermenu setzen):
// Moegliche Optionen (hier die Standardwerte editieren oder ueber das Benutzermenu setzen):
const __OPTCONFIG = {
const __OPTCONFIG = {
     'ersetzeSkills' : {   // Auswahl fuer prognostizierte Einzelskills mit Ende 18 statt der aktuellen (true = Ende 18, false = aktuell)
     'zeigeTal' : {       // Spaltenauswahl fuer Talente (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "substSkills",
                   'Name'      : "showTclasses",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : false,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Einzelwerte Ende",
                   'Label'    : "Talent ein",
                   'Hotkey'    : 'E',
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Einzelwerte aktuell",
                   'AltLabel'  : "Talent aus",
                   'AltHotkey' : 'k',
                   'AltHotkey' : 'T',
                   'FormLabel' : "Prognose Einzelwerte"
                   'FormLabel' : "Talent"
               },
               },
     'zeigeJahrgang' : {   // Auswahl, ob ueber jedem Jahrgang die Ueberschriften gezeigt werden sollen oder alles in einem Block (true = Jahrgaenge, false = ein Block)
     'zeigeQuote' : {     // Spaltenauswahl fuer Aufwertungsschnitt (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showGroupTitle",
                   'Name'      : "showRatio",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Jahrgangs\xFCberschriften",
                   'Label'    : "Quote ein",
                   'Hotkey'    : 'J',
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Nur Trennlinie benutzen",
                   'AltLabel'  : "Quote aus",
                   'AltHotkey' : 'j',
                   'AltHotkey' : 'T',
                   'FormLabel' : "Jahrg\xE4nge gruppieren"
                   'FormLabel' : "Quote"
               },
               },
     'zeigeUxx' : {       // Auswahl, ob in der Ueberschrift ueber jedem Jahrgang zusaetzlich zur Saison noch der Jahrgang in der Form 'Uxx' angegeben wird
     'zeigeAufw' : {       // Spaltenauswahl fuer Aufwertungen (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showUxx",
                   'Name'      : "showProgresses",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : false,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Jahrg\xE4nge anzeigen",
                   'Label'    : "Aufw. ein",
                   'Hotkey'    : 'U',
                   'Hotkey'    : 'W',
                   'AltLabel'  : "Nur Saisons anzeigen",
                   'AltLabel'  : "Aufw. aus",
                   'AltHotkey' : 'u',
                   'AltHotkey' : 'W',
                   'FormLabel' : "Jahrg\xE4nge U13 bis U19"
                   'FormLabel' : "Aufwertung"
               },
               },
     'zeigeWarnung' : {   // Auswahl, ob eine Warnung erscheint, wenn Talente gezogen werden sollten
     'zeigeGeb' : {       // Spaltenauswahl fuer Geburtstage (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showWarning",
                   'Name'      : "showBirthday",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : false,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Ziehwarnung ein",
                   'Label'    : "Geb. ein",
                   'Hotkey'    : 'Z',
                   'Hotkey'    : 'G',
                   'AltLabel'  : "Ziehwarnung aus",
                   'AltLabel'  : "Geb. aus",
                   'AltHotkey' : 'Z',
                   'AltHotkey' : 'G',
                   'FormLabel' : "Ziehwarnung"
                   'FormLabel' : "Geburtstag"
               },
               },
     'zeigeWarnungMonat' : { // Auswahl, ob eine Warnung erscheint, wenn zum naechsten Abrechnungs-ZAT Talente gezogen werden sollten
     'zeigeAlter' : {       // Spaltenauswahl fuer dezimales Alter (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showWarningMonth",
                   'Name'      : "showAge",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Ziehwarnung Monat ein",
                   'Label'    : "Alter ein",
                   'Hotkey'    : 'Z',
                   'Hotkey'    : 'A',
                   'AltLabel'  : "Ziehwarnung Monat aus",
                   'AltLabel'  : "Alter aus",
                   'AltHotkey' : 'Z',
                   'AltHotkey' : 'A',
                   'FormLabel' : "Ziehwarnung Monat"
                   'FormLabel' : "Alter"
               },
               },
     'zeigeWarnungHome' : { // Auswahl, ob eine Meldung im Managerbuero erscheint, wenn Talente gezogen werden sollten
     'zeigeSkill' : {     // Spaltenauswahl fuer die aktuellen Werte (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showWarningHome",
                   'Name'      : "showSkill",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Ziehwarnung B\xFCro ein",
                   'Label'    : "Skill ein",
                   'Hotkey'    : 'z',
                   'Hotkey'    : 'S',
                   'AltLabel'  : "Ziehwarnung B\xFCro aus",
                   'AltLabel'  : "Skill aus",
                   'AltHotkey' : 'z',
                   'AltHotkey' : 'S',
                   'FormLabel' : "Ziehwarnung B\xFCro"
                   'FormLabel' : "Skill"
               },
               },
     'zeigeWarnungDialog' : { // Auswahl, ob die Meldung im Managerbuero als Dialog erscheinen soll
     'zeigePosition' : {   // Position anzeigen
                   'Name'      : "showWarningDialog",
                   'Name'      : "showPos",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : false,
                   'Default'  : false,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Ziehwarnung B\xFCro als Dialog",
                   'Label'    : "Position ein",
                   'Hotkey'    : 'z',
                   'Hotkey'    : 'P',
                   'AltLabel'  : "Ziehwarnung B\xFCro als Textmeldung",
                   'AltLabel'  : "Position aus",
                   'AltHotkey' : 'z',
                   'AltHotkey' : 'P',
                   'FormLabel' : "Ziehwarnung B\xFCro Dialog"
                   'FormLabel' : "Position"
               },
               },
     'zeigeWarnungAufstieg' : { // Auswahl, ob eine Warnung in der Uebersicht erscheint, wenn Talente nach Aufstieg nicht mehr gezogen werden koennen
     'anzahlOpti' : {     // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
                   'Name'      : "showWarningAufstieg",
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Type'      : __OPTTYPES.SW,
                   'Name'      : "anzOpti",
                   'Default'  : true,
                   'Type'      : __OPTTYPES.MC,
                  'ValType'  : "Number",
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'  : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Ziehwarnung Aufstieg ein",
                   'Label'    : "Opti: beste $",
                   'Hotkey'    : 'ä',
                   'Hotkey'    : 'O',
                   'AltLabel' : "Ziehwarnung Aufstieg aus",
                   'FormLabel' : "Opti:|beste $"
                  'AltHotkey' : 'ä',
                  'FormLabel' : "Ziehwarnung Aufstieg"
               },
               },
     'zeigeWarnungLegende' : { // Auswahl, ob eine extra Meldung in Teamuebersicht erscheint, die dort als Legende dient
     'anzahlMW' : {       // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
                   'Name'      : "showWarningLegende",
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Type'      : __OPTTYPES.SW,
                   'Name'      : "anzMW",
                   'Default'  : true,
                   'Type'      : __OPTTYPES.MC,
                  'ValType'  : "Number",
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'  : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Ziehwarnung Legende ein",
                   'Label'    : "MW: beste $",
                   'Hotkey'    : 'L',
                   'Hotkey'    : 'M',
                   'AltLabel' : "Ziehwarnung Legende aus",
                   'FormLabel' : "MW:|beste $"
                  'AltHotkey' : 'L',
                  'FormLabel' : "Ziehwarnung Legende"
               },
               },
     'zeigeBalken' : {     // Spaltenauswahl fuer den Qualitaetsbalken des Talents (true = anzeigen, false = nicht anzeigen)
     'zeigeSkillEnde' : { // Spaltenauswahl fuer die Werte mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showRatioBar",
                   'Name'      : "showSkillEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Balken Qualit\xE4t ein",
                   'Label'    : "Skill Ende ein",
                   'Hotkey'    : 'B',
                   'Hotkey'    : 'i',
                   'AltLabel'  : "Balken Qualit\xE4t aus",
                   'AltLabel'  : "Skill Ende aus",
                   'AltHotkey' : 'B',
                   'AltHotkey' : 'i',
                   'FormLabel' : "Balken Qualit\xE4t"
                   'FormLabel' : "Skill \u03A9"
               },
               },
     'absBalken' : {       // Spaltenauswahl fuer den Guetebalken des Talents absolut statt nach Foerderung (true = absolut, false = relativ nach Foerderung)
     'anzahlOptiEnde' : { // Spaltenauswahl fuer die Werte mit Ende 18:
                   'Name'      : "absBar",
                          // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
                   'Type'      : __OPTTYPES.SW,
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Default'  : true,
                   'Name'      : "anzOptiEnde",
                   'Action'    : __OPTACTION.NXT,
                   'Type'      : __OPTTYPES.MC,
                   'Label'     : "Balken absolut",
                   'ValType'  : "Number",
                   'Hotkey'    : 'u',
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'AltLabel' : "Balken nach F\xF6rderung",
                   'Default'   : 1,
                   'AltHotkey' : 'u',
                   'Action'    : __OPTACTION.NXT,
                   'FormLabel' : "Balken 100%"
                   'Label'     : "Opti Ende: beste $",
                   'Hotkey'   : 't',
                   'FormLabel' : "Opti \u03A9:|beste $"
               },
               },
     'zeigeId' : {         // Spaltenauswahl fuer Identifizierungsmerkmale der Jugendspieler (true = anzeigen, false = nicht anzeigen)
     'anzahlMWEnde' : {   // Spaltenauswahl fuer die Werte mit Ende 18:
                   'Name'      : "showFingerprints",
                          // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
                   'Type'      : __OPTTYPES.SW,
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Hidden'    : true,
                   'Name'      : "anzMWEnde",
                   'Default'  : false,
                   'Type'      : __OPTTYPES.MC,
                   'ValType'  : "Number",
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'  : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Identifikation ein",
                   'Label'    : "MW Ende: beste $",
                   'Hotkey'    : 'T',
                   'Hotkey'    : 'W',
                   'AltLabel' : "Identifikation aus",
                   'FormLabel' : "MW \u03A9:|beste $"
                  'AltHotkey' : 'T',
                  'FormLabel' : "Identifikation"
               },
               },
     'zeigeTal' : {       // Spaltenauswahl fuer Talente (true = anzeigen, false = nicht anzeigen)
     'kennzeichenEnde' : { // Markierung fuer Ende 18
                   'Name'      : "showTclasses",
                   'Name'      : "charEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.MC,
                   'Default'  : true,
                   'ValType'  : "String",
                  'Choice'    : [ " \u03A9", " 18" ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Talent ein",
                   'Label'    : "Ende: $",
                   'Hotkey'    : 'T',
                   'Hotkey'    : 'E',
                   'AltLabel' : "Talent aus",
                   'FormLabel' : "Ende 18:|$"
                  'AltHotkey' : 'T',
                  'FormLabel' : "Talent"
               },
               },
     'zeigeQuote' : {     // Spaltenauswahl fuer Aufwertungsschnitt (true = anzeigen, false = nicht anzeigen)
     'sepStyle' : {       // Stil der Trennlinie
                   'Name'      : "showRatio",
                   'Name'      : "sepStyle",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.MC,
                   'Default'  : true,
                   'ValType'  : "String",
                  'Choice'    : [ "solid", "hidden", "dotted", "dashed", "double", "groove", "ridge",
                                  "inset", "outset", "none" ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Quote ein",
                   'Label'    : "Stil: $",
                   'Hotkey'    : 'T',
                   'Hotkey'    : 'l',
                   'AltLabel' : "Quote aus",
                   'FormLabel' : "Stil:|$"
                  'AltHotkey' : 'T',
                  'FormLabel' : "Quote"
               },
               },
     'zeigeGeb' : {        // Spaltenauswahl fuer Geburtstage (true = anzeigen, false = nicht anzeigen)
     'sepColor' : {        // Farbe der Trennlinie
                   'Name'      : "showBirthday",
                   'Name'      : "sepColor",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.MC,
                   'Default'  : false,
                   'ValType'  : "String",
                  'Choice'    : [ "white", "yellow", "black", "blue", "cyan", "gold", "grey", "green",
                                  "lime", "magenta", "maroon", "navy", "olive", "orange", "purple",
                                  "red", "teal", "transparent" ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Geburtstag ein",
                   'Label'    : "Farbe: $",
                   'Hotkey'    : 'G',
                   'Hotkey'    : 'F',
                   'AltLabel' : "Geburtstag aus",
                   'FormLabel' : "Farbe:|$"
                  'AltHotkey' : 'G',
                  'FormLabel' : "Geburtstag"
               },
               },
     'zeigeAlter' : {     // Spaltenauswahl fuer dezimales Alter (true = anzeigen, false = nicht anzeigen)
     'sepWidth' : {       // Dicke der Trennlinie
                   'Name'      : "showAge",
                   'Name'      : "sepWidth",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.MC,
                   'Default'  : true,
                   'ValType'  : "String",
                  'Choice'    : [ "thin", "medium", "thick" ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Alter ein",
                   'Label'    : "Dicke: $",
                   'Hotkey'    : 'A',
                   'Hotkey'    : 'D',
                   'AltLabel' : "Alter aus",
                   'FormLabel' : "Dicke:|$"
                  'AltHotkey' : 'A',
                  'FormLabel' : "Alter"
               },
               },
     'ersetzeAlter' : {   // Spaltenauswahl fuer dezimales Alter statt ganzzahligen Alters (true = Dezimalbruch, false = ganzzahlig)
     'saison' : {         // Laufende Saison
                   'Name'      : "substAge",
                   'Name'      : "saison",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.MC,
                   'Default'  : true,
                  'ValType'  : "Number",
                  'Choice'    : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ],
                   'Default'  : 10,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Alter dezimal",
                   'Label'    : "Saison: $",
                   'Hotkey'    : 'd',
                   'Hotkey'    : 'a',
                   'AltLabel' : "Alter ganzzahlig",
                   'FormLabel' : "Saison:|$"
                  'AltHotkey' : 'g',
                  'FormLabel' : "Alter ersetzen"
               },
               },
     'zeigeAufw' : {       // Spaltenauswahl fuer Aufwertungen (true = anzeigen, false = nicht anzeigen)
     'aktuellerZat' : {   // Laufender ZAT
                   'Name'      : "showProgresses",
                   'Name'      : "currZAT",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.MC,
                   'Default'  : true,
                   'ValType: "Number",
                  'Permanent' : true,
                  'Choice'    : [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
                                  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
                                  24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
                                  36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
                                  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
                                  60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71 ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Aufwertungen ein",
                   'Label'    : "ZAT: $",
                  'Hotkey'    : 'W',
                  'AltLabel'  : "Aufwertungen aus",
                  'AltHotkey' : 'W',
                  'FormLabel' : "Aufwertungen"
              },
    'shortAufw' : {      // Abgekuerzte Aufwertungsanzeige
                  'Name'      : "shortProgresses",
                  'Type'      : __OPTTYPES.SW,
                  'Default'  : true,
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Aufwertungen kurz",
                  'Hotkey'    : 'A',
                  'AltLabel'  : "Aufwertungen lang",
                  'AltHotkey' : 'A',
                  'FormLabel' : "Kurze Aufwertungen"
              },
    'zeigeZatDone' : {    // Spaltenauswahl fuer die Anzahl der bisherigen Trainings-ZATs (true = anzeigen, false = nicht anzeigen)
                  'Name'      : "showFixZatDone",
                  'Type'      : __OPTTYPES.SW,
                  'Default'  : false,
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Trainings-ZATs ein",
                   'Hotkey'    : 'Z',
                   'Hotkey'    : 'Z',
                   'AltLabel' : "Trainings-ZATs aus",
                   'FormLabel' : "ZAT:|$"
                  'AltHotkey' : 'Z',
                  'FormLabel' : "Trainings-ZATs"
               },
               },
     'zeigeZatLeft' : {   // Spaltenauswahl fuer die Anzahl der Rest-ZATs bis Ende 18 (true = anzeigen, false = nicht anzeigen)
     'datenZat' : {       // Stand der Daten zum Team und ZAT
                   'Name'      : "showFixZatLeft",
                   'Name'      : "dataZAT",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SD,
                   'Default'  : false,
                  'ValType'  : "Number",
                   'Action'    : __OPTACTION.NXT,
                  'Hidden'    : true,
                   'Label'     : "Rest-ZATs ein",
                  'Serial'    : true,
                   'Hotkey'   : 'R',
                  'AutoReset' : true,
                   'AltLabel' : "Rest-ZATs aus",
                  'Permanent' : true,
                   'AltHotkey' : 'R',
                   'Default'  : undefined,
                   'FormLabel' : "Rest-ZATs"
                   'Action'    : __OPTACTION.SET,
                   'Submit'   : undefined,
                   'Cols'     : 1,
                  'Rows'     : 1,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Daten-ZAT:"
               },
               },
     'zeigeFixSkills' : { // Spaltenauswahl fuer die Summe der Fixskills (true = anzeigen, false = nicht anzeigen)
     'birthdays' : {       // Datenspeicher fuer Geburtstage der Jugendspieler
                   'Name'      : "showFixSkills",
                   'Name'      : "birthdays",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SD,
                   'Default'   : true,
                  'Hidden'    : false,
                   'Action'   : __OPTACTION.NXT,
                  'Serial'    : true,
                   'Label'     : "Fixskills ein",
                   'AutoReset' : true,
                   'Hotkey'    : 'F',
                   'Permanent' : true,
                   'AltLabel' : "Fixskills aus",
                   'Default'   : [],
                   'AltHotkey' : 'F',
                   'Submit'    : undefined,
                   'FormLabel' : "Fixskills"
                  'Cols'     : 36,
                   'Rows'     : 2,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Geburtstage:"
               },
               },
     'zeigeTrainiert' : { // Spaltenauswahl fuer die aktuellen trainierten Skills (true = anzeigen, false = nicht anzeigen)
     'tClasses' : {       // Datenspeicher fuer Talente der Jugendspieler (-1=wenig, 0=normal, +1=hoch)
                   'Name'      : "showTrainiert",
                   'Name'      : "tClasses",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SD,
                   'Default'   : true,
                  'Hidden'    : false,
                   'Action'   : __OPTACTION.NXT,
                  'Serial'    : true,
                   'Label'     : "Trainiert ein",
                   'AutoReset' : true,
                   'Hotkey'    : 'T',
                   'Permanent' : true,
                   'AltLabel' : "Trainiert aus",
                   'Default'   : [],
                   'AltHotkey' : 'T',
                   'Submit'    : undefined,
                   'FormLabel' : "Trainiert"
                  'Cols'     : 36,
                   'Rows'     : 2,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Talente:"
               },
               },
     'zeigeAnteilPri' : { // Spaltenauswahl fuer den prozentualen Anteil der aktuellen Hauptskills (true = anzeigen, false = nicht anzeigen)
     'progresses' : {     // Datenspeicher fuer Aufwertungen der Jugendspieler (als Strings)
                   'Name'      : "showAnteilPri",
                   'Name'      : "progresses",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SD,
                   'Default'   : true,
                  'Hidden'    : false,
                   'Action'   : __OPTACTION.NXT,
                  'Serial'    : true,
                   'Label'     : "Anteil Hauptskills ein",
                   'AutoReset' : true,
                   'Hotkey'    : 'H',
                   'Permanent' : true,
                   'AltLabel' : "Anteil Hauptskills aus",
                   'Default'   : [],
                   'AltHotkey' : 'H',
                   'Submit'    : undefined,
                   'FormLabel' : "Anteil Hauptskills"
                  'Cols'     : 36,
                   'Rows'     : 7,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Aufwertungen:"
               },
               },
     'zeigeAnteilSec' : { // Spaltenauswahl fuer den prozentualen Anteil der aktuellen Nebenskills (true = anzeigen, false = nicht anzeigen)
     'zatAges' : {         // Datenspeicher fuer (gebrochene) Alter der Jugendspieler
                   'Name'      : "showAnteilSec",
                   'Name'      : "zatAges",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SD,
                   'Default'   : false,
                   'Hidden'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Serial'    : true,
                   'Label'     : "Anteil Nebenskills ein",
                  'AutoReset' : true,
                   'Hotkey'    : 'N',
                  'Permanent' : true,
                   'AltLabel' : "Anteil Nebenskills aus",
                   'Default'   : [],
                   'AltHotkey' : 'N',
                   'Submit'    : undefined,
                   'FormLabel' : "Anteil Nebenskills"
                  'Cols'     : 36,
                   'Rows'     : 2,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "ZAT-Alter:"
               },
               },
     'zeigePrios' : {     // Spaltenauswahl fuer den Schnitt der Hauptskills (true = anzeigen, false = nicht anzeigen)
     'trainiert' : {       // Datenspeicher fuer Trainingsquoten der Jugendspieler
                   'Name'      : "showPrios",
                   'Name'      : "numProgresses",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SD,
                   'Default'   : true,
                  'Hidden'    : false,
                   'Action'   : __OPTACTION.NXT,
                  'Serial'    : true,
                   'Label'     : "Prios ein",
                   'AutoReset' : true,
                   'Hotkey'    : 'r',
                   'Permanent' : true,
                   'AltLabel' : "Prios aus",
                   'Default'   : [],
                   'AltHotkey' : 'r',
                   'Submit'    : undefined,
                   'FormLabel' : "Prios"
                  'Cols'     : 36,
                   'Rows'     : 2,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Trainiert:"
               },
               },
     'zeigeSkill' : {     // Spaltenauswahl fuer die aktuellen Werte (true = anzeigen, false = nicht anzeigen)
     'positions' : {       // Datenspeicher fuer optimale Positionen der Jugendspieler
                   'Name'      : "showSkill",
                   'Name'      : "positions",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SD,
                   'Default'   : true,
                  'Hidden'    : false,
                   'Action'   : __OPTACTION.NXT,
                  'Serial'    : true,
                   'Label'     : "Skill ein",
                   'AutoReset' : true,
                   'Hotkey'    : 'S',
                   'Permanent' : true,
                   'AltLabel' : "Skill aus",
                   'Default'   : [],
                   'AltHotkey' : 'S',
                   'Submit'    : undefined,
                   'FormLabel' : "Skill"
                  'Cols'     : 36,
                   'Rows'     : 3,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Positionen:"
               },
               },
     'zeigePosition' : {   // Position anzeigen
     'team' : {           // Datenspeicher fuer Daten des Erst- bzw. Zweitteams
                   'Name'      : "showPos",
                   'Name'      : "team",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SD,
                   'Default'   : false,
                   'Hidden'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Serial'    : true,
                   'Label'     : "Position ein",
                  'Permanent' : true,
                   'Hotkey'    : 'P',
                   'Default'  : { 'Team' : undefined, 'Liga' : undefined, 'Land' : undefined, 'LdNr' : 0, 'LgNr' : 0 },
                   'AltLabel' : "Position aus",
                   'Submit'    : undefined,
                   'AltHotkey' : 'P',
                  'Cols'     : 36,
                   'FormLabel' : "Position"
                   'Rows'     : 6,
                   'Replace'   : null,
                  'Space'     : 1,
                   'Label'     : "Verein:"
               },
               },
     'anzahlOpti' : {     // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
     'reset' : {           // Optionen auf die "Werkseinstellungen" zuruecksetzen
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                  'Name'      : "reset",
                   'Name'      : "anzOpti",
                  'Type'      : __OPTTYPES.SI,
                  'Action'    : __OPTACTION.RST,
                  'Label'    : "Standard-Optionen",
                  'Hotkey'    : 'r',
                  'FormLabel' : ""
              },
    'storage' : {        // Browserspeicher fuer die Klicks auf Optionen
                   'Name'      : "storage",
                   'Type'      : __OPTTYPES.MC,
                   'Type'      : __OPTTYPES.MC,
                   'ValType'  : 'Number',
                   'ValType'  : "String",
                  'SelValue'  : false,
                   'Choice'    : Object.keys(__OPTMEM),
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                  'Default'  : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Opti: beste $",
                   'Label'    : "Speicher: $",
                   'Hotkey'    : 'O',
                   'Hotkey'    : 'c',
                   'FormLabel' : "Opti:|beste $"
                   'FormLabel' : "Speicher:|$"
               },
               },
     'anzahlMW' : {       // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
     'oldStorage' : {     // Vorheriger Browserspeicher fuer die Klicks auf Optionen
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "oldStorage",
                   'Name'      : "anzMW",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.MC,
                   'AutoReset' : true,
                  'ValType'  : 'Number',
                   'Hidden'    : true
                  'SelValue'  : false,
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                  'Default'  : 1,
                  'Action'    : __OPTACTION.NXT,
                   'Label'     : "MW: beste $",
                   'Hotkey'    : 'M',
                  'FormLabel' : "MW:|beste $"
               },
               },
     'zeigeTrainiertEnde' : { // Spaltenauswahl fuer die trainierten Skills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
     'showForm' : {       // Optionen auf der Webseite (true = anzeigen, false = nicht anzeigen)
                  'Name'      : "showTrainiertEnde",
                   'Name'      : "showForm",
                  'Type'      : __OPTTYPES.SW,
                  'Default'  : true,
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Trainiert Ende ein",
                  'Hotkey'    : 'n',
                  'AltLabel'  : "Trainiert Ende aus",
                  'AltHotkey' : 'n',
                  'FormLabel' : "Trainiert \u03A9"
              },
    'zeigeAnteilPriEnde' : {  // Spaltenauswahl fuer den prozentualen Anteil der Hauptskills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showAnteilPriEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                  'FormType'  : __OPTTYPES.SI,
                  'Permanent' : true,
                   'Default'  : false,
                   'Default'  : false,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Anteil Hauptskills Ende ein",
                   'Label'    : "Optionen anzeigen",
                   'Hotkey'    : 'u',
                   'Hotkey'    : 'O',
                   'AltLabel'  : "Anteil Hauptskills Ende aus",
                   'AltLabel'  : "Optionen verbergen",
                   'AltHotkey' : 'u',
                   'AltHotkey' : 'O',
                   'FormLabel' : "Anteil Hauptskills \u03A9"
                   'FormLabel' : ""
               },
               }
    'zeigeAnteilSecEnde' : {  // Spaltenauswahl fuer den prozentualen Anteil der Nebenskills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
};
                  'Name'      : "showAnteilSecEnde",
 
                  'Type'      : __OPTTYPES.SW,
// ==================== Invarianter Abschnitt fuer Optionen ====================
                  'Default'  : false,
 
                  'Action'    : __OPTACTION.NXT,
// ==================== Abschnitt fuer diverse Utilities ====================
                  'Label'    : "Anteil Nebenskills Ende ein",
 
                  'Hotkey'    : 'b',
// Gibt einen Wert zurueck. Ist dieser nicht definiert oder null, wird ein Alternativwert geliefert
                  'AltLabel'  : "Anteil Nebenskills Ende aus",
// value: Ein Wert. Ist dieser nicht undefined oder null, wird er zurueckgeliefert (oder retValue)
                  'AltHotkey' : 'b',
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
                  'FormLabel' : "Anteil Nebenskills \u03A9"
// retValue: Falls definiert, Rueckgabe-Wert fuer den Fall, dass value nicht undefined oder null ist
              },
// return Der Wert. Sind weder value noch defValue definiert, dann undefined
     'zeigePriosEnde' : {  // Spaltenauswahl fuer den Schnitt der Hauptskills (true = anzeigen, false = nicht anzeigen)
function getValue(value, defValue = undefined, retValue = undefined) {
                  'Name'      : "showPriosEnde",
     return ((value === undefined) || (value === null)) ? defValue : (retValue === undefined) ? value : retValue;
                  'Type'      : __OPTTYPES.SW,
}
                  'Default'  : true,
 
                  'Action'    : __OPTACTION.NXT,
// Gibt einen Wert zurueck. Ist dieser nicht definiert, wird ein Alternativwert geliefert
                  'Label'    : "Prios Ende ein",
// value: Ein Wert. Ist dieser definiet und in den Grenzen, wird er zurueckgeliefert
                  'Hotkey'    : 'o',
// minValue: Untere Grenze fuer den Wert, falls angegeben
                  'AltLabel'  : "Prios Ende aus",
// minValue: Obere Grenze fuer den Wert, falls angegeben
                  'AltHotkey' : 'o',
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist oder der Wert ausserhalb liegt
                  'FormLabel' : "Prios \u03A9"
// return Der Wert. Sind weder value (in den Grenzen) noch defValue definiert, dann undefined
              },
function getValueIn(value, minValue = undefined, maxValue = undefined, defValue = undefined) {
    'zeigeSkillEnde' : {  // Spaltenauswahl fuer die Werte mit Ende 18 (true = anzeigen, false = nicht anzeigen)
    const __VALUE = getValue(value, defValue);
                  'Name'      : "showSkillEnde",
 
                  'Type'      : __OPTTYPES.SW,
    if ((minValue !== undefined) && (__VALUE < minValue)) {
                  'Default'  : true,
        return defValue;
                  'Action'    : __OPTACTION.NXT,
     }
                  'Label'     : "Skill Ende ein",
    if ((maxValue !== undefined) && (__VALUE > maxValue)) {
                  'Hotkey'    : 'i',
        return defValue;
                  'AltLabel'  : "Skill Ende aus",
    }
                  'AltHotkey' : 'i',
 
                  'FormLabel' : "Skill \u03A9"
    return __VALUE;
              },
}
    'anzahlOptiEnde' : // Spaltenauswahl fuer die Werte mit Ende 18:
 
                          // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
// Ermittelt den naechsten Wert aus einer Array-Liste
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
// arr: Array-Liste mit den moeglichen Werte
                  'Name'      : "anzOptiEnde",
// value: Vorher gesetzter Wert
                  'Type'      : __OPTTYPES.MC,
// return Naechster Wert in der Array-Liste
                  'ValType'  : 'Number',
function getNextValue(arr, value) {
                  'SelValue'  : false,
    const __POS = arr.indexOf(value) + 1;
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
 
                  'Default'  : 1,
    return arr[getValueIn(__POS, 0, arr.length - 1, 0)];
                  'Action'    : __OPTACTION.NXT,
}
                  'Label'     : "Opti Ende: beste $",
 
                  'Hotkey'    : 't',
// Gibt ein Produkt zurueck. Ist einer der Multiplikanten nicht definiert, wird ein Alternativwert geliefert
                  'FormLabel' : "Opti \u03A9:|beste $"
// valueA: Ein Multipliksnt. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
              },
// valueB: Ein Multipliksnt. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
    'anzahlMWEnde' : {    // Spaltenauswahl fuer die Werte mit Ende 18:
// digits: Anzahl der Stellen nach dem Komma fuer das Produkt (Default: 0)
                          // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
// defValue: Default-Wert fuer den Fall, dass ein Multiplikant nicht gesetzt ist (Default: NaN)
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
// return Das Produkt auf digits Stellen genau. Ist dieses nicht definiert, dann defValue
                  'Name'      : "anzMWEnde",
function getMulValue(valueA, valueB, digits = 0, defValue = NaN) {
                  'Type'      : __OPTTYPES.MC,
    let product = defValue;
                  'ValType'  : 'Number',
 
                  'SelValue'  : false,
     if ((valueA !== undefined) && (valueB !== undefined)) {
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
        product = parseFloat(valueA) * parseFloat(valueB);
                  'Default'  : 1,
    }
                  'Action'    : __OPTACTION.NXT,
 
                  'Label'    : "MW Ende: beste $",
    return parseFloat(product.toFixed(digits));
                  'Hotkey'    : 'W',
}
                  'FormLabel' : "MW \u03A9:|beste $"
 
              },
// Speichert einen beliebiegen (strukturierten) Wert unter einem Namen ab
     'kennzeichenEnde' : {  // Markierung fuer Ende 18
// name: GM_setValue-Name, unter dem die Daten gespeichert werden
                  'Name'      : "charEnde",
// value: Beliebiger (strukturierter) Wert
                  'Type'      : __OPTTYPES.MC,
// return String-Darstellung des Wertes
                  'ValType'  : 'String',
function serialize(name, value) {
                  'FreeValue' : true,
    const __STREAM = (value !== undefined) ? JSON.stringify(value) : value;
                  'MinChoice' : 0,
 
                  'Choice'    : [ " \u03A9", " 18" ],
    console.log(name + " >> " + __STREAM);
                  'Action'    : __OPTACTION.NXT,
 
                  'Label'     : "Ende: $",
    GM_setValue(name, __STREAM);
                  'Hotkey'    : 'E',
 
                  'FormLabel' : "Ende 18:|$"
    return __STREAM;
              },
}
     'sepStyle' : {        // Stil der Trennlinie
 
                  'Name'      : "sepStyle",
// Holt einen beliebiegen (strukturierter) Wert unter einem Namen zurueck
                  'Type'      : __OPTTYPES.MC,
// name: GM_setValue-Name, unter dem die Daten gespeichert werden
                  'ValType'  : 'String',
// defValue: Default-Wert fuer den Fall, dass nichts gespeichert ist
                  'Choice'    : [ 'solid', 'hidden', 'dotted', 'dashed', 'double', 'groove', 'ridge',
// return Objekt, das unter dem Namen gespeichert war
                                  'inset', 'outset', 'none' ],
function deserialize(name, defValue = undefined) {
                  'Action'    : __OPTACTION.NXT,
    const __STREAM = GM_getValue(name, defValue);
                  'Label'    : "Stil: $",
 
                  'Hotkey'    : 'l',
    console.log(name + " << " + __STREAM);
                  'FormLabel' : "Stil:|$"
 
              },
    if ((__STREAM !== undefined) && (__STREAM.length !== 0)) {
     'sepColor' : {        // Farbe der Trennlinie
        try {
                  'Name'      : "sepColor",
            return JSON.parse(__STREAM);
                  'Type'      : __OPTTYPES.MC,
        } catch (ex) {
                  'ValType'  : 'String',
            console.error(name + ": " + ex.message);
                  'FreeValue' : true,
        }
                  'Choice'    : [ 'white', 'yellow', 'black', 'blue', 'cyan', 'gold', 'grey', 'green',
     }
                                  'lime', 'magenta', 'maroon', 'navy', 'olive', 'orange', 'purple',
 
                                  'red', 'teal', 'transparent' ],
    return undefined;
                  'Action'    : __OPTACTION.NXT,
}
                  'Label'    : "Farbe: $",
 
                  'Hotkey'    : 'F',
// Setzt eine Option dauerhaft und laedt die Seite neu
                  'FormLabel' : "Farbe:|$"
// name: Name der Option als Speicherort
              },
// value: Zu setzender Wert
    'sepWidth' : {        // Dicke der Trennlinie
// reload: Seite mit neuem Wert neu laden
                  'Name'      : "sepWidth",
// return Gespeicherter Wert fuer setOptValue()
                  'Type'      : __OPTTYPES.MC,
function setStored(name, value, reload = true, serial = false) {
                  'ValType'  : 'String',
    if (serial) {
                  'FreeValue' : true,
        serialize(name, value);
                  'Choice'   : [ 'thin', 'medium', 'thick' ],
    } else {
                  'Action'    : __OPTACTION.NXT,
        GM_setValue(name, value);
                  'Label'     : "Dicke: $",
     }
                  'Hotkey'    : 'D',
 
                  'FormLabel' : "Dicke:|$"
    if (reload) {
              },
        window.location.reload();
    'saison' : {          // Laufende Saison
     }
                  'Name'      : "saison",
 
                  'Type'      : __OPTTYPES.MC,
    return value;
                  'ValType'  : 'Number',
}
                  'FreeValue' : true,
 
                  'SelValue'  : false,
// Setzt den naechsten Wert aus einer Array-Liste als Option
                  'Choice'    : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],
// arr: Array-Liste mit den moeglichen Optionen
                  'Default'  : 16,
// name: Name der Option als Speicherort
                  'Action'    : __OPTACTION.NXT,
// value: Vorher gesetzter Wert
                  'Label'    : "Saison: $",
// reload: Seite mit neuem Wert neu laden
                  'Hotkey'    : 'a',
// return Gespeicherter Wert fuer setOptValue()
                  'FormLabel' : "Saison:|$"
function setNextStored(arr, name, value, reload = true, serial = false) {
              },
    return setStored(name, getNextValue(arr, value), reload, serial);
     'aktuellerZat' : {    // Laufender ZAT
}
                  'Name'      : "currZAT",
 
                  'Type'      : __OPTTYPES.MC,
// Fuehrt die in einem Storage gespeicherte Operation aus
                  'ValType'  : 'Number',
// optSet: Set mit den Optionen
                  'Permanent' : true,
// memory: __OPTMEM.normal = bis Browserende gespeichert (sessionStorage), __OPTMEM.unbegrenzt = unbegrenzt gespeichert (localStorage), __OPTMEM.inaktiv
                  'SelValue'  : false,
function runStored(optSet, memory = undefined) {
                  'Choice'    : [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
     const __STORAGE = getMemory(memory);
                                  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
    const __MEMORY = __STORAGE.Value;
                                  24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
    const __RUNPREFIX = __STORAGE.Prefix;
                                  36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
 
                                  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
    if (__MEMORY !== undefined) {
                                  60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
        const __GETITEM = function(item) {
                                  72 ],
                              return __MEMORY.getItem(__RUNPREFIX + item);
                  'Action'    : __OPTACTION.NXT,
                          };
                  'Label'    : "ZAT: $",
        const __DELITEM = function(item) {
                  'Hotkey'    : 'Z',
                              return __MEMORY.removeItem(__RUNPREFIX + item);
                  'FormLabel' : "ZAT:|$"
                          };
              },
        const __CMD = ((__MEMORY !== undefined) ? __GETITEM('cmd') : undefined);
    'datenZat' : {        // Stand der Daten zum Team und ZAT
 
                  'Name'      : "dataZAT",
        if (__CMD !== undefined) {
                  'Type'      : __OPTTYPES.SD,
            const __KEY = __GETITEM('key');
                  'ValType'   : 'Number',
            let value = __GETITEM('val');
                  'Hidden'   : true,
 
                  'Serial'   : true,
            try {
                  'AutoReset' : true,
                value = JSON.parse(value);
                  'Permanent' : true,
            } catch (ex) {
                  'Default'   : undefined,
                console.error("runStored(): " + __CMD + " '" + __KEY + "' hat illegalen Wert '" + value + "'");
                  'Action'    : __OPTACTION.SET,
                // ... meist kann man den String selber aber speichern, daher kein "return"...
                  'Submit'    : undefined,
            }
                  'Cols'      : 1,
 
                  'Rows'      : 1,
            const __VAL = value;
                  'Replace'  : null,
 
                  'Space'    : 0,
            switch (__OPTACTION[__CMD]) {
                  'Label'    : "Daten-ZAT:"
            case __OPTACTION.SET : console.log("SET '" + __KEY + "' " + __VAL);
              },
                                  setStored(__KEY, __VAL, false, false);
    'oldDatenZat' : {     // Stand der Daten zum Team und ZAT
                                  break;
                  'Name'      : "oldDataZAT",
            case __OPTACTION.NXT : console.log("SETNEXT '" + __KEY + "' " + __VAL);
                  'Type'      : __OPTTYPES.SD,
                                  //setNextStored(__CONFIG.Choice, __KEY, __VAL, false, false);
                  'ValType'  : 'Number',
                                  setStored(__KEY, __VAL, false, false);
                  'Hidden'    : true,
                                  break;
                  'Serial'    : true,
            case __OPTACTION.RST : console.log("RESET");
                  'AutoReset' : true,
                                  resetOptions(optSet, false);
                  'Permanent' : true,
                                  break;
                  'Default'  : undefined,
            default :             break;
                  'Action'    : __OPTACTION.SET,
            }
                  'Submit'    : undefined,
        }
                  'Cols'      : 1,
 
                  'Rows'      : 1,
        __DELITEM('cmd');
                  'Replace'  : null,
        __DELITEM('key');
                  'Space'    : 0,
        __DELITEM('val');
                  'Label'    : "Vorheriger Daten-ZAT:"
    }
              },
}
    'foerderung' : {     // Jugendfoerderung
 
                  'Name'      : "donation",
// Gibt eine Option sicher zurueck
                  'Type'      : __OPTTYPES.MC,
// opt: Config und Value der Option, ggfs. undefined
                  'ValType'   : 'Number',
// defOpt: Rueckgabewert, falls undefined
                  'Permanent' : true,
// return Daten zur Option (oder defOpt)
                  'SelValue'  : false,
function getOpt(opt, defOpt = { }) {
                  'Choice'    : [ 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000,
     return getValue(opt, defOpt);
                                  5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000 ],
}
                  'Default'  : 10000,
 
                  'Action'    : __OPTACTION.NXT,
// Gibt eine Option sicher zurueck (Version mit Key)
                  'Label'     : "F\xF6rderung: $",
// optSet: Platz fuer die gesetzten Optionen (und Config)
                  'Hotkey'    : 'F',
// item: Key der Option
                  'FormLabel' : "F\xF6rderung:|$"
// defOpt: Rueckgabewert, falls nicht zu finden
              },
// return Daten zur Option (oder defOpt)
    'team' : {            // Datenspeicher fuer Daten des Erst- bzw. Zweitteams
function getOptByName(optSet, item, defOpt = { }) {
                  'Name'      : "team",
    if ((optSet !== undefined) && (item !== undefined)) {
                  'Type'     : __OPTTYPES.SD,
        return getOpt(optSet[item], defOpt);
                  'Hidden'    : false,
    } else {
                  'Serial'    : true,
        return defOpt;
                  'Permanent' : true,
    }
                  'Default'  : undefined,  // new Team() // { 'Team' : undefined, 'Liga' : undefined, 'Land' : undefined, 'LdNr' : 0, 'LgNr' : 0 }
}
                  'Submit'    : undefined,
 
                  'Cols'      : 36,
// Gibt die Konfigurationsdaten einer Option zurueck
                  'Rows'      : 6,
// opt: Config und Value der Option
                  'Replace'  : null,
// defConfig: Rueckgabewert, falls Config nicht zu finden
                  'Space'    : 1,
// return Konfigurationsdaten der Option
                  'Label'    : "Verein:"
function getOptConfig(opt, defConfig = { }) {
              },
    return getValue(getOpt(opt).Config, defConfig);
     'fingerprints' : {   // Datenspeicher fuer Identifizierungsmerkmale der Jugendspieler
}
                  'Name'      : "fingerprints",
 
                  'Type'      : __OPTTYPES.SD,
// Setzt den Namen einer Option
                  'Hidden'    : true,
// opt: Config und Value der Option
                  'Serial'    : true,
// name: Zu setzender Name der Option
                  'AutoReset' : true,
// reload: Seite mit neuem Wert neu laden
                  'Permanent' : true,
// return Gesetzter Name der Option
                  'Default'  : [],
function setOptName(opt, name) {
                  'Submit'    : undefined,
     const __NAME = getOptName(opt);
                  'Cols'      : 36,
 
                  'Rows'      : 6,
    console.log("RENAME " + __NAME + " => " + name);
                  'Replace'  : null,
 
                  'Space'    : 0,
    return (getOptConfig(opt).Name = name);
                  'Label'    : "Identifikation:"
}
              },
 
    'birthdays' : {      // Datenspeicher fuer Geburtstage der Jugendspieler
// Gibt den Namen einer Option zurueck
                  'Name'      : "birthdays",
// opt: Config und Value der Option
                  'Type'      : __OPTTYPES.SD,
// return Name der Option
                  'Hidden'    : true,
function getOptName(opt) {
                  'Serial'    : true,
    return getOptConfig(opt).Name;
                  'AutoReset' : true,
}
                  'Permanent' : true,
 
                  'Default'  : [],
// Setzt den Wert einer Option
                  'Submit'    : undefined,
// opt: Config und Value der Option
                  'Cols'      : 36,
// name: Zu setzender Wert der Option
                  'Rows'      : 2,
// return Gesetzter Wert
                  'Replace'  : null,
function setOptValue(opt, value) {
                  'Space'    : 0,
    return (opt !== undefined) ? (opt.Value = value) : undefined;
                  'Label'     : "Geburtstage:"
}
              },
 
     'tClasses' : {        // Datenspeicher fuer Talente der Jugendspieler (-1=wenig, 0=normal, +1=hoch)
// Gibt den Wert einer Option zurueck
                  'Name'     : "tClasses",
// opt: Config und Value der Option
                  'Type'      : __OPTTYPES.SD,
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
                  'Hidden'    : true,
// return Gesetzter Wert
                  'Serial'    : true,
function getOptValue(opt, defValue = undefined) {
                  'AutoReset' : true,
    return getValue((opt !== undefined) ? opt.Value : undefined, defValue);
                  'Permanent' : true,
}
                  'Default'  : [],
 
                  'Submit'    : undefined,
// ==================== Ende Abschnitt fuer diverse Utilities ====================
                  'Cols'      : 36,
 
                  'Rows'      : 2,
// ==================== Abschnitt fuer Speicher und die Scriptdatenbank ====================
                  'Replace'  : null,
 
                  'Space'    : 0,
// Namen des Default-, Dauer- und Null-Memories...
                  'Label'    : "Talente:"
const __MEMNORMAL   = 'normal';
              },
const __MEMINFINITE = 'unbegrenzt';
    'progresses' : {      // Datenspeicher fuer Aufwertungen der Jugendspieler (als Strings)
const __MEMINAKTIVE = 'inaktiv';
                  'Name'      : "progresses",
 
                  'Type'      : __OPTTYPES.SD,
// Definition des Default-, Dauer- und Null-Memories...
                  'Hidden'    : true,
const __OPTMEMNORMAL   = __OPTMEM[__MEMNORMAL];
                  'Serial'    : true,
const __OPTMEMINFINITE = __OPTMEM[__MEMINFINITE];
                  'AutoReset' : true,
const __OPTMEMINAKTIVE = __OPTMEM[__MEMINAKTIVE];
                  'Permanent' : true,
 
                  'Default'  : [],
// Medium fuer die Datenbank (Speicher)
                  'Submit'    : undefined,
let myOptMem = __OPTMEMNORMAL;
                  'Cols'      : 36,
 
                  'Rows'      : 7,
// Speicher fuer die DB-Daten
                  'Replace'  : null,
const __DBMEM = __OPTMEMNORMAL.Value;
                  'Space'    : 0,
 
                  'Label'    : "Aufwertungen:"
// Infos ueber dieses Script-Modul
              },
const __DBMOD = { };
    'ziehAnz' : {        // Datenspeicher fuer Anzahl zu ziehender Jugendspieler bis zur naechsten Abrechnung
 
                  'Name'      : "drawCounts",
// Inhaltsverzeichnis der DB-Daten (indiziert durch die Script-Namen)
                  'Type'      : __OPTTYPES.SD,
const __DBTOC = { };
                  'Hidden'    : true,
 
                  'Serial'    : true,
// Daten zu den Modulen (indiziert durch die Script-Namen)
                  'AutoReset' : false,
const __DBDATA = { };
                  'Permanent' : true,
 
                  'Default'  : [],
// ==================== Abschnitt fuer Speicher ====================
                  'Submit'    : undefined,
 
                  'Cols'      : 25,
// Ermittelt fuer die uebergebene Speicher-Konfiguration einen Speicher
                  'Rows'      : 1,
// memory: __OPTMEM.normal = bis Browserende gespeichert (sessionStorage), __OPTMEM.unbegrenzt = unbegrenzt gespeichert (localStorage), __OPTMEM.inaktiv
                  'Replace'  : null,
// return memory, falls okay, sonst einen Defaultwert
                  'Space'    : 0,
function getMemory(memory = undefined) {
                  'Label'    : "Zu ziehen:"
    return getValue(memory, getValue(myOptMem, __OPTMEMNORMAL));
              },
}
    'ziehAnzAufstieg' : { // Datenspeicher fuer Anzahl zu ziehender Jugendspieler bis zur naechsten Abrechnung im Falle eines Aufstiegs
 
                  'Name'      : "drawCountsAufstieg",
// Kompatibilitaetsfunktion: Testet, ob der uebergebene Speicher genutzt werden kann
                  'Type'      : __OPTTYPES.MC,
// memory: __OPTMEM.normal = bis Browserende gespeichert (sessionStorage), __OPTMEM.unbegrenzt = unbegrenzt gespeichert (localStorage), __OPTMEM.inaktiv
                  'ValType'  : 'Number',
function canUseMemory(memory = undefined) {
                  'Hidden'    : true,
    const __STORAGE = getMemory(memory);
                  'AutoReset' : false,
    const __MEMORY = __STORAGE.Value;
                  'Permanent' : true,
    let ret = false;
                  'FreeValue' : true,
 
                  'SelValue'  : false,
    if (__MEMORY !== undefined) {
                  'Choice'    : [ 0, 1, 2, 3, 4, 5 ],
        const __TESTPREFIX = 'canUseStorageTest';
                  'Default'  : 0,
        const __TESTDATA = Math.random().toString();
                  'Action'    : __OPTACTION.NXT,
        const __TESTITEM = __TESTPREFIX + __TESTDATA;
                  'Label'    : "Zu ziehen bei Aufstieg: $",
 
                  'Hotkey'    : 'z',
        __MEMORY.setItem(__TESTITEM, __TESTDATA);
                  'FormLabel' : "Zu ziehen bei Aufstieg:|$"
        ret = (__MEMORY.getItem(__TESTITEM) === __TESTDATA);
              },
        __MEMORY.removeItem(__TESTITEM);
    'zatAges' : {        // Datenspeicher fuer (gebrochene) Alter der Jugendspieler
    }
                  'Name'      : "zatAges",
 
                  'Type'      : __OPTTYPES.SD,
     console.log("canUseStorage(" + __STORAGE.Name + ") = " + ret);
                  'Hidden'    : true,
 
                  'Serial'    : true,
    return ret;
                  'AutoReset' : true,
}
                  'Permanent' : true,
 
                  'Default'  : [],
// Restauriert den vorherigen Speicher (der in einer Option definiert ist)
                  'Submit'    : undefined,
// opt: Option zur Wahl des Speichers
                  'Cols'      : 36,
// return Gesuchter Speicher oder Null-Speicher ('inaktiv')
                  'Rows'      : 2,
function restoreMemoryByOpt(opt) {
                  'Replace'  : null,
    // Memory Storage fuer vorherige Speicherung...
                  'Space'    : 0,
    const __STORAGE = loadOption(getOpt(opt), true);
                  'Label'    : "ZAT-Alter:"
 
              },
    return __OPTMEM[getValue(__STORAGE, __MEMNORMAL)];
    'trainiert' : {      // Datenspeicher fuer Trainingsquoten der Jugendspieler
}
                  'Name'      : "numProgresses",
 
                  'Type'      : __OPTTYPES.SD,
// Initialisiert den Speicher (der in einer Option definiert ist) und merkt sich diesen ggfs.
                  'Hidden'    : true,
// opt: Option zur Wahl des Speichers
                  'Serial'    : true,
// saveOpt: Option zur Speicherung der Wahl des Speichers (fuer restoreMemoryByOpt)
                  'AutoReset' : true,
// return Gesuchter Speicher oder Null-Speicher ('inaktiv'), falls speichern nicht moeglich ist
                  'Permanent' : true,
function startMemoryByOpt(opt, saveOpt = undefined) {
                  'Default'  : [],
    // Memory Storage fuer naechste Speicherung...
                  'Submit'    : undefined,
    let storage = getOptValue(opt, __MEMNORMAL);
                  'Cols'      : 36,
    let optMem = __OPTMEM[storage];
                  'Rows'      : 2,
 
                  'Replace'  : null,
    if (! canUseMemory(optMem)) {
                  'Space'    : 0,
        if (storage !== __MEMINAKTIVE) {
                  'Label'    : "Trainiert:"
            storage = __MEMINAKTIVE;
              },
            optMem = __OPTMEM[storage];
    'positions' : {      // Datenspeicher fuer optimale Positionen der Jugendspieler
        }
                  'Name'      : "positions",
     }
                  'Type'      : __OPTTYPES.SD,
 
                  'Hidden'    : true,
    if (saveOpt !== undefined) {
                  'Serial'    : true,
        setOpt(saveOpt, storage, false);
                  'AutoReset' : true,
    }
                  'Permanent' : true,
 
                  'Default'  : [],
    return optMem;
                  'Submit'    : undefined,
}
                  'Cols'      : 36,
 
                  'Rows'      : 3,
// ==================== Ende Abschnitt fuer Speicher ====================
                  'Replace'  : null,
 
                  'Space'    : 0,
// ==================== Abschnitt fuer die Scriptdatenbank ====================
                  'Label'    : "Positionen:"
 
              },
// Initialisiert die Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
    'skills' : {          // Datenspeicher fuer aktuelle Einzelskills der Jugendspieler
// optSet: Gesetzte Optionen (und Config)
                  'Name'      : "skills",
function initScriptDB(optSet) {
                  'Type'      : __OPTTYPES.SD,
    const __INFO = GM_info;
                  'Hidden'    : true,
    const __META = __INFO.script;
                  'Serial'    : true,
 
                  'AutoReset' : true,
    //console.log(__INFO);
                  'Permanent' : true,
 
                  'Default'  : [],
    __DBTOC.versions = getValue(JSON.parse(__DBMEM.getItem('__DBTOC.versions')), { });
                  'Submit'    : undefined,
 
                  'Cols'      : 36,
    // Infos zu diesem Script...
                  'Rows'      : 20,
    __DBMOD.name = __META.name;
                  'Replace'  : null,
    __DBMOD.version = __META.version;
                  'Space'    : 0,
 
                  'Label'    : "Skills:"
    console.log(__DBMOD);
              },
 
    'hauptLS'  : {        // Option 'ligaSize' aus Modul 'OS2.haupt', hier als 'hauptLS'
     // Zunaechst den alten Eintrag entfernen...
                  'Shared'    : { /*'namespace' : "http://os.ongapo.com/",*/ 'module' : "OS2.haupt", 'item' : 'ligaSize' },
    __DBTOC.versions[__DBMOD.name] = undefined;
                  'Hidden'    : true,
 
                  'FormLabel' : "Liga:|$er (haupt)"
     // ... und die Daten der Fremdscripte laden...
              },
    for (let module in __DBTOC.versions) {
    'hauptZat' : {        // Option 'datenZat' aus Modul 'OS2.haupt', hier als 'hauptZat'
        __DBDATA[module] = getValue(JSON.parse(__DBMEM.getItem('__DBDATA.' + module)), { });
                  'Shared'    : { /*'namespace' : "http://os.ongapo.com/",*/ 'module' : "OS2.haupt", 'item' : 'datenZat' },
    }
                  'Hidden'    : true,
}
                  'Cols'      : 36,
 
                  'Rows'      : 6,
// Setzt die Daten dieses Scriptes in der Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
                  'Label'    : "ZAT:"
// optSet: Gesetzte Optionen (und Config)
              },
function updateScriptDB(optSet) {
    'haupt' : {          // Alle Optionen des Moduls 'OS2.haupt'
    // Eintrag ins Inhaltsverzeichnis...
                  'Shared'    : { 'module' : "OS2.haupt", 'item' : '$' },
    __DBTOC.versions[__DBMOD.name] = __DBMOD.version;
                  'Type'      : __OPTTYPES.SD,
                  'Hidden'    : true,
                  'Serial'    : true,
                  'Cols'      : 36,
                  'Rows'      : 6,
                  'Replace'  : null,
                  'Space'    : 4,
                  'Label'    : "Haupt:"
              },
    'data' : {            // Optionen aller Module
                  'Shared'    : { 'module' : '$' },
                  'Type'      : __OPTTYPES.SD,
                  'Hidden'    : true,
                  'Serial'    : true,
                  'Cols'      : 36,
                  'Rows'      : 6,
                  'Replace'  : null,
                  'Space'    : 4,
                  'Label'    : "Data:"
              },
    'reset' : {          // Optionen auf die "Werkseinstellungen" zuruecksetzen
                  'FormPrio'  : undefined,
                  'Name'      : "reset",
                  'Type'      : __OPTTYPES.SI,
                  'Action'    : __OPTACTION.RST,
                  'Label'    : "Standard-Optionen",
                  'Hotkey'    : 'r',
                  'FormLabel' : ""
              },
    'storage' : {        // Browserspeicher fuer die Klicks auf Optionen
                  'FormPrio'  : undefined,
                  'Name'      : "storage",
                  'Type'      : __OPTTYPES.MC,
                  'ValType'  : 'String',
                  'Choice'    : Object.keys(__OPTMEM),
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Speicher: $",
                  'Hotkey'    : 'c',
                  'FormLabel' : "Speicher:|$"
              },
    'oldStorage' : {      // Vorheriger Browserspeicher fuer die Klicks auf Optionen
                  'FormPrio'  : undefined,
                  'Name'      : "oldStorage",
                  'Type'      : __OPTTYPES.SD,
                  'PreInit'  : true,
                  'AutoReset' : true,
                  'Hidden'    : true
              },
    'showForm' : {        // Optionen auf der Webseite (true = anzeigen, false = nicht anzeigen)
                  'FormPrio'  : 1,
                  'Name'      : "showForm",
                  'Type'      : __OPTTYPES.SW,
                  'FormType'  : __OPTTYPES.SI,
                  'Permanent' : true,
                  'Default'  : false,
                  'Title'    : "$V Optionen",
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Optionen anzeigen",
                  'Hotkey'    : 'O',
                  'AltTitle'  : "$V schlie\xDFen",
                  'AltLabel'  : "Optionen verbergen",
                  'AltHotkey' : 'O',
                  'FormLabel' : ""
              }
};


// ==================== Invarianter Abschnitt fuer Optionen ====================
    // Permanente Speicherung der Eintraege...
    __DBMEM.setItem('__DBTOC.versions', JSON.stringify(__DBTOC.versions));
    __DBMEM.setItem('__DBDATA.' + __DBMOD.name, JSON.stringify(optSet));


// Ein Satz von Logfunktionen, die je nach Loglevel zur Verfuegung stehen. Aufruf: __LOG[level](text)
    // Jetzt die inzwischen gefuellten Daten *dieses* Scripts ergaenzen...
const __LOG = {
    __DBDATA[__DBMOD.name] = getValue(optSet, { });
                  'logFun'    : [
                                    console.error,  // [0] Alert
                                    console.error,  // [1] Error
                                    console.log,    // [2] Log: Release
                                    console.log,    // [3] Log: Info
                                    console.log,    // [4] Log: Debug
                                    console.log,    // [5] Log: Verbose
                                    console.log,    // [6] Log: Very verbose
                                    console.warn    // [7] Log: Testing
                                ],
                  'init'      : function(win, logLevel = 1) {
                                    for (let level = 0; level < this.logFun.length; level++) {
                                        this[level] = ((level > logLevel) ? function() { } : this.logFun[level]);
                                    }
                                },
                  'stringify' : safeStringify,      // JSON.stringify
                  'changed'  : function(oldVal, newVal) {
                                    const __OLDVAL = this.stringify(oldVal);
                                    const __NEWVAL = this.stringify(newVal);
 
                                    return ((__OLDVAL !== __NEWVAL) ? __OLDVAL + " => " : "") + __NEWVAL;
                                }
              };
 
__LOG.init(window, __LOGLEVEL);


// Kompatibilitaetsfunktion zur Ermittlung des Namens einer Funktion (falle <Function>.name nicht vorhanden ist)
     console.log(__DBDATA);
if (Function.prototype.name === undefined) {
     Object.defineProperty(Function.prototype, 'name', {
            get : function() {
                      return /function ([^(\s]*)/.exec(this.toString())[1];
                  }
        });
}
}


// Ergaenzung fuer Strings: Links oder rechts auffuellen nach Vorlage
// ==================== Ende Abschnitt fuer die Scriptdatenbank ====================
// padStr: Vorlage, z.B. "00" fuer zweistellige Zahlen
// padLeft: true = rechtsbuendig, false = linksbuendig
// clip: Abschneiden, falls zu lang
// return Rechts- oder linksbuendiger String, der so lang ist wie die Vorlage
String.prototype.pad = function(padStr, padLeft = true, clip = false) {
    const __LEN = ((clip || (padStr.length > this.length)) ? padStr.length : this.length);


    return (padLeft ? String(padStr + this).slice(- __LEN) : String(this + padStr).slice(0, __LEN));
// ==================== Ende Abschnitt fuer Speicher und die Scriptdatenbank ====================
};


// Ersetzt in einem String {0}, {1}, ... durch die entsprechenden Parameter
// ==================== Abschnitt fuer das Benutzermenu ====================
// arguments: Parameter, die fuer {0}, {1}, ... eingesetzt werden sollen
// return Resultierender String
String.prototype.format = function() {
    const __ARGS = arguments;
    return this.replace(/{(\d+)}/g, function(match, argIdx) {
                                        const __ARG = __ARGS[argIdx];
                                        return ((__ARG !== undefined) ? __ARG : match);
                                    });
};


// Gibt eine Meldung in der Console aus und oeffnet ein Bestaetigungsfenster mit der Meldung
// Zeigt den Eintrag im Menu einer Option
// label: Eine Ueberschrift
// opt: Derzeitiger Wert der Option
// message: Der Meldungs-Text
// menuOn: Text zum Setzen im Menu
// data: Ein Wert. Ist er angegeben, wird er in der Console ausgegeben
// funOn: Funktion zum Setzen
// return Liefert die Parameter zurueck
// keyOn: Hotkey zum Setzen im Menu
function showAlert(label, message, data = undefined) {
// menuOff: Text zum Ausschalten im Menu
     __LOG[0](label + ": " + message);
// funOff: Funktion zum Ausschalten
// keyOff: Hotkey zum Ausschalten im Menu
function registerMenuOption(opt, menuOn, funOn, keyOn, menuOff, funOff, keyOff) {
    const __ON  = (opt ? '*' : "");
     const __OFF = (opt ? "" : '*');


     if (data !== undefined) {
     console.log("OPTION " + __ON + menuOn + __ON + " / " + __OFF + menuOff + __OFF);
        __LOG[2](data);
    }


    alert(label + "\n\n" + message);
     if (opt) {
 
         GM_registerMenuCommand(menuOff, funOff, keyOff);
    return arguments;
     } else {
}
         GM_registerMenuCommand(menuOn, funOn, keyOn);
 
// Gibt eine Meldung in der Console aus und oeffnet ein Bestaetigungsfenster
// mit der Meldung zu einer Exception oder einer Fehlermeldung
// label: Eine Ueberschrift
// ex: Exception oder sonstiges Fehlerobjekt
// return Liefert die showAlert()-Parameter zurueck
function showException(label, ex) {
     if (ex && ex.message) { // Exception
         showAlert(label, ex.message, ex);
     } else { // sonstiger Fehler
         showAlert(label, ex);
     }
     }
}
}


// Standard-Callback-Funktion fuer onRejected, also abgefangener Fehler
// Zeigt den Eintrag im Menu einer Option mit Wahl des naechsten Wertes
// in einer Promise bei Exceptions oder Fehler bzw. Rejections
// opt: Derzeitiger Wert der Option
// error: Parameter von reject() im Promise-Objekt, der von Promise.catch() erhalten wurde
// arr: Array-Liste mit den moeglichen Optionen
// return Liefert die showAlert()-Parameter zurueck
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
function defaultCatch(error) {
// fun: Funktion zum Setzen des naechsten Wertes
     try {
// key: Hotkey zum Setzen des naechsten Wertes im Menu
        const __LABEL = `[${error.lineNumber}] ${__DBMOD.Name}`;
function registerNextMenuOption(opt, arr, menu, fun, key) {
     const __MENU = menu.replace('$', opt);
    let options = "OPTION " + __MENU;


         if (error && error.message) { // Exception
    for (let value of arr) {
             return showException(__LABEL, error.message, error);
         if (value === opt) {
             options += " / *" + value + '*';
         } else {
         } else {
             return showException(__LABEL, error);
             options += " / " + value;
         }
         }
    } catch (ex) {
        return showException(`[${ex.lineNumber}] ${__DBMOD.Name}`, ex.message, ex);
     }
     }
    console.log(options);
    GM_registerMenuCommand(__MENU, fun, key);
}
}


// ==================== Abschnitt fuer Klasse Class ====================
// Zeigt den Eintrag im Menu einer Option, falls nicht hidden
// opt: Derzeitiger Wert der Option
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
// fun: Funktion zum Setzen des naechsten Wertes
// key: Hotkey zum Setzen des naechsten Wertes im Menu
// hidden: Angabe, ob Menupunkt nicht sichtbar sein soll (default: sichtbar)
// serial: Serialization fuer komplexe Daten
function registerDataOption(opt, menu, fun, key, hidden = false, serial = true) {
    const __VALUE = ((serial && (opt !== undefined)) ? JSON.stringify(opt) : opt);
    const __MENU = getValue(menu, "").replace('$', __VALUE);
    const __OPTIONS = (hidden ? "HIDDEN " : "") + "OPTION " + __MENU +
                      getValue(__VALUE, "", " = " + __VALUE);


function Class(className, baseClass, initFun) {
    console.log(__OPTIONS);
    'use strict';


     try {
     if (! hidden) {
        const __BASE = ((baseClass !== undefined) ? baseClass : Object);
         GM_registerMenuCommand(__MENU, fun, key);
         const __BASEPROTO = (__BASE ? __BASE.prototype : undefined);
    }
        const __BASECLASS = (__BASEPROTO ? __BASEPROTO.__class : undefined);
}


        this.className = (className || '?');
// Zeigt den Eintrag im Menu einer Option
        this.baseClass = __BASECLASS;
// opt: Config und Value der Option
        Object.setConst(this, 'baseProto', __BASEPROTO, false);
function registerOption(opt) {
    const __CONFIG = getOptConfig(opt);


        if (! initFun) {
    if (! __CONFIG.HiddenMenu) {
            const __BASEINIT = (__BASECLASS || { }).init;
        switch (__CONFIG.Type) {
 
        case __OPTTYPES.MC : registerNextMenuOption(getOptValue(opt), __CONFIG.Choice,
            if (__BASEINIT) {
                                                                  __CONFIG.Label, opt.Action, __CONFIG.Hotkey);
                initFun = function() {
                            break;
                              // Basisklassen-Init aufrufen...
        case __OPTTYPES.SW : registerMenuOption(getOptValue(opt), __CONFIG.Label, opt.Action, __CONFIG.Hotkey,
                              return __BASEINIT.call(this, arguments);
                                                                  __CONFIG.AltLabel, opt.Action, __CONFIG.AltHotkey);
                          };
                            break;
            } else {
        case __OPTTYPES.TF : registerMenuOption(getOptValue(opt), __CONFIG.Label, opt.Action, __CONFIG.Hotkey,
                initFun = function() {
                                                                  __CONFIG.AltLabel, opt.AltAction, __CONFIG.AltHotkey);
                              // Basisklassen-Init fehlt (und Basisklasse ist nicht Object)...
                            break;
                              return false;
        case __OPTTYPES.SD : registerDataOption(getOptValue(opt), __CONFIG.Label, opt.Action, __CONFIG.Hotkey,
                          };
                                                                  __CONFIG.HiddenMenu, __CONFIG.Serial);
            }
                            break;
        case __OPTTYPES.SI : registerDataOption(getOptValue(opt), __CONFIG.Label, opt.Action, __CONFIG.Hotkey,
                                                                  __CONFIG.HiddenMenu, __CONFIG.Serial);
                            break;
        default :            break;
         }
         }
 
    } else {
         console.assert((__BASE === null) || ((typeof __BASE) === 'function'), "No function:", __BASE);
         // Nur Anzeige im Log...
        console.assert((typeof initFun) === 'function', "Not a function:", initFun);
        registerDataOption(getOptValue(opt), getOptName(opt), opt.Action, __CONFIG.Hotkey, __CONFIG.HiddenMenu, __CONFIG.Serial);
 
        this.init = initFun;
    } catch (ex) {
        showAlert('[' + ex.lineNumber + "] Error in Class " + className, ex.message, ex);
     }
     }
}
}


Class.define = function(subClass, baseClass, members = undefined, initFun = undefined, createProto = true) {
// ==================== Ende Abschnitt fuer das Benutzermenu ====================
        return (subClass.prototype = subClass.subclass(baseClass, members, initFun, createProto));
    };


Object.setConst = function(obj, item, value, config) {
// Initialisiert die gesetzten Option
        return Object.defineProperty(obj, item, {
// config: Konfiguration der Option
                        enumerable  : false,
// return Initialwert der gesetzten Option
                        configurable : (config || true),
function initOptValue(config) {
                        writable     : false,
     let value = config.Default; // Standard
                        value       : value
                    });
    };


Object.setConst(Object.prototype, 'subclass', function(baseClass, members, initFun, createProto) {
    switch (config.Type) {
        'use strict';
    case __OPTTYPES.MC : if ((value === undefined) && (config.Choice !== undefined)) {
 
                            value = config.Choice[0];
        try {
                        }
            const __MEMBERS = (members || { });
                        break;
            const __CREATEPROTO = ((createProto === undefined) ? true : createProto);
    case __OPTTYPES.SW : break;
 
    case __OPTTYPES.TF : break;
            console.assert((typeof this) === 'function', "Not a function:", this);
    case __OPTTYPES.SD : config.Serial = true;
            console.assert((typeof __MEMBERS) === 'object', "Not an object:", __MEMBERS);
                        break;
    case __OPTTYPES.SI : break;
    default :           break;
    }


            const __CLASS = new Class(this.name || __MEMBERS.__name, baseClass, initFun || __MEMBERS.__init);
    if (config.Serial || config.Hidden) {
            const __PROTO = (__CREATEPROTO ? Object.create(__CLASS.baseProto) : this.prototype);
        config.HiddenMenu = true;
    }


            for (let item in __MEMBERS) {
    return value;
                if ((item !== '__name') && (item !== '__init')) {
}
                    Object.setConst(__PROTO, item, __MEMBERS[item]);
                }
            }


            Object.setConst(__PROTO, '__class', __CLASS, ! __CREATEPROTO);
// Initialisiert die Menue-Funktion einer Option
// optAction: Typ der Funktion
// item: Key der Option
// optSet: Platz fuer die gesetzten Optionen (und Config)
// return Funktion fuer die Option
function initOptAction(optAction, item = undefined, optSet = undefined) {
    var fun;


            return __PROTO;
    if (optAction !== undefined) {
        } catch (ex) {
        const __CONFIG = getOptConfig(getOptByName(optSet, item));
            showAlert('[' + ex.lineNumber + "] Error in subclassing", ex.message, ex);
         const __RELOAD = ((__CONFIG !== undefined) ? __CONFIG.ActionReload : false);
         }
    }, false);


Class.define(Object, null, {
        switch (optAction) {
                    '__init'      : function() {
        case __OPTACTION.SET : fun = function() {
                                        // Oberstes Basisklassen-Init...
                                      return setOptByName(optSet, item, optSet.SetValue, __RELOAD);
                                        return true;
                                  };
                                    },
                              break;
                    'getClass'    : function() {
        case __OPTACTION.NXT : fun = function() {
                                        return this.__class;
                                      return setNextOptByName(optSet, item, optSet.SetValue, __RELOAD);
                                    },
                                  };
                    'getClassName' : function() {
                              break;
                                        const __CLASS = this.getClass();
        case __OPTACTION.RST : fun = function() {
 
                                      return resetOptions(optSet, __RELOAD);
                                        return (__CLASS ? __CLASS.getName() : undefined);
                                  };
                                    },
                              break;
                    'setConst'    : function(item, value, config = undefined) {
         default :              break;
                                        return Object.setConst(this, item, value, config);
                                    }
                }, undefined, false);
 
Class.define(Function, Object);
 
Class.define(Class, Object, {
                    'getName'      : function() {
                                        return this.className;
                                    }
                });
 
// ==================== Ende Abschnitt fuer Klasse Class ====================
 
// ==================== Abschnitt fuer Klasse Delims ====================
 
// Basisklasse fuer die Verwaltung der Trennzeichen und Symbole von Pfaden
// delim: Trennzeichen zwischen zwei Ebenen (oder Objekt/Delims mit entsprechenden Properties)
// back: (Optional) Name des relativen Vaterverzeichnisses
// root: (Optional) Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// home: (Optional) Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Delims(delim, back, root, home) {
    'use strict';
 
    if ((typeof delim) === 'object') {
         // Erster Parameter ist Objekt mit den Properties...
        if (back === undefined) {
            back = delim.back;
         }
         }
        if (root === undefined) {
            root = delim.root;
        }
        if (home === undefined) {
            home = delim.home;
        }
        delim = delim.delim;
     }
     }


     this.setDelim(delim);
     return fun;
    this.setBack(back);
    this.setRoot(root);
    this.setHome(home);
}
}


Class.define(Delims, Object, {
// Initialisiert die gesetzten Optionen
              'setDelim'      : function(delim = undefined) {
// optConfig: Konfiguration der Optionen
                                    this.delim = delim;
// optSet: Platz fuer die gesetzten Optionen
                                },
// return Gefuelltes Objekt mit den gesetzten Optionen
              'setBack'        : function(back = undefined) {
function initOptions(optConfig, optSet = undefined) {
                                    this.back = back;
    var value;
                                },
              'setRoot'        : function(root = undefined) {
                                    this.root = root;
                                },
              'setHome'        : function(home = undefined) {
                                    this.home = home;
                                }
          });


// ==================== Ende Abschnitt fuer Klasse Delims ====================
    if (optSet === undefined) {
        optSet = { };
    }


// ==================== Abschnitt fuer Klasse UriDelims ====================
    for (let opt in optConfig) {
        const __CONFIG = optConfig[opt];
        const __ALTACTION = getValue(__CONFIG.AltAction, __CONFIG.Action);


// Basisklasse fuer die Verwaltung der Trennzeichen und Symbole von URIs
        optSet[opt] = {
// delim: Trennzeichen zwischen zwei Ebenen (oder Objekt/Delims mit entsprechenden Properties)
            'Config'    : __CONFIG,
// back: (Optional) Name des relativen Vaterverzeichnisses
            'Value'    : initOptValue(__CONFIG),
// root: (Optional) Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
            'SetValue' : undefined,
// home: (Optional) Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
            'Action'   : initOptAction(__CONFIG.Action, opt, optSet),
// scheme: (Optional) Trennzeichen fuer den Schema-/Protokollnamen vorne
             'AltAction' : initOptAction(__ALTACTION, opt, optSet)
// host: (Optional) Prefix fuer Hostnamen hinter dem Scheme-Trenner
         };
// port: (Optional) Trennzeichen vor der Portangabe, falls vorhanden
// query: (Optional) Trennzeichen fuer die Query-Parameter hinter dem Pfad
// parSep: (Optional) Trennzeichen zwischen je zwei Parametern
// parAss: (Optional) Trennzwischen zwischen Key und Value
// node: (Optional) Trennzeichen fuer den Knotennamen hinten (Fragment, Kapitel)
function UriDelims(delim, back, root, home, scheme, host, port, query, qrySep, qryAss, node) {
    'use strict';
 
    if ((typeof delim) === 'object') {
        // Erster Parameter ist Objekt mit den Properties...
        if (scheme === undefined) {
            scheme = delim.scheme;
        }
        if (host === undefined) {
             host = delim.host;
        }
        if (port === undefined) {
            port = delim.port;
         }
        if (query === undefined) {
            query = delim.query;
        }
        if (qrySep === undefined) {
            qrySep = delim.qrySep;
        }
        if (qryAss === undefined) {
            qryAss = delim.qryAss;
        }
        if (node === undefined) {
            node = delim.node;
        }
     }
     }


     Delims.call(this, delim, back, root, home);
     return optSet;
 
    this.setScheme(scheme);
    this.setHost(host);
    this.setPort(port);
    this.setQuery(query);
    this.setQrySep(qrySep);
    this.setQryAss(qryAss);
    this.setNode(node);
}
}


Class.define(UriDelims, Delims, {
// Initialisiert die gesetzten Optionen und den Speicher und laedt die Optionen zum Start
              'setScheme'      : function(scheme = undefined) {
// optConfig: Konfiguration der Optionen
                                    this.scheme = scheme;
// optSet: Platz fuer die gesetzten Optionen
                                },
// return Gefuelltes Objekt mit den gesetzten Optionen
              'setHost'        : function(host = undefined) {
function startOptions(optConfig, optSet = undefined) {
                                    this.host = host;
    optSet = initOptions(optConfig, optSet);
                                },
              'setPort'        : function(port = undefined) {
                                    this.port = port;
                                },
              'setQuery'      : function(query = undefined) {
                                    this.query = query;
                                },
              'setQrySep'      : function(qrySep = undefined) {
                                    this.qrySep = qrySep;
                                },
              'setQryAss'      : function(qryAss = undefined) {
                                    this.qryAss = qryAss;
                                },
              'setNode'        : function(node = undefined) {
                                    this.node = node;
                                }
          });


// ==================== Ende Abschnitt fuer Klasse UriDelims ====================
    // Memory Storage fuer vorherige Speicherung...
    myOptMem = restoreMemoryByOpt(optSet.oldStorage);


// ==================== Abschnitt fuer Klasse Path ====================
    runStored(optSet);
    loadOptions(optSet);


// Basisklasse fuer die Verwaltung eines Pfades
    // Memory Storage fuer naechste Speicherung...
// homePath: Absoluter Startpfad als String
    myOptMem = startMemoryByOpt(optSet.storage, optSet.oldStorage);
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Path(homePath, delims) {
    'use strict';


     this.dirs = [];
     initScriptDB(optSet);
    this.setDelims(delims);
    this.homeDirs = this.getDirs(homePath, { 'home' : "" });


     this.home();
     return optSet;
}
}


Class.define(Path, Object, {
// Installiert die Visualisierung und Steuerung der Optionen
                  'root'          : function() {
// optSet: Platz fuer die gesetzten Optionen
                                        this.dirs.splice(0, this.dirs.length);
// optParams: Eventuell notwendige Parameter zur Initialisierung
                                    },
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
                  'home'          : function() {
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
                                        this.dirs = this.homeDirs.slice();
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
                                    },
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
                  'up'             : function() {
// 'formWidth': Anzahl der Elemente pro Zeile
                                        this.dirs.pop();
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
                                    },
function showOptions(optSet = undefined, optParams = { 'hideMenu' : false }) {
                  'down'           : function(subDir) {
    updateScriptDB(optSet);
                                        this.dirs.push(subDir);
                                    },
                  'setDelims'      : function(delims = undefined) {
                                        this.setConst('delims', new Delims(delims));
                                    },
                  'setDelim'      : function(delim = undefined) {
                                        this.delims.setDelim(delim || '/');
                                    },
                  'setBackDelim'   : function(backDelim = undefined) {
                                        this.delims.setBack(backDelim || "..");
                                    },
                  'setRootDelim'  : function(rootDelim = undefined) {
                                        this.delims.setRoot(rootDelim || "");
                                    },
                  'setHomeDelim'   : function(homeDelim = undefined) {
                                        this.delims.setHome(homeDelim || '~');
                                    },
                  'setSchemeDelim' : function(schemeDelim = undefined) {
                                        this.delims.setScheme(schemeDelim || ':');
                                    },
                  'setHostDelim'  : function(hostDelim = undefined) {
                                        this.delims.setHost(hostDelim || '//');
                                    },
                  'setPortDelim'  : function(portDelim = undefined) {
                                        this.delims.setHost(portDelim || ':');
                                    },
                  'setQueryDelim'  : function(queryDelim = undefined) {
                                        this.delims.setQuery(queryDelim || '?');
                                    },
                  'setParSepDelim' : function(parSepDelim = undefined) {
                                        this.delims.setParSep(parSepDelim || '&');
                                    },
                  'setParAssDelim' : function(parAssDelim = undefined) {
                                        this.delims.setParAss(parAssDelim || '=');
                                    },
                  'setNodeDelim'   : function(nodeDelim = undefined) {
                                        this.delims.setNode(nodeDelim || '#');
                                    },
                  'getLeaf'        : function(dirs = undefined) {
                                        const __DIRS = (dirs || this.dirs);


                                        return ((__DIRS && __DIRS.length) ? __DIRS.slice(-1)[0] : "");
    if (! optParams.hideMenu) {
                                    },
        buildMenu(optSet);
                  'getPath'        : function(dirs = undefined, delims = undefined) {
    }
                                        const __DELIMS = new Delims(delims);
                                        const __DELIM = (__DELIMS.delim || this.delims.delim);
                                        const __ROOTDELIM = ((__DELIMS.root !== undefined) ? __DELIMS.root : this.delims.root);
                                        const __DIRS = (dirs || this.dirs);


                                        return __ROOTDELIM + __DELIM + __DIRS.join(__DELIM);
    if ((optParams.menuAnchor !== undefined) && (myOptMem !== __OPTMEMINAKTIVE)) {
                                    },
        buildForm(optParams.menuAnchor, optSet, optParams);
                  'getDirs'        : function(path = undefined, delims = undefined) {
    }
                                        const __DELIMS = new Delims(delims);
}
                                        const __DELIM = (__DELIMS.delim || this.delims.delim);
                                        const __ROOTDELIM = ((__DELIMS.root !== undefined) ? __DELIMS.root : this.delims.root);
                                        const __HOMEDELIM = ((__DELIMS.home !== undefined) ? __DELIMS.home : this.delims.home);
                                        const __DIRS = (path ? path.split(__DELIM) : []);
                                        const __FIRST = __DIRS[0];


                                        if (__FIRST && (__FIRST !== __ROOTDELIM) && (__FIRST !== __HOMEDELIM)) {
// Setzt eine Option auf einen vorgegebenen Wert
                                            showAlert("Kein absoluter Pfad", this.getPath(__DIRS), this);
// Fuer kontrollierte Auswahl des Values siehe setNextOpt()
                                        }
// opt: Config und vorheriger Value der Option
// value: (Bei allen Typen) Zu setzender Wert
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Wert
function setOpt(opt, value, reload = false) {
    return setOptValue(opt, setStored(getOptName(opt), value, reload, getOptConfig(opt).Serial));
}


                                        return __DIRS.slice(1);
// Ermittelt die naechste moegliche Option
                                    }
// opt: Config und Value der Option
                });
// value: Ggfs. zu setzender Wert
 
// return Zu setzender Wert
// ==================== Ende Abschnitt fuer Klasse Path ====================
function getNextOpt(opt, value = undefined) {
 
     const __CONFIG = getOptConfig(opt);
// ==================== Abschnitt fuer Klasse URI ====================
     const __VALUE = getOptValue(opt, value);
 
// Basisklasse fuer die Verwaltung einer URI/URL
// homePath: Absoluter Startpfad als String
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function URI(homePath, delims) {
     'use strict';
 
    Path.call(this);
 
     const __HOSTPORT = this.getHostPort(homePath);


     this.scheme = this.getSchemePrefix(homePath);
     switch (__CONFIG.Type) {
     this.host = __HOSTPORT.host;
    case __OPTTYPES.MC : return getValue(value, getNextValue(__CONFIG.Choice, __VALUE));
     this.port = this.parseValue(__HOSTPORT.port);
     case __OPTTYPES.SW : return getValue(value, ! __VALUE);
     this.query = this.parseQuery(this.getQueryString(homePath));
     case __OPTTYPES.TF : return getValue(value, ! __VALUE);
     this.node = this.getNodeSuffix(homePath);
     case __OPTTYPES.SD : return getValue(value, __VALUE);
     case __OPTTYPES.SI : break;
    default :            break;
    }


     this.homeDirs = this.getDirs(homePath, { 'home' : "" });
     return __VALUE;
 
    this.home();
}
}


Class.define(URI, Path, {
// Setzt die naechste moegliche Option
              'setDelims'        : function() {
// opt: Config und Value der Option
                                        this.setConst('delims', new UriDelims('/', "..", "", '~', ':', "//", ':', '?', '&', '=', '#'));
// value: Default fuer ggfs. zu setzenden Wert
                                    },
// reload: Seite mit neuem Wert neu laden
              'setSchemeDelim'    : function(schemeDelim = undefined) {
// return Gesetzter Wert
                                        this.delims.setScheme(schemeDelim || ':');
function setNextOpt(opt, value = undefined, reload = true) {
                                    },
     const __CONFIG = getOptConfig(opt);
              'setQueryDelim'    : function(queryDelim = undefined) {
                                        this.delims.setQuery(queryDelim || '?');
                                    },
              'setParSepDelim'    : function(parSepDelim = undefined) {
                                        this.delims.setParSep(parSepDelim || '&');
                                    },
              'setParAssDelim'    : function(parAssDelim = undefined) {
                                        this.delims.setParAss(parAssDelim || '=');
                                    },
              'setNodeDelim'      : function(nodeDelim = undefined) {
                                        this.delims.setNode(nodeDelim || '#');
                                    },
              'getServerPath'     : function(path = undefined) {
                                        return this.stripHostPort(this.stripQueryString(this.stripNodeSuffix(this.stripSchemePrefix(path))));
                                    },
              'getHostPort'      : function(path = undefined) {
                                        const __HOSTDELIM = this.delims.host;
                                        const __PORTDELIM = this.delims.port;
                                        const __ROOTDELIM = this.delims.root + this.delims.delim;
                                        const __NOSCHEME = this.stripSchemePrefix(path);
                                        const __INDEXHOST = (__NOSCHEME ? __NOSCHEME.indexOf(__HOSTDELIM) : -1);
                                        const __PATH = ((~ __INDEXHOST) ? __NOSCHEME.substring(__INDEXHOST + __HOSTDELIM.length) : __NOSCHEME);
                                        const __INDEXHOSTPORT = (__PATH ? __PATH.indexOf(__ROOTDELIM) : -1);
                                        const __HOSTPORT = ((~ __INDEXHOSTPORT) ? __PATH.substring(0, __INDEXHOSTPORT) : undefined);
                                        const __INDEXPORT = (__HOSTPORT ? __HOSTPORT.indexOf(__PORTDELIM) : -1);
                                        const __HOST = ((~ __INDEXPORT) ? __HOSTPORT.substring(0, __INDEXPORT) : __HOSTPORT);
                                        const __PORT = ((~ __INDEXPORT) ? __HOSTPORT.substring(__INDEXPORT + __PORTDELIM.length) : undefined);


                                        return {
    return setOpt(opt, getNextOpt(opt, value), reload);
                                                    'host' : __HOST,
}
                                                    'port' : __PORT
                                                };
                                    },
              'stripHostPort'    : function(path = undefined) {
                                        const __HOSTDELIM = this.delims.host;
                                        const __ROOTDELIM = this.delims.root + this.delims.delim;
                                        const __INDEXHOST = (path ? path.indexOf(__HOSTDELIM) : -1);
                                        const __PATH = ((~ __INDEXHOST) ? path.substring(__INDEXHOST + __HOSTDELIM.length) : path);
                                        const __INDEXHOSTPORT = (__PATH ? __PATH.indexOf(__ROOTDELIM) : -1);


                                        return ((~ __INDEXHOSTPORT) ? __PATH.substring(__INDEXHOSTPORT) : __PATH);
// Setzt eine Option auf einen vorgegebenen Wert (Version mit Key)
                                    },
// Fuer kontrollierte Auswahl des Values siehe setNextOptByName()
              'getSchemePrefix'  : function(path = undefined) {
// optSet: Platz fuer die gesetzten Optionen (und Config)
                                        const __SCHEMEDELIM = this.delims.scheme;
// item: Key der Option
                                        const __INDEXSCHEME = (path ? path.indexOf(__SCHEMEDELIM) : -1);
// value: (Bei allen Typen) Zu setzender Wert
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Wert
function setOptByName(optSet, item, value, reload = false) {
    const __OPT = getOptByName(optSet, item);


                                        return ((~ __INDEXSCHEME) ? path.substring(0, __INDEXSCHEME) : undefined);
    return setOpt(__OPT, value, reload);
                                    },
}
              'stripSchemePrefix' : function(path = undefined) {
                                        const __SCHEMEDELIM = this.delims.scheme;
                                        const __INDEXSCHEME = (path ? path.indexOf(__SCHEMEDELIM) : -1);


                                        return ((~ __INDEXSCHEME) ? path.substring(__INDEXSCHEME + __INDEXSCHEME.length) : path);
// Ermittelt die naechste moegliche Option (Version mit Key)
                                    },
// opt: Config und Value der Option
              'getNodeSuffix'    : function(path = undefined) {
// optSet: Platz fuer die gesetzten Optionen (und Config)
                                        const __NODEDELIM = this.delims.node;
// item: Key der Option
                                        const __INDEXNODE = (path ? path.lastIndexOf(__NODEDELIM) : -1);
// value: Ggfs. zu setzender Wert
 
// return Zu setzender Wert
                                        return ((~ __INDEXNODE) ? path.substring(__INDEXNODE + __NODEDELIM.length) : undefined);
function getNextOptByName(optSet, item, value = undefined) {
                                    },
    const __OPT = getOptByName(optSet, item);
              'stripNodeSuffix'  : function(path = undefined) {
                                        const __NODEDELIM = this.delims.node;
                                        const __INDEXNODE = (path ? path.lastIndexOf(__NODEDELIM) : -1);


                                        return ((~ __INDEXNODE) ? path.substring(0, __INDEXNODE) : path);
    return getNextOpt(__OPT, value);
                                    },
}
              'getQueryString'    : function(path = undefined) {
                                        const __QUERYDELIM = this.delims.query;
                                        const __PATH = this.stripNodeSuffix(path);
                                        const __INDEXQUERY = (__PATH ? __PATH.indexOf(__QUERYDELIM) : -1);


                                        return ((~ __INDEXQUERY) ? __PATH.substring(__INDEXQUERY + __QUERYDELIM.length) : undefined);
// Setzt die naechste moegliche Option (Version mit Key)
                                    },
// opt: Config und Value der Option
              'stripQueryString'  : function(path = undefined) {
// optSet: Platz fuer die gesetzten Optionen (und Config)
                                        const __QUERYDELIM = this.delims.query;
// item: Key der Option
                                        const __INDEXQUERY = (path ? path.indexOf(__QUERYDELIM) : -1);
// value: Ggfs. zu setzender Wert
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Wert
function setNextOptByName(optSet, item, value = undefined, reload = true) {
    const __OPT = getOptByName(optSet, item);


                                        return ((~ __INDEXQUERY) ? path.substring(0, __INDEXQUERY) : path);
    return setNextOpt(__OPT, value, reload);
                                    },
}
              'formatParams'      : function(params, formatFun, delim = ' ', assign = '=') {
                                        const __PARAMS = [];


                                        for (let param in params) {
// Baut das Benutzermenu auf
                                            __PARAMS.push(param + assign + formatFun(params[param]));
// optSet: Gesetzte Optionen
                                        }
function buildMenu(optSet) {
    console.log("buildMenu()");


                                        return __PARAMS.join(delim);
    for (let opt in optSet) {
                                    },
        registerOption(optSet[opt]);
              'parseParams'      : function(params, parseFun, delim = ' ', assign = '=') {
    }
                                        const __RET = { };
}


                                        if (params) {
// Laedt eine (ueber Menu) gesetzte Option
                                            const __PARAMS = params.split(delim);
// opt: Zu ladende Option
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Gesetzter Wert der gelandenen Option
function loadOption(opt, force = false) {
    const __CONFIG = getOptConfig(opt);


                                            for (let index = 0; index < __PARAMS.length; index++) {
    if (! force && __CONFIG.AutoReset) {
                                                const __PARAM = __PARAMS[index];
        return setOptValue(opt, initOptValue(__CONFIG));
    } else if (__CONFIG.Serial) {
        return setOptValue(opt, deserialize(getOptName(opt), getOptValue(opt)));
    } else {
        return setOptValue(opt, GM_getValue(getOptName(opt), getOptValue(opt)));
    }
}


                                                if (__PARAM) {
// Laedt die (ueber Menu) gesetzten Optionen
                                                    const __INDEX = __PARAM.indexOf(assign);
// optSet: Set mit den Optionen
                                                    const __KEY = ((~ __INDEX) ? __PARAM.substring(0, __INDEX) : __PARAM);
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
                                                    const __VAL = ((~ __INDEX) ? parseFun(__PARAM.substring(__INDEX + assign.length)) : true);
// return Set mit den geladenen Optionen
 
function loadOptions(optSet, force = false) {
                                                    __RET[__KEY] = __VAL;
    for (let opt in optSet) {
                                                }
        loadOption(optSet[opt], force);
                                            }
    }
                                        }
 
                                        return __RET;
                                    },
              'rawValue'          : function(value) {
                                        return value;
                                    },
              'parseValue'        : function(value) {
                                        const __VALUE = Number(value);


                                        if (__VALUE == value) {  // schwacher Vergleich true, also Number
    return optSet;
                                            return __VALUE;
}
                                        } else {
                                            const __LOWER = (value ? value.toLowerCase() : undefined);


                                            if ((__LOWER === 'true') || (__LOWER === 'false')) {
// Entfernt eine (ueber Menu) gesetzte Option (falls nicht 'Permanent')
                                                return (value === 'true');
// opt: Gesetzte Option
                                            }
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
                                        }
// reset: Setzt bei Erfolg auf Initialwert der Option
function deleteOption(opt, force = false, reset = true) {
    const __CONFIG = getOptConfig(opt);


                                        return value;
    if (force || ! __CONFIG.Permanent) {
                                    },
        const __NAME = getOptName(opt);
              'getQuery'          : function(delims = { }) {
                                        const __QRYSEP = ((delims.qrySep !== undefined) ? delims.qrySep : this.delims.qrySep);
                                        const __QRYASS = ((delims.qryAss !== undefined) ? delims.qryAss : this.delims.qryAss);


                                        return this.formatParams(this.query, this.rawValue, __QRYSEP, __QRYASS);
        console.log("DELETE " + __NAME);
                                    },
              'parseQuery'        : function(path = undefined, delims = { }) {
                                        const __QRYSEP = ((delims.qrySep !== undefined) ? delims.qrySep : this.delims.qrySep);
                                        const __QRYASS = ((delims.qryAss !== undefined) ? delims.qryAss : this.delims.qryAss);


                                        return this.parseParams(path, this.parseValue, __QRYSEP, __QRYASS);
        GM_deleteValue(__NAME);
                                    },
              'setQuery'          : function(query) {
                                        this.query = query;
                                    },
              'setQueryPar'      : function(key, value) {
                                        this.query[key] = value;
                                    },
              'getQueryPar'      : function(key) {
                                        return this.query[key];
                                    },
              'getPath'          : function(dirs = undefined, delims = undefined) {
                                        const __DELIMS = new UriDelims(delims);
                                        const __SCHEMEDELIM = ((__DELIMS.scheme !== undefined) ? __DELIMS.scheme : this.delims.scheme);
                                        const __HOSTDELIM = ((__DELIMS.host !== undefined) ? __DELIMS.host : this.delims.host);
                                        const __PORTDELIM = ((__DELIMS.port !== undefined) ? __DELIMS.port : this.delims.port);
                                        const __QUERYDELIM = ((__DELIMS.query !== undefined) ? __DELIMS.query : this.delims.query);
                                        const __NODEDELIM = ((__DELIMS.node !== undefined) ? __DELIMS.node : this.delims.node);
                                        const __SCHEMENAME = this.scheme;
                                        const __SCHEME = (__SCHEMENAME ? __SCHEMENAME + __SCHEMEDELIM : "");
                                        const __HOSTNAME = this.host;
                                        const __HOST = (__HOSTNAME ? __HOSTDELIM + __HOSTNAME : "");
                                        const __PORTNR = this.port;
                                        const __PORT = ((__HOSTNAME && __PORTNR) ? __PORTDELIM + __PORTNR : "");
                                        const __QUERYSTR = this.getQuery();
                                        const __QUERY = (__QUERYSTR ? __QUERYDELIM + __QUERYSTR : "");
                                        const __NODENAME = this.node;
                                        const __NODE = (__NODENAME ? __NODEDELIM + __NODENAME : "");


                                        return __SCHEME + __HOST + __PORT + Path.prototype.getPath.call(this, dirs, delims) + __QUERY + __NODE;
        if (reset) {
                                    },
            setOptValue(opt, initOptValue(__CONFIG));
              'getDirs'          : function(path = undefined, delims = undefined) {
        }
                                        const __PATH = this.getServerPath(path);
    }
}


                                        return Path.prototype.getDirs.call(this, __PATH);
// Entfernt die (ueber Menu) gesetzten Optionen (falls nicht 'Permanent')
                                    }
// optSet: Gesetzte Optionen
          });
// optSelect: Liste von ausgewaehlten Optionen, true = entfernen, false = nicht entfernen
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
// reset: Setzt bei Erfolg auf Initialwert der Option
function deleteOptions(optSet, optSelect = undefined, force = false, reset = true) {
    const __DELETEALL = (optSelect === undefined) || (optSelect === true);
    const __OPTSELECT = getValue(optSelect, { });


// ==================== Ende Abschnitt fuer Klasse URI ====================
    for (let opt in optSet) {
        if (getValue(__OPTSELECT[opt], __DELETEALL)) {
            deleteOption(optSet[opt], force, reset);
        }
    }
}


// ==================== Abschnitt fuer Klasse Directory ====================
// Benennt eine Option um und laedt sie ggfs. nach
// opt: Gesetzte Option
// name: Neu zu setzender Name (Speicheradresse)
// reload: Wert nachladen statt beizubehalten
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Umbenannte Option
function renameOption(opt, name, reload = false, force = false) {
    const __NAME = getOptName(opt);


// Basisklasse fuer eine Verzeichnisstruktur
    if (__NAME !== name) {
// homePath: Absoluter Startpfad als String
        deleteOption(opt, true, ! reload);
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Directory(homePath, delims) {
    'use strict';


    Path.call(this, homePath, delims);
        setOptName(opt, name);
}


Class.define(Directory, Path, {
        if (reload) {
                    'chDir' : function(subDir = undefined) {
            loadOption(opt, force);
                                  if (subDir === undefined) {
        }
                                      this.root();
    }
                                  } else if ((typeof subDir) === 'object') {
                                      for (let sub of subDir) {
                                          this.chDir(sub);
                                      }
                                  } else {
                                      if (subDir === this.delims.home) {
                                          this.home();
                                      } else if (subDir === this.delims.back) {
                                          this.up();
                                      } else {
                                          this.down(subDir);
                                      }
                                  }
                              },
                    'pwd'  : function() {
                                  return this.getPath();
                              }
                });


// ==================== Ende Abschnitt fuer Klasse Directory ====================
    return opt;
}


// ==================== Abschnitt fuer Klasse ObjRef ====================
// Ermittelt einen neuen Namen mit einem Prefix. Parameter fuer renameOptions()
 
// name: Gesetzter Name (Speicheradresse)
// Basisklasse fuer eine Objekt-Referenz
// prefix: Prefix, das vorangestellt werden soll
function ObjRef(rootObj) {
// return Neu zu setzender Name (Speicheradresse)
    'use strict';
function prefixName(name, prefix) {
 
     return (prefix + name);
    Directory.call(this, undefined, new Delims('/', "..", '/', '~'));
 
     this.setConst('rootObj', rootObj); // Wichtig: Verweis nicht verfolgen! Gefahr durch Zyklen!
}
}


Class.define(ObjRef, Directory, {
// Ermittelt einen neuen Namen mit einem Postfix. Parameter fuer renameOptions()
                    'valueOf' : function() {
// name: Gesetzter Name (Speicheradresse)
                                    let ret = this.rootObj;
// postfix: Postfix, das angehaengt werden soll
 
// return Neu zu setzender Name (Speicheradresse)
                                    for (let name of this.dirs) {
function postfixName(name, postfix) {
                                        if (ret === undefined) {
     return (name + postfix);
                                            break;
                                        }
                                        ret = ret[name];
                                    }
 
                                    return ret;
                                }
                });
 
// ==================== Ende Abschnitt fuer Klasse ObjRef ====================
 
// ==================== Abschnitt fuer diverse Utilities ====================
 
// Gibt einen Wert zurueck. Ist dieser nicht definiert oder null, wird ein Alternativwert geliefert
// value: Ein Wert. Ist dieser nicht undefined oder null, wird er zurueckgeliefert (oder retValue)
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
// retValue: Falls definiert, Rueckgabe-Wert fuer den Fall, dass value nicht undefined oder null ist
// return Der Wert. Sind weder value noch defValue definiert, dann undefined
function getValue(value, defValue = undefined, retValue = undefined) {
     return ((value === undefined) || (value === null)) ? defValue : (retValue === undefined) ? value : retValue;
}
}


// Gibt einen Wert zurueck. Ist dieser nicht definiert, wird ein Alternativwert geliefert
// Benennt selektierte Optionen nach einem Schema um und laedt sie ggfs. nach
// value: Ein Wert. Ist dieser definiet und in den Grenzen, wird er zurueckgeliefert
// optSet: Gesetzte Optionen
// minValue: Untere Grenze fuer den Wert, falls angegeben
// optSelect: Liste von ausgewaehlten Optionen, true = nachladen, false = nicht nachladen
// minValue: Obere Grenze fuer den Wert, falls angegeben
// 'reload': Option nachladen?
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist oder der Wert ausserhalb liegt
// 'force': Option auch mit 'AutoReset'-Attribut nachladen?
// return Der Wert. Sind weder value (in den Grenzen) noch defValue definiert, dann undefined
// renameParam: Wird an renameFun uebergeen
function getValueIn(value, minValue = undefined, maxValue = undefined, defValue = undefined) {
// renameFun: function(name, param) zur Ermittlung des neuen Namens
    const __VALUE = getValue(value, defValue);
// name: Neu zu setzender Name (Speicheradresse)
 
// reload: Wert nachladen statt beizubehalten
     if ((minValue !== undefined) && (__VALUE < minValue)) {
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
         return defValue;
// return Umbenannte Option
    }
function renameOptions(optSet, optSelect, renameParam = undefined, renameFun = prefixName) {
    if ((maxValue !== undefined) && (__VALUE > maxValue)) {
     if (renameFun === undefined) {
        return defValue;
         console.error("RENAME: Illegale Funktion!");
     }
     }
    for (let opt in optSelect) {
        const __OPTPARAMS = optSelect[opt];
        const __OPT = optSet[opt];


    return __VALUE;
        if (__OPT === undefined) {
}
            console.error("RENAME: Option '" + opt + "' nicht gefunden!");
 
        } else {
// Ermittelt den naechsten Wert aus einer Array-Liste
            const __NAME = getOptName(__OPT);
// arr: Array-Liste mit den moeglichen Werte
            const __NEWNAME = renameFun(__NAME, renameParam);
// value: Vorher gesetzter Wert
            // Laedt die unter dem neuen Namen gespeicherten Daten nach?
// return Naechster Wert in der Array-Liste
            const __RELOAD = ((typeof __OPTPARAMS === 'boolean') ? __OPTPARAMS : __OPTPARAMS.reload);
function getNextValue(arr, value) {
            // Laedt auch Optionen mit 'AutoReset'-Attribut?
    const __POS = arr.indexOf(value) + 1;
            const __FORCE = ((typeof __OPTPARAMS === 'boolean') ? true : __OPTPARAMS.force);
 
            renameOption(__OPT, __NEWNAME, __FORCE);


    return arr[getValueIn(__POS, 0, arr.length - 1, 0)];
            if (__RELOAD) {
                // ... und nachladen...
                loadOption(__OPT, __FORCE);
            }
        }
    }
}
}


// Gibt ein Produkt zurueck. Ist einer der Multiplikanten nicht definiert, wird ein Alternativwert geliefert
// Setzt die Optionen in optSet auf die "Werkseinstellungen" des Skripts
// valueA: Ein Multiplikant. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
// optSet: Gesetzte Optionen
// valueB: Ein Multiplikant. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
// reload: Seite mit "Werkseinstellungen" neu laden
// digits: Anzahl der Stellen nach dem Komma fuer das Produkt (Default: 0)
function resetOptions(optSet, reload = true) {
// defValue: Default-Wert fuer den Fall, dass ein Multiplikant nicht gesetzt ist (Default: NaN)
    // Alle (nicht 'Permanent') gesetzten Optionen entfernen...
// return Das Produkt auf digits Stellen genau. Ist dieses nicht definiert, dann defValue
    deleteOptions(optSet, true, false, ! reload);
function getMulValue(valueA, valueB, digits = 0, defValue = NaN) {
    let product = defValue;


     if ((valueA !== undefined) && (valueB !== undefined)) {
     if (reload) {
         product = parseFloat(valueA) * parseFloat(valueB);
         // ... und Seite neu laden (mit "Werkseinstellungen")...
    }
        window.location.reload();
 
    if (isNaN(product)) {
        product = defValue;
     }
     }
    return parseFloat(product.toFixed(digits));
}
}


// Gibt eine Ordinalzahl zur uebergebenen Zahl zurueck
// ==================== Spezialisierter Abschnitt fuer Optionen ====================
// value: Eine ganze Zahl
// defValue: Default-Wert fuer den Fall, dass der Wert nicht gesetzt ist (Default: '*')
// return Die Ordinalzahl als String der Form "n." oder defValue, falls nicht angegeben
function getOrdinal(value, defValue = '*') {
    return getValue(value, defValue, value + '.');
}


// Hilfsfunktion fuer Array.sort(): Vergleich zweier Zahlen
// Gesetzte Optionen (wird von initOptions() angelegt und von loadOptions() gefuellt):
// valueA: Erster Zahlenwert
const __OPTSET = { };
// valueB: Zweiter Zahlenwert
 
// return -1 = kleiner, 0 = gleich, +1 = groesser
// Teamparameter fuer getrennte Speicherung der Optionen fuer Erst- und Zweitteam...
function compareNumber(valueA, valueB) {
const __MYTEAM = { 'Team' : undefined, 'Liga' : undefined, 'Land' : undefined, 'LdNr' : 0, 'LgNr' : 0 };
    return +(valueA > valueB) || (+(valueA === valueB) - 1);
}


// Ueberprueft, ob ein Objekt einer bestimmten Klasse angehoert (ggfs. per Vererbung)
// Optionen mit Daten, die ZAT- und Team-bezogen gemerkt werden...
// obj: Ein (generisches) Objekt
const __DATAOPTS = {
// base: Eine Objektklasse (Konstruktor-Funktion)
                      'datenZat'  : true,
// return true, wenn der Prototyp rekursiv gefunden werden konnte
                      'birthdays'  : true,
function instanceOf(obj, base) {
                      'tClasses'  : true,
    while (obj !== null) {
                      'progresses' : true,
        if (obj === base.prototype) {
                      'zatAges'    : true,
            return true;
                      'trainiert'  : true,
        }
                      'positions: true
        if ((typeof obj) === 'xml') { // Sonderfall mit Selbstbezug
                  };
            return (base.prototype === XML.prototype);
        }
        obj = Object.getPrototypeOf(obj);
    }


    return false;
// Behandelt die Optionen und laedt das Benutzermenu
}
// optConfig: Konfiguration der Optionen
// optSet: Platz fuer die gesetzten Optionen
// optParams: Eventuell notwendige Parameter zur Initialisierung
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
// 'teamParams': Getrennte "ligaSize"-Option wird genutzt, hier: __MYTEAM mit 'LdNr'/'LgNr' des Erst- bzw. Zweitteams
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return Gefuelltes Objekt mit den gesetzten Optionen
function buildOptions(optConfig, optSet = undefined, optParams = { 'hideMenu' : false }) {
    const __TEAMPARAMS = optParams.teamParams; // Ermittelte Parameter


// Liefert alle Basisklassen des Objekts (inkl. Vererbung)
    optSet = startOptions(optConfig, optSet);
// obj: Ein (generisches) Objekt
// return true, wenn der Prototyp rekursiv gefunden werden konnte
function getPrototypes(obj) {
    let ret = [];


     while (obj !== null) {
     if (__TEAMPARAMS !== undefined) {
         const __PROTO = Object.getPrototypeOf(obj);
        __MYTEAM.Team = __TEAMPARAMS.Team;
        __MYTEAM.Liga = __TEAMPARAMS.Liga;
        __MYTEAM.Land = __TEAMPARAMS.Land;
        __MYTEAM.LdNr = __TEAMPARAMS.LdNr;
        __MYTEAM.LgNr = __TEAMPARAMS.LgNr;
        console.log("Ermittelt: " + JSON.stringify(__MYTEAM));
        // ... und abspeichern...
        setOpt(optSet.team, __MYTEAM, false);
    } else {
         const __TEAM = getOptValue(optSet.team); // Gespeicherte Parameter


         ret.push(__PROTO);
         if ((__TEAM !== undefined) && (__TEAM.Land !== undefined)) {
        if ((typeof obj) === 'xml') { // Sonderfall mit Selbstbezug
            __MYTEAM.Team = __TEAM.Team;
             break;
            __MYTEAM.Liga = __TEAM.Liga;
            __MYTEAM.Land = __TEAM.Land;
            __MYTEAM.LdNr = __TEAM.LdNr;
            __MYTEAM.LgNr = __TEAM.LgNr;
            console.log("Gespeichert: " + JSON.stringify(__MYTEAM));
        } else {
             console.error("Unbekannt: " + JSON.stringify(__TEAM));
         }
         }
        obj = __PROTO;
     }
     }


     return ret;
     if (__MYTEAM.LdNr !== undefined) {
}
        // Prefix fuer die Optionen 'datenZat', 'birthdays', 'tClasses', 'progresses',
        // 'zatAges', 'trainiert' und 'positions' zur gesonderten Behandlung...
        const __PREFIX = __MYTEAM.LdNr.toString() + __MYTEAM.LgNr.toString();


// Liefert alle Attribute/Properties des Objekts (inkl. Vererbung)
        // Team-bezogene Daten umbenennen...
// obj: Ein (generisches) Objekt
        renameOptions(optSet, __DATAOPTS, __PREFIX, prefixName);
// return Array von Items (Property-Namen)
     }
function getAllProperties(obj) {
     let ret = [];


     for (let o = obj; o !== null; o = Object.getPrototypeOf(o)) {
     showOptions(optSet, optParams);
      ret = ret.concat(Object.getOwnPropertyNames(o));
    }


     return ret;
     return optSet;
}
}


// Ueberpruefung, ob ein Item aktiv ist oder nicht
// ==================== Abschnitt fuer diverse Utilities ====================
// item: Name des betroffenen Items
// inList: Checkliste der inkludierten Items (Positivliste, true fuer aktiv)
// exList: Checkliste der exkludierten Items (Negativliste, true fuer inaktiv)
// return Angabe, ob das Item aktiv ist
function checkItem(item, inList = undefined, exList = undefined) {
    let active = true;


     if (inList) {
// Legt Input-Felder in einem Form-Konstrukt an, falls noetig
         active = (inList[item] === true); // gesetzt und true
// form: <form>...</form>
    }
// props: Map von name:value-Paaren
    if (exList) {
// type: Typ der Input-Felder (Default: unsichtbare Daten)
        if (exList[item] === true) {  // gesetzt und true
// return Ergaenztes Form-Konstrukt
             active = false; // NICHT anzeigen
function addInputField(form, props, type = "hidden") {
     for (let fieldName in props) {
         let field = form[fieldName];
        if (! field) {
            field = document.createElement("input");
            field.type = type;
            field.name = fieldName;
             form.appendChild(field);
         }
         }
        field.value = props[fieldName];
     }
     }


     return active;
     return form;
}
}


// Fuegt Properties zu einem Objekt hinzu, die in einem zweiten stehen. Doppelte Werte werden ueberschrieben
// Legt unsichtbare Input-Daten in einem Form-Konstrukt an, falls noetig
// data: Objekt, dem Daten hinzugefuegt werden
// form: <form>...</form>
// addData: Objekt, das zusaetzliche Properties enthaelt
// props: Map von name:value-Paaren
// addList: Checkliste der zu setzenden Items (true fuer kopieren), falls angegeben
// return Ergaenztes Form-Konstrukt
// ignList: Checkliste der ignorierten Items (true fuer auslassen), falls angegeben
function addHiddenField(form, props) {
// return Das gemergete Objekt mit allen Properties
     return addInputField(form, props, "hidden");
function addProps(data, addData, addList = undefined, ignList = undefined) {
     for (let item in getValue(addData, { })) {
        if (checkItem(item, addList, ignList)) {
            data[item] = addData[item];
        }
    }
 
    return data;
}
}


// Entfernt Properties in einem Objekt
// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
// data: Objekt, deren Properties bearbeitet werden
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// delList: Checkliste der zu loeschenden Items (true fuer loeschen), falls angegeben
// type: Name des Events, z.B. "click"
// ignList: Checkliste der ignorierten Items (true fuer auslassen), falls angegeben
// callback: Funktion als Reaktion
// return Das veraenderte Objekt ohne die geloeschten Properties
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
function delProps(data, delList = undefined, ignList = undefined) {
// return false bei Misserfolg
     for (let item in getValue(data, { })) {
function addEvent(obj, type, callback, capture = false) {
         if (checkItem(item, delList, ignList)) {
     if (obj.addEventListener) {
            delete data[item];
         return obj.addEventListener(type, callback, capture);
        }
     } else if (obj.attachEvent) {
     }
        return obj.attachEvent("on" + type, callback);
 
    } else {
    return data;
        console.log("Could not add " + type + " event:");
}
        console.log(callback);
 
 
// Gibt den Wert einer Property zurueck. Ist dieser nicht definiert oder null, wird er vorher gesetzt
         return false;
// obj: Ein Objekt. Ist dieses undefined oder null, wird defValue zurueckgeliefert
// item: Key des Properties
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
// return Der Wert des Properties. Sind das obj oder das Property und defValue undefined oder null, dann undefined
function getProp(obj, item, defValue = undefined) {
    if ((obj === undefined) || (obj === null)) {
         return defValue;
     }
     }
}


     const __PROP = obj[item];
// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function removeEvent(obj, type, callback, capture = false) {
     if (obj.removeEventListener) {
        return obj.removeEventListener(type, callback, capture);
    } else if (obj.detachEvent) {
        return obj.detachEvent("on" + type, callback);
    } else {
        console.log("Could not remove " + type + " event:");
        console.log(callback);


    if ((__PROP !== undefined) && (__PROP !== null)) {
         return false;
         return __PROP;
     }
     }
    return (obj[item] = defValue);
}
}


// Sicheres obj.valueOf() fuer alle Daten
// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
// data: Objekt oder Wert
// id: ID des betroffenen Eingabeelements
// return Bei Objekten valueOf() oder das Objekt selber, bei Werten der Wert
// type: Name des Events, z.B. "click"
function valueOf(data) {
// callback: Funktion als Reaktion
     return (((typeof data) === 'object') ? data.valueOf() : data);
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
}
// return false bei Misserfolg
function addDocEvent(id, type, callback, capture = false) {
     const __OBJ = document.getElementById(id);


// Sicheres JSON.stringify(), das auch mit Zyklen umgehen kann
     return addEvent(__OBJ, type, callback, capture);
// value: Auszugebene Daten. Siehe JSON.stringify()
// replacer: Elementersetzer. Siehe JSON.stringify()
// space: Verschoenerung. Siehe JSON.stringify()
// cycleReplacer: Ersetzer im Falle von Zyklen
// return String mit Ausgabe der Objektdaten
function safeStringify(value, replacer = undefined, space = undefined, cycleReplacer = undefined) {
     return JSON.stringify(value, serializer(replacer, cycleReplacer), space);
}
}


// Hilfsfunktion fuer safeStringify(): Kapselt replacer und einen cycleReplacer fuer Zyklen
// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
// replacer: Elementersetzer. Siehe JSON.stringify()
// id: ID des betroffenen Eingabeelements
// cycleReplacer: Ersetzer im Falle von Zyklen
// type: Name des Events, z.B. "click"
// return Ersetzer-Funktion fuer JSON.stringify(), die beide Ersetzer vereint
// callback: Funktion als Reaktion
function serializer(replacer = undefined, cycleReplacer = undefined) {
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
     const __STACK = [];
// return false bei Misserfolg
    const __KEYS = [];
function removeDocEvent(id, type, callback, capture = false) {
 
     const __OBJ = document.getElementById(id);
    if (! cycleReplacer) {
        cycleReplacer = function(key, value) {
                if (__STACK[0] === value) {
                    return "[~]";
                }
                return "[~." + __KEYS.slice(0, __STACK.indexOf(value)).join('.') + ']';
            };
    }


     return function(key, value) {
     return removeEvent(__OBJ, type, callback, capture);
            if (__STACK.length) {
}
                const __THISPOS = __STACK.indexOf(this);


                if (~ __THISPOS) {
// Hilfsfunktion fuer die Ueberpruefung, ob ein Item sichtbar sein soll
                    __STACK.splice(__THISPOS + 1);
// item: Name des betroffenen Items
                    __KEYS.splice(__THISPOS, Infinity, key);
// showList: Checkliste der sichtbaren Items (true fuer sichtbar)
                } else {
// hideList: Checkliste der unsichtbaren Items (true fuer unsichtbar)
                    __STACK.push(this);
// return Angabe, ob das Item sichtbar sein soll
                    __KEYS.push(key);
function checkVisible(item, showList, hideList = undefined) {
                }
    let show = true;
                if (~ __STACK.indexOf(value)) {
                    value = cycleReplacer.call(this, key, value);
                }
            } else {
                __STACK.push(value);
            }


            return ((! replacer) ? value : replacer.call(this, key, value));
    if (showList !== undefined) {
         };
         show = (showList[item] === true)// gesetzt und true
}
    }
 
    if (hideList !== undefined) {
// Replacer fuer JSON.stringify() oder safeStringify(), der Arrays kompakter darstellt
        if (hideList[item] === true) { // gesetzt und true
// key: Der uebergebene Schluessel
            show = false;  // NICHT anzeigen
// value: Der uebergebene Wert
         }
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
function replaceArraySimple(key, value) {
    if (Array.isArray(value)) {
         return "[ " + value.join(", ") + " ]";
     }
     }


     return value;
     return show;
}
}


// Replacer fuer JSON.stringify() oder safeStringify(), der Arrays kompakter darstellt
// Hilfsfunktion fuer die Ermittlung eines Elements der Seite
// key: Der uebergebene Schluessel
// name: Name des Elements (siehe "name=")
// value: Der uebergebene Wert
// index: Laufende Nummer des Elements (0-based), Default: 0
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
// doc: Dokument (document)
function replaceArray(key, value) {
// return Gesuchtes Element mit der lfd. Nummer index oder undefined (falls nicht gefunden)
     if (Array.isArray(value)) {
function getElement(name, index = 0, doc = document) {
        __RET = value.map(function(element) {
     const __TAGS = document.getElementsByName(name);
                              return safeStringify(element, replaceArray, 0);
    const __TABLE = (__TAGS === undefined) ? undefined : __TAGS[index];
                          });


        return __RET;
     return __TABLE;
    }
 
     return value;
}
}


// Moegliche einfache Ersetzungen mit '$'...
// Hilfsfunktion fuer die Ermittlung eines Elements der Seite (Default: Tabelle)
let textSubst;
// index: Laufende Nummer des Elements (0-based)
// tag: Tag des Elements ("table")
// doc: Dokument (document)
// return Gesuchtes Element oder undefined (falls nicht gefunden)
function getTable(index, tag = "table", doc = document) {
    const __TAGS = document.getElementsByTagName(tag);
    const __TABLE = (__TAGS === undefined) ? undefined : __TAGS[index];


// Substituiert '$'-Parameter in einem Text
    return __TABLE;
// text: Urspruenglicher Text mit '$'-Befehlen
}
// par1: Der (erste) uebergebene Parameter
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
function substParam(text, par1) {
    let ret = getValue(text, "");


    if (! textSubst) {
// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
        textSubst  = {
// index: Laufende Nummer des Elements (0-based)
                'n' : __DBMOD.name,
// doc: Dokument (document)
                'v' : __DBMOD.version,
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
                'V' : __DBMOD.Name
function getRows(index, doc = document) {
            };
    const __TABLE = getTable(index, "table", doc);
    }
    const __ROWS = (__TABLE === undefined) ? undefined : __TABLE.rows;


    for (let ch in textSubst) {
     return __ROWS;
        const __SUBST = textSubst[ch];
 
        ret = ret.replace('$' + ch, __SUBST);
    }
 
     return ret.replace('$', par1);
}
}


// Fuegt in die uebergebene Zahl Tausender-Trennpunkte ein
// ==================== Abschnitt fuer Optionen auf der Seite ====================
// Wandelt einen etwaig vorhandenen Dezimalpunkt in ein Komma um
// numberString: Dezimalzahl als String
// return Diese Dezimalzahl als String mit Tausender-Trennpunkten und Komma statt Dezimalpunkt
function getNumberString(numberString) {
    if (numberString.lastIndexOf('.') !== -1) {
        // Zahl enthaelt Dezimalpunkt
        const __VORKOMMA = numberString.substring(0, numberString.lastIndexOf('.'));
        const __NACHKOMMA = numberString.substring(numberString.lastIndexOf('.') + 1, numberString.length);


         return getNumberString(__VORKOMMA) + ',' + __NACHKOMMA;
// Liefert den Funktionsaufruf zur Option als String
    } else {
// opt: Auszufuehrende Option
        // Kein Dezimalpunkt, fuege Tausender-Trennpunkte ein:
// isAlt: Angabe, ob AltAction statt Action gemeint ist
         // String umdrehen, nach jedem dritten Zeichen Punkt einfuegen, dann wieder umdrehen:
// value: Ggfs. zu setzender Wert
         const __TEMP = reverseString(numberString);
// serial: Serialization fuer String-Werte (Select, Textarea)
         let result = "";
// memory: __OPTMEM.normal = bis Browserende gespeichert (sessionStorage), __OPTMEM.unbegrenzt = unbegrenzt gespeichert (localStorage), __OPTMEM.inaktiv
// return String mit dem (reinen) Funktionsaufruf
function getFormAction(opt, isAlt = false, value = undefined, serial = undefined, memory = undefined) {
    const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;
    const __MEMSTR = __STORAGE.Display;
    const __RUNPREFIX = __STORAGE.Prefix;
 
    if (__MEMORY !== undefined) {
         const __RELOAD = "window.location.reload()";
        const __SETITEM = function(item, val, quotes = true) {
                              return (__MEMSTR + ".setItem('" + __RUNPREFIX + item + "', " + (quotes ? "'" + val + "'" : val) + "),");
                          };
        const __SETITEMS = function(cmd, key = undefined, val = undefined) {
                              return ('(' + __SETITEM('cmd', cmd) + ((key === undefined) ? "" :
                                      __SETITEM('key', key) + __SETITEM('val', val, false)) + __RELOAD + ')');
                          };
         const __CONFIG = getOptConfig(opt);
        const __SERIAL = getValue(serial, getValue(__CONFIG.Serial, false));
        const __THISVAL = ((__CONFIG.ValType === "String") ? "'\\x22' + this.value + '\\x22'" : "this.value");
         const __TVALUE = getValue(__CONFIG.ValType, __THISVAL, "new " + __CONFIG.ValType + '(' + __THISVAL + ')');
         const __VALSTR = ((value !== undefined) ? JSON.stringify(value) : __SERIAL ? "JSON.stringify(" + __TVALUE + ')' : __TVALUE);
        const __ACTION = (isAlt ? getValue(__CONFIG.AltAction, __CONFIG.Action) : __CONFIG.Action);


         for (let i = 0; i < __TEMP.length; i++) {
         if (__ACTION !== undefined) {
             if ((i > 0) && (i % 3 === 0)) {
            switch (__ACTION) {
                result += '.';
            case __OPTACTION.SET : //return "doActionSet('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                  return __SETITEMS('SET', getOptName(opt), __VALSTR);
             case __OPTACTION.NXT : //return "doActionNxt('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                  return __SETITEMS('NXT', getOptName(opt), __VALSTR);
            case __OPTACTION.RST : //return "doActionRst()";
                                  return __SETITEMS('RST');
            default :              break;
             }
             }
            result += __TEMP.substr(i, 1);
         }
         }
    }


        return reverseString(result);
    return undefined;
    }
}
}


// Dreht den uebergebenen String um
// Liefert die Funktionsaufruf zur Option als String
// string: Eine Zeichenkette
// opt: Auszufuehrende Option
// return Dieselbe Zeichenkette rueckwaerts
// isAlt: Angabe, ob AltAction statt Action gemeint ist
function reverseString(string) {
// value: Ggfs. zu setzender Wert
     let result = "";
// type: Event-Typ fuer <input>, z.B. "click" fuer "onclick="
// serial: Serialization fuer String-Werte (Select, Textarea)
// memory: __OPTMEM.normal = bis Browserende gespeichert (sessionStorage), __OPTMEM.unbegrenzt = unbegrenzt gespeichert (localStorage), __OPTMEM.inaktiv
// return String mit dem (reinen) Funktionsaufruf
function getFormActionEvent(opt, isAlt = false, value = undefined, type = "click", serial = undefined, memory = undefined) {
     const __ACTION = getFormAction(opt, isAlt, value, serial, memory);


     for (let i = string.length - 1; i >= 0; i--) {
     return getValue(__ACTION, "", ' on' + type + '="' + __ACTION + '"');
        result += string.substr(i, 1);
    }
 
    return result;
}
}


// Speichert einen String/Integer/Boolean-Wert unter einem Namen ab
// Zeigt eine Option auf der Seite als Auswahlbox an
// name: GM.setValue()-Name, unter dem die Daten gespeichert werden
// opt: Anzuzeigende Option
// value: Zu speichernder String/Integer/Boolean-Wert
// return String mit dem HTML-Code
// return Promise auf ein Objekt, das 'name' und 'value' der Operation enthaelt
function getOptionSelect(opt) {
function storeValue(name, value) {
    const __CONFIG = getOptConfig(opt);
     __LOG[4](name + " >> " + value);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt);
    const __ACTION = getFormActionEvent(opt, false, undefined, "change", undefined);
    const __FORMLABEL = getValue(__CONFIG.FormLabel, __CONFIG.Label);
     const __LABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
    let element = '<select name="' + __NAME + '" id="' + __NAME + '"' + __ACTION + '>';


     return GM.setValue(name, value).then(voidValue => {
     for (let value of __CONFIG.Choice) {
            __LOG[5]("OK " + name + " >> " + value);
        element += '\n<option value="' + value + '"' +
                  ((value === __VALUE) ? ' SELECTED' : "") +
                  '>' + value + '</option>';
    }
    element += '\n</select>';


            return Promise.resolve({
    return __LABEL.replace('$', element);
                    'name' : name,
                    'value' : value
                });
        }, defaultCatch);
}
}


// Holt einen String/Integer/Boolean-Wert unter einem Namen zurueck
// Zeigt eine Option auf der Seite als Radiobutton an
// name: GM.getValue()-Name, unter dem die Daten gespeichert wurden
// opt: Anzuzeigende Option
// defValue: Default-Wert fuer den Fall, dass nichts gespeichert ist
// return String mit dem HTML-Code
// return Promise fuer den String/Integer/Boolean-Wert, der unter dem Namen gespeichert war
function getOptionRadio(opt) {
function summonValue(name, defValue = undefined) {
    const __CONFIG = getOptConfig(opt);
     return GM.getValue(name, defValue).then(value => {
    const __NAME = getOptName(opt);
            __LOG[4](name + " << " + value);
     const __VALUE = getOptValue(opt, false);
 
    const __ACTION = getFormActionEvent(opt, false, true, "click", false);
            return Promise.resolve(value);
    const __ALTACTION = getFormActionEvent(opt, true, false, "click", false);
        }, ex => {
    const __ELEMENTON  = '<input type="radio" name="' + __NAME +
            __LOG[0](name + ": " + ex.message);
                        '" id="' + __NAME + 'ON" value="1"' +
                        (__VALUE ? ' CHECKED' : __ACTION) +
                        ' /><label for="' + __NAME + 'ON">' +
                        __CONFIG.Label + '</label>';
    const __ELEMENTOFF = '<input type="radio" name="' + __NAME +
                        '" id="' + __NAME + 'OFF" value="0"' +
                        (__VALUE ? __ALTACTION : ' CHECKED') +
                        ' /><label for="' + __NAME + 'OFF">' +
                        __CONFIG.AltLabel + '</label>';


            return Promise.reject(ex);
    return [ __ELEMENTON, __ELEMENTOFF ];
        }, defaultCatch);
}
}


// Speichert einen beliebiegen (strukturierten) Wert unter einem Namen ab
// Zeigt eine Option auf der Seite als Checkbox an
// name: GM.setValue()-Name, unter dem die Daten gespeichert werden
// opt: Anzuzeigende Option
// value: Beliebiger (strukturierter) Wert
// return String mit dem HTML-Code
// return Promise auf ein Objekt, das 'name' und 'value' in der String-Darstellung des Wertes enthaelt
function getOptionCheckbox(opt) {
function serialize(name, value) {
     const __CONFIG = getOptConfig(opt);
     const __STREAM = ((value !== undefined) ? safeStringify(value) : value);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, "click", false);
    const __FORMLABEL = getValue(__CONFIG.FormLabel, __CONFIG.Label);


     return storeValue(name, __STREAM);
     return '<input type="checkbox" name="' + __NAME +
          '" id="' + __NAME + '" value="' + __VALUE + '"' +
          (__VALUE ? ' CHECKED' : "") + __ACTION + ' /><label for="' +
          __NAME + '">' + __FORMLABEL + '</label>';
}
}


// Holt einen beliebiegen (strukturierter) Wert unter einem Namen zurueck
// Zeigt eine Option auf der Seite als Daten-Textfeld an
// name: GM.getValue()-Name, unter dem die Daten gespeichert wurden
// opt: Anzuzeigende Option
// defValue: Default-Wert fuer den Fall, dass nichts gespeichert ist
// return String mit dem HTML-Code
// return Promise fuer das Objekt, das unter dem Namen gespeichert war
function getOptionTextarea(opt) {
function deserialize(name, defValue = undefined) {
    const __CONFIG = getOptConfig(opt);
     return summonValue(name).then(stream => {
    const __NAME = getOptName(opt);
            if (stream && stream.length) {
     const __VALUE = getOptValue(opt);
                return JSON.parse(stream);
    const __ACTION = getFormActionEvent(opt, false, undefined, "submit", undefined);
            } else {
    const __SUBMIT = getValue(__CONFIG.Submit, "");
                return defValue;
    const __ONSUBMIT = ((__SUBMIT.length > 0) ? ' onKeyDown="' + __SUBMIT + '"': "");
            }
    const __FORMLABEL = getValue(__CONFIG.FormLabel, __CONFIG.Label);
        });
    const __ELEMENTLABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
}
    const __ELEMENTTEXT = '<textarea name="' + __NAME + '" id="' + __NAME + '" cols="' + __CONFIG.Cols +
                          '" rows="' + __CONFIG.Rows + '"' + __ONSUBMIT + __ACTION + '>' +
                          JSON.stringify(__VALUE, __CONFIG.Replace, __CONFIG.Space) + '</textarea>';


// Setzt die Seite gemaess der Aenderungen zurueck...
     return [ __ELEMENTLABEL, __ELEMENTTEXT ];
// reload: Seite wird ganz neu geladen
function refreshPage(reload = true) {
     if (reload) {
        __LOG[2]("Seite wird neu geladen...");
        window.location.reload();
    }
}
}


// Setzt eine Option dauerhaft und laedt die Seite neu
// Zeigt eine Option auf der Seite als Button an
// name: Name der Option als Speicherort
// opt: Anzuzeigende Option
// value: Zu setzender Wert
// return String mit dem HTML-Code
// reload: Seite mit neuem Wert neu laden
function getOptionButton(opt) {
// serial: Serialization fuer komplexe Daten
    const __CONFIG = getOptConfig(opt);
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
    const __NAME = getOptName(opt);
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
    const __VALUE = getOptValue(opt, false);
// return Gespeicherter Wert fuer setOptValue()
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, "click", false);
function setStored(name, value, reload = false, serial = false, onFulfilled = undefined, onRejected = undefined) {
     const __BUTTONLABEL = (__VALUE ? __CONFIG.AltLabel : __CONFIG.Label);
     (serial ? serialize(name, value)
    const __FORMLABEL = getValue(__CONFIG.FormLabel, __CONFIG.Label);
            : storeValue(name, value))
                .then(onFulfilled, onRejected)
                .then(() => refreshPage(reload), defaultCatch); // Ende der Kette...


     return value;
     return '<label for="' + __NAME + '">' + __FORMLABEL +
          '</label><input type="button" name="' + __NAME +
          '" id="' + __NAME + '" value="' + __BUTTONLABEL + '"' +
          __ACTION + '/>';
}
}


// Setzt den naechsten Wert aus einer Array-Liste als Option
// Zeigt eine Option auf der Seite an (je nach Typ)
// arr: Array-Liste mit den moeglichen Optionen
// opt: Anzuzeigende Option
// name: Name der Option als Speicherort
// return String mit dem HTML-Code
// value: Vorher gesetzter Wert
function getOptionElement(opt) {
// reload: Seite mit neuem Wert neu laden
    const __CONFIG = getOptConfig(opt);
// serial: Serialization fuer komplexe Daten
    const __TYPE = getValue(__CONFIG.FormType, __CONFIG.Type);
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
    let element = "";
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
 
// return Gespeicherter Wert fuer setOptValue()
    if (! __CONFIG.Hidden) {
function setNextStored(arr, name, value, reload = false, serial = false, onFulfilled = undefined, onRejected = undefined) {
        switch (__TYPE) {
    return setStored(name, getNextValue(arr, value), reload, serial, onFulfilled, onRejected);
        case __OPTTYPES.MC : element = getOptionSelect(opt);
}
                            break;
        case __OPTTYPES.SW : if (__CONFIG.FormLabel !== undefined) {
                                element = getOptionCheckbox(opt);
                            } else {
                                element = getOptionRadio(opt);
                            }
                            break;
        case __OPTTYPES.TF : element = getOptionCheckbox(opt);
                            break;
        case __OPTTYPES.SD : element = getOptionTextarea(opt);
                            break;
        case __OPTTYPES.SI : element = getOptionButton(opt);
                            break;
        default :            break;
        }


// Fuehrt die in einem Storage gespeicherte Operation aus
        if (element.length === 2) {
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
            element = '<div>' + element[0] + '<br />' + element[1] + '</div>';
// return Array von Objekten mit 'cmd' / 'key' / 'val' (derzeit maximal ein Kommando) oder undefined
        }
function getStoredCmds(memory = undefined) {
     }
    const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;
     const __RUNPREFIX = __STORAGE.Prefix;
    const __STOREDCMDS = [];


     if (__MEMORY !== undefined) {
     return element;
        const __GETITEM = function(item) {
}
                              return __MEMORY.getItem(__RUNPREFIX + item);
                          };
        const __DELITEM = function(item) {
                              return __MEMORY.removeItem(__RUNPREFIX + item);
                          };
        const __CMD = ((__MEMORY !== undefined) ? __GETITEM('cmd') : undefined);


        if (__CMD !== undefined) {
// Baut das Benutzermenu auf der Seite auf
            const __KEY = __GETITEM('key');
// optSet: Gesetzte Optionen
            let value = __GETITEM('val');
// optParams: Eventuell notwendige Parameter
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return String mit dem HTML-Code
function getForm(optSet, optParams = { }) {
    const __FORM = '<form id="options" method="POST"><table><tbody><tr>';
    const __FORMEND = '</tr></tbody></table></form>';
    const __FORMWIDTH = getValue(optParams.formWidth, 3);
    const __FORMBREAK = getValue(optParams.formBreak, __FORMWIDTH);
    const __SHOWFORM = getOptValue(optSet.showForm, true) ? optParams.showForm : { 'showForm' : true };
    let form = __FORM;
    let count = 0;  // Bisher angezeigte Optionen
    let column = 0;  // Spalte der letzten Option (1-basierend)


            try {
    for (let opt in optSet) {
                value = JSON.parse(value);
        if (checkVisible(opt, __SHOWFORM, optParams.hideForm)) {
             } catch (ex) {
            const __ELEMENT = getOptionElement(optSet[opt]);
                __LOG[0]("getStoredCmds(): " + __CMD + " '" + __KEY + "' hat illegalen Wert '" + value + "'");
             const __TDOPT = (__ELEMENT.indexOf('|') < 0) ? ' colspan="2"' : "";
                // ... meist kann man den String selber aber speichern, daher kein "return"...
            }


             __STOREDCMDS.push({
             if (__ELEMENT.length > 0) {
                                'cmd' : __CMD,
                if (++count > __FORMBREAK) {
                                'key' : __KEY,
                    if (++column > __FORMWIDTH) {
                                'val' : value
                        column = 1;
                            });
                    }
                }
                if (column === 1) {
                    form += '</tr><tr>';
                }
                form += '\n<td' + __TDOPT + '>' + __ELEMENT.replace('|', '</td><td>') + '</td>';
            }
         }
         }
        __DELITEM('cmd');
        __DELITEM('key');
        __DELITEM('val');
     }
     }
    form += '\n' + __FORMEND;


     return (__STOREDCMDS.length ? __STOREDCMDS : undefined);
     return form;
}
}


// Fuehrt die in einem Storage gespeicherte Operation aus
// Fuegt das Script in die Seite ein
// storedCmds: Array von Objekten mit 'cmd' / 'key' / 'val' (siehe getStoredCmds())
// optSet: Gesetzte Optionen
// optSet: Object mit den Optionen
// optParams: Eventuell notwendige Parameter
// beforeLoad: Angabe, ob nach der Speicherung noch loadOptions() aufgerufen wird
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return String mit dem HTML-Code fuer das Script
// return Promise auf ein Array von Operationen (wie storedCmds), die fuer die naechste Phase uebrig bleiben
function getScript(optSet, optParams = { }) {
async function runStoredCmds(storedCmds, optSet = undefined, beforeLoad = undefined, onFulfilled = undefined, onRejected = undefined) {
    //const __SCRIPT = '<script type="text/javascript">function activateMenu() { console.log("TADAAA!"); }</script>';
     const __BEFORELOAD = getValue(beforeLoad, true);
     //const __SCRIPT = '<script type="text/javascript">\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionRst(key, value) { alert("RESET"); }\n</script>';
     const __STOREDCMDS = getValue(storedCmds, []);
     //const __FORM = '<form method="POST"><input type="button" id="showOpts" name="showOpts" value="Optionen anzeigen" onclick="activateMenu()" /></form>';
     const __LOADEDCMDS = [];
     const __SCRIPT = "";
     let invalidated = false;
 
     //window.eval('function activateMenu() { console.log("TADAAA!"); }');


     while (__STOREDCMDS.length) {
     return __SCRIPT;
        const __STORED = __STOREDCMDS.shift();
}
        const __CMD = __STORED.cmd;
        const __KEY = __STORED.key;
        const __VAL = __STORED.val;


        if (__BEFORELOAD) {
// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
            if (__STOREDCMDS.length) {
// anchor: Element, das als Anker fuer die Anzeige dient
                await invalidateOpts(optSet);  // alle Optionen invalidieren
// optSet: Gesetzte Optionen
                invalidated = true;
// optParams: Eventuell notwendige Parameter
            }
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
            switch (__OPTACTION[__CMD]) {
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
            case __OPTACTION.SET : __LOG[4]("SET '" + __KEY + "' " + __VAL);
// 'formWidth': Anzahl der Elemente pro Zeile
                                  setStored(__KEY, __VAL, false, false, onFulfilled, onRejected);
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
                                  break;
function buildForm(anchor, optSet, optParams = { }) {
            case __OPTACTION.NXT : __LOG[4]("SETNEXT '" + __KEY + "' " + __VAL);
    console.log("buildForm()");
                                  //setNextStored(__CONFIG.Choice, __KEY, __VAL, false, false, onFulfilled, onRejected);
 
                                  setStored(__KEY, __VAL, false, false, onFulfilled, onRejected);
    const __FORM = getForm(optSet, optParams);
                                  break;
    const __SCRIPT = getScript(optSet, optParams);
            case __OPTACTION.RST : __LOG[4]("RESET (delayed)");
                                  __LOADEDCMDS.push(__STORED);
                                  break;
            default :              break;
            }
        } else {
            switch (__OPTACTION[__CMD]) {
            case __OPTACTION.SET :
            case __OPTACTION.NXT : __LOG[2]("SET/SETNEXT (undefined)");
                                  break;
            case __OPTACTION.RST : __LOG[4]("RESET");
                                  await resetOptions(optSet, false);
                                  await loadOptions(optSet); // Reset auf umbenannte Optionen anwenden!
                                  break;
            default :              break;
            }
        }
    }


     return (__LOADEDCMDS.length ? __LOADEDCMDS : undefined);
     addForm(anchor, __FORM, __SCRIPT);
}
}


// Gibt eine Option sicher zurueck
// Informationen zu hinzugefuegten Forms
// opt: Config und Value der Option, ggfs. undefined
const __FORMS = { };
// defOpt: Rueckgabewert, falls undefined
// return Daten zur Option (oder defOpt)
function getOpt(opt, defOpt = { }) {
    return getValue(opt, defOpt);
}


// Gibt eine Option sicher zurueck (Version mit Key)
// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
// optSet: Platz fuer die gesetzten Optionen (und Config)
// anchor: Element, das als Anker fuer die Anzeige dient
// item: Key der Option
// form: HTML-Form des Optionsmenu (hinten angefuegt)
// defOpt: Rueckgabewert, falls nicht zu finden
// script: Script mit Reaktionen
// return Daten zur Option (oder defOpt)
function addForm(anchor, form = "", script = "") {
function getOptByName(optSet, item, defOpt = { }) {
     const __OLDFORM = __FORMS[anchor];
     if ((optSet !== undefined) && (item !== undefined)) {
    const __REST = (__OLDFORM === undefined) ? anchor.innerHTML :
        return getOpt(optSet[item], defOpt);
                  anchor.innerHTML.substring(0, anchor.innerHTML.length - __OLDFORM.Script.length - __OLDFORM.Form.length);
     } else {
 
        return defOpt;
     __FORMS[anchor] = { 'Script' : script, 'Form' : form };
    }
}


// Gibt die Konfigurationsdaten einer Option zurueck
     anchor.innerHTML = __REST + script + form;
// opt: Config und Value der Option
// defConfig: Rueckgabewert, falls Config nicht zu finden
// return Konfigurationsdaten der Option
function getOptConfig(opt, defConfig = { }) {
     return getValue(getOpt(opt).Config, defConfig);
}
}


// Setzt den Namen einer Option
// ==================== Ende Abschnitt fuer Optionen ====================
// opt: Config und Value der Option
// name: Zu setzender Name der Option
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Name der Option
function setOptName(opt, name) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);


    if (__NAME !== name) {
// ==================== Abschnitt genereller Code zur Anzeige der Jugend ====================
        __LOG[4]("RENAME " + __NAME + " => " + name);


        __CONFIG.Name = name;
// Zeitpunktangaben
     }
const __TIME = {
    'cre' : 0,  // Jugendspieler angelegt (mit 12 Jahren)
    'beg' : 1,  // Jugendspieler darf trainieren (wird 13 Jahre alt)
    'now' : 2,  // Aktueller ZAT
     'end' : 3  // Jugendspieler wird Ende 18 gezogen (Geb. - 1 bzw. ZAT 71 fuer '?')
};


    return name;
// Funktionen ***************************************************************************
}


// Gibt den Namen einer Option zurueck
// Erschafft die Spieler-Objekte und fuellt sie mit Werten (reloadData: true = Teamuebersicht, false = Spielereinzelwerte)
// opt: Config und Value der Option
function init(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, reloadData = false) {
// return Name der Option
     const __SAISON = getOptValue(optSet.saison);
function getOptName(opt) {
    const __CURRZAT = getOptValue(optSet.aktuellerZat);
     const __CONFIG = getOptConfig(opt);
     const __BIRTHDAYS = getOptValue(optSet.birthdays, []);
     const __NAME = __CONFIG.Name;
    const __TCLASSES = getOptValue(optSet.tClasses, []);
    const __PROGRESSES = getOptValue(optSet.progresses, []);
    const __ZATAGES = getOptValue(optSet.zatAges, []);
    const __TRAINIERT = getOptValue(optSet.trainiert, []);
    const __POSITIONS = getOptValue(optSet.positions, []);
    const __PLAYERS = [];


     if (! __NAME) {
     for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++, j++) {
         const __SHARED = __CONFIG.Shared;
         const __CELLS = playerRows[i].cells;
        const __AGE = getIntFromHTML(__CELLS, colIdx.Age);
        const __SKILLS = getSkillsFromHTML(__CELLS, colIdx);
        const __ISGOALIE = isGoalieFromHTML(__CELLS, colIdx.Age);
        const __NEWPLAYER = new PlayerRecord(__AGE, __SKILLS, __ISGOALIE);


         if (__SHARED && ! opt.Loaded) {
         __NEWPLAYER.initPlayer(__SAISON, __CURRZAT, __BIRTHDAYS[j], __TCLASSES[j], __PROGRESSES[j]);
            const __OBJREF = getSharedRef(__SHARED, opt.Item);


             return __OBJREF.getPath();
        if (reloadData) {
             __NEWPLAYER.setZusatz(__ZATAGES[j], __TRAINIERT[j], __POSITIONS[j]);
         }
         }


         showAlert("Error", "Option ohne Namen", safeStringify(__CONFIG));
         __PLAYERS[j] = __NEWPLAYER;
     }
     }


    return __NAME;
     if (reloadData) {
}
         storePlayerData(__PLAYERS, playerRows, optSet, colIdx, offsetUpper, offsetLower);
 
// Setzt den Wert einer Option
// opt: Config und Value der Option
// name: Zu setzender Wert der Option
// return Gesetzter Wert
function setOptValue(opt, value) {
     if (opt !== undefined) {
         if (! opt.ReadOnly) {
            __LOG[6](getOptName(opt) + ": " + __LOG.changed(opt.Value, value));
 
            opt.Value = value;
        }
        return opt.Value;
     } else {
     } else {
         return undefined;
         calcPlayerData(__PLAYERS, optSet);
     }
     }
    return __PLAYERS;
}
}


// Gibt den Wert einer Option zurueck
// Berechnet die abgeleiteten Werte in den Spieler-Objekten neu und speichert diese
// opt: Config und Value der Option
function calcPlayerData(players, optSet) {
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
    const __ZATAGES = [];
// load: Laedt die Option per loadOption(), falls noetig
    const __TRAINIERT = [];
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
    const __POSITIONS = [];
// return Gesetzter Wert
 
function getOptValue(opt, defValue = undefined, load = true, asyncLoad = false, force = false) {
    for (let i = 0; i < players.length; i++) {
    let value;
        const __ZUSATZ = players[i].calcZusatz();


    if (opt !== undefined) {
        __ZATAGES[i]  = __ZUSATZ.zatAge;
        if (load && ! opt.Loaded) {
        __TRAINIERT[i] = __ZUSATZ.trainiert;
            if (! opt.Promise) {
         __POSITIONS[i] = __ZUSATZ.bestPos;
                loadOption(opt, force);
            }
            if (! asyncLoad) {
                __LOG[0]("Warnung: getOptValue(" + getOptName(opt) + ") fordert zum Nachladen auf, daher nur Default-Wert!");
            }
         } else {
            value = opt.Value;
        }
     }
     }


     return valueOf(getValue(value, defValue));
     setOpt(optSet.zatAges, __ZATAGES, false);
    setOpt(optSet.trainiert, __TRAINIERT, false);
    setOpt(optSet.positions, __POSITIONS, false);
}
}


// ==================== Ende Abschnitt fuer diverse Utilities ====================
// Berechnet die abgeleiteten Werte in den Spieler-Objekten neu und speichert diese
function storePlayerData(players, playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0) {
    const __BIRTHDAYS = [];
    const __TCLASSES = [];
    const __PROGRESSES = [];


// ==================== Abschnitt fuer Speicher und die Scriptdatenbank ====================
    for (let i = offsetUpper; i < playerRows.length - offsetLower; i++) {
        const __CELLS = playerRows[i].cells;


// Namen des Default-, Temporaer- und Null-Memories...
        __BIRTHDAYS[i - offsetUpper] = getIntFromHTML(__CELLS, colIdx.Geb);
const __MEMNORMAL  = 'normal';
        __TCLASSES[i - offsetUpper] = getTalentFromHTML(__CELLS, colIdx.Tal);
const __MEMSESSION  = 'begrenzt';
        __PROGRESSES[i - offsetUpper] = getStringFromHTML(__CELLS, colIdx.Auf);
const __MEMINAKTIVE = 'inaktiv';
    }


// Definition des Default-, Dauer- und Null-Memories...
    setOpt(optSet.birthdays, __BIRTHDAYS, false);
const __OPTMEMNORMAL  = __OPTMEM[__MEMNORMAL];
    setOpt(optSet.tClasses, __TCLASSES, false);
const __OPTMEMSESSION  = __OPTMEM[__MEMSESSION];
    setOpt(optSet.progresses, __PROGRESSES, false);
const __OPTMEMINAKTIVE = __OPTMEM[__MEMINAKTIVE];
}


// Medium fuer die Datenbank (Speicher)
// Trennt die Gruppen (z.B. Jahrgaenge) mit Linien
let myOptMem = __OPTMEMNORMAL;
function separateGroups(rows, borderString, colIdxSort = 0, offsetUpper = 1, offsetLower = 0, offsetLeft = -1, offsetRight = 0) {
let myOptMemSize;
    if (offsetLeft < 0) {
        offsetLeft = colIdxSort; // ab Sortierspalte
    }


// Infos ueber dieses Script-Modul
    for (let i = offsetUpper; i < rows.length - offsetLower - 1; i++) {
const __DBMOD = new ScriptModule();
        if (rows[i].cells[colIdxSort].textContent !== rows[i + 1].cells[colIdxSort].textContent) {
            for (let j = offsetLeft; j < rows[i].cells.length - offsetRight; j++) {
                rows[i].cells[j].style.borderBottom = borderString;
            }
        }
    }
}


// Inhaltsverzeichnis der DB-Daten (indiziert durch die Script-Namen)
// Klasse ColumnManager *****************************************************************
const __DBTOC = { };


// Daten zu den Modulen (indiziert durch die Script-Namen)
function ColumnManager(optSet, showCol = undefined) {
const __DBDATA = { };
    const __SHOWALL = (showCol === undefined) || (showCol === true);
    const __SHOWCOL = getValue(showCol, { });


// ==================== Abschnitt fuer Speicher ====================
    this.geb = getValue(__SHOWCOL.zeigeGeb, __SHOWALL) && getOptValue(optSet.zeigeGeb);
    this.tal = getValue(__SHOWCOL.zeigeTal, __SHOWALL) && getOptValue(optSet.zeigeTal);
    this.quo = getValue(__SHOWCOL.zeigeQuote, __SHOWALL) && getOptValue(optSet.zeigeQuote);
    this.aufw = getValue(__SHOWCOL.zeigeAufw, __SHOWALL) && getOptValue(optSet.zeigeAufw);
    this.alter = getValue(__SHOWCOL.zeigeAlter, __SHOWALL) && getOptValue(optSet.zeigeAlter);
    this.skill = getValue(__SHOWCOL.zeigeSkill, __SHOWALL) && getOptValue(optSet.zeigeSkill);
    this.pos = getValue(__SHOWCOL.zeigePosition, __SHOWALL) && getOptValue(optSet.zeigePosition);
    this.anzOpti = getValue(__SHOWCOL.zeigeOpti, __SHOWALL) ? getOptValue(optSet.anzahlOpti) : 0;
    this.anzMw = getValue(__SHOWCOL.zeigeMW, __SHOWALL) ? getOptValue(optSet.anzahlMW) : 0;
    this.skillE = getValue(__SHOWCOL.zeigeSkillEnde, __SHOWALL) && getOptValue(optSet.zeigeSkillEnde);
    this.anzOptiE = getValue(__SHOWCOL.zeigeOptiEnde, __SHOWALL) ? getOptValue(optSet.anzahlOptiEnde) : 0;
    this.anzMwE = getValue(__SHOWCOL.zeigeMWEnde, __SHOWALL) ? getOptValue(optSet.anzahlMWEnde) : 0;
    this.kennzE = getOptValue(optSet.kennzeichenEnde);


// Ermittelt fuer die uebergebene Speicher-Konfiguration einen Speicher
    this.toString = function() {
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
        let result = "Skillschnitt\t\t" + this.skill + '\n';
// defMemory: Ersatz-Wert, falls memory undefined. Soll nur memory genutzt werden, dann z.B. null uebergeben!
        result += "Beste Position\t" + this.pos + '\n';
// return memory, falls okay, sonst einen Defaultwert
        result += "Optis\t\t\t" + this.anzOpti + '\n';
function getMemory(memory = undefined, defMemory = getValue(myOptMem, __OPTMEMNORMAL)) {
        result += "Marktwerte\t\t" + this.anzMw + '\n';
    return getValue(memory, defMemory);
        result += "Skillschnitt Ende\t" + this.skillE + '\n';
}
        result += "Optis Ende\t\t" + this.anzOptiE + '\n';
        result += "Marktwerte Ende\t" + this.anzMwE + '\n';


// Kompatibilitaetsfunktion: Testet, ob der uebergebene Speicher genutzt werden kann
        return result;
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
     };
// return true, wenn der Speichertest erfolgreich war
function canUseMemory(memory = undefined) {
     const __STORAGE = getMemory(memory, { });
    const __MEMORY = __STORAGE.Value;
    let ret = false;


     if (__MEMORY !== undefined) {
     this.addCell = function(tableRow) {
         const __TESTPREFIX = 'canUseStorageTest';
         tableRow.insertCell(-1);
         const __TESTDATA = Math.random().toString();
         return tableRow.cells.length - 1;
        const __TESTITEM = __TESTPREFIX + __TESTDATA;
    };


        __MEMORY.setItem(__TESTITEM, __TESTDATA);
    this.addAndFillCell = function(tableRow, value, color, digits = 2) {
         ret = (__MEMORY.getItem(__TESTITEM) === __TESTDATA);
         if (isFinite(value) && (value !== true) && (value !== false)) {
         __MEMORY.removeItem(__TESTITEM);
            // Zahl einfuegen
     }
            if (value < 1000) {
                // Mit Nachkommastellen darstellen
                tableRow.cells[this.addCell(tableRow)].textContent = parseFloat(value).toFixed(digits);
            } else {
                // Mit Tausenderpunkten darstellen
                tableRow.cells[this.addCell(tableRow)].textContent = getNumberString(value.toString());
            }
         } else {
            // String einfuegen
            tableRow.cells[this.addCell(tableRow)].textContent = value;
        }
        tableRow.cells[tableRow.cells.length - 1].style.color = color;
     };


     __LOG[2]("canUseStorage(" + __STORAGE.Name + ") = " + ret);
     this.addTitles = function(headers, titleColor = "#FFFFFF") {
        // Spaltentitel zentrieren
        headers.align = "center";


    return ret;
        // Titel fuer die aktuellen Werte
}
        if (this.tal) {
            this.addAndFillCell(headers, "Talent", titleColor);
        }
        if (this.quo) {
            this.addAndFillCell(headers, "Quote", titleColor);
        }
        if (this.aufw) {
            this.addAndFillCell(headers, "Aufwertung", titleColor);
        }
        if (this.geb) {
            this.addAndFillCell(headers, "Geb.", titleColor);
        }
        if (this.alter) {
            this.addAndFillCell(headers, "Alter", titleColor);
        }
        if (this.skill) {
            this.addAndFillCell(headers, "Skill", titleColor);
        }
        if (this.pos) {
            this.addAndFillCell(headers, "Pos", titleColor);
        }
        for (let i = 1; i <= 6; i++) {
            if (i <= this.anzOpti) {
                this.addAndFillCell(headers, "Opti " + i, titleColor);
            }
            if (i <= this.anzMw) {
                this.addAndFillCell(headers, "MW " + i, titleColor);
            }
        }


// Ermittelt die Groesse des benutzten Speichers
        // Titel fuer die Werte mit Ende 18
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
        if (this.skillE) {
// return Groesse des genutzten Speichers in Bytes
            this.addAndFillCell(headers, "Skill" + this.kennzE, titleColor);
function getMemSize(memory = undefined) {
         }
    const __STORAGE = getMemory(memory);
         for (let i = 1; i <= 6; i++) {
    const __MEMORY = __STORAGE.Value;
            if (i <= this.anzOptiE) {
 
                this.addAndFillCell(headers, "Opti " + i + this.kennzE, titleColor);
    //getMemUsage(__MEMORY);
            }
 
            if (i <= this.anzMwE) {
    if (__MEMORY !== undefined) {
                this.addAndFillCell(headers, "MW " + i + this.kennzE, titleColor);
        const __SIZE = safeStringify(__MEMORY).length;
 
        __LOG[2]("MEM: " + __SIZE + " bytes");
         return __SIZE;
    } else {
         return 0;
    }
}
 
// Gibt rekursiv und detailliert die Groesse des benutzten Speichers fuer ein Objekt aus
// value: (Enumerierbares) Objekt oder Wert, dessen Groesse gemessen wird
// out: Logfunktion, etwa __LOG[4]
// depth: Gewuenschte Rekursionstiefe (0 = nur dieses Objekt, -1 = alle Ebenen)
// name: Name des Objekts
function getMemUsage(value = undefined, out = undefined, depth = -1, name = '$') {
    const __OUT = (out || __LOG[4]);
 
    if ((typeof value) === 'string') {
        const __SIZE = value.length;
 
        __OUT("USAGE: " + name + '\t' + __SIZE + '\t' + value.slice(0, 255));
    } else if ((typeof value) === 'object') {
        if (depth === 0) {
            const __SIZE = safeStringify(value).length;
 
            __OUT("USAGE: " + name + '\t' + __SIZE);
        } else {
            depth--;
            for (let sub in value) {
                getMemUsage(value[sub], __OUT, depth, name + '.' + sub);
             }
             }
            getMemUsage(value, __OUT, 0, name);
         }
         }
     } else {
     };  // Ende addTitles()
      const __DATA = (((typeof value) === 'function') ? "" : '\t' + value);


         __OUT("USAGE: " + name + '\t' + (typeof value) + __DATA);
    this.addValues = function(player, playerRow, color = "#FFFFFF") {
    }
         const __COLOR = (player.isGoalie ? getColor("TOR") : color);
}
        const __POS1COLOR = getColor(player.getPos());


// Restauriert den vorherigen Speicher (der in einer Option definiert ist)
        // Aktuelle Werte
// opt: Option zur Wahl des Speichers
        if (this.tal) {
// return Promise auf gesuchten Speicher oder Null-Speicher ('inaktiv')
            this.addAndFillCell(playerRow, player.getTalent(), __COLOR);
async function restoreMemoryByOpt(opt) {
        }
    // Memory Storage fuer vorherige Speicherung...
        if (this.quo) {
    const __STORAGE = await getOptValue(opt, __MEMNORMAL, true, true, true);
            this.addAndFillCell(playerRow, player.getAufwertungsSchnitt(), __COLOR, 2);
        }
        if (this.aufw) {
            this.addAndFillCell(playerRow, player.getAufwert(), __COLOR);
        }
        if (this.geb) {
            this.addAndFillCell(playerRow, player.getGeb(), __COLOR, 0);
        }
        if (this.alter) {
            this.addAndFillCell(playerRow, player.getAge(), __COLOR, 2);
        }
        if (this.skill) {
            this.addAndFillCell(playerRow, player.getSkill(), __COLOR, 2);
        }
        if (this.pos) {
            this.addAndFillCell(playerRow, player.getPos(), __POS1COLOR);
        }
        for (let i = 1; i <= 6; i++) {
            const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
            const __COLI = getColor(__POSI);


    return __OPTMEM[__STORAGE];
            if (i <= this.anzOpti) {
}
                if ((i === 1) || ! player.isGoalie) {
                    // Opti anzeigen
                    this.addAndFillCell(playerRow, player.getOpti(__POSI), __COLI, 2);
                } else {
                    // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
                    this.addCell(playerRow);
                }
            }
            if (i <= this.anzMw) {
                if ((i === 1) || ! player.isGoalie) {
                    // MW anzeigen
                    this.addAndFillCell(playerRow, player.getMarketValue(__POSI), __COLI, 0);
                } else {
                    // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                    this.addCell(playerRow);
                }
            }
        }


// Initialisiert den Speicher (der in einer Option definiert ist) und merkt sich diesen ggfs.
        // Werte mit Ende 18
// opt: Option zur Wahl des Speichers
        if (this.skillE) {
// saveOpt: Option zur Speicherung der Wahl des Speichers (fuer restoreMemoryByOpt)
            this.addAndFillCell(playerRow, player.getSkill(__TIME.end), __COLOR, 2);
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesuchter Speicher oder Null-Speicher ('inaktiv'), falls speichern nicht moeglich ist
function startMemoryByOpt(opt, saveOpt = undefined, onFulfilled = undefined, onRejected = undefined) {
    // Memory Storage fuer naechste Speicherung...
    let storage = getOptValue(opt, __MEMNORMAL);
    let optMem = __OPTMEM[storage];
 
    if (! canUseMemory(optMem)) {
        if (storage !== __MEMINAKTIVE) {
            storage = __MEMINAKTIVE;
            optMem = __OPTMEM[storage];
         }
         }
    }
        for (let i = 1; i <= 6; i++) {
            const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
            const __COLI = getColor(__POSI);


    if (saveOpt !== undefined) {
            if (i <= this.anzOptiE) {
        setOpt(saveOpt, storage, false, onFulfilled, onRejected);
                if ((i === 1) || ! player.isGoalie) {
    }
                    // Opti anzeigen
 
                    this.addAndFillCell(playerRow, player.getOpti(__POSI, __TIME.end), __COLI, 2);
     return optMem;
                } else {
                    // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
                    this.addCell(playerRow);
                }
            }
            if (i <= this.anzMwE) {
                if ((i === 1) || ! player.isGoalie) {
                    // MW anzeigen
                    this.addAndFillCell(playerRow, player.getMarketValue(__POSI, __TIME.end), __COLI, 0);
                } else {
                    // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                    this.addCell(playerRow);
                }
            }
        }
     }; // Ende addValues(player, playerRow)
}
}


// ==================== Ende Abschnitt fuer Speicher ====================
// Klasse PlayerRecord ******************************************************************


// ==================== Abschnitt fuer die Scriptdatenbank ====================
function PlayerRecord(age, skills, isGoalie) {
    // Zu benutzende Marktwertformel
    const __MWFORMEL = {
        'alt' : 0,  // Marktwertformel bis Saison 9 inklusive
        'S10' : 1  // Marktwertformel MW5 ab Saison 10
    };


// Initialisiert das Script-Modul und ermittelt die beschreibenden Daten
    this.mwFormel = __MWFORMEL.S10;  // Neue Formel, genauer in initPlayer()
// meta: Metadaten des Scripts (Default: GM.info.script)
// return Beschreibende Daten fuer __DBMOD
function ScriptModule(meta) {
    'use strict';


     const __META = getValue(meta, GM.info.script);
     this.age = age;
     const __PROPS = {
     this.skills = skills;
                'name'        : true,
     this.isGoalie = isGoalie;
                'version'     : true,
                'namespace'  : true,
                'description' : true
            };


     const __DBMOD = { };
     // in this.initPlayer() definiert:
    // this.zatGeb: ZAT, an dem der Spieler Geburtstag hat, -1 fuer "noch nicht zugewiesen", also '?'
    // this.zatAge: Bisherige erfolgte Trainings-ZATs
    // this.talent: Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
    // this.aufwert: Aufwertungsstring
    // this.mwFormel: Benutzte MW-Formel, siehe __MWFORMEL
    // this.positions[][]: Positionstext und Opti; TOR-Index ist 5
    // this.skillsEnd[]: Berechnet aus this.skills, this.age und aktuellerZat


     __LOG[5](__META);
     // in this calcZusatz()/setZusatz() definiert:
    // this.trainiert: Anzahl der erfolgreichen Trainingspunkte
    // this.bestPos: erster (bester) Positionstext


     // Infos zu diesem Script...
     this.toString = function() {
    addProps(__DBMOD, __META, __PROPS);
        let result = "Alter\t\t" + this.age + "\n\n";
        result += "Aktuelle Werte\n";
        result += "Skillschnitt\t" + this.getSkill().toFixed(2) + '\n';
        result += "Optis und Marktwerte";


    // Voller Name fuer die Ausgabe...
        for (let pos of this.positions) {
    Object.defineProperty(__DBMOD, 'Name', {
            result += "\n\t" + pos + '\t';
                    get : function() {
            result += this.getOpti(pos).toFixed(2) + '\t';
                              return this.name + " (" + this.version + ')';
            result += getNumberString(this.getMarketValue(pos).toString());
                          },
        }
                    set : undefined
                });


    __LOG[2](__DBMOD);
        result += "\n\nWerte mit Ende 18\n";
        result += "Skillschnitt\t" + this.getSkill(__TIME.end).toFixed(2) + '\n';
        result += "Optis und Marktwerte";


    return __DBMOD;
        for (let pos of this.positions) {
}
            result += "\n\t" + this.getPos()[i] + '\t';
            result += this.getOpti(pos, __TIME.end).toFixed(2) + '\t';
            result += getNumberString(this.getMarketValue(pos, __TIME.end).toString());
        }


Class.define(ScriptModule, Object);
        return result;
    };  // Ende this.toString()


// Initialisiert die Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
    // Berechnet die Opti-Werte, sortiert das Positionsfeld und berechnet die Einzelskills mit Ende 18
// optSet: Gesetzte Optionen (und Config)
    this.initPlayer = function(saison, currZAT, gebZAT, tclass, progresses) {
function initScriptDB(optSet) {
        this.zatGeb = gebZAT;
    // Speicher fuer die DB-Daten...
        this.zatAge = this.calcZatAge(currZAT);
    const __DBMEM = myOptMem.Value;
        this.talent = tclass;
        this.aufwert = progresses;
        this.mwFormel = (saison < 10) ? __MWFORMEL.alt : __MWFORMEL.S10;


    __DBTOC.versions = getValue((__DBMEM === undefined) ? undefined : JSON.parse(__DBMEM.getItem('__DBTOC.versions')), { });
        this.positions = [];
    __DBTOC.namespaces = getValue((__DBMEM === undefined) ? undefined : JSON.parse(__DBMEM.getItem('__DBTOC.namespaces')), { });
        // ABW
        this.positions[0] = [];
        this.positions[0][0] = "ABW";
        this.positions[0][1] = this.getOpti("ABW");
        // DMI
        this.positions[1] = [];
        this.positions[1][0] = "DMI";
        this.positions[1][1] = this.getOpti("DMI");
        // MIT
        this.positions[2] = [];
        this.positions[2][0] = "MIT";
        this.positions[2][1] = this.getOpti("MIT");
        // OMI
        this.positions[3] = [];
        this.positions[3][0] = "OMI";
        this.positions[3][1] = this.getOpti("OMI");
        // STU
        this.positions[4] = [];
        this.positions[4][0] = "STU";
        this.positions[4][1] = this.getOpti("STU");
        // TOR
        this.positions[5] = [];
        this.positions[5][0] = "TOR";
        this.positions[5][1] = this.getOpti("TOR");


    // Zunaechst den alten Eintrag entfernen...
        // Sortieren
    delete __DBTOC.versions[__DBMOD.name];
        sortPositionArray(this.positions);
    delete __DBTOC.namespaces[__DBMOD.name];


    if (__DBMEM !== undefined) {
         // Einzelskills mit Ende 18 berechnen
         // ... und die Daten der Fremdscripte laden...
         this.skillsEnd = [];
         for (let module in __DBTOC.versions) {
            scriptDB(module, getValue(JSON.parse(__DBMEM.getItem('__DBDATA.' + module)), { }));
        }
    }
}


// Setzt die Daten dieses Scriptes in der Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
        const __ADDRATIO = (this.getZatDone(__TIME.end) - this.getZatDone()) / this.getZatDone();
// optSet: Gesetzte Optionen (und Config)
function updateScriptDB(optSet) {
    // Eintrag ins Inhaltsverzeichnis...
    __DBTOC.versions[__DBMOD.name] = __DBMOD.version;
    __DBTOC.namespaces[__DBMOD.name] = __DBMOD.namespace;


    // Speicher fuer die DB-Daten...
        for (let i in this.skills) {
    const __DBMEM = myOptMem.Value;
            const __SKILL = this.skills[i];
            let progSkill = __SKILL;


    if (__DBMEM !== undefined) {
            if (isTrainableSkill(i)) {
        // Permanente Speicherung der Eintraege...
                // Auf ganze Zahl runden und parseInt(), da das sonst irgendwie als String interpretiert wird
        __DBMEM.setItem('__DBTOC.versions', safeStringify(__DBTOC.versions));
                const __ADDSKILL = getMulValue(__ADDRATIO, __SKILL, 0, NaN);
        __DBMEM.setItem('__DBTOC.namespaces', safeStringify(__DBTOC.namespaces));
        __DBMEM.setItem('__DBDATA.' + __DBMOD.name, safeStringify(optSet));


        // Aktualisierung der Speichergroesse...
                progSkill += __ADDSKILL;
        myOptMemSize = getMemSize(myOptMem);
            }
    }


    // Jetzt die inzwischen gefuellten Daten *dieses* Scripts ergaenzen...
            this.skillsEnd[i] = Math.min(progSkill, 99);
     scriptDB(__DBMOD.name, getValue(optSet, { }));
        }
     };  // Ende this.initPlayer()


     __LOG[2](__DBDATA);
     // Setzt Nebenwerte fuer den Spieler (geht ohne initPlayer())
}
    this.setZusatz = function(zatAge, trainiert, bestPos) {
        this.zatAge = zatAge;
        this.trainiert = trainiert;
        this.bestPos = bestPos;
    };


// Holt die globalen Daten zu einem Modul aus der Scriptdatenbank
    // Ermittelt Nebenwerte fuer den Spieler und gibt sie alle zurueck (nach initPlayer())
// module: Gesetzte Optionen (und Config)
    this.calcZusatz = function() {
// initValue: Falls angegeben, zugewiesener Startwert
        // this.zatAge bereits in initPlayer() berechnet
// return Daten zu diesem Modul
        this.trainiert = this.getTrainiert(true); // neu berechnet aus Skills
function scriptDB(module, initValue = undefined) {
        this.bestPos = this.getPos(-1);  // hier: -1 explizit angeben, da neu ermittelt (this.bestPos noch nicht belegt)
    const __NAMESPACE = __DBTOC.namespaces[module];
    const __DBMODS = getProp(__DBDATA, __NAMESPACE, { });


    if (initValue !== undefined) {
         return {
         return (__DBMODS[module] = initValue);
                  zatAge    : this.zatAge,
    } else {
                  trainiert : this.trainiert,
        return getProp(__DBMODS, module, { });
                  bestPos  : this.bestPos
     }
              };
}
     };


// ==================== Ende Abschnitt fuer die Scriptdatenbank ====================
    this.getGeb = function() {
        return (this.zatGeb < 0) ? '?' : this.zatGeb;
    };


// ==================== Ende Abschnitt fuer Speicher und die Scriptdatenbank ====================
    this.calcZatAge = function(currZAT) {
        let ZATs = (this.age - ((currZAT < this.zatGeb) ? 12 : 13)) * 72;  // Basiszeit fuer die Jahre seit Jahrgang 13


// ==================== Abschnitt fuer das Benutzermenu ====================
        if (this.zatGeb < 0) {
            return ZATs + currZAT;  // Zaehlung begann Anfang der Saison (und der Geburtstag wird erst nach dem Ziehen bestimmt)
        } else {
            return ZATs + currZAT - this.zatGeb;  // Verschiebung relativ zum Geburtstag (von -zatGeb, ..., 0, ..., 71 - zatGeb)
        }
    };


// Zeigt den Eintrag im Menu einer Option
    this.getZatAge = function(when = __TIME.now) {
// val: Derzeitiger Wert der Option
        if (when === __TIME.end) {
// menuOn: Text zum Setzen im Menu
            return (18 - 12) * 72 - 1; // (max.) Trainings-ZATs bis Ende 18
// funOn: Funktion zum Setzen
        } else {
// keyOn: Hotkey zum Setzen im Menu
            return this.zatAge;
// menuOff: Text zum Ausschalten im Menu
        }
// funOff: Funktion zum Ausschalten
     };
// keyOff: Hotkey zum Ausschalten im Menu
// return Promise von GM.registerMenuCommand()
function registerMenuOption(val, menuOn, funOn, keyOn, menuOff, funOff, keyOff) {
    const __ON = (val ? '*' : "");
     const __OFF = (val ? "" : '*');


     __LOG[3]("OPTION " + __ON + menuOn + __ON + " / " + __OFF + menuOff + __OFF);
     this.getZatDone = function(when = __TIME.now) {
        return Math.max(0, this.getZatAge(when));
    };


     if (val) {
     this.getAge = function(when = __TIME.now) {
         return GM.registerMenuCommand(menuOff, funOff, keyOff).then(result => menuOn);
         if (this.mwFormel === __MWFORMEL.alt) {
    } else {
            return (when === __TIME.end) ? 18 : this.age;
        return GM.registerMenuCommand(menuOn, funOn, keyOn).then(result => menuOff);
        } else { // Geburtstage ab Saison 10...
    }
            return (13.00 + this.getZatAge(when) / 72);
}
        }
    };


// Zeigt den Eintrag im Menu einer Option mit Wahl des naechsten Wertes
    this.getTrainiert = function(recalc = false) {
// val: Derzeitiger Wert der Option
        let sum = 0;
// arr: Array-Liste mit den moeglichen Optionen
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
// fun: Funktion zum Setzen des naechsten Wertes
// key: Hotkey zum Setzen des naechsten Wertes im Menu
// return Promise von GM.registerMenuCommand()
function registerNextMenuOption(val, arr, menu, fun, key) {
    const __MENU = substParam(menu, val);
    let options = "OPTION " + __MENU;


    for (let value of arr) {
        if (recalc || (this.trainiert === undefined)) {
        if (value === val) {
            for (let i in this.skills) {
            options += " / *" + value + '*';
                if (isTrainableSkill(i)) {
                    sum += this.skills[i];
                }
            }
         } else {
         } else {
             options += " / " + value;
             sum += this.trainiert;
         }
         }
    }
    __LOG[3](options);


    return GM.registerMenuCommand(__MENU, fun, key).then(result => __MENU);
        return sum;
}
    };


// Zeigt den Eintrag im Menu einer Option, falls nicht hidden
    this.getAufwertungsSchnitt = function() {
// val: Derzeitiger Wert der Option
        return parseFloat(this.getTrainiert() / this.getZatDone());
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
     };
// fun: Funktion zum Setzen des naechsten Wertes
// key: Hotkey zum Setzen des naechsten Wertes im Menu
// hidden: Angabe, ob Menupunkt nicht sichtbar sein soll (Default: sichtbar)
// serial: Serialization fuer komplexe Daten
// return Promise von GM.registerMenuCommand() (oder String-Version des Wertes)
function registerDataOption(val, menu, fun, key, hidden = false, serial = true) {
    const __VALUE = ((serial && (val !== undefined)) ? safeStringify(val) : val);
    const __MENU = substParam(menu, __VALUE);
     const __OPTIONS = (hidden ? "HIDDEN " : "") + "OPTION " + __MENU +
                      getValue(__VALUE, "", " = " + __VALUE);


     __LOG[hidden ? 4 : 3](__OPTIONS);
     this.getPos = function(idx = undefined) {
        const __IDXOFFSET = 1;


    if (hidden) {
        switch (getValue(idx, 0)) {
         return Promise.resolve(__VALUE);
         case -1 : return (this.bestPos = this.positions[isGoalie ? 5 : 0][0]);
    } else {
        case  0 : return this.bestPos;
         return GM.registerMenuCommand(__MENU, fun, key).then(result => __MENU);
         default : return this.positions[idx - __IDXOFFSET][0];
    }
        }
}
    };


// Zeigt den Eintrag im Menu einer Option
    this.getTalent = function() {
// opt: Config und Value der Option
        return (this.talent < 0) ? "wenig" : (this.talent > 0) ? "hoch" : "normal";
// return Promise von GM.registerMenuCommand() (oder String-Version des Wertes)
     };
function registerOption(opt) {
 
     const __CONFIG = getOptConfig(opt);
     this.getAufwert = function() {
     const __VALUE = getOptValue(opt);
        return (this.aufwert.length > 0) ? this.aufwert : "keine";
    const __LABEL = __CONFIG.Label;
     };
     const __ACTION = opt.Action;
 
     const __HOTKEY = __CONFIG.Hotkey;
     this.getSkill = function(when = __TIME.now) {
    const __HIDDEN = __CONFIG.HiddenMenu;
        const __SKILLS = (when === __TIME.end) ? this.skillsEnd : this.skills;
    const __SERIAL = __CONFIG.Serial;
        let result = 0;


    if (! __CONFIG.HiddenMenu) {
         for (let skill of __SKILLS) {
         switch (__CONFIG.Type) {
            result += skill;
        case __OPTTYPES.MC : return registerNextMenuOption(__VALUE, __CONFIG.Choice, __LABEL, __ACTION, __HOTKEY);
        case __OPTTYPES.SW : return registerMenuOption(__VALUE, __LABEL, __ACTION, __HOTKEY,
                                                      __CONFIG.AltLabel, __ACTION, __CONFIG.AltHotkey);
        case __OPTTYPES.TF : return registerMenuOption(__VALUE, __LABEL, __ACTION, __HOTKEY,
                                                      __CONFIG.AltLabel, opt.AltAction, __CONFIG.AltHotkey);
        case __OPTTYPES.SD : return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
        case __OPTTYPES.SI : return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
        default :            return Promise.resolve(__VALUE);
         }
         }
    } else {
        // Nur Anzeige im Log...
        return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
    }
}


// ==================== Ende Abschnitt fuer das Benutzermenu ====================
        return result / __SKILLS.length;
    };


// Initialisiert die gesetzten Option
    this.getOpti = function(pos, when = __TIME.now) {
// config: Konfiguration der Option
        const __SKILLS = (when === __TIME.end) ? this.skillsEnd : this.skills;
// setValue: Zu uebernehmender Default-Wert (z.B. der jetzt gesetzte)
        const __IDXPRISKILLS = getIdxPriSkills(pos);
// return Initialwert der gesetzten Option
        const __IDXSECSKILLS = getIdxSecSkills(pos);
function initOptValue(config, setValue = undefined) {
        let sumPriSkills = 0;
    let value = getValue(setValue, config.Default); // Standard
        let sumSecSkills = 0;


    if (config.SharedData !== undefined) {
        for (let idx of __IDXPRISKILLS) {
        value = config.SharedData;
            sumPriSkills += __SKILLS[idx];
    }
        }
        for (let idx of __IDXSECSKILLS) {
            sumSecSkills += __SKILLS[idx];
        }


    switch (config.Type) {
        return (5 * sumPriSkills + sumSecSkills) / 27;
     case __OPTTYPES.MC : if ((value === undefined) && (config.Choice !== undefined)) {
     };
                            value = config.Choice[0];
                        }
                        break;
    case __OPTTYPES.SW : break;
    case __OPTTYPES.TF : break;
    case __OPTTYPES.SD : config.Serial = true;
                        break;
    case __OPTTYPES.SI : break;
    default :            break;
    }


     if (config.Serial || config.Hidden) {
     this.getMarketValue = function(pos, when = __TIME.now) {
         config.HiddenMenu = true;
         const __AGE = this.getAge(when);
    }


    return value;
        if (this.mwFormel === __MWFORMEL.alt) {
}
            return Math.round(Math.pow((1 + this.getSkill(when)/100) * (1 + this.getOpti(pos, when)/100) * (2 - __AGE/100), 10) * 2);   // Alte Formel bis Saison 9
        } else {  // MW-Formel ab Saison 10...
            const __MW5TF = 1.00;  // Zwischen 0.97 und 1.03


// Initialisiert die Menue-Funktion einer Option
            return Math.round(Math.pow(1 + this.getSkill(when)/100, 5.65) * Math.pow(1 + this.getOpti(pos, when)/100, 8.1) * Math.pow(1 + (100 - __AGE)/49, 10) * __MW5TF);
// optAction: Typ der Funktion
// item: Key der Option
// optSet: Platz fuer die gesetzten Optionen (und Config)
// optConfig: Konfiguration der Option
// return Funktion fuer die Option
function initOptAction(optAction, item = undefined, optSet = undefined, optConfig = undefined) {
    let fun;
 
    if (optAction !== undefined) {
        const __CONFIG = ((optConfig !== undefined) ? optConfig : getOptConfig(getOptByName(optSet, item)));
        const __RELOAD = getValue(getValue(__CONFIG, { }).ActionReload, true);
 
        switch (optAction) {
        case __OPTACTION.SET : fun = function() {
                                      return setOptByName(optSet, item, __CONFIG.SetValue, __RELOAD).catch(defaultCatch);
                                  };
                              break;
        case __OPTACTION.NXT : fun = function() {
                                      return promptNextOptByName(optSet, item, __CONFIG.SetValue, __RELOAD,
                                                  __CONFIG.FreeValue, __CONFIG.SelValue, __CONFIG.MinChoice).catch(defaultCatch);
                                  };
                              break;
        case __OPTACTION.RST : fun = function() {
                                      return resetOptions(optSet, __RELOAD).then(
                                              result => __LOG[3]("RESETTING (" + result + ")..."),
                                              defaultCatch);
                                  };
                              break;
        default :              break;
         }
         }
     }
     };
 
    return fun;
}
}


// Gibt fuer einen 'Shared'-Eintrag eine ObjRef zurueck
// Funktionen fuer die HTML-Seite *******************************************************
// shared: Object mit den Angaben 'namespace', 'module' und ggfs. 'item'
// item: Key der Option
// return ObjRef, die das Ziel definiert
function getSharedRef(shared, item = undefined) {
    if (shared === undefined) {
        return undefined;
    }


    const __OBJREF = new ObjRef(__DBDATA)// Gemeinsame Daten
// Liest eine Zahl aus der Spalte einer Zeile der Tabelle aus (z.B. Alter, Geburtsdatum)
    const __PROPS = [ 'namespace', 'module', 'item' ];
// cells: Die Zellen einer Zeile
    const __DEFAULTS = [ __DBMOD.namespace, __DBMOD.name, item ];
// colIdxInt: Spaltenindex der gesuchten Werte
 
// return Spalteneintrag als Zahl (-1 fuer "keine Zahl", undefined fuer "nicht gefunden")
    for (let stage in __PROPS) {
function getIntFromHTML(cells, colIdxInt) {
        const __DEFAULT = __DEFAULTS[stage];
    const __CELL = cells[colIdxInt];
        const __PROP = __PROPS[stage];
    const __TEXT = getValue(__CELL, { }).textContent;
        const __NAME = shared[__PROP];


        if (__NAME === '$') {
    if (__TEXT !== undefined) {
             break;
        try {
         }
            const __VALUE = parseInt(__TEXT, 10);
 
            if (! isNaN(__VALUE)) {
                return __VALUE;
             }
         } catch (ex) { }


         __OBJREF.chDir(getValue(__NAME, __DEFAULT));
         return -1;
     }
     }


     return __OBJREF;
     return undefined;
}
}


// Gibt diese Config oder, falls 'Shared', ein Referenz-Objekt mit gemeinsamen Daten zurueck
// Liest eine Dezimalzahl aus der Spalte einer Zeile der Tabelle aus
// optConfig: Konfiguration der Option
// cells: Die Zellen einer Zeile
// item: Key der Option
// colIdxInt: Spaltenindex der gesuchten Werte
// return Entweder optConfig oder gemergete Daten auf Basis des in 'Shared' angegebenen Objekts
// return Spalteneintrag als Dezimalzahl (undefined fuer "keine Zahl" oder "nicht gefunden")
function getSharedConfig(optConfig, item = undefined) {
function getFloatFromHTML(cells, colIdxFloat) {
     let config = getValue(optConfig, { });
     const __CELL = cells[colIdxFloat];
    const __SHARED = config.Shared;
    const __TEXT = getValue(__CELL, { }).textContent;


     if (__SHARED !== undefined) {
     if (__TEXT !== undefined) {
         const __OBJREF = getSharedRef(__SHARED, item);  // Gemeinsame Daten
         try {
 
             return parseFloat(__TEXT);
        if (getValue(__SHARED.item, '$') !== '$') { // __OBJREF ist ein Item
         } catch (ex) { }
             const __REF = valueOf(__OBJREF);
 
            config = { };  // Neu aufbauen...
            addProps(config, getOptConfig(__REF));
            addProps(config, optConfig);
            config.setConst('SharedData', getOptValue(__REF), false);  // Wert muss schon da sein, NICHT nachladen, sonst ggfs. Promise
         } else {  // __OBJREF enthaelt die Daten selbst
            if (! config.Name) {
                config.Name = __OBJREF.getPath();
            }
            config.setConst('SharedData', __OBJREF);  // Achtung: Ggfs. zirkulaer!
        }
     }
     }


     return config;
     return undefined;
}
}


// Initialisiert die gesetzten Optionen
// Liest einen String aus der Spalte einer Zeile der Tabelle aus
// optConfig: Konfiguration der Optionen
// cells: Die Zellen einer Zeile
// optSet: Platz fuer die gesetzten Optionen
// colIdxStr: Spaltenindex der gesuchten Werte
// preInit: Vorinitialisierung einzelner Optionen mit 'PreInit'-Attribut
// return Spalteneintrag als String ("" fuer "nicht gefunden")
// return Gefuelltes Objekt mit den gesetzten Optionen
function getStringFromHTML(cells, colIdxStr) {
function initOptions(optConfig, optSet = undefined, preInit = undefined) {
    const __CELL = cells[colIdxStr];
     let value;
    const __TEXT = getValue(__CELL, { }).textContent;
 
     return getValue(__TEXT.toString(), "");
}


    if (optSet === undefined) {
// Liest die Talentklasse ("wenig", "normal", "hoch") aus der Spalte einer Zeile der Tabelle aus
        optSet = { };
// cells: Die Zellen einer Zeile
    }
// colIdxStr: Spaltenindex der gesuchten Werte
// return Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
function getTalentFromHTML(cells, colIdxTal) {
    const __TEXT = getStringFromHTML(cells, colIdxTal);


     for (let opt in optConfig) {
     return parseInt((__TEXT === "wenig") ? -1 : (__TEXT === "hoch") ? +1 : 0, 10);
        const __OPTCONFIG = optConfig[opt];
}
        const __PREINIT = getValue(__OPTCONFIG.PreInit, false, true);
        const __ISSHARED = getValue(__OPTCONFIG.Shared, false, true);


        if ((preInit === undefined) || (__PREINIT === preInit)) {
// Liest die Einzelskills aus der Spalte einer Zeile der Tabelle aus
            const __CONFIG = getSharedConfig(__OPTCONFIG, opt);
// cells: Die Zellen einer Zeile
            const __ALTACTION = getValue(__CONFIG.AltAction, __CONFIG.Action);
// colIdx: Liste von Spaltenindices der gesuchten Werte mit den Eintraegen
            // Gab es vorher einen Aufruf, der einen Stub-Eintrag erzeugt hat, und wurden Daten geladen?
// 'Einz' (erste Spalte) und 'Zus' (Spalte hinter dem letzten Eintrag)
            const __LOADED = ((preInit === false) && optSet[opt].Loaded);
// return Skills als Array von Zahlen
            const __PROMISE = ((__LOADED || ! optSet[opt]) ? undefined : optSet[opt].Promise);
function getSkillsFromHTML(cells, colIdx) {
            const __VALUE = (__LOADED ? optSet[opt].Value : undefined);
    const __RESULT = [];


            optSet[opt] = {
     for (let i = colIdx.Einz; i < colIdx.Zus; i++) {
                'Item'      : opt,
        __RESULT[i - colIdx.Einz] = getIntFromHTML(cells, i);
                'Config'    : __CONFIG,
                'Loaded'    : (__ISSHARED || __LOADED),
                'Promise'  : __PROMISE,
                'Value'     : initOptValue(__CONFIG, __VALUE),
                'SetValue'  : __CONFIG.SetValue,
                'ReadOnly'  : (__ISSHARED || __CONFIG.ReadOnly),
                'Action'    : initOptAction(__CONFIG.Action, opt, optSet, __CONFIG),
                'AltAction' : initOptAction(__ALTACTION, opt, optSet, __CONFIG)
            };
        } else if (preInit) { // erstmal nur Stub
            optSet[opt] = {
                'Item'      : opt,
                'Config'    : __OPTCONFIG,
                'Loaded'    : false,
                'Promise'  : undefined,
                'Value'    : initOptValue(__OPTCONFIG),
                'ReadOnly'  : (__ISSHARED || __OPTCONFIG.ReadOnly)
            };
        }
     }
     }


     return optSet;
     return __RESULT;
}
}


    // Abhaengigkeiten:
// Liest aus, ob der Spieler Torwart oder Feldspieler ist
    // ================
// return Angabe, der Spieler Torwart oder Feldspieler ist
    // initOptions (PreInit):
function isGoalieFromHTML(cells, colIdxClass) {
    // restoreMemoryByOpt: PreInit oldStorage
     return (cells[colIdxClass].className === "TOR");
    // getStoredCmds: restoreMemoryByOpt
}
    // runStoredCmds (beforeLoad): getStoredCmds
    // loadOptions (PreInit): PreInit
    // startMemoryByOpt: storage oldStorage
    // initScriptDB: startMemoryByOpt
    // initOptions (Rest): PreInit
    // getMyTeam callback (getOptPrefix): initTeam
    // __MYTEAM (initTeam): initOptions
    // renameOptions: getOptPrefix
    // runStoredCmds (afterLoad): getStoredCmds, renameOptions
    // loadOptions (Rest): PreInit/Rest runStoredCmds
    // updateScriptDB: startMemoryByOpt
    // showOptions: startMemoryByOpt renameOptions
    // buildMenu: showOptions
    // buildForm: showOptions
 
// Initialisiert die gesetzten Optionen und den Speicher und laedt die Optionen zum Start
// optConfig: Konfiguration der Optionen
// optSet: Platz fuer die gesetzten Optionen
// return Promise auf gefuelltes Objekt mit den gesetzten Optionen
async function startOptions(optConfig, optSet = undefined, classification = undefined) {
     optSet = initOptions(optConfig, optSet, true); // PreInit


    // Memory Storage fuer vorherige Speicherung...
// Hilfsfunktionen **********************************************************************
    myOptMemSize = getMemSize(myOptMem = await restoreMemoryByOpt(optSet.oldStorage));


     // Zwischengespeicherte Befehle auslesen...
// Sortiert das Positionsfeld per BubbleSort
     const __STOREDCMDS = getStoredCmds(myOptMem);
function sortPositionArray(array) {
    const __TEMP = [];
    let transposed = true;
     // TOR soll immer die letzte Position im Feld sein, deshalb - 1
     let length = array.length - 1;


     // ... ermittelte Befehle ausfuehren...
     while (transposed && (length > 1)) {
    const __LOADEDCMDS = await runStoredCmds(__STOREDCMDS, optSet, true)// BeforeLoad
        transposed = false;
        for (let i = 0; i < length - 1; i++) {
            // Vergleich Opti-Werte:
            if (array[i][1] < array[i + 1][1]) {
                // vertauschen
                __TEMP[0] = array[i][0];
                __TEMP[1] = array[i][1];
                array[i][0] = array[i + 1][0];
                array[i][1] = array[i + 1][1];
                array[i + 1][0] = __TEMP[0];
                array[i + 1][1] = __TEMP[1];
                transposed = true;
            }
        }
        length--;
    }
}


    // Bisher noch nicht geladenene Optionen laden...
// Fuegt in die uebergebene Zahl Tausender-Trennpunkte ein
     await loadOptions(optSet);
// Wandelt einen etwaig vorhandenen Dezimalpunkt in ein Komma um
 
function getNumberString(numberString) {
    // Memory Storage fuer naechste Speicherung...
     if (numberString.lastIndexOf(".") !== -1) {
    myOptMemSize = getMemSize(myOptMem = startMemoryByOpt(optSet.storage, optSet.oldStorage));
        // Zahl enthaelt Dezimalpunkt
        const __VORKOMMA = numberString.substring(0, numberString.lastIndexOf("."));
        const __NACHKOMMA = numberString.substring(numberString.lastIndexOf(".") + 1, numberString.length);


     // Globale Daten ermitteln...
        return getNumberString(__VORKOMMA) + "," + __NACHKOMMA;
    initScriptDB(optSet);
     } else {
        // Kein Dezimalpunkt, fuege Tausender-Trennpunkte ein:
        // String umdrehen, nach jedem dritten Zeichen Punkt einfuegen, dann wieder umdrehen:
        const __TEMP = reverseString(numberString);
        let result = "";


    optSet = initOptions(optConfig, optSet, false); // Rest
        for (let i = 0; i < __TEMP.length; i++) {
            if ((i > 0) && (i % 3 === 0)) {
                result += ".";
            }
            result += __TEMP.substr(i, 1);
        }


    if (classification !== undefined) {
         return reverseString(result);
        // Umbenennungen durchfuehren...
         await classification.renameOptions();
     }
     }
}


    // ... ermittelte Befehle ausfuehren...
// Dreht den uebergebenen String um
    await runStoredCmds(__LOADEDCMDS, optSet, false); // Rest
function reverseString(string) {
    let result = "";


     // Als globale Daten speichern...
     for (let i = string.length - 1; i >= 0; i--) {
    updateScriptDB(optSet);
        result += string.substr(i, 1);
    }


     return optSet;
     return result;
}
}


// Installiert die Visualisierung und Steuerung der Optionen
// Schaut nach, ob der uebergebene Index zu einem trainierbaren Skill gehoert
// optSet: Platz fuer die gesetzten Optionen
// Die Indizes gehen von 0 (SCH) bis 16 (EIN)
// optParams: Eventuell notwendige Parameter zur Initialisierung
function isTrainableSkill(idx) {
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
     const __TRAINABLESKILLS = [0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 15];
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
    const __IDX = parseInt(idx, 10);
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
     let result = false;
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return Liefert die gesetzten Optionen zurueck
function showOptions(optSet = undefined, optParams = { 'hideMenu' : false }) {
     if (! optParams.hideMenu) {
        buildMenu(optSet).then(() => __LOG[3]("Menu OK"));
     }


     if ((optParams.menuAnchor !== undefined) && (myOptMem !== __OPTMEMINAKTIVE)) {
     for (let idxTrainable of __TRAINABLESKILLS) {
         buildForm(optParams.menuAnchor, optSet, optParams);
        if (__IDX === idxTrainable) {
            result = true;
            break;
         }
     }
     }


     return optSet;
     return result;
}
}


// Setzt eine Option auf einen vorgegebenen Wert
// Gibt die Indizes der Primaerskills zurueck
// Fuer kontrollierte Auswahl des Values siehe setNextOpt()
function getIdxPriSkills(pos) {
// opt: Config und vorheriger Value der Option
    switch (pos) {
// value: (Bei allen Typen) Zu setzender Wert
        case "TOR" : return new Array(2, 3, 4, 5);
// reload: Seite mit neuem Wert neu laden
        case "ABW" : return new Array(2, 3, 4, 15);
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
        case "DMI" : return new Array(1, 4, 9, 11);
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
        case "MIT" : return new Array(1, 3, 9, 11);
// return Gesetzter Wert
        case "OMI" : return new Array(1, 5, 9, 11);
function setOpt(opt, value, reload = false, onFulfilled = undefined, onRejected = undefined) {
        case "STU" : return new Array(0, 2, 3, 5);
    return setOptValue(opt, setStored(getOptName(opt), value, reload, getOptConfig(opt).Serial, onFulfilled, onRejected));
        default : return [];
    }
}
}


// Ermittelt die naechste moegliche Option
// Gibt die Indizes der Sekundaerskills zurueck
// opt: Config und Value der Option
function getIdxSecSkills(pos) {
// value: Ggfs. zu setzender Wert
    switch (pos) {
// return Zu setzender Wert
        case "TOR" : return new Array(0, 1, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
function getNextOpt(opt, value = undefined) {
        case "ABW" : return new Array(0, 1, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16);
    const __CONFIG = getOptConfig(opt);
        case "DMI" : return new Array(0, 2, 3, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16);
    const __VALUE = getOptValue(opt, value);
        case "MIT" : return new Array(0, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16);
        case "OMI" : return new Array(0, 2, 3, 4, 6, 7, 8, 10, 12, 13, 14, 15, 16);
        case "STU" : return new Array(1, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        default : return [];
    }
}


     switch (__CONFIG.Type) {
// Gibt die zur Position gehoerige Farbe zurueck
    case __OPTTYPES.MC : return getValue(value, getNextValue(__CONFIG.Choice, __VALUE));
function getColor(pos) {
    case __OPTTYPES.SW : return getValue(value, ! __VALUE);
     switch (pos) {
    case __OPTTYPES.TF : return getValue(value, ! __VALUE);
        case "TOR" : return "#FFFF00";
    case __OPTTYPES.SD : return getValue(value, __VALUE);
        case "ABW" : return "#00FF00";
    case __OPTTYPES.SI : break;
        case "DMI" : return "#3366FF";
    default :           break;
        case "MIT" : return "#66FFFF";
        case "OMI" : return "#FF66FF";
        case "STU" : return "#FF0000";
        case "LEI" : return "#FFFFFF";
        default : return "";
     }
     }
    return __VALUE;
}
}


// Setzt die naechste moegliche Option
// ==================== Ende Abschnitt genereller Code zur Anzeige der Jugend ====================
// opt: Config und Value der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function setNextOpt(opt, value = undefined, reload = false, onFulfilled = undefined, onRejected = undefined) {
    return setOpt(opt, getNextOpt(opt, value), reload, onFulfilled, onRejected);
}


// Setzt die naechste moegliche Option oder fragt ab einer gewissen Anzahl interaktiv ab
// ==================== Abschnitt fuer interne IDs auf den Seiten ====================
// opt: Config und Value der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// freeValue: Angabe, ob Freitext zugelassen ist (Default: false)
// minChoice: Ab wievielen Auswahlmoeglichkeiten soll abgefragt werden? (Default: 3)
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function promptNextOpt(opt, value = undefined, reload = false, freeValue = false, selValue = true, minChoice = 3, onFulfilled = undefined, onRejected = undefined) {
    const __CONFIG = getOptConfig(opt);
    const __CHOICE = __CONFIG.Choice;


    if (value || (! __CHOICE) || (__CHOICE.length < minChoice)) {
const __GAMETYPES = {    // "Blind FSS gesucht!"
         return setNextOpt(opt, value, reload, onFulfilled, onRejected);
        'unbekannt'  : -1,
     }
        "reserviert" :  0,
        "Frei"      :  0,
        "spielfrei"  :  0,
        "Friendly"  :  1,
        "Liga"      :  2,
         "LP"        :  3,
        "OSEQ"      :  4,
        "OSE"        :  5,
        "OSCQ"      :  6,
        "OSC"        :  7
     };


    const __VALUE = getOptValue(opt, value);
const __LIGANRN = {
        'unbekannt'  :  0,
        '1. Liga'    :  1,
        '2. Liga A'  :  2,
        '2. Liga B'  :  3,
        '3. Liga A'  :  4,
        '3. Liga B'  :  5,
        '3. Liga C'  :  6,
        '3. Liga D'  :  7
    };


    try {
const __LANDNRN = {
         const __NEXTVAL = getNextValue(__CHOICE, __VALUE);
         'unbekannt'              :  0,
         let message = "";
         'Albanien'              :  45,
 
        'Andorra'                :  95,
         if (selValue) {
         'Armenien'              :  83,
            for (let index = 0; index < __CHOICE.length; index++) {
        'Aserbaidschan'          : 104,
                message += (index + 1) + ") " + __CHOICE[index] + '\n';
        'Belgien'               :  12,
            }
        'Bosnien-Herzegowina'    :  66,
            message += "\nNummer oder Wert eingeben:";
        'Bulgarien'              : 42,
         } else {
         'D\xE4nemark'            :   8,
            message = __CHOICE.join(" / ") + "\n\nWert eingeben:";
         'Deutschland'            :  6,
         }
        'England'                :  1,
 
         'Estland'                :  57,
         const __ANSWER = prompt(message, __NEXTVAL);
        'Far\xF6er'              :  68,
 
         'Finnland'              :  40,
         if (__ANSWER) {
        'Frankreich'             :  32,
             const __INDEX = parseInt(__ANSWER, 10) - 1;
        'Georgien'              : 49,
            let nextVal = (selValue ? __CHOICE[__INDEX] : undefined);
        'Griechenland'          :  30,
 
        'Irland'                 :  5,
            if (nextVal === undefined) {
        'Island'                :  29,
                 const __VALTYPE = getValue(__CONFIG.ValType, 'String');
        'Israel'                 :  23,
                 const __CASTVAL = this[__VALTYPE](__ANSWER);
        'Italien'                :  10,
 
        'Kasachstan'             : 105,
                 if (freeValue || (~ __CHOICE.indexOf(__CASTVAL))) {
        'Kroatien'              :  24,
                    nextVal = __CASTVAL;
        'Lettland'              :  97,
                }
        'Liechtenstein'          :  92,
             }
        'Litauen'                :  72,
 
        'Luxemburg'              :  93,
            if (nextVal !== __VALUE) {
        'Malta'                  :  69,
                if (nextVal) {
        'Mazedonien'            :  86,
                    return setOpt(opt, nextVal, reload, onFulfilled, onRejected);
        'Moldawien'              :  87,
                }
        'Niederlande'            :  11,
 
        'Nordirland'            :  4,
                const __LABEL = substParam(__CONFIG.Label, __VALUE);
        'Norwegen'              :  9,
 
        '\xD6sterreich'          : 14,
                showAlert(__LABEL, "Ung\xFCltige Eingabe: " + __ANSWER);
        'Polen'                  :  25,
            }
         'Portugal'              :  17,
         }
        'Rum\xE4nien'            :  28,
    } catch (ex) {
         'Russland'              : 19,
         __LOG[0]("promptNextOpt: " + ex.message);
        'San Marino'            :  98,
    }
        'Schottland'            :  2,
 
        'Schweden'              :  27,
    return __VALUE;
        'Schweiz'                :  37,
}
        'Serbien und Montenegro' :  41,
 
        'Slowakei'              :  70,
// Setzt eine Option auf einen vorgegebenen Wert (Version mit Key)
        'Slowenien'              :  21,
// Fuer kontrollierte Auswahl des Values siehe setNextOptByName()
        'Spanien'                : 13,
// optSet: Platz fuer die gesetzten Optionen (und Config)
        'Tschechien'            : 18,
// item: Key der Option
        'T\xFCrkei'              : 39,
// value: (Bei allen Typen) Zu setzender Wert
        'Ukraine'                : 20,
// reload: Seite mit neuem Wert neu laden
        'Ungarn'                : 26,
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
        'Wales'                  :   3,
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
        'Weissrussland'          :  71,
// return Gesetzter Wert
        'Zypern'                :  38
function setOptByName(optSet, item, value, reload = false, onFulfilled = undefined, onRejected = undefined) {
     };
     const __OPT = getOptByName(optSet, item);


    return setOpt(__OPT, value, reload, onFulfilled, onRejected);
// ==================== Abschnitt fuer Daten des Spielplans ====================
 
// Gibt die ID fuer den Namen eines Wettbewerbs zurueck
// gameType: Name des Wettbewerbs eines Spiels
// return OS2-ID fuer den Spieltyp (1 bis 7), 0 fuer spielfrei/Frei/reserviert, -1 fuer ungueltig
function getGameTypeID(gameType) {
    return getValue(__GAMETYPES[gameType], __GAMETYPES.unbekannt);
}
}


// Ermittelt die naechste moegliche Option (Version mit Key)
// Gibt die ID des Landes mit dem uebergebenen Namen zurueck.
// optSet: Platz fuer die gesetzten Optionen (und Config)
// land: Name des Landes
// item: Key der Option
// return OS2-ID des Landes, 0 fuer ungueltig
// value: Default fuer ggfs. zu setzenden Wert
function getLandNr(land) {
// return Zu setzender Wert
     return getValue(__LANDNRN[land], __LANDNRN.unbekannt);
function getNextOptByName(optSet, item, value = undefined) {
    const __OPT = getOptByName(optSet, item);
 
     return getNextOpt(__OPT, value);
}
}


// Setzt die naechste moegliche Option (Version mit Key)
// Gibt die ID der Liga mit dem uebergebenen Namen zurueck.
// optSet: Platz fuer die gesetzten Optionen (und Config)
// land: Name der Liga
// item: Key der Option
// return OS2-ID der Liga, 0 fuer ungueltig
// value: Default fuer ggfs. zu setzenden Wert
function getLigaNr(liga) {
// reload: Seite mit neuem Wert neu laden
     return getValue(__LIGANRN[liga], __LIGANRN.unbekannt);
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function setNextOptByName(optSet, item, value = undefined, reload = false, onFulfilled = undefined, onRejected = undefined) {
    const __OPT = getOptByName(optSet, item);
 
     return setNextOpt(__OPT, value, reload, onFulfilled, onRejected);
}
}


// Setzt die naechste moegliche Option oder fragt ab einer gewissen Anzahl interaktiv ab (Version mit Key)
// ==================== Abschnitt fuer sonstige Parameter ====================
// optSet: Platz fuer die gesetzten Optionen (und Config)
// item: Key der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// freeValue: Angabe, ob Freitext zugelassen ist (Default: false)
// minChoice: Ab wievielen Auswahlmoeglichkeiten soll abgefragt werden? (Default: 3)
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function promptNextOptByName(optSet, item, value = undefined, reload = false, freeValue = false, selValue = true, minChoice = 3, onFulfilled = undefined, onRejected = undefined) {
    const __OPT = getOptByName(optSet, item);


    return promptNextOpt(__OPT, value, reload, freeValue, selValue, minChoice, onFulfilled, onRejected);
const __TEAMSEARCHHAUPT = {  // Parameter zum Team "<b>Willkommen im Managerb&uuml;ro von TEAM</b><br>LIGA LAND<a href=..."
}
        'Zeile'  : 0,
        'Spalte' : 1,
        'start'  : " von ",
        'middle' : "</b><br>",
        'liga'  : ". Liga",
        'land'  : ' ',
        'end'    : "<a href="
    };


// Baut das Benutzermenu auf (asynchron im Hintergrund)
const __TEAMSEARCHTEAM = {  // Parameter zum Team "<b>TEAM - LIGA <a href=...>LAND</a></b>"
// optSet: Gesetzte Optionen
        'Zeile'  : 0,
// return Promise auf void
        'Spalte' : 0,
async function buildMenu(optSet) {
        'start'  : "<b>",
     __LOG[3]("buildMenu()");
        'middle' : " - ",
        'liga'  : ". Liga",
        'land'  : 'target="_blank">',
        'end'    : "</a></b>"
     };


    for (let opt in optSet) {
// Ermittelt, wie das eigene Team heisst und aus welchem Land bzw. Liga es kommt (zur Unterscheidung von Erst- und Zweitteam)
        await registerOption(optSet[opt]).then(
// cell: Tabellenzelle mit den Parametern zum Team "startTEAMmiddleLIGA...landLANDend", LIGA = "#liga[ (A|B|C|D)]"
                result => __LOG[6](`REGISTEROPTION[${opt}] = ${result}`),
// teamSeach: Muster fuer die Suche, die Eintraege fuer 'start', 'middle', 'liga', 'land' und 'end' enthaelt
                defaultCatch);
// return Im Beispiel { 'Team' : "TEAM", 'Liga' : "LIGA", 'Land' : "LAND", 'LdNr' : LAND-NUMMER, 'LgNr' : LIGA-NUMMER },
     }
//        z.B. { 'Team' : "Choromonets Odessa", 'Liga' : "1. Liga", 'Land' : "Ukraine", 'LdNr' : 20, 'LgNr' : 1 }
}
function getTeamParamsFromTable(table, teamSearch = undefined) {
    const __TEAMSEARCH  = getValue(teamSearch, __TEAMSEARCHHAUPT);
    const __TEAMCELLROW  = getValue(__TEAMSEARCH.Zeile, 0);
    const __TEAMCELLCOL  = getValue(__TEAMSEARCH.Spalte, 0);
    const __TEAMCELLSTR  = table.rows[__TEAMCELLROW].cells[__TEAMCELLCOL].innerHTML;
    const __SEARCHSTART  = __TEAMSEARCH.start;
    const __SEARCHMIDDLE = __TEAMSEARCH.middle;
    const __SEARCHLIGA  = __TEAMSEARCH.liga;
    const __SEARCHLAND  = __TEAMSEARCH.land;
    const __SEARCHEND    = __TEAMSEARCH.end;
    const __INDEXSTART  = __TEAMCELLSTR.indexOf(__SEARCHSTART);
     const __INDEXEND    = __TEAMCELLSTR.indexOf(__SEARCHEND);


// Invalidiert eine (ueber Menu) gesetzte Option
    let teamParams = __TEAMCELLSTR.substring(__INDEXSTART + __SEARCHSTART.length, __INDEXEND);
// opt: Zu invalidierende Option
     const __INDEXLIGA = teamParams.indexOf(__SEARCHLIGA);
// force: Invalidiert auch Optionen mit 'AutoReset'-Attribut
    const __INDEXMIDDLE = teamParams.indexOf(__SEARCHMIDDLE);
// return Promise auf resultierenden Wert
function invalidateOpt(opt, force = false) {
     return Promise.resolve(opt.Promise).then(value => {
            if (opt.Loaded && ! opt.ReadOnly) {
                const __CONFIG = getOptConfig(opt);


                // Wert "ungeladen"...
    let land = (__INDEXLIGA > 0) ? teamParams.substring(__INDEXLIGA + __SEARCHLIGA.length) : undefined;
                opt.Loaded = (force || ! __CONFIG.AutoReset);
    const __TEAM = (__INDEXMIDDLE > 0) ? teamParams.substring(0, __INDEXMIDDLE) : undefined;
    let liga = ((__INDEXLIGA > 0) && (__INDEXMIDDLE > 0)) ? teamParams.substring(__INDEXMIDDLE + __SEARCHMIDDLE.length) : undefined;


                if (opt.Loaded && __CONFIG.AutoReset) {
    if (land !== undefined) {
                    // Nur zuruecksetzen, gilt als geladen...
        if (land.charAt(2) === ' ') {   // Land z.B. hinter "2. Liga A " statt "1. Liga "
                    setOptValue(opt, initOptValue(__CONFIG));
            land = land.substr(2);
                }
        }
            }
        if (liga !== undefined) {
            liga = liga.substring(0, liga.length - land.length);
        }
        const __INDEXLAND = land.indexOf(__SEARCHLAND);
        if (__INDEXLAND > -1) {
            land = land.substr(__INDEXLAND + __SEARCHLAND.length);
        }
    }
 
    const __RET = {
        'Team' : __TEAM,
        'Liga' : liga,
        'Land' : land,
        'LdNr' : getLandNr(land),
        'LgNr' : getLigaNr(liga)
    };


            return getOptValue(opt);
    return __RET;
        }, defaultCatch);
}
}


// Invalidiert die (ueber Menu) gesetzten Optionen
// Verarbeitet die URL der Seite und ermittelt die Nummer der gewuenschten Unterseite
// optSet: Object mit den Optionen
// url: Adresse der Seite
// force: Invalidiert auch Optionen mit 'AutoReset'-Attribut
// return Parameter aus der URL der Seite als Nummer
// return Promise auf Object mit den geladenen Optionen
function getPageIdFromURL(url) {
async function invalidateOpts(optSet, force = false) {
    // Variablen zur Identifikation der Seite
     for (let opt in optSet) {
    const __SUCH = "page=";
        const __OPT = optSet[opt];
    const __INDEXS = url.lastIndexOf(__SUCH);
    const __HAUPT = url.match(/haupt\.php/);        // Ansicht "Haupt" (Managerbuero)
     const __JU = url.match(/ju\.php/);              // Ansicht "Jugendteam"
    let page = -1;                                 // Seitenindex (Rueckgabewert)


         await invalidateOpt(__OPT, force);
    // Wert von page (Seitenindex) ermitteln...
    // Annahme: Entscheidend ist jeweils das letzte Vorkommnis von "page=" und ggf. von '&'
    if (__HAUPT) {
        page = 0;
    } else if (__JU) {
        if (__INDEXS < 0) {
            page = 1;
        } else if (url.indexOf('&', __INDEXS) < 0) {
            // Wert von page setzt sich aus allen Zeichen hinter "page=" zusammen
            page = parseInt(url.substring(__INDEXS + __SUCH.length, url.length), 10);
         } else {
            // Wert von page setzt sich aus allen Zeichen zwischen "page=" und '&' zusammen
            page = parseInt(url.substring(__INDEXS + __SUCH.length, url.indexOf('&', __INDEXS)), 10);
        }
     }
     }


     return optSet;
     return page;
}
}


// Laedt eine (ueber Menu) gesetzte Option
// Gibt die laufende Nummer des ZATs im Text einer Zelle zurueck
// opt: Zu ladende Option
// cell: Tabellenzelle mit der ZAT-Nummer im Text
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return ZAT-Nummer im Text
// return Promise auf gesetzten Wert der gelandenen Option
function getZATNrFromCell(cell) {
function loadOption(opt, force = false) {
     const __TEXT = cell.textContent.split(' ');
     if (! opt.Promise) {
    let ZATNr = 0;
        const __CONFIG = getOptConfig(opt);
        const __ISSHARED = getValue(__CONFIG.Shared, false, true);
        const __NAME = getOptName(opt);
        const __DEFAULT = getOptValue(opt, undefined, false, false, false);
        let value;


        if (opt.Loaded && ! __ISSHARED) {
    for (let i = 1; (ZATNr === 0) && (i < __TEXT.length); i++) {
            const __ERROR = "Error: Oprion '" + __NAME + "' bereits geladen!";
        if (__TEXT[i - 1] === "ZAT") {
 
            if (__TEXT[i] !== "ist") {
            __LOG[0](__MESSAGE);
                ZATNr = parseInt(__TEXT[i], 10);
 
             }
             return Promise.reject(__MESSAGE);
         }
         }
    }


        if (opt.ReadOnly || __ISSHARED) {
    return ZATNr;
            value = __DEFAULT;
}
        } else if (! force && __CONFIG.AutoReset) {
            value = initOptValue(__CONFIG);
        } else {
            value = (__CONFIG.Serial ?
                            deserialize(__NAME, __DEFAULT) :
                            GM.getValue(__NAME, __DEFAULT));
        }


        opt.Promise = Promise.resolve(value).then(value => {
// ==================== Ende Abschnitt fuer sonstige Parameter ====================
                // Paranoide Sicherheitsabfrage (das sollte nie passieren!)...
                if (opt.Loaded || ! opt.Promise) {
                    showAlert("Error", "Unerwarteter Widerspruch zwischen opt.Loaded und opt.Promise", safeStringify(opt));
                }
                __LOG[5]("LOAD " + __NAME + ": " + __LOG.changed(__DEFAULT, value));


                // Wert intern setzen...
// ==================== Hauptprogramm ====================
                const __VAL = setOptValue(opt, value);


                // Wert als geladen markieren...
// Verarbeitet Ansicht "Haupt" (Managerbuero) zur Ermittlung des aktuellen ZATs
                opt.Promise = undefined;
function procHaupt() {
                opt.Loaded = true;
    const __TEAMPARAMS = getTeamParamsFromTable(getTable(1), __TEAMSEARCHHAUPT); // Link mit Team, Liga, Land...


                return __VAL;
    buildOptions(__OPTCONFIG, __OPTSET, {
            }, defaultCatch);
                    'teamParams' : __TEAMPARAMS,
    }
                    'hideMenu'  : true
                });


     return opt.Promise;
     const __NEXTZAT = getZATNrFromCell(getRows(0)[2].cells[0]); // "Der naechste ZAT ist ZAT xx und ..."
}
    const __CURRZAT = __NEXTZAT - 1;
    const __DATAZAT = getOptValue(__OPTSET.datenZat);


// Laedt die (ueber Menu) gesetzten Optionen
    if (__CURRZAT >= 0) {
// optSet: Object mit den Optionen
        console.log("Aktueller ZAT: " + __CURRZAT);
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Array mit Promises neuer Ladevorgaenge (fuer Objekte mit 'name' und 'value')
function loadOptions(optSet, force = false) {
    const __PROMISES = [];


    for (let opt in optSet) {
        // Neuen aktuellen ZAT speichern...
        const __OPT = optSet[opt];
        setOpt(__OPTSET.aktuellerZat, __CURRZAT, false);


         if (! __OPT.Loaded) {
         if (__CURRZAT !== __DATAZAT) {
             const __PROMISE = loadOption(__OPT, force).then(value => {
             console.log(__DATAZAT + " => " + __CURRZAT);
                    __LOG[5]("LOADED " + opt + " << " + value);


                    return Promise.resolve({
            // ... und ZAT-bezogene Daten als veraltet markieren
                            'name'  : opt,
            deleteOptions(__OPTSET, __DATAOPTS, true, true);
                            'value' : value
                        });
                }, defaultCatch);


             __PROMISES.push(__PROMISE);
             // Neuen Daten-ZAT speichern...
            setOpt(__OPTSET.datenZat, __CURRZAT, false);
         }
         }
     }
     }
    return Promise.all(__PROMISES);
}
}


// Entfernt eine (ueber Menu) gesetzte Option (falls nicht 'Permanent')
// Verarbeitet Ansicht "Teamuebersicht"
// opt: Gesetzte Option
function procTeamuebersicht() {
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
    const __ROWOFFSETUPPER = 1;    // Header-Zeile
// reset: Setzt bei Erfolg auf Initialwert der Option (auch fuer nicht 'AutoReset')
     const __ROWOFFSETLOWER = 1;     // Ziehen-Button
// return Promise von GM.deleteValue() (oder void)
function deleteOption(opt, force = false, reset = true) {
     const __CONFIG = getOptConfig(opt);


     if (force || ! __CONFIG.Permanent) {
     const __COLUMNINDEX = {
         const __NAME = getOptName(opt);
         'Age'  : 0,
        'Geb'  : 1,
        'Flg'  : 2,
        'Land'  : 3,
        'U'    : 4,
        'Skill' : 5,
        'Tal'  : 6,
        'Akt'  : 7,
        'Auf'  : 8,
        'Zus'  : 9
    };


         __LOG[4]("DELETE " + __NAME);
    if (getElement('transfer') !== undefined) {
        console.log("Ziehen-Seite");
    } else if (getRows(1) === undefined) {
         console.log("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else {
        buildOptions(__OPTCONFIG, __OPTSET, {
                        'menuAnchor' : getTable(0, "div"),
                        'showForm' : {
                                        'sepStyle'      : true,
                                        'sepColor'      : true,
                                        'sepWidth'      : true,
                                        'aktuellerZat'  : true,
                                        'team'          : true,
                                        'zeigeAlter'    : true,
                                        'zeigeQuote'    : true,
                                        'zeigePosition' : true,
                                        'zatAges'      : true,
                                        'trainiert'    : true,
                                        'positions'    : true,
                                        'reset'        : true,
                                        'showForm'      : true
                                      },
                        'formWidth' : 1
                    });


         return GM.deleteValue(__NAME).then(voidValue => {
         const __COLMAN = new ColumnManager(__OPTSET, {
                if (reset || __CONFIG.AutoReset) {
                                        'zeigeAlter'    : getOptValue(__OPTSET.zatAges, false, true),
                    setOptValue(opt, initOptValue(__CONFIG));
                                        'zeigeQuote'    : getOptValue(__OPTSET.trainiert, false, true),
                }
                                        'zeigePosition' : getOptValue(__OPTSET.positions, false, true)
            }, defaultCatch);
                                    });
    }
        const __ROWS = getRows(1);
        const __HEADERS = __ROWS[0];
        const __TITLECOLOR = getColor("LEI"); // "#FFFFFF"


    return Promise.resolve();
        __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
}


// Entfernt die (ueber Menu) gesetzten Optionen (falls nicht 'Permanent')
        const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, true);
// optSet: Gesetzte Optionen
// optSelect: Liste von ausgewaehlten Optionen, true = entfernen, false = nicht entfernen
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
// reset: Setzt bei Erfolg auf Initialwert der Option
// return Promise auf diesen Vorgang
async function deleteOptions(optSet, optSelect = undefined, force = false, reset = true) {
    const __DELETEALL = ((optSelect === undefined) || (optSelect === true));
    const __OPTSELECT = getValue(optSelect, { });


    for (let opt in optSet) {
        for (let i = 0; i < __PLAYERS.length; i++) {
        if (getValue(__OPTSELECT[opt], __DELETEALL)) {
            __COLMAN.addValues(__PLAYERS[i], __ROWS[i + __ROWOFFSETUPPER], __TITLECOLOR);
            await deleteOption(optSet[opt], force, reset);
         }
         }
        // Format der Trennlinie zwischen den Monaten...
        const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
        separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Age, __ROWOFFSETUPPER, __ROWOFFSETLOWER, -1, 0);
     }
     }
    return Promise.resolve();
}
}


// Benennt eine Option um und laedt sie ggfs. nach
// Verarbeitet Ansicht "Spielereinzelwerte"
// opt: Gesetzte Option
function procSpielereinzelwerte() {
// name: Neu zu setzender Name (Speicheradresse)
     const __ROWOFFSETUPPER = 1;    // Header-Zeile
// reload: Wert nachladen statt beizubehalten
     const __ROWOFFSETLOWER = 0;
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Promise auf umbenannte Option
async function renameOption(opt, name, reload = false, force = false) {
    const __NAME = getOptName(opt);
 
    if (__NAME !== name) {
        await deleteOption(opt, true, ! reload);
 
        setOptName(opt, name);
 
        await invalidateOpt(opt, opt.Loaded);
 
        if (reload) {
            opt.Loaded = false;
 
            await loadOption(opt, force);
        }
    }
 
    return Promise.resolve(opt);
}
 
// Ermittelt einen neuen Namen mit einem Prefix. Parameter fuer renameOptions()
// name: Gesetzter Name (Speicheradresse)
// prefix: Prefix, das vorangestellt werden soll
// return Neu zu setzender Name (Speicheradresse)
function prefixName(name, prefix) {
    return (prefix + name);
}
 
// Ermittelt einen neuen Namen mit einem Postfix. Parameter fuer renameOptions()
// name: Gesetzter Name (Speicheradresse)
// postfix: Postfix, das angehaengt werden soll
// return Neu zu setzender Name (Speicheradresse)
function postfixName(name, postfix) {
    return (name + postfix);
}
 
// Benennt selektierte Optionen nach einem Schema um und laedt sie ggfs. nach
// optSet: Gesetzte Optionen
// optSelect: Liste von ausgewaehlten Optionen, true = nachladen, false = nicht nachladen
// 'reload': Option nachladen?
// 'force': Option auch mit 'AutoReset'-Attribut nachladen?
// renameParam: Wird an renameFun uebergeen
// renameFun: function(name, param) zur Ermittlung des neuen Namens
// - name: Neu zu setzender Name (Speicheradresse)
// - param: Parameter "renameParam" von oben, z.B. Prefix oder Postfix
// return Promise auf diesen Vorgang
async function renameOptions(optSet, optSelect, renameParam = undefined, renameFun = prefixName) {
    if (renameFun === undefined) {
        __LOG[0]("RENAME: Illegale Funktion!");
    }
    for (let opt in optSelect) {
        const __OPTPARAMS = optSelect[opt];
        const __OPT = optSet[opt];
 
        if (__OPT === undefined) {
            __LOG[0]("RENAME: Option '" + opt + "' nicht gefunden!");
        } else {
            const __NAME = getOptName(__OPT);
            const __NEWNAME = renameFun(__NAME, renameParam);
            const __ISSCALAR = ((typeof __OPTPARAMS) === 'boolean');
            // Laedt die unter dem neuen Namen gespeicherten Daten nach?
            const __RELOAD = (__ISSCALAR ? __OPTPARAMS : __OPTPARAMS.reload);
            // Laedt auch Optionen mit 'AutoReset'-Attribut?
            const __FORCE = (__ISSCALAR ? true : __OPTPARAMS.force);
 
            await renameOption(__OPT, __NEWNAME, __RELOAD, __FORCE);
        }
    }
}
 
// Setzt die Optionen in optSet auf die "Werkseinstellungen" des Skripts
// optSet: Gesetzte Optionen
// reload: Seite mit "Werkseinstellungen" neu laden
// return Promise auf diesen Vorgang
async function resetOptions(optSet, reload = true) {
    // Alle (nicht 'Permanent') gesetzten Optionen entfernen...
    await deleteOptions(optSet, true, false, ! reload);
 
    // ... und ggfs. Seite neu laden (mit "Werkseinstellungen")...
    refreshPage(reload);
}
 
// ==================== Abschnitt fuer diverse Utilities ====================
 
// Legt Input-Felder in einem Form-Konstrukt an, falls noetig
// form: <form>...</form>
// props: Map von name:value-Paaren
// type: Typ der Input-Felder (Default: unsichtbare Daten)
// return Ergaenztes Form-Konstrukt
function addInputField(form, props, type = 'hidden') {
    for (let fieldName in props) {
        let field = form[fieldName];
        if (! field) {
            field = document.createElement('input');
            field.type = type;
            field.name = fieldName;
            form.appendChild(field);
        }
        field.value = props[fieldName];
    }
 
    return form;
}
 
// Legt unsichtbare Input-Daten in einem Form-Konstrukt an, falls noetig
// form: <form>...</form>
// props: Map von name:value-Paaren
// return Ergaenztes Form-Konstrukt
function addHiddenField(form, props) {
    return addInputField(form, props, 'hidden');
}
 
// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function addEvent(obj, type, callback, capture = false) {
    if (obj.addEventListener) {
        return obj.addEventListener(type, callback, capture);
    } else if (obj.attachEvent) {
        return obj.attachEvent('on' + type, callback);
    } else {
        __LOG[0]("Could not add " + type + " event:");
        __LOG[2](callback);
 
        return false;
    }
}
 
// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function removeEvent(obj, type, callback, capture = false) {
    if (obj.removeEventListener) {
        return obj.removeEventListener(type, callback, capture);
    } else if (obj.detachEvent) {
        return obj.detachEvent('on' + type, callback);
    } else {
        __LOG[0]("Could not remove " + type + " event:");
        __LOG[2](callback);
 
        return false;
    }
}
 
// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
// id: ID des betroffenen Eingabeelements
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function addDocEvent(id, type, callback, capture = false) {
    const __OBJ = document.getElementById(id);
 
    return addEvent(__OBJ, type, callback, capture);
}
 
// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
// id: ID des betroffenen Eingabeelements
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function removeDocEvent(id, type, callback, capture = false) {
    const __OBJ = document.getElementById(id);
 
    return removeEvent(__OBJ, type, callback, capture);
}
 
// Hilfsfunktion fuer die Ermittlung eines Elements der Seite
// name: Name des Elements (siehe "name=")
// index: Laufende Nummer des Elements (0-based), Default: 0
// doc: Dokument (document)
// return Gesuchtes Element mit der lfd. Nummer index oder undefined (falls nicht gefunden)
function getElement(name, index = 0, doc = document) {
    const __TAGS = doc.getElementsByName(name);
    const __TABLE = (__TAGS ? __TAGS[index] : undefined);
 
    return __TABLE;
}
 
// Hilfsfunktion fuer die Ermittlung eines Elements der Seite (Default: Tabelle)
// index: Laufende Nummer des Elements (0-based)
// tag: Tag des Elements ("table")
// doc: Dokument (document)
// return Gesuchtes Element oder undefined (falls nicht gefunden)
function getTable(index, tag = 'table', doc = document) {
    const __TAGS = doc.getElementsByTagName(tag);
    const __TABLE = (__TAGS ? __TAGS[index] : undefined);
 
    return __TABLE;
}
 
// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
// name: Name des Tabellen-Elements (siehe "name=")
// index: Laufende Nummer des Tabellen-Elements (0-based), Default: 0
// doc: Dokument (document)
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
function getElementRows(name, index = 0, doc = document) {
    const __TABLE = getElement(name, index, doc);
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);
 
    return __ROWS;
}
 
// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
// index: Laufende Nummer des Elements (0-based)
// doc: Dokument (document)
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
function getRows(index, doc = document) {
    const __TABLE = getTable(index, 'table', doc);
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);
 
    return __ROWS;
}
 
// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
// id: ID des Tabellen-Elements
// doc: Dokument (document)
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
function getRowsById(id, doc = document) {
    const __TABLE = doc.getElementById(id);
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);
 
    return __ROWS;
}
 
// ==================== Abschnitt fuer Optionen auf der Seite ====================
 
// Liefert den Funktionsaufruf zur Option als String
// opt: Auszufuehrende Option
// isAlt: Angabe, ob AltAction statt Action gemeint ist
// value: Ggfs. zu setzender Wert
// serial: Serialization fuer String-Werte (Select, Textarea)
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return String mit dem (reinen) Funktionsaufruf
function getFormAction(opt, isAlt = false, value = undefined, serial = undefined, memory = undefined) {
    const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;
    const __MEMSTR = __STORAGE.Display;
    const __RUNPREFIX = __STORAGE.Prefix;
 
    if (__MEMORY !== undefined) {
        const __RELOAD = "window.location.reload()";
        const __SETITEM = function(item, val, quotes = true) {
                              return (__MEMSTR + ".setItem('" + __RUNPREFIX + item + "', " + (quotes ? "'" + val + "'" : val) + "),");
                          };
        const __SETITEMS = function(cmd, key = undefined, val = undefined) {
                              return ('(' + __SETITEM('cmd', cmd) + ((key === undefined) ? "" :
                                      __SETITEM('key', key) + __SETITEM('val', val, false)) + __RELOAD + ')');
                          };
        const __CONFIG = getOptConfig(opt);
        const __SERIAL = getValue(serial, getValue(__CONFIG.Serial, false));
        const __THISVAL = ((__CONFIG.ValType === 'String') ? "'\\x22' + this.value + '\\x22'" : "this.value");
        const __TVALUE = getValue(__CONFIG.ValType, __THISVAL, "new " + __CONFIG.ValType + '(' + __THISVAL + ')');
        const __VALSTR = ((value !== undefined) ? safeStringify(value) : __SERIAL ? "JSON.stringify(" + __TVALUE + ')' : __TVALUE);
        const __ACTION = (isAlt ? getValue(__CONFIG.AltAction, __CONFIG.Action) : __CONFIG.Action);
 
        if (__ACTION !== undefined) {
            switch (__ACTION) {
            case __OPTACTION.SET : //return "doActionSet('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                  return __SETITEMS('SET', getOptName(opt), __VALSTR);
            case __OPTACTION.NXT : //return "doActionNxt('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                  return __SETITEMS('NXT', getOptName(opt), __VALSTR);
            case __OPTACTION.RST : //return "doActionRst()";
                                  return __SETITEMS('RST');
            default :              break;
            }
        }
    }
 
    return undefined;
}
 
// Liefert die Funktionsaufruf zur Option als String
// opt: Auszufuehrende Option
// isAlt: Angabe, ob AltAction statt Action gemeint ist
// value: Ggfs. zu setzender Wert
// type: Event-Typ fuer <input>, z.B. "click" fuer "onclick="
// serial: Serialization fuer String-Werte (Select, Textarea)
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return String mit dem (reinen) Funktionsaufruf
function getFormActionEvent(opt, isAlt = false, value = undefined, type = 'click', serial = undefined, memory = undefined) {
    const __ACTION = getFormAction(opt, isAlt, value, serial, memory);
 
    return getValue(__ACTION, "", ' on' + type + '="' + __ACTION + '"');
}
 
// Hilfsfunktion: Wendet eine Konvertierung auf jede "Zeile" innerhalb eines Textes an
// text: Urspruenglicher Text
// convFun: function(line, index, arr): Konvertiert line in "Zeile" line des Arrays arr
// separator: Zeilentrenner im Text (Default: '\n')
// thisArg: optionaler this-Parameter fuer die Konvertierung
// limit: optionale Begrenzung der Zeilen
// return String mit dem neuen Text
function eachLine(text, convFun, separator = '\n', thisArg = undefined, limit = undefined) {
    const __ARR = text.split(separator, limit);
    const __RES = __ARR.map(convFun, thisArg);
 
    return __RES.join(separator);
}
 
// Hilfsfunktion: Ergaenzt einen HTML-Code um einen Titel (ToolTip)
// html: Urspruenglicher HTML-Code (z.B. ein HTML-Element oder Text)
// title: Im ToolTip angezeigter Text
// separator: Zeilentrenner im Text (Default: '|')
// limit: optionale Begrenzung der Zeilen
// return String mit dem neuen HTML-Code
function withTitle(html, title, separator = '|', limit = undefined) {
    if (title && title.length) {
        return eachLine(html, line => '<abbr title="' + title + '">' + line + '</abbr>', separator, undefined, limit);
    } else {
        return html;
    }
}
 
// Hilfsfunktion: Ermittelt einen Label- oder FormLabel-Eintrag (Default)
// label: Config-Eintrag fuer Label oder FormLabel
// defLabel: Ersatzwert, falls label nicht angegeben
// isSelect: Angabe, ob ein Parameter angezeigt wird (Default: false)
// isForm: Angabe, ob ein FormLabel gesucht ist (Default: true)
// return Vollstaendiger Label- oder FormLabel-Eintrag
function formatLabel(label, defLabel = undefined, isSelect = false, isForm = true) {
    const __LABEL = getValue(label, defLabel);
 
    if (isSelect && __LABEL && (substParam(__LABEL, '_') === __LABEL)) {
        return __LABEL + (isForm ? "|$" : " $");
    } else {
        return __LABEL;
    }
}
 
// Zeigt eine Option auf der Seite als Auswahlbox an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionSelect(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt);
    const __ACTION = getFormActionEvent(opt, false, undefined, 'change', undefined);
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label, true);
    const __TITLE = substParam(getValue(__CONFIG.Title, __CONFIG.Label), __VALUE);
    const __LABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
    let element = '<select name="' + __NAME + '" id="' + __NAME + '"' + __ACTION + '>';
 
    if (__CONFIG.FreeValue && ! (~ __CONFIG.Choice.indexOf(__VALUE))) {
        element += '\n<option value="' + __VALUE + '" SELECTED>' + __VALUE + '</option>';
    }
    for (let value of __CONFIG.Choice) {
        element += '\n<option value="' + value + '"' +
                  ((value === __VALUE) ? ' SELECTED' : "") +
                  '>' + value + '</option>';
    }
    element += '\n</select>';
 
    return withTitle(substParam(__LABEL, element), __TITLE);
}
 
// Zeigt eine Option auf der Seite als Radiobutton an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionRadio(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, false, true, 'click', false);
    const __ALTACTION = getFormActionEvent(opt, true, false, 'click', false);
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel); // nur nutzen, falls angegeben
    const __TITLE = getValue(__CONFIG.Title, '$');
    const __TITLEON = substParam(__TITLE, __CONFIG.Label);
    const __TITLEOFF = substParam(getValue(__CONFIG.AltTitle, __TITLE), __CONFIG.AltLabel);
    const __ELEMENTON  = '<input type="radio" name="' + __NAME +
                        '" id="' + __NAME + 'ON" value="1"' +
                        (__VALUE ? ' CHECKED' : __ACTION) +
                        ' /><label for="' + __NAME + 'ON">' +
                        __CONFIG.Label + '</label>';
    const __ELEMENTOFF = '<input type="radio" name="' + __NAME +
                        '" id="' + __NAME + 'OFF" value="0"' +
                        (__VALUE ? __ALTACTION : ' CHECKED') +
                        ' /><label for="' + __NAME + 'OFF">' +
                        __CONFIG.AltLabel + '</label>';
    const __ELEMENT = [
                          withTitle(__FORMLABEL, __VALUE ? __TITLEON : __TITLEOFF),
                          withTitle(__ELEMENTON, __TITLEON),
                          withTitle(__ELEMENTOFF, __TITLEOFF)
                      ];
 
    return ((__FORMLABEL && __FORMLABEL.length) ? __ELEMENT : __ELEMENT.slice(1, 3));
}
 
// Zeigt eine Option auf der Seite als Checkbox an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionCheckbox(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, 'click', false);
    const __VALUELABEL = (__VALUE ? __CONFIG.Label : getValue(__CONFIG.AltLabel, __CONFIG.Label));
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label);
    const __TITLE = substParam(getValue(__VALUE ? __CONFIG.Title : getValue(__CONFIG.AltTitle, __CONFIG.Title), '$'), __VALUELABEL);
 
    return withTitle('<input type="checkbox" name="' + __NAME +
                    '" id="' + __NAME + '" value="' + __VALUE + '"' +
                    (__VALUE ? ' CHECKED' : "") + __ACTION + ' /><label for="' +
                    __NAME + '">' + __FORMLABEL + '</label>', __TITLE);
}
 
// Zeigt eine Option auf der Seite als Daten-Textfeld an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionTextarea(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt);
    const __ACTION = getFormActionEvent(opt, false, undefined, 'submit', undefined);
    const __SUBMIT = getValue(__CONFIG.Submit, "");
    //const __ONSUBMIT = (__SUBMIT.length ? ' onKeyDown="' + __SUBMIT + '"': "");
    const __ONSUBMIT = (__SUBMIT ? ' onKeyDown="' + __SUBMIT + '"': "");
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label);
    const __TITLE = substParam(getValue(__CONFIG.Title, '$'), __FORMLABEL);
    const __ELEMENTLABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
    const __ELEMENTTEXT = '<textarea name="' + __NAME + '" id="' + __NAME + '" cols="' + __CONFIG.Cols +
                          '" rows="' + __CONFIG.Rows + '"' + __ONSUBMIT + __ACTION + '>' +
                          safeStringify(__VALUE, __CONFIG.Replace, __CONFIG.Space) + '</textarea>';
 
    return [ withTitle(__ELEMENTLABEL, __TITLE), __ELEMENTTEXT ];
}
 
// Zeigt eine Option auf der Seite als Button an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionButton(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, 'click', false);
    const __BUTTONLABEL = (__VALUE ? getValue(__CONFIG.AltLabel, __CONFIG.Label) : __CONFIG.Label);
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __BUTTONLABEL);
    const __BUTTONTITLE = substParam(getValue(__VALUE ? getValue(__CONFIG.AltTitle, __CONFIG.Title) : __CONFIG.Title, '$'), __BUTTONLABEL);
 
    return '<label for="' + __NAME + '">' + __FORMLABEL + '</label>' +
          withTitle('<input type="button" name="" + ' + __NAME +
                    '" id="' + __NAME + '" value="' + __BUTTONLABEL +
                    '"' + __ACTION + '/>', __BUTTONTITLE);
}
 
// Zeigt eine Option auf der Seite an (je nach Typ)
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionElement(opt) {
    const __CONFIG = getOptConfig(opt);
    const __TYPE = getValue(__CONFIG.FormType, __CONFIG.Type);
    let element = "";
 
    if (! __CONFIG.Hidden) {
        switch (__TYPE) {
        case __OPTTYPES.MC : element = getOptionSelect(opt);
                            break;
        case __OPTTYPES.SW : if (__CONFIG.FormLabel !== undefined) {
                                element = getOptionCheckbox(opt);
                            } else {
                                element = getOptionRadio(opt);
                            }
                            break;
        case __OPTTYPES.TF : element = getOptionCheckbox(opt);
                            break;
        case __OPTTYPES.SD : element = getOptionTextarea(opt);
                            break;
        case __OPTTYPES.SI : element = getOptionButton(opt);
                            break;
        default :            break;
        }
 
        if ((typeof element) !== 'string') {
            element = '<div>' + Array.from(element).join('<br />') + '</div>';
        }
    }
 
    return element;
}
 
// Gruppiert die Daten eines Objects nach einem Kriterium
// data: Object mit Daten
// byFun: function(val), die das Kriterium ermittelt. Default: value
// filterFun: function(key, index, arr), die das Kriterium key im Array arr an der Stelle index vergleicht. Default: Wert identisch
// sortFun: function(a, b), nach der die Kriterien sortiert werden. Default: Array.sort()
// return Neues Object mit Eintraegen der Form <Kriterium> : [ <alle Keys zu diesem Kriterium> ]
function groupData(data, byFun, filterFun, sortFun) {
    const __BYFUN = (byFun || (val => val));
    const __FILTERFUN = (filterFun || ((key, index, arr) => (arr[index] === key)));
    const __KEYS = Object.keys(data);
    const __VALS = Object.values(data);
    const __BYKEYS = __VALS.map(__BYFUN);
    const __BYKEYSET = new Set(__BYKEYS);
    const __BYKEYARRAY = [...__BYKEYSET];
    const __SORTEDKEYS = __BYKEYARRAY.sort(sortFun);
    const __GROUPEDKEYS = __SORTEDKEYS.map(byVal => __KEYS.filter((key, index, arr) => __FILTERFUN(byVal, index, __BYKEYS)));
    const __ASSIGN = ((keyArr, valArr) => Object.assign({ }, ...keyArr.map((key, index) => ({ [key] : valArr[index] }))));
 
    return __ASSIGN(__SORTEDKEYS, __GROUPEDKEYS);
}
 
// Baut das Benutzermenu auf der Seite auf
// optSet: Gesetzte Optionen
// optParams: Eventuell notwendige Parameter
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return String mit dem HTML-Code
function getForm(optSet, optParams = { }) {
    const __FORM = '<form id="options" method="POST"><table><tbody><tr>';
    const __FORMEND = '</tr></tbody></table></form>';
    const __FORMWIDTH = getValue(optParams.formWidth, 3);
    const __FORMBREAK = getValue(optParams.formBreak, __FORMWIDTH);
    const __SHOWFORM = getOptValue(optSet.showForm, true) ? optParams.showForm : { 'showForm' : true };
    const __PRIOOPTS = groupData(optSet, opt => getOptConfig(opt).FormPrio);
    let form = __FORM;
    let count = 0;  // Bisher angezeigte Optionen
    let column = 0;  // Spalte der letzten Option (1-basierend)
 
    for (let optKeys of Object.values(__PRIOOPTS)) {
        for (let optKey of optKeys) {
            if (checkItem(optKey, __SHOWFORM, optParams.hideForm)) {
                const __ELEMENT = getOptionElement(optSet[optKey]);
                const __TDOPT = ((~ __ELEMENT.indexOf('|')) ? "" : ' colspan="2"');
 
                if (__ELEMENT) {
                    if (++count > __FORMBREAK) {
                        if (++column > __FORMWIDTH) {
                            column = 1;
                        }
                    }
                    if (column === 1) {
                        form += '</tr><tr>';
                    }
                    form += '\n<td' + __TDOPT + '>' + __ELEMENT.replace('|', '</td><td>') + '</td>';
                }
            }
        }
    }
    form += '\n' + __FORMEND;
 
    return form;
}
 
// Fuegt das Script in die Seite ein
// optSet: Gesetzte Optionen
// optParams: Eventuell notwendige Parameter
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// return String mit dem HTML-Code fuer das Script
function getScript(optSet, optParams = { }) {
    //const __SCRIPT = '<script type="text/javascript">function activateMenu() { console.log("TADAAA!"); }</script>';
    //const __SCRIPT = '<script type="text/javascript">\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionRst(key, value) { alert("RESET"); }\n</script>';
    //const __FORM = '<form method="POST"><input type="button" id="showOpts" name="showOpts" value="Optionen anzeigen" onclick="activateMenu()" /></form>';
    const __SCRIPT = "";
 
    //window.eval('function activateMenu() { console.log("TADAAA!"); }');
 
    return __SCRIPT;
}
 
// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
// anchor: Element, das als Anker fuer die Anzeige dient
// optSet: Gesetzte Optionen
// optParams: Eventuell notwendige Parameter
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
function buildForm(anchor, optSet, optParams = { }) {
    __LOG[3]("buildForm()");
 
    const __FORM = getForm(optSet, optParams);
    const __SCRIPT = getScript(optSet, optParams);
 
    addForm(anchor, __FORM, __SCRIPT);
}
 
// Informationen zu hinzugefuegten Forms
const __FORMS = { };
 
// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
// anchor: Element, das als Anker fuer die Anzeige dient
// form: HTML-Form des Optionsmenu (hinten angefuegt)
// script: Script mit Reaktionen
function addForm(anchor, form = "", script = "") {
    const __OLDFORM = __FORMS[anchor];
    const __REST = (__OLDFORM === undefined) ? anchor.innerHTML :
                  anchor.innerHTML.substring(0, anchor.innerHTML.length - __OLDFORM.Script.length - __OLDFORM.Form.length);
 
    __FORMS[anchor] = {
                          'Script' : script,
                          'Form'  : form
                      };
 
    anchor.innerHTML = __REST + script + form;
}
 
// ==================== Abschnitt fuer Klasse Classification ====================
 
// Basisklasse fuer eine Klassifikation der Optionen nach Kriterium (z.B. Erst- und Zweitteam oder Fremdteam)
function Classification() {
    'use strict';
 
    this.renameFun = prefixName;
    //this.renameParamFun = undefined;
    this.optSet = undefined;
    this.optSelect = { };
}
 
Class.define(Classification, Object, {
                    'renameOptions' : function() {
                                          const __PARAM = this.renameParamFun();
 
                                          if (__PARAM !== undefined) {
                                              // Klassifizierte Optionen umbenennen...
                                              return renameOptions(this.optSet, this.optSelect, __PARAM, this.renameFun);
                                          } else {
                                              return Promise.resolve();
                                          }
                                      },
                    'deleteOptions' : function(ignList) {
                                          const __OPTSELECT = addProps([], this.optSelect, null, ignList);
 
                                          return deleteOptions(this.optSet, __OPTSELECT, true, true);
                                      }
                });
 
// ==================== Ende Abschnitt fuer Klasse Classification ====================
 
// ==================== Abschnitt fuer Klasse TeamClassification ====================
 
// Klasse fuer die Klassifikation der Optionen nach Team (Erst- und Zweitteam oder Fremdteam)
function TeamClassification() {
    'use strict';
 
    Classification.call(this);
 
    this.team = undefined;
    this.teamParams = undefined;
}
 
Class.define(TeamClassification, Classification, {
                    'renameParamFun' : function() {
                                          const __MYTEAM = (this.team = getMyTeam(this.optSet, this.teamParams, this.team));
 
                                          if (__MYTEAM.LdNr) {
                                              // Prefix fuer die Optionen mit gesonderten Behandlung...
                                              return __MYTEAM.LdNr.toString() + '.' + __MYTEAM.LgNr.toString() + ':';
                                          } else {
                                              return undefined;
                                          }
                                      }
                });
 
// ==================== Ende Abschnitt fuer Klasse TeamClassification ====================
 
// ==================== Abschnitt fuer Klasse Team ====================
 
// Klasse fuer Teamdaten
function Team(team, land, liga) {
    'use strict';
 
    this.Team = team;
    this.Land = land;
    this.Liga = liga;
    this.LdNr = getLandNr(land);
    this.LgNr = getLigaNr(liga);
}
 
Class.define(Team, Object, {
                    '__TEAMITEMS' : {  // Items, die in Team als Teamdaten gesetzt werden...
                                        'Team' : true,
                                        'Liga' : true,
                                        'Land' : true,
                                        'LdNr' : true,
                                        'LgNr' : true
                                    }
                });
 
// ==================== Ende Abschnitt fuer Klasse Team ====================
 
// ==================== Abschnitt fuer Klasse Verein ====================
 
// Klasse fuer Vereinsdaten
function Verein(team, land, liga, id, manager, flags) {
    'use strict';
 
    Team.call(this, team, land, liga);
 
    this.ID = id;
    this.Manager = manager;
    this.Flags = (flags || []);
}
 
Class.define(Verein, Team, {
                    '__TEAMITEMS' : {  // Items, die in Verein als Teamdaten gesetzt werden...
                                        'Team'    : true,
                                        'Liga'    : true,
                                        'Land'    : true,
                                        'LdNr'    : true,
                                        'LgNr'    : true,
                                        'ID'      : true,
                                        'Manager' : true,
                                        'Flags'  : true
                                    }
                });
 
// ==================== Ende Abschnitt fuer Klasse Verein ====================
 
// ==================== Spezialisierter Abschnitt fuer Optionen ====================
 
// Gesetzte Optionen (wird von initOptions() angelegt und von loadOptions() gefuellt):
const __OPTSET = { };
 
// Teamparameter fuer getrennte Speicherung der Optionen fuer Erst- und Zweitteam...
const __TEAMCLASS = new TeamClassification();
 
// Optionen mit Daten, die ZAT- und Team-bezogen gemerkt werden...
__TEAMCLASS.optSelect = {
                            'datenZat'        : true,
                            'oldDatenZat'    : true,
                            'fingerprints'    : true,
                            'birthdays'      : true,
                            'tClasses'        : true,
                            'progresses'      : true,
                            'ziehAnz'        : true,
                            'ziehAnzAufstieg' : true,
                            'zatAges'        : true,
                            'trainiert'      : true,
                            'positions'      : true,
                            'skills'          : true,
                            'foerderung'      : true
                        };
 
// Gibt die Teamdaten zurueck und aktualisiert sie ggfs. in der Option
// optSet: Platz fuer die gesetzten Optionen
// teamParams: Dynamisch ermittelte Teamdaten ('Team', 'Liga', 'Land', 'LdNr' und 'LgNr')
// myTeam: Objekt fuer die Teamdaten
// return Die Teamdaten oder undefined bei Fehler
function getMyTeam(optSet = undefined, teamParams = undefined, myTeam = new Team()) {
    if (teamParams !== undefined) {
        addProps(myTeam, teamParams, myTeam.__TEAMITEMS);
        __LOG[2]("Ermittelt: " + safeStringify(myTeam));
        // ... und abspeichern, falls erweunscht...
        if (optSet && optSet.team) {
            setOpt(optSet.team, myTeam, false);
        }
    } else {
        const __TEAM = ((optSet && optSet.team) ? getOptValue(optSet.team) : undefined);  // Gespeicherte Parameter
 
        if ((__TEAM !== undefined) && (__TEAM.Land !== undefined)) {
            addProps(myTeam, __TEAM, myTeam.__TEAMITEMS);
            __LOG[2]("Gespeichert: " + safeStringify(myTeam));
        } else {
            __LOG[6]("Team nicht ermittelt: " + safeStringify(__TEAM));
        }
    }
 
    return myTeam;
}
 
// Behandelt die Optionen und laedt das Benutzermenu
// optConfig: Konfiguration der Optionen
// optSet: Platz fuer die gesetzten Optionen
// optParams: Eventuell notwendige Parameter zur Initialisierung
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
// 'teamParams': Getrennte Daten-Option wird genutzt, hier: Team() mit 'LdNr'/'LgNr' des Erst- bzw. Zweitteams
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return Promise auf gefuelltes Objekt mit den gesetzten Optionen
function buildOptions(optConfig, optSet = undefined, optParams = { 'hideMenu' : false }) {
    // Klassifikation ueber Land und Liga des Teams...
    __TEAMCLASS.optSet = optSet;  // Classification mit optSet verknuepfen
    __TEAMCLASS.teamParams = optParams.teamParams;  // Ermittelte Parameter
 
    return startOptions(optConfig, optSet, __TEAMCLASS).then(optSet => {
                    if (optParams.getDonation) {
                        // Jugendfoerderung aus der Options-HTML-Seite ermitteln...
                        const __BOXDONATION = document.getElementsByTagName('option');
                        const __DONATION = getSelectionFromComboBox(__BOXDONATION, 10000, 'Number');
 
                        __LOG[3]("Jugendf\xF6rderung: " + __DONATION + " Euro");
 
                        // ... und abspeichern...
                        setOpt(optSet.foerderung, __DONATION, false);
                    }
 
                    return showOptions(optSet, optParams);
                }, defaultCatch);
}
 
// ==================== Ende Abschnitt fuer Optionen ====================
 
// ==================== Abschnitt genereller Code zur Anzeige der Jugend ====================
 
// Funktionen ***************************************************************************
 
// Erschafft die Spieler-Objekte und fuellt sie mit Werten
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// optSet: Gesetzte Optionen (und Config)
// colIdx: Liste von Spaltenindices der gesuchten Werte
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
// offsetLower: Ignorierte Zeilen unterhalb der Daten
// page: 1: Teamuebersicht, 2: Spielereinzelwerte, 3: Opt. Skill, 4: Optionen, Default: 0
function init(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, page = 0) {
    storePlayerDataFromHTML(playerRows, optSet, colIdx, offsetUpper, offsetLower, page);
 
    const __SAISON = getOptValue(optSet.saison);
    const __AKTZAT = getOptValue(optSet.aktuellerZat);
    const __GEALTERT = ((__AKTZAT >= 72) ? (getIntFromHTML(playerRows[playerRows.length - offsetLower - 1].cells, colIdx.Age) < 13) : false);
    const __CURRZAT = (__GEALTERT ? 0 : __AKTZAT);
    const __LGNR = __TEAMCLASS.team.LgNr;
    const __KLASSE = (__LGNR > 1) ? (__LGNR > 3) ? 3 : 2 : 1;
    const __DONATION = getOptValue(optSet.foerderung);
    const __BIRTHDAYS = getOptValue(optSet.birthdays, []);
    const __TCLASSES = getOptValue(optSet.tClasses, []);
    const __PROGRESSES = getOptValue(optSet.progresses, []);
    const __ZATAGES = getOptValue(optSet.zatAges, []);
    const __TRAINIERT = getOptValue(optSet.trainiert, []);
    const __POSITIONS = getOptValue(optSet.positions, []);
    const __SKILLS = getOptValue(optSet.skills, []);
    const __ISSKILLPAGE = (page === 2);
    const __BASEDATA = [ __BIRTHDAYS, __TCLASSES, __PROGRESSES ];  // fuer initPlayer
    const __DATA = (__ISSKILLPAGE ? [ __SKILLS, __BASEDATA ] : [ __BASEDATA, __SKILLS ]);  // fuer initPlayer: [0] = von HTML-Seite, [1] = aus gespeicherten Daten
    const __IDMAP = getPlayerIdMap(optSet);
    const __CATIDS = __IDMAP.catIds;
    const __PLAYERS = [];
 
    __LOG[5](__IDMAP);
 
    for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++) {
        const __CELLS = playerRows[i].cells;
 
        if (__CELLS.length > 1) {
            const __LAND = getStringFromHTML(__CELLS, colIdx.Land);
            const __AGE = getIntFromHTML(__CELLS, colIdx.Age);
            const __ISGOALIE = isGoalieFromHTML(__CELLS, colIdx.Age);
            const __AKTION = getElementFromHTML(__CELLS, colIdx.Akt);
 
            const __NEWPLAYER = new PlayerRecord(__LAND, __AGE, __ISGOALIE, __SAISON, __CURRZAT, __DONATION);
 
            __NEWPLAYER.initPlayer(__DATA[0], j, __ISSKILLPAGE);
 
            const __IDX = selectPlayerIndex(__NEWPLAYER, j, __CATIDS);
 
            __NEWPLAYER.initPlayer(__DATA[1], __IDX, ! __ISSKILLPAGE);
 
            __NEWPLAYER.prognoseSkills();
 
            if (! __ISSKILLPAGE) {
                __NEWPLAYER.setZusatz(__ZATAGES[__IDX], __TRAINIERT[__IDX], __POSITIONS[__IDX]);
            }
 
            __NEWPLAYER.createWarnDraw(__AKTION, __KLASSE);
 
            __PLAYERS[j++] = __NEWPLAYER;
        }
    }
 
    if (__ISSKILLPAGE) {
        calcPlayerData(__PLAYERS, optSet);
    } else {
        setPlayerData(__PLAYERS, optSet);
    }
 
    storePlayerIds(__PLAYERS, optSet);
 
    return __PLAYERS;
}
 
// Berechnet die Identifikations-IDs (Fingerprints) der Spieler neu und speichert diese
function getPlayerIdMap(optSet) {
    const __FINGERPRINTS = getOptValue(optSet.fingerprints, []);
    const __MAP = {
                      'ids'    : { },
                      'cats'  : [],
                      'catIds' : { }
                  };
    const __IDS = __MAP.ids;
    const __CATS = __MAP.cats;
    const __CATIDS = __MAP.catIds;
 
    for (let i = 0; i < __FINGERPRINTS.length; i++) {
        const __ID = __FINGERPRINTS[i];
        const __CAT = PlayerRecord.prototype.getCatFromFingerPrint(__ID);
 
        if (__ID) {
            if (! __CATIDS[__CAT]) {
                __CATIDS[__CAT] = { };
            }
            __IDS[__ID] = i;
            __CATS[i] = __CAT;
            __CATIDS[__CAT][__ID] = i;
        }
    }
 
    return __MAP;
}
 
// Berechnet die Identifikations-IDs (Fingerprints) der Spieler neu und speichert diese
// players: Array von PlayerRecord mit den Spielerdaten
// optSet: Gesetzte Optionen (und Config)
function storePlayerIds(players, optSet) {
    const __FINGERPRINTS = [];
 
    for (let i = 0; i < players.length; i++) {
        const __PLAYER = players[i];
 
        if ((__PLAYER.zatGeb !== undefined) && (__PLAYER.talent !== undefined) && (__PLAYER.positions !== undefined)) {
            __FINGERPRINTS[i]  = __PLAYER.getFingerPrint();
        }
    }
 
    setOpt(optSet.fingerprints, __FINGERPRINTS, false);
}
 
// Sucht fuer den Spieler den Eintrag aus catIds heraus und gibt den (geloeschten) Index zurueck
// player: PlayerRecord mit den Daten eines Spielers
// index: Position des Spielers im neuen Array von Spielerdaten
// catIds: PlayerIdMap zum Finden des Spielers ueber die Spielerdaten
// return Original-Index der Daten dieses Spielers im Array von Spielerdaten
function selectPlayerIndex(player, index, catIds) {
    const __MYCAT = player.getCat();
    const __CATS = catIds[__MYCAT];
    const __ID = player.findInFingerPrints(__CATS);
    let idx = index;
 
    if (__ID !== undefined) {
        idx = __CATS[__ID];
        delete __CATS[__ID];
    }
 
    return idx;
}
 
// Speichtert die abgeleiteten Werte in den Spieler-Objekten
// players: Array von PlayerRecord mit den Spielerdaten
// optSet: Gesetzte Optionen (und Config)
function setPlayerData(players, optSet) {
    const __ZIEHANZAHL = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
    let ziehAnzAufstieg = 0;
    const __ZATAGES = [];
    const __TRAINIERT = [];
    const __POSITIONS = [];
 
    for (let i = 0; i < players.length; i++) {
        const __ZUSATZ = players[i].calcZusatz();
 
        if (__ZUSATZ.zatAge !== undefined) {  // braucht Geburtstag fuer gueltige Werte!
            const __INDEX = players[i].calcZiehIndex();  // Lfd. Nummer des Abrechnungsmonats (0-basiert)
 
            if ((__INDEX >= 0) && (__INDEX < __ZIEHANZAHL.length)) {
                __ZIEHANZAHL[__INDEX]++;
            }
 
            __ZATAGES[i] = __ZUSATZ.zatAge;
        }
        if (players[i].isZiehAufstieg()) {
            ziehAnzAufstieg++;
        }
        __TRAINIERT[i] = __ZUSATZ.trainiert;
        __POSITIONS[i] = __ZUSATZ.bestPos;
    }
 
    setOpt(optSet.ziehAnz, __ZIEHANZAHL, false);
    setOpt(optSet.ziehAnzAufstieg, ziehAnzAufstieg, false);
    setOpt(optSet.zatAges, __ZATAGES, false);
    setOpt(optSet.trainiert, __TRAINIERT, false);
    setOpt(optSet.positions, __POSITIONS, false);
}
 
// Berechnet die abgeleiteten Werte in den Spieler-Objekten neu und speichert diese
// players: Array von PlayerRecord mit den Spielerdaten
// optSet: Gesetzte Optionen (und Config)
function calcPlayerData(players, optSet) {
    const __ZATAGES = [];
    const __TRAINIERT = [];
    const __POSITIONS = [];
 
    for (let i = 0; i < players.length; i++) {
        const __ZUSATZ = players[i].calcZusatz();
 
        if (__ZUSATZ.zatAge !== undefined) {  // braucht Geburtstag fuer gueltige Werte!
            __ZATAGES[i] = __ZUSATZ.zatAge;
        }
        __TRAINIERT[i] = __ZUSATZ.trainiert;
        __POSITIONS[i] = __ZUSATZ.bestPos;
    }
 
    setOpt(optSet.zatAges, __ZATAGES, false);
    setOpt(optSet.trainiert, __TRAINIERT, false);
    setOpt(optSet.positions, __POSITIONS, false);
}
 
// Ermittelt die fuer diese Seite relevanten Werte in den Spieler-Objekten aus den Daten der Seite und speichert diese
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// optSet: Gesetzte Optionen (und Config)
// colIdx: Liste von Spaltenindices der gesuchten Werte
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
// offsetLower: Ignorierte Zeilen unterhalb der Daten
// page: 1: Teamuebersicht, 2: Spielereinzelwerte, 3: Opt. Skill, 4: Optionen, Default: 0
function storePlayerDataFromHTML(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, page = 0) {
    const __COLDEFS = [ { }, {
                                'birthdays'  : { 'name' : 'birthdays', 'getFun' : getIntFromHTML, 'params' : [ colIdx.Geb ] },
                                'tClasses'  : { 'name' : 'tClasses', 'getFun' : getTalentFromHTML, 'params' : [ colIdx.Tal ] },
                                'progresses' : { 'name' : 'progresses', 'getFun' : getAufwertFromHTML, 'params' : [ colIdx.Auf, getOptValue(optSet.shortAufw, true) ] }
                            }, {
                                'skills'    : { 'name' : 'skills', 'getFun' : getSkillsFromHTML, 'params' : [ colIdx ]}
                            } ][getValueIn(page, 1, 2, 0)];
 
    return storePlayerDataColsFromHTML(playerRows, optSet, __COLDEFS, offsetUpper, offsetLower);
}
 
// Ermittelt bestimmte Werte in den Spieler-Objekten aus den Daten der Seite und speichert diese
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// optSet: Gesetzte Optionen (und Config)
// colDefs: Informationen zu ausgewaehlten Datenspalten
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
// offsetLower: Ignorierte Zeilen unterhalb der Daten
function storePlayerDataColsFromHTML(playerRows, optSet, colDefs, offsetUpper = 1, offsetLower = 0) {
    const __DATA = { };
 
    for (let key in colDefs) {
        __DATA[key] = [];
    }
 
    for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++) {
        const __CELLS = playerRows[i].cells;
 
        if (__CELLS.length > 1) {
            for (let key in colDefs) {
                const __COLDEF = colDefs[key];
 
                __DATA[key][j] = __COLDEF.getFun(__CELLS, ...__COLDEF.params);
            }
            j++;
        }
    }
 
    for (let key in colDefs) {
        const __COLDEF = colDefs[key];
 
        __LOG[7]('Schreibe ' + __COLDEF.name + ': ' + __DATA[key]);
 
        setOpt(optSet[__COLDEF.name], __DATA[key], false);
    }
}
 
// Trennt die Gruppen (z.B. Jahrgaenge) mit Linien
function separateGroups(rows, borderString, colIdxSort = 0, offsetUpper = 1, offsetLower = 0, offsetLeft = -1, offsetRight = 0, formatFun = sameValue) {
    if (offsetLeft < 0) {
        offsetLeft = colIdxSort;  // ab Sortierspalte
    }
 
    for (let i = offsetUpper, newVal, oldVal = formatFun((rows[i].cells[colIdxSort] || { }).textContent); i < rows.length - offsetLower - 1; i++, oldVal = newVal) {
        newVal = formatFun((rows[i + 1].cells[colIdxSort] || { }).textContent);
        if (newVal !== oldVal) {
            for (let j = offsetLeft; j < rows[i].cells.length - offsetRight; j++) {
                rows[i].cells[j].style.borderBottom = borderString;
            }
        }
    }
}
 
// Klasse ColumnManager *****************************************************************
 
function ColumnManager(optSet, colIdx, showCol) {
    'use strict';
 
    __LOG[3]("ColumnManager()");
 
    const __SHOWCOL = getValue(showCol, true);
    const __SHOWALL = ((__SHOWCOL === true) || (__SHOWCOL.Default === true));
 
    const __BIRTHDAYS = getOptValue(optSet.birthdays, []).length;
    const __TCLASSES = getOptValue(optSet.tClasses, []).length;
    const __PROGRESSES = getOptValue(optSet.progresses, []).length;
 
    const __ZATAGES = getOptValue(optSet.zatAges, []).length;
    const __TRAINIERT = getOptValue(optSet.trainiert, []).length;
    const __POSITIONS = getOptValue(optSet.positions, []).length;
 
    const __EINZELSKILLS = getOptValue(optSet.skills, []).length;
    const __PROJECTION = (__EINZELSKILLS && __ZATAGES);
 
    this.colIdx = colIdx;
 
    this.saison = getOptValue(optSet.saison);
    this.gt = getOptValue(optSet.zeigeJahrgang);
    this.gtUxx = getOptValue(optSet.zeigeUxx);
 
    this.fpId = (__BIRTHDAYS && __TCLASSES && __POSITIONS && getValue(__SHOWCOL.zeigeId, __SHOWALL) && getOptValue(optSet.zeigeId));
    this.warn = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnung, __SHOWALL) && getOptValue(optSet.zeigeWarnung));
    this.warnMonth = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungMonat, __SHOWALL) && getOptValue(optSet.zeigeWarnungMonat));
    this.warnHome = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungHome, __SHOWALL) && getOptValue(optSet.zeigeWarnungHome));
    this.warnDialog = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungDialog, __SHOWALL) && getOptValue(optSet.zeigeWarnungDialog));
    this.warnAufstieg = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungAufstieg, __SHOWALL) && getOptValue(optSet.zeigeWarnungAufstieg));
    this.warnLegende = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungLegende, __SHOWALL) && getOptValue(optSet.zeigeWarnungLegende));
    this.bar = (__PROJECTION && getValue(__SHOWCOL.zeigeBalken, __SHOWALL) && getOptValue(optSet.zeigeBalken));
    this.barAbs = getOptValue(optSet.absBalken);
    this.donor = getOptValue(optSet.foerderung);
    this.geb = (__BIRTHDAYS && getValue(__SHOWCOL.zeigeGeb, __SHOWALL) && getOptValue(optSet.zeigeGeb));
    this.tal = (__TCLASSES && getValue(__SHOWCOL.zeigeTal, __SHOWALL) && getOptValue(optSet.zeigeTal));
    this.quo = (__ZATAGES && __TRAINIERT && getValue(__SHOWCOL.zeigeQuote, __SHOWALL) && getOptValue(optSet.zeigeQuote));
    this.aufw = (__PROGRESSES && getValue(__SHOWCOL.zeigeAufw, __SHOWALL) && getOptValue(optSet.zeigeAufw));
    this.substAge = (__ZATAGES && getValue(__SHOWCOL.ersetzeAlter, __SHOWALL) && getOptValue(optSet.ersetzeAlter));
    this.alter = (__ZATAGES && getValue(__SHOWCOL.zeigeAlter, __SHOWALL) && getOptValue(optSet.zeigeAlter));
    this.fix = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeFixSkills, __SHOWALL) && getOptValue(optSet.zeigeFixSkills));
    this.tr = (__EINZELSKILLS && __TRAINIERT && getValue(__SHOWCOL.zeigeTrainiert, __SHOWALL) && getOptValue(optSet.zeigeTrainiert));
    this.zat = (__ZATAGES && getValue(__SHOWCOL.zeigeZatDone, __SHOWALL) && getOptValue(optSet.zeigeZatDone));
    this.antHpt = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeAnteilPri, __SHOWALL) && getOptValue(optSet.zeigeAnteilPri));
    this.antNeb = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeAnteilSec, __SHOWALL) && getOptValue(optSet.zeigeAnteilSec));
    this.pri = (__EINZELSKILLS && getValue(__SHOWCOL.zeigePrios, __SHOWALL) && getOptValue(optSet.zeigePrios));
    this.skill = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeSkill, __SHOWALL) && getOptValue(optSet.zeigeSkill));
    this.pos = (__EINZELSKILLS && __POSITIONS && getValue(__SHOWCOL.zeigePosition, __SHOWALL) && getOptValue(optSet.zeigePosition));
    this.anzOpti = ((__EINZELSKILLS && getValue(__SHOWCOL.zeigeOpti, __SHOWALL)) ? getOptValue(optSet.anzahlOpti) : 0);
    this.anzMw =  ((__PROJECTION && getValue(__SHOWCOL.zeigeMW, __SHOWALL)) ? getOptValue(optSet.anzahlMW) : 0);
    this.substSkills = (__PROJECTION && getValue(__SHOWCOL.ersetzeSkills, __SHOWALL) && getOptValue(optSet.ersetzeSkills));
    this.trE = (__PROJECTION && __TRAINIERT && getValue(__SHOWCOL.zeigeTrainiertEnde, __SHOWALL) && getOptValue(optSet.zeigeTrainiertEnde));
    this.zatE = (__ZATAGES && getValue(__SHOWCOL.zeigeZatLeft, __SHOWALL) && getOptValue(optSet.zeigeZatLeft));
    this.antHptE = (__PROJECTION && getValue(__SHOWCOL.zeigeAnteilPriEnde, __SHOWALL) && getOptValue(optSet.zeigeAnteilPriEnde));
    this.antNebE = (__PROJECTION && getValue(__SHOWCOL.zeigeAnteilSecEnde, __SHOWALL) && getOptValue(optSet.zeigeAnteilSecEnde));
    this.priE = (__PROJECTION && getValue(__SHOWCOL.zeigePriosEnde, __SHOWALL) && getOptValue(optSet.zeigePriosEnde));
    this.skillE = (__PROJECTION && getValue(__SHOWCOL.zeigeSkillEnde, __SHOWALL) && getOptValue(optSet.zeigeSkillEnde));
    this.anzOptiE = ((__PROJECTION && getValue(__SHOWCOL.zeigeOptiEnde, __SHOWALL)) ? getOptValue(optSet.anzahlOptiEnde) : 0);
    this.anzMwE = ((__PROJECTION && getValue(__SHOWCOL.zeigeMWEnde, __SHOWALL)) ? getOptValue(optSet.anzahlMWEnde) : 0);
    this.kennzE = getOptValue(optSet.kennzeichenEnde);
}
 
Class.define(ColumnManager, Object, {
        'toString'      : function() {  // Bisher nur die noetigsten Parameter ausgegeben...
                              let result = "Skillschnitt\t\t" + this.skill + '\n';
                              result += "Beste Position\t" + this.pos + '\n';
                              result += "Optis\t\t\t" + this.anzOpti + '\n';
                              result += "Marktwerte\t\t" + this.anzMw + '\n';
                              result += "Skillschnitt Ende\t" + this.skillE + '\n';
                              result += "Optis Ende\t\t" + this.anzOptiE + '\n';
                              result += "Marktwerte Ende\t" + this.anzMwE + '\n';
 
                              return result;
                          },
        'addCell'        : function(tableRow) {
                              return tableRow.insertCell(-1);
                          },
        'addAndFillCell' : function(tableRow, value, color, align, digits = 2) {
                              let text = value;
 
                              if ((value || (value === 0)) && isFinite(value) && (value !== true) && (value !== false)) {
                                  // Zahl einfuegen
                                  if (value < 1000) {
                                      // Mit Nachkommastellen darstellen
                                      text = parseFloat(value).toFixed(digits);
                                  } else {
                                      // Mit Tausenderpunkten darstellen
                                      text = getNumberString(value.toString());
                                  }
                              }
 
                              // String, Boolean oder Zahl einfuegen...
                              const __CELL = this.addCell(tableRow);
 
                              __CELL.innerHTML = text;
                              if (color) {
                                  __CELL.style.color = color;
                              }
                              if (align) {
                                  __CELL.align = align;
                              }
 
                              return __CELL;
                          },
        'addAndBarCell'  : function(tableRow, value, scale = 100, offset = 0, width = 100, height = 10, zoom = 100) {
                              const __VALUE = ((scale && isFinite(value)) ? ((value - offset) / Math.max(1, scale - offset) * 100) : 0);
 
                              // HTML-Code fuer Anteilsbalken einfuegen...
                              const __CELL = this.addCell(tableRow);
 
                              __CELL.innerHTML = this.getBarImg(__VALUE, width, height, zoom);
                              __CELL.align = 'left';
 
                              return __CELL;
                          },
        'getBarImg'      : function(value, width = 100, height = 10, zoom = 100) {
                              const __IMAGE = Math.min(99, Math.max(0, getMulValue(value, 1, 0, 0)));
                              const __LENGTH = getMulValue(width / 100, getMulValue(zoom / 100, value, 0, 0), 0, 0);
                              const __WIDTH = Math.min(width, __LENGTH);
                              const __HEIGHT = Math.max(3, getMulValue(zoom / 100, height * (__LENGTH / __WIDTH), 0, 0));
 
                              // HTML-Code fuer Anteilsbalken...
                              return '<img src="images/balken/' + __IMAGE + '.GIF" width="' + __WIDTH + '" height=' + __HEIGHT + '>';
                          },
        'addTitles'      : function(headers, titleColor = "#FFFFFF") {
                              // Spaltentitel zentrieren
                              headers.align = "center";
 
                              // Titel fuer die aktuellen Werte
                              if (this.fpId) {
                                  this.addAndFillCell(headers, "Identifikation", titleColor);
                              }
                              if (this.bar) {
                                  this.addAndFillCell(headers, "Qualit\xE4t", titleColor);
                              }
                              if (this.tal) {
                                  this.addAndFillCell(headers, "Talent", titleColor);
                              }
                              if (this.quo) {
                                  this.addAndFillCell(headers, "Quote", titleColor);
                              }
                              if (this.aufw) {
                                  this.addAndFillCell(headers, "Aufwertung", titleColor);
                              }
                              if (this.geb) {
                                  this.addAndFillCell(headers, "Geb.", titleColor);
                              }
                              if (this.alter && ! this.substAge) {
                                  this.addAndFillCell(headers, "Alter", titleColor);
                              }
                              if (this.fix) {
                                  this.addAndFillCell(headers, "fix", titleColor);
                              }
                              if (this.tr) {
                                  this.addAndFillCell(headers, "tr.", titleColor);
                              }
                              if (this.zat) {
                                  this.addAndFillCell(headers, "ZAT", titleColor);
                              }
                              if (this.antHpt) {
                                  this.addAndFillCell(headers, "%H", titleColor);
                              }
                              if (this.antNeb) {
                                  this.addAndFillCell(headers, "%N", titleColor);
                              }
                              if (this.pri) {
                                  this.addAndFillCell(headers, "Prios", titleColor);
                              }
                              if (this.skill) {
                                  this.addAndFillCell(headers, "Skill", titleColor);
                              }
                              if (this.pos) {
                                  this.addAndFillCell(headers, "Pos", titleColor);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  if (i <= this.anzOpti) {
                                      this.addAndFillCell(headers, "Opti " + i, titleColor);
                                  }
                                  if (i <= this.anzMw) {
                                      this.addAndFillCell(headers, "MW " + i, titleColor);
                                  }
                              }
 
                              // Titel fuer die Werte mit Ende 18
                              if (this.trE) {
                                  this.addAndFillCell(headers, "tr." + this.kennzE, titleColor);
                              }
                              if (this.zatE) {
                                  this.addAndFillCell(headers, "ZAT" + this.kennzE, titleColor);
                              }
                              if (this.antHptE) {
                                  this.addAndFillCell(headers, "%H" + this.kennzE, titleColor);
                              }
                              if (this.antNebE) {
                                  this.addAndFillCell(headers, "%N" + this.kennzE, titleColor);
                              }
                              if (this.priE) {
                                  this.addAndFillCell(headers, "Prios" + this.kennzE, titleColor);
                              }
                              if (this.skillE) {
                                  this.addAndFillCell(headers, "Skill" + this.kennzE, titleColor);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  if (i <= this.anzOptiE) {
                                      this.addAndFillCell(headers, "Opti " + i + this.kennzE, titleColor);
                                  }
                                  if (i <= this.anzMwE) {
                                      this.addAndFillCell(headers, "MW " + i + this.kennzE, titleColor);
                                  }
                              }
                          },  // Ende addTitles()
        'addValues'      : function(player, playerRow, color = "#FFFFFF") {
                              // Warnlevel des Spielers anpassen...
                              const __WARNDRAW = player.warnDraw || player.warnDrawAufstieg || __NOWARNDRAW;
                              __WARNDRAW.setWarn(this.warn, this.warnMonth, this.warnAufstieg);
 
                              const __IDXPRI = getIdxPriSkills(player.getPos());
                              const __COLOR = __WARNDRAW.getColor(player.isGoalie ? getColor('TOR') : color); // Angepasst an Ziehwarnung
                              const __POS1COLOR = getColor((player.getPosPercent() > 99.99) ? 'LEI' : player.getPos());
                              const __OSBLAU = getColor("");
 
                              // Aktuelle Werte
                              if (this.fpId) {
                                  this.addAndFillCell(playerRow, player.getFingerPrint(), __COLOR);
                              }
                              if (this.bar) {
                                  const __VALUE = player.getPrios(player.getPos(), player.__TIME.end);
                                  const __SCALE = (this.barAbs ? 100 : (this.donor / 125));
                                  const __OFFSET = (this.barAbs ? 0 : Math.pow(__SCALE / 20, 2));
                                  const __ZOOM = 50 + __SCALE / 2;
 
                                  this.addAndBarCell(playerRow, __VALUE, __SCALE, __OFFSET, 100, 10, __ZOOM);
                              }
                              if (this.tal) {
                                  this.addAndFillCell(playerRow, player.getTalent(), __COLOR);
                              }
                              if (this.quo) {
                                  this.addAndFillCell(playerRow, player.getAufwertungsSchnitt(), __COLOR, null, 2);
                              }
                              if (this.colIdx.Auf) {
                                  convertStringFromHTML(playerRow.cells, this.colIdx.Auf, function(aufwert) {
                                                                                              return player.boldPriSkillNames(aufwert);
                                                                                          });
                              }
                              if (this.aufw) {
                                  this.addAndFillCell(playerRow, player.boldPriSkillNames(player.getAufwert()), __COLOR, 'left');
                              }
                              if (this.geb) {
                                  this.addAndFillCell(playerRow, player.getGeb(), __COLOR, null, 0);
                              }
                              if (this.substAge) {
                                  convertStringFromHTML(playerRow.cells, this.colIdx.Age, function(unused) {
                                                                                              return parseFloat(player.getAge()).toFixed(2);
                                                                                          });
                              } else if (this.alter) {
                                  this.addAndFillCell(playerRow, player.getAge(), __COLOR, null, 2);
                              }
                              if (__WARNDRAW.monthDraw()) {  // Abrechnungszeitraum vor dem letztmoeglichen Ziehen...
                                  formatCell(playerRow.cells[this.colIdx.Age], true, __WARNDRAW.colAlert, null, 1.0);
                              }
                              if (this.fix) {
                                  this.addAndFillCell(playerRow, player.getFixSkills(), __COLOR, null, 0);
                              }
                              if (this.tr) {
                                  this.addAndFillCell(playerRow, player.getTrainableSkills(), __COLOR, null, 0);
                              }
                              if (this.zat) {
                                  this.addAndFillCell(playerRow, player.getZatDone(), __COLOR, null, 0);
                              }
                              if (this.antHpt) {
                                  this.addAndFillCell(playerRow, player.getPriPercent(player.getPos()), __COLOR, null, 0);
                              }
                              if (this.antNeb) {
                                  this.addAndFillCell(playerRow, player.getSecPercent(player.getPos()), __COLOR, null, 0);
                              }
                              if (this.pri) {
                                  this.addAndFillCell(playerRow, player.getPrios(player.getPos()), __COLOR, null, 1);
                              }
                              if (this.skill) {
                                  this.addAndFillCell(playerRow, player.getSkill(), __COLOR, null, 2);
                              }
                              if (this.pos) {
                                  this.addAndFillCell(playerRow, player.getPos(), __POS1COLOR);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
                                  const __COLI = getColor(__POSI);
 
                                  if (i <= this.anzOpti) {
                                      if ((i === 1) || ! player.isGoalie) {
                                          // Opti anzeigen
                                          this.addAndFillCell(playerRow, player.getOpti(__POSI), __COLI, null, 2);
                                      } else {
                                          // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                                  if (i <= this.anzMw) {
                                      if ((i === 1) || ! player.isGoalie) {
                                          // MW anzeigen
                                          this.addAndFillCell(playerRow, player.getMarketValue(__POSI), __COLI, null, 0);
                                      } else {
                                          // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                              }
 
                              // Einzelwerte mit Ende 18
                              if (this.colIdx.Einz) {
                                  if (this.substSkills) {
                                      convertArrayFromHTML(playerRow.cells, this.colIdx.Einz, player.skillsEnd, function(value, cell, unused, index) {
                                                                                                                    if (~ __IDXPRI.indexOf(index)) {
                                                                                                                        formatCell(cell, true, __OSBLAU, __POS1COLOR, 1.0);
                                                                                                                    }
                                                                                                                    return value;
                                                                                                                });
                                  } else {
                                      convertArrayFromHTML(playerRow.cells, this.colIdx.Einz, player.skills.length, function(value, cell, unused, index) {
                                                                                                                        if (~ __IDXPRI.indexOf(index)) {
                                                                                                                            formatCell(cell, true, __POS1COLOR, null, 1.0);
                                                                                                                        }
                                                                                                                        return value;
                                                                                                                    });
                                  }
                              }
                              if (this.trE) {
                                  this.addAndFillCell(playerRow, player.getTrainableSkills(player.__TIME.end), __COLOR, null, 1);
                              }
                              if (this.zatE) {
                                  this.addAndFillCell(playerRow, player.getZatLeft(), __COLOR, null, 0);
                              }
                              if (this.antHptE) {
                                  this.addAndFillCell(playerRow, player.getPriPercent(player.getPos(), player.__TIME.end), __COLOR, null, 0);
                              }
                              if (this.antNebE) {
                                  this.addAndFillCell(playerRow, player.getSecPercent(player.getPos(), player.__TIME.end), __COLOR, null, 0);
                              }
                              if (this.priE) {
                                  this.addAndFillCell(playerRow, player.getPrios(player.getPos(), player.__TIME.end), __COLOR, null, 1);
                              }
                              if (this.skillE) {
                                  this.addAndFillCell(playerRow, player.getSkill(player.__TIME.end), __COLOR, null, 2);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
                                  const __COLI = getColor(__POSI);
 
                                  if (i <= this.anzOptiE) {
                                      if ((i === 1) || ! player.isGoalie) {
                                          // Opti anzeigen
                                          this.addAndFillCell(playerRow, player.getOpti(__POSI, player.__TIME.end), __COLI, null, 2);
                                      } else {
                                          // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                                  if (i <= this.anzMwE) {
                                      if ((i === 1) || ! player.isGoalie) {
                                          // MW anzeigen
                                          this.addAndFillCell(playerRow, player.getMarketValue(__POSI, player.__TIME.end), __COLI, null, 0);
                                      } else {
                                          // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                              }
                          },  // Ende addValues(player, playerRow)
        'setGroupTitle'  : function(tableRow) {
                              if (this.gtUxx) {
                                  const __CELL = tableRow.cells[0];
                                  const __SAI = __CELL.innerHTML.match(/Saison (\d+)/)[1];
                                  const __JG = 13 + this.saison - __SAI;
 
                                  __CELL.innerHTML = __CELL.innerHTML.replace('Jahrgang', 'U' + __JG + ' - $&');
                              }
 
                              tableRow.style.display = (this.gt ? '' : 'none');
                          }  // Ende setGroupTitle(tableRow)
    });
 
// Klasse PlayerRecord ******************************************************************
 
function PlayerRecord(land, age, isGoalie, saison, currZAT, donation) {
    'use strict';
 
    this.land = land;
    this.age = age;
    this.isGoalie = isGoalie;
 
    this.saison = saison;
    this.currZAT = currZAT;
    this.donation = donation;
    this.mwFormel = ((this.saison < 10) ? this.__MWFORMEL.alt : this.__MWFORMEL.S10);
 
    // in new PlayerRecord() definiert:
    // this.land: TLA des Geburtslandes
    // this.age: Ganzzahliges Alter des Spielers
    // this.isGoalie: Angabe, ob es ein TOR ist
    // this.mwFormel: Benutzte MW-Formel, siehe __MWFORMEL
    // this.donation: Jugendfoerderungsbetrag in Euro
 
    // in this.initPlayer() definiert:
    // this.zatGeb: ZAT, an dem der Spieler Geburtstag hat, -1 fuer "noch nicht zugewiesen", also '?'
    // this.zatAge: Bisherige erfolgte Trainings-ZATs
    // this.birth: Universell eindeutige Nummer des Geburtstags-ZATs des Spielers
    // this.talent: Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
    // this.aufwert: Aufwertungsstring
 
    // in this.calcSkills() definiert:
    // this.positions[][]: Positionstexte und Optis; TOR-Index ist 5
    // this.skills[]: Einzelskills
    // this.skillsEnd[]: Berechnet aus this.skills, this.age und aktuellerZat
    // this.zatLeft: ZATs bis zum Ende 18 (letzte Ziehmoeglichkeit)
    // this.restEnd: Korrekturterm zum Ausgleich von Rundungsfehlern mit Ende 18
    //              (also Skills, die nicht explizit in this.skillsEnd stehen)
 
    // in this.calcZusatz()/setZusatz() definiert:
    // this.trainiert: Anzahl der erfolgreichen Trainingspunkte
    // indirekt this.zatAge und this.bestPos
 
    // in this.createWarnDraw() definiert:
    // this.warnDraw: Behandlung von Warnungen Ende 18
    // this.warnDrawAufstieg: Behandlung von Warnungen bei Aufstieg
 
    // in this.getPos() definiert:
    // this.bestPos: erster (bester) Positionstext
}
 
Class.define(PlayerRecord, Object, {
        '__TIME'                : {  // Zeitpunktangaben
                                      'cre' : 0,  // Jugendspieler angelegt (mit 12 Jahren)
                                      'beg' : 1,  // Jugendspieler darf trainieren (wird 13 Jahre alt)
                                      'now' : 2,  // Aktueller ZAT
                                      'end' : 3  // Jugendspieler wird Ende 18 gezogen (Geb. - 1 bzw. ZAT 71 fuer '?')
                                  },
        '__MWFORMEL'            : {  // Zu benutzende Marktwertformel
                                      'alt' : 0,  // Marktwertformel bis Saison 9 inklusive
                                      'S10' : 1  // Marktwertformel MW5 ab Saison 10
                                  },
        '__MAXPRISKILLS'        : 4 * 99,
        'toString'              : function() {  // Bisher nur die noetigsten Werte ausgegeben...
                                      let result = "Alter\t\t" + this.age + "\n\n";
                                      result += "Aktuelle Werte\n";
                                      result += "Skillschnitt\t" + this.getSkill().toFixed(2) + '\n';
                                      result += "Optis und Marktwerte";
 
                                      for (let pos of this.positions) {
                                          result += "\n\t" + pos + '\t';
                                          result += this.getOpti(pos).toFixed(2) + '\t';
                                          result += getNumberString(this.getMarketValue(pos).toString());
                                      }
 
                                      result += "\n\nWerte mit Ende 18\n";
                                      result += "Skillschnitt\t" + this.getSkill(this.__TIME.end).toFixed(2) + '\n';
                                      result += "Optis und Marktwerte";
 
                                      for (let pos of this.positions) {
                                          result += "\n\t" + this.getPos()[i] + '\t';
                                          result += this.getOpti(pos, this.__TIME.end).toFixed(2) + '\t';
                                          result += getNumberString(this.getMarketValue(pos, this.__TIME.end).toString());
                                      }
 
                                      return result;
                                  },  // Ende this.toString()
        'initPlayer'            : function(data, index, isSkillData = false) {  // isSkillData: true = Skilldaten, false = Basiswerte (Geb., Talent, Aufwertungen) oder keine
                                      if (data !== undefined) {
                                          if (isSkillData) {
                                              this.setSkills(data[index]);
                                          } else if (data.length >= 2){
                                              this.setGeb(data[0][index]);
                                              this.talent = data[1][index];
                                              this.aufwert = data[2][index];
                                          } else {
                                              // keine Daten
                                          }
                                      }
                                  },  // Ende this.initPlayer()
        'createWarnDraw'        : function(ziehmich = null, klasse = 1) {  // ziehmich: input Element zum Ziehen; klasse: Spielklasse 1, 2, 3
                                      // Objekte fuer die Verwaltung der Ziehwarnungen...
                                      this.warnDraw = undefined;
                                      this.warnDrawAufstieg = undefined;
                                      if (ziehmich) {
                                          const __LASTZAT = this.currZAT + this.getZatLeft();
 
                                          if (__LASTZAT < 72) {  // U19
                                              this.warnDraw = new WarnDrawPlayer(this, getColor('STU'));  // rot
                                              __LOG[4](this.getAge().toFixed(2), "rot");
                                          } else if (__LASTZAT < Math.max(2, klasse) * 72) {  // Rest bis inkl. U18 (Liga 1 und 2) bzw. U17 (Liga 3)
                                              // do nothing
                                          } else if (__LASTZAT < (klasse + 1) * 72) {  // U17/U16 je nach Liga 2/3
                                              this.warnDrawAufstieg = new WarnDrawPlayer(this, getColor('OMI'));  // magenta
                                              this.warnDrawAufstieg.setAufstieg();
                                              __LOG[4](this.getAge().toFixed(2), "magenta");
                                          }
                                      }
                                  },  // Ende this.createWarnDraw()
        'setSkills'            : function(skills) {
                                      // Berechnet die Opti-Werte, sortiert das Positionsfeld und berechnet die Einzelskills mit Ende 18
                                    this.skills = skills;
 
                                      const __POSREIHEN = [ 'ABW', 'DMI', 'MIT', 'OMI', 'STU', 'TOR' ];
                                      this.positions = [];
                                      for (let index = 0; index < __POSREIHEN.length; index++) {
                                          const __REIHE = __POSREIHEN[index];
 
                                          this.positions[index] = [ __REIHE, this.getOpti(__REIHE) ];
                                      }
 
                                      // Sortieren
                                      sortPositionArray(this.positions);
                                  },  // Ende this.setSkills()
        'prognoseSkills'        : function() {
                                      // Einzelskills mit Ende 18 berechnen
                                      this.skillsEnd = [];
 
                                      const __ZATDONE = this.getZatDone();
                                      const __ZATTOGO = this.getZatLeft();
                                      const __ADDRATIO = (__ZATDONE ? __ZATTOGO / __ZATDONE : 0);
 
                                      let addSkill = __ZATTOGO * this.getAufwertungsSchnitt();
 
                                      for (let i in this.skills) {
                                          const __SKILL = this.skills[i];
                                          let progSkill = __SKILL;
 
                                          if (isTrainableSkill(i)) {
                                              // Auf ganze Zahl runden und parseInt(), da das sonst irgendwie als String interpretiert wird
                                              const __ADDSKILL = Math.min(99 - progSkill, getMulValue(__ADDRATIO, __SKILL, 0, NaN));
 
                                              progSkill += __ADDSKILL;
                                              addSkill -= __ADDSKILL;
                                          }
 
                                          this.skillsEnd[i] = progSkill;
                                      }
                                      this.restEnd = addSkill;
                                  },  // Ende this.prognoseSkills()
        'setZusatz'            : function(zatAge, trainiert, bestPos) {
                                      // Setzt Nebenwerte fuer den Spieler (geht ohne initPlayer())
                                      if (zatAge !== undefined) {
                                          this.zatAge = zatAge;
                                      }
                                      this.trainiert = trainiert;
                                      this.bestPos = bestPos;
                                  },
        'calcZusatz'            : function() {
                                      // Ermittelt Nebenwerte fuer den Spieler und gibt sie alle zurueck (nach initPlayer())
                                      // this.zatAge und this.skills bereits in initPlayer() berechnet
                                      this.trainiert = this.getTrainiert(true);  // neu berechnet aus Skills
                                      let bestPos = this.getPos(-1);  // hier: -1 explizit angeben, damit neu ermittelt (falls this.bestPos noch nicht belegt)
 
                                      return {
                                                'zatAge'    : this.zatAge,
                                                'trainiert'  : this.trainiert,
                                                'bestPos'    : bestPos
                                            };
                                  },
        'getGeb'                : function() {
                                      return (this.zatGeb < 0) ? '?' : this.zatGeb;
                                  },
        'setGeb'                : function(gebZAT) {
                                      this.zatGeb = gebZAT;
                                      this.zatAge = this.calcZatAge(this.currZAT);
                                      this.birth = (36 + this.saison) * 72 + this.currZAT - this.zatAge;
                                  },
        'calcZatAge'            : function(currZAT) {
                                      let zatAge;
 
                                      if (this.zatGeb !== undefined) {
                                          let ZATs = 72 * (this.age - ((currZAT < this.zatGeb) ? 12 : 13));  // Basiszeit fuer die Jahre seit Jahrgang 13
 
                                          if (this.zatGeb < 0) {
                                              zatAge = ZATs + currZAT;  // Zaehlung begann Anfang der Saison (und der Geburtstag wird erst nach dem Ziehen bestimmt)
                                          } else {
                                              zatAge = ZATs + currZAT - this.zatGeb;  // Verschiebung relativ zum Geburtstag (von -zatGeb, ..., 0, ..., 71 - zatGeb)
                                          }
                                      }
 
                                      return zatAge;
                                  },
        'getZatAge'            : function(when = this.__TIME.now) {
                                      if (when === this.__TIME.end) {
                                          return (18 - 12) * 72 - 1;  // (max.) Trainings-ZATs bis Ende 18
                                      } else if (this.zatAge !== undefined) {
                                          return this.zatAge;
                                      } else {
                                          __LOG[4]("Empty getZatAge()");
 
                                          return NaN;
                                      }
                                  },
        'getZatDone'            : function(when = this.__TIME.now) {
                                      return Math.max(0, this.getZatAge(when));
                                  },
        'getZatLeft'            : function(when = this.__TIME.now) {
                                      if (this.zatLeft === undefined) {
                                          this.zatLeft = this.getZatDone(this.__TIME.end) - this.getZatDone(when);
                                      }
 
                                      return this.zatLeft;
                                  },
        'calcZiehIndex'        : function() {
                                      //const __RESTZAT = this.getZatAge(this.__TIME.end) - this.getZatAge() + this.currZAT;
                                      //const __INDEX = parseInt(__RESTZAT / 6 + 1) - 1;  // Lfd. Nummer des Abrechnungsmonats (0-basiert)
 
                                      return (this.warnDraw && this.warnDraw.calcZiehIndex(this.currZAT));
                                  },
        'isZiehAufstieg'        : function() {
                                      return (this.warnDrawAufstieg && this.warnDrawAufstieg.isZiehAufstieg());
                                  },
        'getAge'                : function(when = this.__TIME.now) {
                                      if (this.mwFormel === this.__MWFORMEL.alt) {
                                          return (when === this.__TIME.end) ? 18 : this.age;
                                      } else {  // Geburtstage ab Saison 10...
                                          return (13.00 + this.getZatAge(when) / 72);
                                      }
                                  },
        'getTrainiert'          : function(recalc = false) {
                                      if (recalc || (this.trainiert === undefined)) {
                                          this.trainiert = this.getTrainableSkills();
                                      }
 
                                      return this.trainiert;
                                  },
        'getAufwertungsSchnitt' : function() {
                                      const __ZATDONE = this.getZatDone();
 
                                      if (__ZATDONE) {
                                          return parseFloat(this.getTrainiert() / __ZATDONE);
                                      } else {
                                          // Je nach Talentklasse mittlerer Aufwertungsschnitt aller Talente der Klasse
                                          // (gewichtet nach Verteilung der Talentstufen in dieser Talentklasse)
                                          return (1 + (this.talent / 3.6)) * (this.donation / 10000);
                                      }
                                  },
        'getPos'                : function(idx = 0) {
                                      const __IDXOFFSET = 1;
 
                                      switch (idx) {
                                      case -1 : return (this.bestPos = this.positions[this.isGoalie ? 5 : 0][0]);
                                      case  0 : return this.bestPos;
                                      default : return this.positions[idx - __IDXOFFSET][0];
                                      }
                                  },
        'getPosPercent'        : function(idx = 0) {
                                      const __IDXOFFSET = 1;
                                      const __OPTI = this.positions[this.isGoalie ? 5 : 0][1];
                                      let optiSec = __OPTI;
 
                                      switch (idx) {
                                      case -1 : break;  // __OPTI
                                      case  0 : optiSec = (this.isGoalie ? 0 : this.positions[1][1]);  // Backup-Wert (TOR: keiner)
                                                break;
                                      default : optiSec = this.positions[idx - __IDXOFFSET][1];
                                      }
 
                                      return parseFloat(100 * optiSec / __OPTI);
                                  },
        'getTalent'            : function() {
                                      return (this.talent < 0) ? 'wenig' : (this.talent > 0) ? 'hoch' : 'normal';
                                  },
        'getAufwert'            : function() {
                                      return this.aufwert;
                                  },
        'boldPriSkillNames'    : function(text) {
                                      const __PRISKILLNAMES = this.getPriSkillNames();
 
                                      return (! text) ? text : text.replace(/\w+/g, function(name) {
                                                                                        return ((~ __PRISKILLNAMES.indexOf(name)) ? '<b>' + name + '</b>' : name);
                                                                                    });
                                  },
        'getPriSkillNames'      : function(pos = undefined) {
                                      return getSkillNameArray(getIdxPriSkills(pos ? pos : this.getPos()), this.isGoalie);
                                  },
        'getSkillSum'          : function(when = this.__TIME.now, idxSkills = undefined, restRate = 15) {
                                      let cachedItem;
 
                                      if (idxSkills === undefined) {  // Gesamtsumme ueber alle Skills wird gecached...
                                          cachedItem = ((when === this.__TIME.end) ? 'skillSumEnd' : 'skillSum');
 
                                          const __CACHED = this[cachedItem];
 
                                          if (__CACHED !== undefined) {
                                              return __CACHED;
                                          }
 
                                          idxSkills = getIdxAllSkills();
                                      }
 
                                      const __SKILLS = ((when === this.__TIME.end) ? this.skillsEnd : this.skills);
                                      let sumSkills = ((when === this.__TIME.end) ? (restRate / 15) * this.restEnd : 0);
 
                                      if (__SKILLS) {
                                          for (let idx of idxSkills) {
                                              sumSkills += __SKILLS[idx];
                                          }
                                      }
 
                                      if (cachedItem !== undefined) {
                                          this[cachedItem] = sumSkills;
                                      }
 
                                      return sumSkills;
                                  },
        'getSkill'              : function(when = this.__TIME.now) {
                                      return this.getSkillSum(when) / 17;
                                  },
        'getOpti'              : function(pos, when = this.__TIME.now) {
                                      const __SUMALLSKILLS = this.getSkillSum(when);
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);
/*if (this.zatGeb === 24) {
    console.error("__OVERFLOW = " + __OVERFLOW);
    console.error("__SUMALLSKILLS = " + __SUMALLSKILLS);
    console.error("__SUMPRISKILLS = " + __SUMPRISKILLS);
    console.error("getOpti(" + pos + ") = " + ((4 * (__SUMPRISKILLS - __OVERFLOW) + __SUMALLSKILLS) / 27));
}*/
                                      return (4 * (__SUMPRISKILLS - __OVERFLOW) + __SUMALLSKILLS) / 27;
                                  },
        'getPrios'              : function(pos, when = this.__TIME.now) {
                                      return Math.min(this.__MAXPRISKILLS, this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4)) / 4;
                                  },
        'getPriPercent'        : function(pos, when = this.__TIME.now) {
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __SUMSECSKILLS = this.getSkillSum(when, getIdxSecSkills(pos), 7);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);
 
                                      return (100 * (__SUMPRISKILLS - __OVERFLOW)) / (__SUMPRISKILLS + __SUMSECSKILLS);
                                  },
        'getSecPercent'        : function(pos, when = this.__TIME.now) {
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __SUMSECSKILLS = this.getSkillSum(when, getIdxSecSkills(pos), 7);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);
 
                                      return (100 * (__SUMSECSKILLS + __OVERFLOW)) / (__SUMPRISKILLS + __SUMSECSKILLS);
                                  },
        'getTrainableSkills'    : function(when = this.__TIME.now) {
                                      return this.getSkillSum(when, getIdxTrainableSkills());
                                  },
        'getFixSkills'          : function() {
                                      return this.getSkillSum(this.__TIME.now, getIdxFixSkills());
                                  },
        'getMarketValue'        : function(pos, when = this.__TIME.now) {
                                      const __AGE = this.getAge(when);
 
                                      if (this.mwFormel === this.__MWFORMEL.alt) {
                                          return Math.round(Math.pow((1 + this.getSkill(when)/100) * (1 + this.getOpti(pos, when)/100) * (2 - __AGE/100), 10) * 2);    // Alte Formel bis Saison 9
                                      } else {  // MW-Formel ab Saison 10...
                                          const __MW5TF = 1.00;  // Zwischen 0.97 und 1.03
 
                                          return Math.round(Math.pow(1 + this.getSkill(when)/100, 5.65) * Math.pow(1 + this.getOpti(pos, when)/100, 8.1) * Math.pow(1 + (100 - __AGE)/49, 10) * __MW5TF);
                                      }
                                  },
        'getFingerPrint'        : function() {
                                      // Jeweils gleichbreite Werte: (Alter/Geb.=>Monat), Land, Talent ('-', '=', '+')...
                                      const __BASEPART = padNumber(this.birth / 6, 3) + padLeft(this.land, -3);
                                      const __TALENT = '-=+'[this.talent + 1];
 
                                      if (this.skills === undefined) {
                                          return __BASEPART + getValue(__TALENT, "");
                                      } else {
                                          const __SKILLS = this.skills;
                                          const __FIXSKILLS = getIdxFixSkills().slice(-4);  // ohne die Nullen aus FUQ und ERF
                                          const __FIXSKILLSTR = __FIXSKILLS.map(function(idx) {
                                                                                    return padNumber(__SKILLS[idx], -2);
                                                                                }).join("");
 
                                          // Jeweils gleichbreite Werte: Zusaetzlich vier der sechs Fixskills...
                                          return (__BASEPART + getValue(__TALENT, '?') + __FIXSKILLSTR);
                                      }
                                  },
        'isFingerPrint'        : function(fpA, fpB) {
                                      if (fpA && fpB) {
                                          if (fpA === fpB) {
                                              return true;  // voellig identisch
                                          } else if (this.isBaseFingerPrint(fpA, fpB)) {
                                              return 1;  // schwaches true
                                          }
                                      }
 
                                      return false;
                                  },
        'isBaseFingerPrint'    : function(fpA, fpB) {
                                      if (fpA && fpB) {
                                          if (this.getBaseFingerPrint(fpA) === this.getBaseFingerPrint(fpB)) {
                                              // Base ist identisch...
                                              if ((getValue(fpA[6], '?') === '?') || (getValue(fpB[6], '?') === '?') || (fpA[6] === fpB[6])) {
                                                  // ... und auch das Talent-Zeichen ist leer oder '?'...
                                                  return true;
                                              }
                                          }
                                      }
 
                                      return false;
                                  },
        'getBaseFingerPrint'    : function(fingerprint) {
                                      return (fingerprint ? fingerprint.slice(0, 6) : undefined);
                                  },
        'getCatFromFingerPrint' : function(fingerprint) {
                                      return (fingerprint ? floorValue((fingerprint.slice(0, 3) - 1) / 12) : undefined);
                                  },
        'getCat'                : function() {
                                      return (this.birth ? floorValue((this.birth - 1) / 72) : undefined);
                                  },
        'findInFingerPrints'    : function(fingerprints) {
                                      const __MYFINGERPRINT = this.getFingerPrint();  // ggfs. unvollstaendiger Fingerprint
                                      const __MYCAT = this.getCat();
                                      const __RET = [];
 
                                      if (__MYCAT !== undefined) {
                                          for (let id in fingerprints) {
                                              const __CAT = this.getCatFromFingerPrint(id);
 
                                              if (__CAT === __MYCAT) {
                                                  if (this.isFingerPrint(id, __MYFINGERPRINT)) {
                                                      __RET.push(id);
                                                      break;  // erster Treffer zaehlt
                                                  }
                                              }
                                          }
                                      }
 
                                      return ((__RET.length === 1) ? __RET[0] : undefined);
                                  }
    });
 
// Klasse WarnDrawPlayer *****************************************************************
 
function WarnDrawPlayer(player, alertColor) {
    'use strict';
 
    this.player = player;
 
    if (this.player !== undefined) {
        // Default Warnlevel...
        this.setZatLeft(player.getZatLeft());
        this.currZAT = player.currZAT;
        this.setWarn(true, true, true);
        this.colAlert = alertColor || this.alertColor();
    } else {
        // Kein Warnlevel...
        this.setZatLeft(undefined);
        this.currZAT = undefined;
        this.setWarn(false, false, false);
        this.colAlert = undefined;
    }
}
 
Class.define(WarnDrawPlayer, Object, {
        '__MONATEBISABR'    : 1,
        '__ZATWARNVORLAUF'  : 1,
        '__ZATMONATVORLAUF' : 6,
        'setZatLeft'        : function(zatLeft) {
                                  this.zatLeft = zatLeft;
                              },
        'setWarn'          : function(warn, warnMonth, warnAufstieg) {
                                  this.warn = (this.aufstieg ? warnAufstieg : warn);
                                  this.warnMonth = warnMonth;
                              },
        'alertColor'        : function() {
                                  return getColor('STU');  // rot
                              },
        'getColor'          : function(color) {
                                  return ((this.mustDraw() && this.colAlert) ? this.colAlert : color);
                              },
        'calcZiehIndex'    : function(currZAT) {
                                  const __RESTZAT = this.zatLeft + currZAT;
                                  const __INDEX = parseInt(__RESTZAT / 6 + 1) - this.__MONATEBISABR;  // Lfd. Nummer des Abrechnungsmonats (0-basiert)
 
                                  return __INDEX;
                              },
        'isZiehAufstieg'    : function() {
                                  return this.aufstieg;
                              },
        'setAufstieg'      : function() {
                                  this.aufstieg = true;
 
                                  if (this.isZiehAufstieg()) {
                                      this.setZatLeft(72 - this.currZAT - this.__ZATWARNVORLAUF);
                                  }
 
                                  return this.zatLeft;
                              },
        'mustDraw'          : function() {
                                  return ((this.warn || this.warnMonth) && (this.zatLeft < this.__ZATWARNVORLAUF));
                              },
        'monthDraw'        : function() {
                                  return (this.mustDraw() || (this.warn && (this.aufstieg || this.warnMonth) && (this.zatLeft < this.__ZATMONATVORLAUF)));  // Abrechnungszeitraum vor dem letztmoeglichen Ziehen...
                              }
    });
 
const __NOWARNDRAW = new WarnDrawPlayer(undefined, undefined);  // inaktives Objekt
 
// Klasse WarnDrawMessage *****************************************************************
 
function WarnDrawMessage(optSet, currZAT) {
    'use strict';
 
    this.optSet = optSet;
 
    this.warn = getOptValue(this.optSet.zeigeWarnung, true);
    this.warnMonth = getOptValue(this.optSet.zeigeWarnungMonat, true);
    this.warnHome = getOptValue(this.optSet.zeigeWarnungHome, true);
    this.warnDialog = getOptValue(this.optSet.zeigeWarnungDialog, false);
    this.warnAufstieg = getOptValue(this.optSet.zeigeWarnungAufstieg, true);
    this.warnLegende = getOptValue(this.optSet.zeigeWarnungLegende, true);
 
    this.out = {
                  'supertag' : true,
                  'top'      : true,
                  'link'    : true,
                  'label'    : true,
                  'bottom'  : true
              };
 
    this.setOptionHome();
 
    this.startMessage(currZAT);
}
 
Class.define(WarnDrawMessage, Object, {
        '__ZATWARNVORLAUF'  : 1,
        '__ZATMONATVORLAUF' : 6,
        'startMessage'      : function(currZAT) {
                                  this.setZat(currZAT);
                                  this.createMessage();
                              },
        'setZat'            : function(currZAT) {
                                  this.currZAT = currZAT;
 
                                  if (currZAT === undefined) {
                                      this.abrZAT = undefined;
                                      this.rest  = undefined;
                                      this.anzahl = undefined;
                                  } else {
                                      this.configureZat();
                                  }
                              },
        'setOptionHome'    : function() {
                                  this.warnOption = this.hasHome();
                              },
        'setOptionLegende'  : function() {
                                  this.warnOption = this.hasLegende();
                              },
        'configureZat'      : function() {
                                  const __ZIEHANZAHL = getOptValue(this.optSet.ziehAnz, []);
                                  const __INDEX = parseInt(this.currZAT / 6);
 
                                  this.abrZAT = (__INDEX + 1) * 6;
                                  this.rest  = 5 - (this.currZAT % 6);
                                  this.anzahl = __ZIEHANZAHL[__INDEX];
                              },
        'getTextMessage'    : function() {
                                  return "ZAT " + this.abrZAT + ' ' + ((this.anzahl > 1) ? "m\xFCssen " + this.anzahl : "muss einer") +
                                        " deiner Jugendspieler in das Profiteam \xFCbernommen werden, ansonsten verschwinde" + ((this.anzahl > 1) ? "n sie" : "t er") + '!';
                              },
        'createMessage'    : function() {
                                  this.label = undefined;
                                  this.when = undefined;
                                  this.text = undefined;
 
                                  if (this.hasHome() || this.hasLegende() || this.hasDialog()) {
                                      if (this.anzahl > 0) {
                                          this.text = this.getTextMessage();
 
                                          if (this.warnMonth && (this.rest > 0)) {
                                              this.label = "Warnung";
                                              this.when = "Bis zur n\xE4chsten Abrechnung am ";
                                          } else if ((this.warn || this.warnMonth) && (this.rest === 0)) {
                                              this.label = "LETZTE WARNUNG VOR DER ABRECHNUNG";
                                              this.when = "Bis zum n\xE4chsten ";
                                          }
                                      }
                                  }
                              },
        'hasMessage'        : function() {
                                  return !! this.when;
                              },
        'hasHome'          : function() {
                                  return this.warnHome;
                              },
        'hasLegende'        : function() {
                                  return this.warnLegende;
                              },
        'hasOption'        : function() {
                                  return this.warnOption;
                              },
        'hasDialog'        : function() {
                                  return this.warnDialog;
                              },
        'showMessage'      : function(anchor, tag, appendFind = true) {  // appendFind: true = append, false = insertBefore, "..." search string = insert at find position
                                  let ret = (anchor || { }).innerHTML;
 
                                  if (this.hasMessage()) {
                                      if (this.hasOption()) {
                                          const __OLDHTML = ret;
                                          const __HTML = this.getHTML(tag);
 
                                          if ((typeof appendFind) === 'string') {
                                              const __INDEX = __OLDHTML.indexOf(appendFind);
                                              const __POS = (~ __INDEX) ? __INDEX : __OLDHTML.length;
 
                                              ret = __OLDHTML.substring(0, __POS) + __HTML + __OLDHTML.substring(__POS);
                                          } else if (appendFind) {
                                              ret = __OLDHTML + __HTML;
                                          } else {
                                              ret = __HTML + __OLDHTML;
                                          }
 
                                          anchor.innerHTML = ret;
                                      }
                                  }
 
                                  return ret;
                              },
        'showDialog'        : function(dlgFun) {
                                  if (this.hasMessage()) {
                                      if (this.hasDialog() && (this.rest === 0)) {
                                          dlgFun(this.label, this.when + this.text);
                                      }
                                  }
                              },
        'tagText'          : function(tag, text) {
                                  return ((tag !== undefined) ? this.getOpeningTag(tag) + text + this.getClosingTag(tag) : text);
                              },
        'tagParagraph'      : function(tag, text) {
                                  return this.tagText(tag, this.tagText(this.getSubTag(tag), text));
                              },
        'getSubTag'        : function(tag) {
                                  return ((tag === 'tr') ? 'td' + this.getColorTD() : ((tag === 'p') ? this.getColorTag() : undefined));
                              },
        'getSuperTag'      : function(tag) {
                                  return ((tag === 'p') ? 'div' : undefined);
                              },
        'getOpeningTag'    : function(tag) {
                                  return '<' + tag + '>';
                              },
        'getClosingTag'    : function(tag) {
                                  const __INDEX1 = (tag ? tag.indexOf(' ') : -1);
                                  const __INDEX2 = (tag ? tag.indexOf('=') : -1);
                                  const __INDEX = ((~ __INDEX1) && (~ __INDEX2)) ? Math.min(__INDEX1, __INDEX2) : Math.max(__INDEX1, __INDEX2);
                                  const __TAGNAME = ((~ __INDEX) ? tag.substring(0, __INDEX) : tag);
 
                                  return "</" + __TAGNAME + '>';
                              },
        'getLink'          : function() {
                                  return './ju.php';
                              },
        'getTopHTML'        : function(tag) {
                                  return this.tagParagraph(tag, "&nbsp;");
                              },
        'getBottomHTML'    : function(tag) {
                                  return this.tagParagraph(tag, "&nbsp;");
                              },
        'getColorTag'      : function() {
                                  return "color='red'";  // rot
                              },
        'getColorTD'        : function() {
                                  return " class='STU'";  // rot
                              },
        'getHTML'          : function(tag = 'p') {
                                  return this.tagParagraph((this.out.supertag ? this.getSuperTag(tag) : undefined), (this.out.top ? this.getTopHTML(tag) : "") +
                                        this.tagParagraph(tag, this.tagText('b', this.tagText((this.out.link ? "a href='" + this.getLink() + "'" : undefined),
                                        (this.out.label ? this.label + ": " : "") + this.when + this.text))) + (this.out.bottom ? this.getBottomHTML(tag) : ""));
                              }
    });
 
Object.defineProperty(WarnDrawMessage.prototype, 'innerHTML', {
        get : function() {
                  return this.getHTML('p');
              }
    });
 
// Klasse WarnDrawMessageAufstieg *****************************************************************
 
function WarnDrawMessageAufstieg(optSet, currZAT) {
    'use strict';
 
    WarnDrawMessage.call(this, optSet, currZAT);
 
    this.out.top = false;  // kein Vorschub vor der Zeile
 
    this.warn = (this.warn && this.warnAufstieg);  // kann man ausschalten
    this.startMessage(currZAT);  // 2. Aufruf (zur Korrektur)
}
 
Class.define(WarnDrawMessageAufstieg, WarnDrawMessage, {
        'configureZat'      : function() {
                                  const __ZIEHANZAUFSTIEG = getOptValue(this.optSet.ziehAnzAufstieg, 0);
                                  const __INDEX = parseInt(this.currZAT / 6);
 
                                  this.abrZAT = (__INDEX + 1) * 6;
                                  this.rest  = 5 - (this.currZAT % 6);
                                  this.anzahl = ((this.currZAT + this.__ZATMONATVORLAUF > 72 - this.__ZATWARNVORLAUF) ? __ZIEHANZAUFSTIEG : 0);
 
                                  this.warnDialog = false;    // kein Dialog fuer Aufstiegswarnung
                                  this.warnMonth = this.warn;  // nur im letzten Monat der Saison!
                              },
        'getTextMessage'    : function() {
                                  return "ZAT " + this.abrZAT + " ist im Falle eines Aufstiegs f\xFCr " + ((this.anzahl > 1) ? "" + this.anzahl : "einen") +
                                        " deiner Jugendspieler m\xF6glicherweise die letzte Chance, " + ((this.anzahl > 1) ? " diese noch vor ihrem" : "ihn noch vor seinem") +
                                        " Geburtstag in der n\xE4chsten Saison in das Profiteam zu \xFCbernehmen!";
                              },
        'getColorTag'      : function() {
                                  return "color='magenta'";  // magenta
                              },
        'getColorTD'        : function() {
                                  return " class='OMI'";  // magenta
                              }
    });
 
// Ende Hilfs-Klassen *****************************************************************
 
// Funktionen fuer die HTML-Seite *******************************************************
 
// Liest eine Zahl aus der Spalte einer Zeile der Tabelle aus (z.B. Alter, Geburtsdatum)
// cells: Die Zellen einer Zeile
// colIdxInt: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Zahl (-1 fuer "keine Zahl", undefined fuer "nicht gefunden")
function getIntFromHTML(cells, colIdxInt) {
    const __CELL = getValue(cells[colIdxInt], { });
    const __TEXT = __CELL.textContent;
 
    if (__TEXT !== undefined) {
        try {
            const __VALUE = parseInt(__TEXT, 10);
 
            if (! isNaN(__VALUE)) {
                return __VALUE;
            }
        } catch (ex) { }
 
        return -1;
    }
 
    return undefined;
}
 
// Liest eine Dezimalzahl aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxInt: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Dezimalzahl (undefined fuer "keine Zahl" oder "nicht gefunden")
function getFloatFromHTML(cells, colIdxFloat) {
    const __CELL = getValue(cells[colIdxFloat], { });
    const __TEXT = __CELL.textContent;
 
    if (__TEXT !== undefined) {
        try {
            return parseFloat(__TEXT);
        } catch (ex) { }
    }
 
    return undefined;
}
 
// Liest einen String aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Spalteneintrag als String ("" fuer "nicht gefunden")
function getStringFromHTML(cells, colIdxStr) {
    const __CELL = getValue(cells[colIdxStr], { });
    const __TEXT = __CELL.textContent;
 
    return getValue(__TEXT.toString(), "");
}
 
// Liest ein erstes Element aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Element (null fuer "nicht gefunden")
function getElementFromHTML(cells, colIdxStr) {
    const __CELL = getValue(cells[colIdxStr], { });
 
    return __CELL.firstElementChild;
}
 
// Liest die Talentklasse ("wenig", "normal", "hoch") aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
function getTalentFromHTML(cells, colIdxTal) {
    const __TEXT = getStringFromHTML(cells, colIdxTal);
 
    return parseInt((__TEXT === 'wenig') ? -1 : (__TEXT === 'hoch') ? +1 : 0, 10);
}
 
// Liest die Einzelskills aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdx: Liste von Spaltenindices der gesuchten Werte mit den Eintraegen
// 'Einz' (erste Spalte) und 'Zus' (Spalte hinter dem letzten Eintrag)
// return Skills als Array von Zahlen
function getSkillsFromHTML(cells, colIdx) {
    const __RESULT = [];
 
    for (let i = colIdx.Einz; i < colIdx.Zus; i++) {
        __RESULT[i - colIdx.Einz] = getIntFromHTML(cells, i);
    }
 
    return __RESULT;
}
 
// Liest aus, ob der Spieler Torwart oder Feldspieler ist
// cells: Die Zellen einer Zeile
// colIdxClass: Spaltenindex einer fuer TOR eingefaerbten Zelle
// return Angabe, der Spieler Torwart oder Feldspieler ist
function isGoalieFromHTML(cells, colIdxClass) {
    return (cells[colIdxClass].className === 'TOR');
}
 
// Liest einen String aus der Spalte einer Zeile der Tabelle aus, nachdem dieser konvertiert wurde
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// convertFun: Funktion, die den Wert konvertiert
// return Spalteneintrag als String ("" fuer "nicht gefunden")
function convertStringFromHTML(cells, colIdxStr, convertFun = sameValue) {
    const __CELL = getValue(cells[colIdxStr], { });
    const __TEXT = convertFun(__CELL.textContent, __CELL, colIdxStr, 0);
 
    if (__TEXT !== undefined) {
        __CELL.innerHTML = __TEXT;
    }
 
    return getValue(__TEXT.toString(), "");
}
 
// Liest ein Array von String-Werten aus den Spalten ab einer Zeile der Tabelle aus, nachdem diese konvertiert wurden
// cells: Die Zellen einer Zeile
// colIdxArr: Erster Spaltenindex der gesuchten Werte
// arrOrLength: Entweder ein Datenarray zum Fuellen oder die Anzahl der zu lesenden Werte
// convertFun: Funktion, die die Werte konvertiert
// return Array mit Spalteneintraegen als String ("" fuer "nicht gefunden")
function convertArrayFromHTML(cells, colIdxArr, arrOrLength = 1, convertFun = sameValue) {
    const __ARR = ((typeof arrOrLength === 'number') ? { } : arrOrLength);
    const __LENGTH = getValue(__ARR.length, arrOrLength);
    const __RET = [];
 
    for (let index = 0, colIdx = colIdxArr; index < __LENGTH; index++, colIdx++) {
        const __CELL = getValue(cells[colIdx], { });
        const __TEXT = convertFun(getValue(__ARR[index], __CELL.textContent), __CELL, colIdx, index);
 
        if (__TEXT !== undefined) {
            __CELL.innerHTML = __TEXT;
        }
 
        __RET.push(getValue(__TEXT, "").toString());
    }
 
    return __RET;
}
 
// Konvertiert den Aufwertungstext einer Zelle auf der Jugend-Teamuebersicht
// value: Der Inhalt dieser Zeile ("+1 SKI +1 OPT" bzw. "+2 SKI)
// cell: Zelle, in der der Text stand (optional)
// return Der konvertierte String ("SKI OPT" bzw. "SKI SKI")
function convertAufwertung(value, cell = undefined) {
    if (value !== undefined) {
        value = value.replace(/\+2 (\w+)/, "$1 $1").replace(/\+1 /g, "");
 
        if (cell) {
            if (cell.className === 'TOR') {
                value = convertGoalieSkill(value);
            }
 
            cell.align = 'left';
        }
    }
 
    return value;
}
 
// Konvertiert die allgemeinen Skills in die eines Torwarts
// value: Ein Text, der die Skillnamen enthaelt
// return Der konvertierte String mit Aenderungen (z.B. "FAN" statt "KOB") oder unveraendert
function convertGoalieSkill(value) {
    if (value !== undefined) {
        value = value.replace(/\w+/g, getGoalieSkill);
    }
 
    return value;
}
 
// Konvertiert einen Aufwertungstext fuer einen Skillnamen in den fuer einen Torwart
// name: Allgemeiner Skillname (abgeleitet von den Feldspielern)
// return Der konvertierte String (z.B. "FAN" statt "KOB") oder unveraendert
function getGoalieSkill(name) {
    const __GOALIESKILLS = {
                              'SCH' : 'ABS',
                              'BAK' : 'STS',
                              'KOB' : 'FAN',
                              'ZWK' : 'STB',
                              'DEC' : 'SPL',
                              'GES' : 'REF'
                          };
 
    return getValue(__GOALIESKILLS[name], name);
}
 
// Liest die Aufwertungen eines Spielers aus und konvertiert je nachdem, ob der Spieler Torwart oder Feldspieler ist
// cells: Die Zellen einer Zeile
// colIdxAuf: Spaltenindex der gesuchten Aufwertungen
// shortForm: true = abgekuerzt, false = Originalform
// return Konvertierte Aufwertungen (kurze oder lange Form, aber in jedem Fall fuer Torwart konvertiert)
function getAufwertFromHTML(cells, colIdxAuf, shortForm = true) {
    const __ISGOALIE = isGoalieFromHTML(cells, colIdxAuf);
 
    return convertStringFromHTML(cells, colIdxAuf, (shortForm ? convertAufwertung : __ISGOALIE ? convertGoalieSkill : undefined));
}
 
// Identitaetsfunktion. Konvertiert nichts, sondern liefert einfach den Wert zurueck
// value: Der uebergebene Wert
// return Derselbe Wert
function sameValue(value) {
    return value;
}
 
// Existenzfunktion. Liefert zurueck, ob ein Wert belegt ist
// value: Der uebergebene Wert
// return Angabe ob Wert belegt ist
function existValue(value) {
    return !! value;
}
 
// Liefert den ganzzeiligen Anteil einer Zahl zurueck, indem alles hinter einem Punkt abgeschnitten wird
// value: Eine uebergebene Dezimalzahl
// return Der ganzzeilige Anteil dieser Zahl
function floorValue(value, dot = '.') {
    if ((value === 0) || (value && isFinite(value))) {
        const __VALUE = value.toString();
        const __INDEXDOT = (__VALUE ? __VALUE.indexOf(dot) : -1);
 
        return Number((~ __INDEXDOT) ? __VALUE.substring(0, __INDEXDOT) : __VALUE);
    } else {
        return value;
    }
}
 
// Liefert einen rechtsbuendigen Text zurueck, der links aufgefuellt wird
// value: Ein uebergebener Wert
// size: Zielbreite (clipping fuer < 0: Abschneiden, falls zu lang)
// char: Zeichen zum Auffuellen
// return Ein String, der mindestens |size| lang ist (oder genau, falls size < 0, also clipping)
function padLeft(value, size = 4, char = ' ') {
    const __SIZE = Math.abs(size);
    const __CLIP = (size < 0);
    const __VALUE = (value ? value.toString() : "");
    let i = __VALUE.length;
    let str = "";
 
    while (i < __SIZE) {
        str += char;
        i += char.length;
    }
    str = ((i > __SIZE) ? str.slice(0, __SIZE - __VALUE.length - 1) : str) + __VALUE;
 
    return (__CLIP ? str.slice(size) : str);
}
 
// Liefert eine rechtsbuendigen Zahl zurueck, der links (mit Nullen) aufgefuellt wird
// value: Eine uebergebene Zahl
// size: Zielbreite (Default: 2)
// char: Zeichen zum Auffuellen (Default: '0')
// forceClip: Abschneiden erzwingen, falls zu lang?
// return Eine Zahl als String, der mindestens 'size' lang ist (oder genau, falls size < 0, also clipping)
function padNumber(value, size = 2, char = '0') {
    if ((value === 0) || (value && isFinite(value))) {
        return padLeft(value, size, char);
    } else {
        return value;
    }
}
 
// Hilfsfunktionen **********************************************************************
 
// Sortiert das Positionsfeld per BubbleSort
function sortPositionArray(array) {
    const __TEMP = [];
    let transposed = true;
    // TOR soll immer die letzte Position im Feld sein, deshalb - 1
    let length = array.length - 1;
 
    while (transposed && (length > 1)) {
        transposed = false;
        for (let i = 0; i < length - 1; i++) {
            // Vergleich Opti-Werte:
            if (array[i][1] < array[i + 1][1]) {
                // vertauschen
                __TEMP[0] = array[i][0];
                __TEMP[1] = array[i][1];
                array[i][0] = array[i + 1][0];
                array[i][1] = array[i + 1][1];
                array[i + 1][0] = __TEMP[0];
                array[i + 1][1] = __TEMP[1];
                transposed = true;
            }
        }
        length--;
    }
}
 
// Schaut nach, ob der uebergebene Index zu einem trainierbaren Skill gehoert
// Die Indizes gehen von 0 (SCH) bis 16 (EIN)
function isTrainableSkill(idx) {
    const __TRAINABLESKILLS = getIdxTrainableSkills();
    const __IDX = parseInt(idx, 10);
    let result = false;
 
    for (let idxTrainable of __TRAINABLESKILLS) {
        if (__IDX === idxTrainable) {
            result = true;
            break;
        }
    }
 
    return result;
}
 
// Gibt alle Skill-Namen zurueck
function getAllSkillNames(isGoalie = false) {
    if (isGoalie) {
        return [ 'ABS', 'STS', 'FAN', 'STB', 'SPL', 'REF', 'FUQ', 'ERF', 'AGG', 'PAS', 'AUS', 'UEB', 'WID', 'SEL', 'DIS', 'ZUV', 'EIN' ];
    } else {
        return [ 'SCH', 'BAK', 'KOB', 'ZWK', 'DEC', 'GES', 'FUQ', 'ERF', 'AGG', 'PAS', 'AUS', 'UEB', 'WID', 'SEL', 'DIS', 'ZUV', 'EIN' ];
    }
}
 
// Gibt den Skill-Namen zu einem Index zurueck
function getSkillName(idx, isGoalie = false) {
    const __ALLNAMES = getAllSkillNames(isGoalie);
 
    return __ALLNAMES[idx];
}
 
// Gibt den Skill-Namen zu einem Index-Array zurueck
function getSkillNameArray(idxArr, isGoalie = false) {
    return (idxArr ? idxArr.map(function(item) {
                                    return getSkillName(item, isGoalie);
                                }) : idxArr);
}
 
// Gibt die Indizes aller Skills zurueck
function getIdxAllSkills() {
    return [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
}
 
// Gibt die Indizes der trainierbaren Skills zurueck
function getIdxTrainableSkills() {
    return [ 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 15 ];
}
 
// Gibt die Indizes der Fixskills zurueck
function getIdxFixSkills() {
    return [ 6, 7, 12, 13, 14, 16 ];
}
 
// Gibt die Indizes der Primaerskills zurueck
function getIdxPriSkills(pos) {
    switch (pos) {
        case 'TOR' : return [ 2, 3, 4, 5 ];
        case 'ABW' : return [ 2, 3, 4, 15 ];
        case 'DMI' : return [ 1, 4, 9, 11 ];
        case 'MIT' : return [ 1, 3, 9, 11 ];
        case 'OMI' : return [ 1, 5, 9, 11 ];
        case 'STU' : return [ 0, 2, 3, 5 ];
        default :    return [];
    }
}
 
// Gibt die Indizes der (trainierbaren) Sekundaerskills zurueck
function getIdxSecSkills(pos) {
    switch (pos) {
        case 'TOR' : return [ 0, 1, 8, 9, 10, 11, 15 ];
        case 'ABW' : return [ 0, 1, 5, 8, 9, 10, 11 ];
        case 'DMI' : return [ 0, 2, 3, 5, 8, 10, 15 ];
        case 'MIT' : return [ 0, 2, 4, 5, 8, 10, 15 ];
        case 'OMI' : return [ 0, 2, 3, 4, 8, 10, 15 ];
        case 'STU' : return [ 1, 4, 8, 9, 10, 11, 15 ];
        default :    return [];
    }
}
 
// Gibt die zur Position gehoerige Farbe zurueck
function getColor(pos) {
    switch (pos) {
        case 'TOR' : return "#FFFF00";
        case 'ABW' : return "#00FF00";
        case 'DMI' : return "#3366FF";
        case 'MIT' : return "#66FFFF";
        case 'OMI' : return "#FF66FF";
        case 'STU' : return "#FF0000";
        case 'LEI' : return "#FFFFFF";
        case "" :    return "#111166";  // osBlau
        default :    return "";
    }
}
 
// ==================== Ende Abschnitt genereller Code zur Anzeige der Jugend ====================
 
// ==================== Abschnitt fuer interne IDs auf den Seiten ====================
 
const __GAMETYPENRN = {    // "Blind FSS gesucht!"
        'unbekannt'  : -1,
        'reserviert' :  0,
        'Frei'      :  0,
        'spielfrei'  :  0,
        'Friendly'  :  1,
        'Liga'      :  2,
        'LP'        :  3,
        'OSEQ'      :  4,
        'OSE'        :  5,
        'OSCQ'      :  6,
        'OSC'        :  7,
        'Supercup'  : 10
    };
 
const __GAMETYPEALIASES = {
        'unbekannt'  :  "unbekannt",
        'reserviert' :  undefined,
        'Frei'      :  undefined,
        'spielfrei'  :  "",
        'Friendly'  :  "FSS",
        'Liga'      :  undefined,
        'LP'        :  "Pokal",
        'OSEQ'      :  undefined,
        'OSE'        :  undefined,
        'OSCQ'      :  undefined,
        'OSC'        :  undefined,
        'Supercup'  : "Super"
    };
const __GAMETYPES = reverseMapping(__GAMETYPENRN);
 
const __LIGANRN = {
        'unbekannt'  :  0,
        '1. Liga'    :  1,
        '2. Liga A'  :  2,
        '2. Liga B'  :  3,
        '3. Liga A'  :  4,
        '3. Liga B'  :  5,
        '3. Liga C'  :  6,
        '3. Liga D'  :  7
    };
const __LIGATYPES = reverseMapping(__LIGANRN);
 
const __LANDNRN = {
        'unbekannt'              :  0,
        'Albanien'              :  45,
        'Andorra'                :  95,
        'Armenien'              :  83,
        'Aserbaidschan'          : 104,
        'Belgien'                :  12,
        'Bosnien-Herzegowina'    :  66,
        'Bulgarien'              :  42,
        'D\xE4nemark'            :  8,
        'Deutschland'            :  6,
        'England'                :  1,
        'Estland'                :  57,
        'Far\xF6er'              :  68,
        'Finnland'              :  40,
        'Frankreich'            :  32,
        'Georgien'              :  49,
        'Griechenland'          :  30,
        'Irland'                :  5,
        'Island'                :  29,
        'Israel'                :  23,
        'Italien'                :  10,
        'Kasachstan'            : 105,
        'Kroatien'              :  24,
        'Lettland'              :  97,
        'Liechtenstein'          :  92,
        'Litauen'                :  72,
        'Luxemburg'              :  93,
        'Malta'                  :  69,
        'Mazedonien'            :  86,
        'Moldawien'              :  87,
        'Niederlande'            :  11,
        'Nordirland'            :  4,
        'Norwegen'              :  9,
        '\xD6sterreich'          :  14,
        'Polen'                  :  25,
        'Portugal'              :  17,
        'Rum\xE4nien'            :  28,
        'Russland'              :  19,
        'San Marino'            :  98,
        'Schottland'            :  2,
        'Schweden'              :  27,
        'Schweiz'                :  37,
        'Serbien und Montenegro' :  41,
        'Slowakei'              :  70,
        'Slowenien'              :  21,
        'Spanien'                :  13,
        'Tschechien'            :  18,
        'T\xFCrkei'              :  39,
        'Ukraine'                :  20,
        'Ungarn'                :  26,
        'Wales'                  :  3,
        'Weissrussland'          :  71,
        'Zypern'                :  38
    };
const __LAENDER = reverseMapping(__LANDNRN);
 
const __TLALAND = {
        undefined : 'unbekannt',
        'ALB'    : 'Albanien',
        'AND'    : 'Andorra',
        'ARM'    : 'Armenien',
        'AZE'    : 'Aserbaidschan',
        'BEL'    : 'Belgien',
        'BIH'    : 'Bosnien-Herzegowina',
        'BUL'    : 'Bulgarien',
        'DEN'    : 'D\xE4nemark',
        'GER'    : 'Deutschland',
        'ENG'    : 'England',
        'EST'    : 'Estland',
        'FRO'    : 'Far\xF6er',
        'FIN'    : 'Finnland',
        'FRA'    : 'Frankreich',
        'GEO'    : 'Georgien',
        'GRE'    : 'Griechenland',
        'IRL'    : 'Irland',
        'ISL'    : 'Island',
        'ISR'    : 'Israel',
        'ITA'    : 'Italien',
        'KAZ'    : 'Kasachstan',
        'CRO'    : 'Kroatien',
        'LVA'    : 'Lettland',
        'LIE'    : 'Liechtenstein',
        'LTU'    : 'Litauen',
        'LUX'    : 'Luxemburg',
        'MLT'    : 'Malta',
        'MKD'    : 'Mazedonien',
        'MDA'    : 'Moldawien',
        'NED'    : 'Niederlande',
        'NIR'    : 'Nordirland',
        'NOR'    : 'Norwegen',
        'AUT'    : '\xD6sterreich',
        'POL'    : 'Polen',
        'POR'    : 'Portugal',
        'ROM'    : 'Rum\xE4nien',
        'RUS'    : 'Russland',
        'SMR'    : 'San Marino',
        'SCO'    : 'Schottland',
        'SWE'    : 'Schweden',
        'SUI'    : 'Schweiz',
        'SCG'    : 'Serbien und Montenegro',
        'SVK'    : 'Slowakei',
        'SVN'    : 'Slowenien',
        'ESP'    : 'Spanien',
        'CZE'    : 'Tschechien',
        'TUR'    : 'T\xFCrkei',
        'UKR'    : 'Ukraine',
        'HUN'    : 'Ungarn',
        'WAL'    : 'Wales',
        'BLR'    : 'Weissrussland',
        'CYP'    : 'Zypern'
    };
const __LANDTLAS = reverseMapping(__TLALAND);
 
// ==================== Abschnitt fuer Daten des Spielplans ====================
 
// Gibt die ID fuer den Namen eines Wettbewerbs zurueck
// gameType: Name des Wettbewerbs eines Spiels
// defValue: Default-Wert
// return OS2-ID fuer den Spieltyp (1 bis 7 oder 10), 0 fuer "spielfrei"/"Frei"/"reserviert", -1 fuer ungueltig
function getGameTypeID(gameType, defValue = __GAMETYPENRN.unbekannt) {
    return getValue(__GAMETYPENRN[gameType], defValue);
}
 
// Gibt den Namen eines Wettbewerbs zurueck
// id: OS2-ID des Wettbewerbs eines Spiels (1 bis 7 oder 10), 0 fuer "spielfrei"/"Frei"/"reserviert", -1 fuer ungueltig
// defValue: Default-Wert
// return Spieltyp fuer die uebergebene OS2-ID
function getGameType(id, defValue) {
    return getValue(__GAMETYPES[id], defValue);
}
 
// Gibt den alternativen (Kurznamen) fuer den Namen eines Wettbewerbs zurueck
// gameType: Name des Wettbewerbs eines Spiels
// return Normalerweise den uebergebenen Parameter, in Einzelfaellen eine Kurzversion
function getGameTypeAlias(gameType) {
    return getValue(__GAMETYPEALIASES[gameType], getValue(gameType, __GAMETYPEALIASES.unbekannt));
}
 
// Gibt den Namen des Landes mit dem uebergebenen Kuerzel (TLA) zurueck.
// tla: Kuerzel (TLA) des Landes
// defValue: Default-Wert
// return Name des Landes, 'unbekannt' fuer undefined
function getLandName(tla, defValue = __TLALAND[undefined]) {
    return getValue(__TLALAND[tla], defValue);
}
 
// Gibt die ID des Landes mit dem uebergebenen Namen zurueck.
// land: Name des Landes
// defValue: Default-Wert
// return OS2-ID des Landes, 0 fuer ungueltig
function getLandNr(land, defValue = __LANDNRN.unbekannt) {
    return getValue(__LANDNRN[land], defValue);
}
 
// Gibt die ID der Liga mit dem uebergebenen Namen zurueck.
// land: Name der Liga
// defValue: Default-Wert
// return OS2-ID der Liga, 0 fuer ungueltig
function getLigaNr(liga, defValue = __LIGANRN.unbekannt) {
    return getValue(__LIGANRN[liga], defValue);
}
 
// Kehrt das Mapping eines Objekts um und liefert ein neues Objekt zurueck.
// obj: Objekt mit key => value
// convFun: Konvertierfunktion fuer die Werte
// return Neues Objekt mit value => key (doppelte value-Werte fallen heraus!)
function reverseMapping(obj, convFun) {
    if (! obj) {
        return obj;
    }
 
    const __RET = { };
 
    for (let key in obj) {
        const __VALUE = obj[key];
 
        __RET[__VALUE] = (convFun ? convFun(key) : key);
    }
 
    return __RET;
}
 
// ==================== Abschnitt fuer sonstige Parameter ====================
 
// Formatiert eine Zelle um (mit einfachen Parametern)
// cell: Zu formatierende Zelle
// bold: Inhalt fett darstellen (true = ja, false = nein)
// color: Falls angegeben, die Schriftfarbe
// bgColor: Falls angegeben, die Hintergrundfarbe
// opacity: Falls angegeben, die Opazitaet
// return Die formatierte Zelle
function formatCell(cell, bold = true, color = undefined, bgColor = undefined, opacity = undefined) {
    if (cell) {
        if (bold) {
            cell.style.fontWeight = 'bold';
        }
        if (color) {
            cell.style.color = color;
        }
        if (bgColor) {
            cell.style.backgroundColor = bgColor;
        }
        if (opacity) {
        cell.style.opacity = opacity;
        }
    }
 
    return cell;
}
 
// Ermittelt die auszugewaehlenden Werte eines Selects (Combo-Box) als Array zurueck
// element: 'select'-Element oder dessen Name auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// valFun: Funktion zur Ermittlung des Wertes eines 'option'-Eintrags (getSelectedOptionText, getSelectedValue, ...)
// defValue: Default-Wert, falls nichts selektiert ist
// return Array mit den Options-Werten
function getSelectionArray(element, valType = 'String', valFun = getSelectedValue, defValue = undefined) {
    const __SELECT = ((typeof element) === 'string' ? getValue(document.getElementsByName(element), [])[0] : element);
 
    return (__SELECT ? [].map.call(__SELECT.options, function(option) {
                                                        return this[valType](getValue(valFun(option), defValue));
                                                    }) : undefined);
}
 
// Ermittelt den ausgewaehlten Wert eines Selects (Combo-Box) und gibt diesen zurueck
// element: 'select'-Element oder dessen Name auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// valFun: Funktion zur Ermittlung des Wertes eines 'option'-Eintrags (getSelectedOptionText, getSelectedValue, ...)
// defValue: Default-Wert, falls nichts selektiert ist
// return Ausgewaehlter Wert
function getSelection(element, valType = 'String', valFun = getSelectedOptionText, defValue = undefined) {
    const __SELECT = ((typeof element) === 'string' ? getValue(document.getElementsByName(element), [])[0] : element);
 
    return this[valType](getValue(valFun(__SELECT), defValue));
}
 
// Ermittelt den ausgewaehlten Wert einer Combo-Box und gibt diesen zurueck
// comboBox: Alle 'option'-Eintraege der Combo-Box
// defValue: Default-Wert, falls nichts selektiert ist
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// return Ausgewaehlter Wert
function getSelectionFromComboBox(comboBox, defValue = undefined, valType = 'String') {
    let selection;
 
    for (let i = 0; i < comboBox.length; i++) {
        const __ENTRY = comboBox[i];
 
        if (__ENTRY.outerHTML.match(/selected/)) {
            selection = __ENTRY.textContent;
        }
    }
 
    return this[valType](getValue(selection, defValue));
}
 
// Liefert den Text (textContent) einer selektierten Option
// element: 'select'-Element auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// return Wert der Selektion (textContent)
function getSelectedOptionText(element) {
    const __SELECTEDOPTIONS = getValue(element, { }).selectedOptions;
    const __OPTION = getValue(__SELECTEDOPTIONS, { })[0];
 
    return (__OPTION ? __OPTION.textContent : undefined);
}
 
// Liefert den 'value' einer selektierten Option
// element: 'select'-Element auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// return Wert der Selektion ('value')
function getSelectedValue(element) {
    return getValue(element, { }).value;
}
 
// ==================== Ende Abschnitt fuer sonstige Parameter ====================
 
// ==================== Abschnitt fuer sonstige Parameter des Spielplans ====================
 
const __TEAMSEARCHHAUPT = {  // Parameter zum Team "<b>Willkommen im Managerb&uuml;ro von TEAM</b><br>LIGA LAND<a href=..."
        'Zeile'  : 0,
        'Spalte' : 1,
        'start'  : " von ",
        'middle' : "</b><br>",
        'liga'  : ". Liga",
        'land'  : ' ',
        'end'    : "<a href="
    };
 
const __TEAMSEARCHTEAM = {  // Parameter zum Team "<b>TEAM - LIGA <a href=...>LAND</a></b>"
        'Zeile'  : 0,
        'Spalte' : 0,
        'start'  : "<b>",
        'middle' : " - ",
        'liga'  : ". Liga",
        'land'  : 'target="_blank">',
        'end'    : "</a></b>"
    };
 
// Ermittelt, wie das eigene Team heisst und aus welchem Land bzw. Liga es kommt (zur Unterscheidung von Erst- und Zweitteam)
// cell: Tabellenzelle mit den Parametern zum Team "startTEAMmiddleLIGA...landLANDend", LIGA = "#liga[ (A|B|C|D)]"
// teamSeach: Muster fuer die Suche, die Eintraege fuer 'start', 'middle', 'liga', 'land' und 'end' enthaelt
// return Im Beispiel { 'Team' : "TEAM", 'Liga' : "LIGA", 'Land' : "LAND", 'LdNr' : LAND-NUMMER, 'LgNr' : LIGA-NUMMER },
//        z.B. { 'Team' : "Choromonets Odessa", 'Liga' : "1. Liga", 'Land' : "Ukraine", 'LdNr' : 20, 'LgNr' : 1 }
function getTeamParamsFromTable(table, teamSearch = undefined) {
    const __TEAMSEARCH  = getValue(teamSearch, __TEAMSEARCHHAUPT);
    const __TEAMCELLROW  = getValue(__TEAMSEARCH.Zeile, 0);
    const __TEAMCELLCOL  = getValue(__TEAMSEARCH.Spalte, 0);
    const __TEAMCELLSTR  = (table === undefined) ? "" : table.rows[__TEAMCELLROW].cells[__TEAMCELLCOL].innerHTML;
    const __SEARCHSTART  = __TEAMSEARCH.start;
    const __SEARCHMIDDLE = __TEAMSEARCH.middle;
    const __SEARCHLIGA  = __TEAMSEARCH.liga;
    const __SEARCHLAND  = __TEAMSEARCH.land;
    const __SEARCHEND    = __TEAMSEARCH.end;
    const __INDEXSTART  = __TEAMCELLSTR.indexOf(__SEARCHSTART);
    const __INDEXEND    = __TEAMCELLSTR.indexOf(__SEARCHEND);
 
    let teamParams = __TEAMCELLSTR.substring(__INDEXSTART + __SEARCHSTART.length, __INDEXEND);
    const __INDEXLIGA = teamParams.indexOf(__SEARCHLIGA);
    const __INDEXMIDDLE = teamParams.indexOf(__SEARCHMIDDLE);
 
    let land = ((~ __INDEXLIGA) ? teamParams.substring(__INDEXLIGA + __SEARCHLIGA.length) : undefined);
    const __TEAMNAME = ((~ __INDEXMIDDLE) ? teamParams.substring(0, __INDEXMIDDLE) : undefined);
    let liga = (((~ __INDEXLIGA) && (~ __INDEXMIDDLE)) ? teamParams.substring(__INDEXMIDDLE + __SEARCHMIDDLE.length) : undefined);
 
    if (land !== undefined) {
        if (land.charAt(2) === ' ') {    // Land z.B. hinter "2. Liga A " statt "1. Liga "
            land = land.substr(2);
        }
        if (liga !== undefined) {
            liga = liga.substring(0, liga.length - land.length);
        }
        const __INDEXLAND = land.indexOf(__SEARCHLAND);
        if (~ __INDEXLAND) {
            land = land.substr(__INDEXLAND + __SEARCHLAND.length);
        }
    }
 
    const __TEAM = new Team(__TEAMNAME, land, liga);
 
    return __TEAM;
}
 
// Verarbeitet die URL der Seite und ermittelt die Nummer der gewuenschten Unterseite
// url: Adresse der Seite
// leafs: Liste von Filenamen mit der Default-Seitennummer (falls Query-Parameter nicht gefunden)
// item: Query-Parameter, der die Nummer der Unterseite angibt
// return Parameter aus der URL der Seite als Nummer
function getPageIdFromURL(url, leafs, item = 'page') {
    const __URI = new URI(url);
    const __LEAF = __URI.getLeaf();
 
    for (let leaf in leafs) {
        if (__LEAF === leaf) {
            const __DEFAULT = leafs[leaf];
 
            return getValue(__URI.getQueryPar(item), __DEFAULT);
        }
    }
 
    return -1;
}
 
// Gibt die laufende Nummer des ZATs im Text einer Zelle zurueck
// cell: Tabellenzelle mit der ZAT-Nummer im Text
// return ZAT-Nummer im Text
function getZATNrFromCell(cell) {
    const __TEXT = ((cell === undefined) ? [] : cell.textContent.split(' '));
    let ZATNr = 0;
 
    for (let i = 1; (ZATNr === 0) && (i < __TEXT.length); i++) {
        if (__TEXT[i - 1] === "ZAT") {
            if (__TEXT[i] !== "ist") {
                ZATNr = parseInt(__TEXT[i], 10);
            }
        }
    }
 
    return ZATNr;
}
 
// ==================== Ende Abschnitt fuer sonstige Parameter des Spielplans ====================
 
// ==================== Ende Abschnitt fuer Spielplan und ZATs ====================
 
// ==================== Hauptprogramm ====================
 
// Verarbeitet Ansicht "Haupt" (Managerbuero) zur Ermittlung des aktuellen ZATs
function procHaupt() {
    const __TEAMPARAMS = getTeamParamsFromTable(getTable(1), __TEAMSEARCHHAUPT);  // Link mit Team, Liga, Land...
 
    return buildOptions(__OPTCONFIG, __OPTSET, {
                            'teamParams' : __TEAMPARAMS,
//                            'menuAnchor' : getTable(0, 'div'),
                            'hideMenu'  : true,
                            'showForm'  : {
                                              'zeigeWarnung'        : true,
                                              'zeigeWarnungMonat'    : true,
                                              'zeigeWarnungHome'    : true,
                                              'zeigeWarnungDialog'  : true,
                                              'zeigeWarnungAufstieg' : true,
                                              'zeigeWarnungLegende'  : true,
                                              'ziehAnz'              : true,
                                              'showForm'            : true
                                          }
                        }).then(async optSet => {
            const __ZATCELL = getProp(getProp(getRows(0), 2), 'cells', { })[0];
            const __NEXTZAT = getZATNrFromCell(__ZATCELL);  // "Der naechste ZAT ist ZAT xx und ..."
            const __CURRZAT = __NEXTZAT - 1;
            const __DATAZAT = getOptValue(__OPTSET.datenZat);
 
            // Stand der alten Daten merken...
            setOpt(__OPTSET.oldDatenZat, __DATAZAT, false);
 
            if (__CURRZAT >= 0) {
                __LOG[2]("Aktueller ZAT: " + __CURRZAT);
 
                // Neuen aktuellen ZAT speichern...
                setOpt(__OPTSET.aktuellerZat, __CURRZAT, false);
 
                if (__CURRZAT !== __DATAZAT) {
                    __LOG[2](__LOG.changed(__DATAZAT, __CURRZAT));
 
                    // ... und ZAT-bezogene Daten als veraltet markieren (ausser 'skills', 'positions' und 'ziehAnz')
                    await __TEAMCLASS.deleteOptions({
                                                    'skills'      : true,
                                                    'positions'  : true,
                                                    'datenZat'    : true,
                                                    'oldDatenZat' : true,
                                                    'ziehAnz'    : (__CURRZAT > __DATAZAT)  // nur loeschen, wenn < __DATAZAT
                                                }).catch(defaultCatch);
 
                    // Neuen Daten-ZAT speichern...
                    setOpt(__OPTSET.datenZat, __CURRZAT, false);
                }
            }
 
            const __MSG = new WarnDrawMessage(optSet, __CURRZAT);
            const __MSGAUFSTIEG = new WarnDrawMessageAufstieg(optSet, __CURRZAT);
            const __ANCHOR = getTable(0, 'tbody');
 
            __MSG.showMessage(__ANCHOR, 'tr', true);
            __MSG.showDialog(showAlert);
            __MSGAUFSTIEG.showMessage(__ANCHOR, 'tr', true);
        });
}
 
// Verarbeitet Ansicht "Optionen" zur Ermittlung der Jugendfoerderung
function procOptionen() {
    return buildOptions(__OPTCONFIG, __OPTSET, {
                            'menuAnchor'  : getTable(0, 'div'),
                            'hideMenu'    : true,
                            'getDonation' : true,
                            'showForm'    : {
                                                'foerderung'          : true,
                                                'zeigeWarnung'        : true,
                                                'zeigeWarnungMonat'    : true,
                                                'zeigeWarnungHome'    : true,
                                                'zeigeWarnungDialog'  : true,
                                                'zeigeWarnungAufstieg' : true,
                                                'zeigeWarnungLegende'  : true,
                                                'ziehAnz'              : true,
                                                'showForm'            : true
                                            }
        });
}
 
// Verarbeitet Ansicht "Teamuebersicht"
function procTeamuebersicht() {
     const __ROWOFFSETUPPER = 1;    // Header-Zeile
     const __ROWOFFSETLOWER = 1;     // Ziehen-Button


     const __COLUMNINDEX = {
     const __COLUMNINDEX = {
            'Age'  : 0,
        'Flg'  : 0,
            'Geb'   : 1,
        'Land' : 1,
            'Flg'  : 2,
        'U'    : 2,
            'Land'  : 3,
        'Age'  : 3,
            'U'     : 4,
        'Einz'  : 4,   // ab hier die Einzelskills
            'Skill' : 5,
        'SCH'  : 4,
            'Tal'  : 6,
        'ABS'   : 4,   // TOR
            'Akt'  : 7,
        'BAK'   : 5,
            'Auf'  : 8,
        'STS'  : 5,    // TOR
            'Zus'  : 9
        'KOB'  : 6,
         };
        'FAN'  : 6,    // TOR
        'ZWK'  : 7,
        'STB'  : 7,   // TOR
        'DEC'  : 8,
        'SPL'   : 8,    // TOR
        'GES'  : 9,
        'REF'  : 9,    // TOR
        'FUQ'  : 10,
        'ERF'  : 11,
         'AGG'  : 12,
        'PAS'  : 13,
        'AUS'  : 14,
        'UEB'  : 15,
        'WID'  : 16,
        'SEL'  : 17,
        'DIS'  : 18,
        'ZUV'  : 19,
        'EIN'  : 20,
        'Zus'  : 21    // Zusaetze hinter den Einzelskills
    };


     if (getElement('transfer') !== undefined) {
     if (getRows(1) === undefined) {
        __LOG[2]("Ziehen-Seite");
         console.log("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else if (getRows(1) === undefined) {
         __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
     } else {
     } else {
         return buildOptions(__OPTCONFIG, __OPTSET, {
         buildOptions(__OPTCONFIG, __OPTSET, {
                                'menuAnchor' : getTable(0, 'div'),
                        'menuAnchor' : getTable(0, "div"),
                                'showForm'   : {
                        'hideForm' : {
                                                  'kennzeichenEnde'      : true,
                                        'zatAges'      : true,
                                                  'shortAufw'            : true,
                                        'trainiert'     : true,
                                                  'sepStyle'            : true,
                                        'positions'     : true
                                                  'sepColor'            : true,
                                      },
                                                  'sepWidth'            : true,
                        'formWidth'  : 1
                                                  'saison'              : true,
                    });
                                                  'aktuellerZat'        : true,
                                                  'foerderung'          : true,
                                                  'team'                : true,
                                                  'zeigeJahrgang'        : true,
                                                  'zeigeUxx'            : true,
                                                  'zeigeWarnung'        : true,
                                                  'zeigeWarnungMonat'    : true,
                                                  'zeigeWarnungHome'    : true,
                                                  'zeigeWarnungDialog'  : true,
                                                  'zeigeWarnungAufstieg' : true,
                                                  'zeigeWarnungLegende'  : true,
                                                  'zeigeBalken'          : true,
                                                  'absBalken'            : true,
                                                  'zeigeId'              : true,
                                                  'ersetzeAlter'        : true,
                                                  'zeigeAlter'          : true,
                                                  'zeigeQuote'          : true,
                                                  'zeigePosition'        : true,
                                                  'zeigeZatDone'        : true,
                                                  'zeigeZatLeft'        : true,
                                                  'zeigeFixSkills'      : true,
                                                  'zeigeTrainiert'      : true,
                                                  'zeigeAnteilPri'      : true,
                                                  'zeigeAnteilSec'      : true,
                                                  'zeigePrios'          : true,
                                                  'anzahlOpti'          : true,
                                                  'anzahlMW'            : true,
                                                  'zeigeTrainiertEnde'  : true,
                                                  'zeigeAnteilPriEnde'  : true,
                                                  'zeigeAnteilSecEnde'  : true,
                                                  'zeigePriosEnde'      : true,
                                                  'zeigeSkillEnde'      : true,
                                                  'anzahlOptiEnde'      : true,
                                                  'anzahlMWEnde'        : true,
                                                  'ziehAnz'              : true,
                                                  'zatAges'              : true,
                                                  'trainiert'           : true,
                                                  'positions'           : true,
                                                  'skills'              : true,
                                                  'reset'                : true,
                                                  'showForm'            : true
                                              },
                                'formWidth'  : 1
                            }).then(optSet => {
                const __ROWS = getRows(1);
                const __HEADERS = __ROWS[0];
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"
 
                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 1);
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, {
                                                    'Default'            : true,
                                                    'ersetzeSkills'      : false,
                                                    'zeigeGeb'          : false,
                                                    'zeigeSkill'        : false,
                                                    'zeigeTal'          : false,
                                                    'zeigeAufw'          : false
                                                });
 
                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);


                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
        const __COLMAN = new ColumnManager(__OPTSET);
                    if (__ROWS[i].cells.length > 1) {
        const __ROWS = getRows(1);
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
        const __HEADERS = __ROWS[0];
                    } else {
        const __TITLECOLOR = getColor("LEI"); // "#FFFFFF"
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }


                // Format der Trennlinie zwischen den Jahrgaengen...
        __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);


                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
        const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, false);
                }


                const __CURRZAT = getOptValue(__OPTSET.datenZat);
        for (let i = 0; i < __PLAYERS.length; i++) {
                const __MSG = new WarnDrawMessage(__OPTSET, __CURRZAT);
            __COLMAN.addValues(__PLAYERS[i], __ROWS[i + __ROWOFFSETUPPER], __TITLECOLOR);
                const __MSGAUFSTIEG = new WarnDrawMessageAufstieg(__OPTSET, __CURRZAT);
         }
                const __ANCHOR = getTable(0, 'div');
                const __SEARCH = '<form method="POST">';
 
                // Kompaktere Darstellung und ohne Links...
                __MSG.out.top = false;
                __MSG.out.label = false;
                __MSG.out.link = false;
                __MSG.out.bottom = false;
                __MSGAUFSTIEG.out.label = false;
                __MSGAUFSTIEG.out.link = false;
                __MSGAUFSTIEG.out.bottom = false;
 
                __MSG.setOptionLegende();
                __MSGAUFSTIEG.setOptionLegende();
 
                __MSG.showMessage(__ANCHOR, 'p', __SEARCH);
                __MSGAUFSTIEG.showMessage(__ANCHOR, 'p', __SEARCH);
            });
    }
 
    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}
 
// Verarbeitet Ansicht "Spielereinzelwerte"
function procSpielereinzelwerte() {
    const __ROWOFFSETUPPER = 1;    // Header-Zeile
    const __ROWOFFSETLOWER = 0;
 
    const __COLUMNINDEX = {
            'Flg'  : 0,
            'Land'  : 1,
            'U'    : 2,
            'X'    : 3,
            'Age'  : 4,
            'Einz'  : 5,    // ab hier die Einzelskills
            'SCH'  : 5,
            'ABS'  : 5,    // TOR
            'BAK'  : 6,
            'STS'  : 6,    // TOR
            'KOB'  : 7,
            'FAN'  : 7,    // TOR
            'ZWK'  : 8,
            'STB'  : 8,    // TOR
            'DEC'  : 9,
            'SPL'  : 9,    // TOR
            'GES'  : 10,
            'REF'  : 10,  // TOR
            'FUQ'  : 11,
            'ERF'  : 12,
            'AGG'  : 13,
            'PAS'  : 14,
            'AUS'  : 15,
            'UEB'  : 16,
            'WID'  : 17,
            'SEL'  : 18,
            'DIS'  : 19,
            'ZUV'  : 20,
            'EIN'  : 21,
            'Zus'  : 22    // Zusaetze hinter den Einzelskills
         };


    if (getRows(1) === undefined) {
         // Format der Trennlinie zwischen den Monaten...
         __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
         const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
    } else {
         return buildOptions(__OPTCONFIG, __OPTSET, {
                                'menuAnchor' : getTable(0, 'div'),
                                'hideForm'   : {
                                                  'zeigeWarnung'        : false,
                                                  'zeigeWarnungMonat'    : false,
                                                  'zeigeWarnungHome'    : false,
                                                  'zeigeWarnungDialog'  : false,
                                                  'zeigeWarnungAufstieg' : false,
                                                  'zeigeWarnungLegende'  : false,
                                                  'ziehAnz'              : true,
                                                  'zatAges'              : true,
                                                  'trainiert'            : true,
                                                  'positions'            : true,
                                                  'skills'              : true,
                                                  'shortAufw'            : true
                                              },
                                'formWidth'  : 1
                            }).then(optSet => {
                const __ROWS = getRows(1);
                const __HEADERS = __ROWS[0];
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"


                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 2);
        separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Age, __ROWOFFSETUPPER, __ROWOFFSETLOWER, -1, 0);
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, true);
 
                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
 
                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
                    if (__ROWS[i].cells.length > 1) {
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
                    } else {
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }
 
                // Format der Trennlinie zwischen den Jahrgaengen...
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
 
                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
                }
            });
     }
     }
    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}
}


// Verarbeitet Ansicht "Opt. Skill"
// URL-Legende:
function procOptSkill() {
// page=0: Managerbuero
    const __ROWOFFSETUPPER = 1;    // Header-Zeile
// page=1: Teamuebersicht
    const __ROWOFFSETLOWER = 0;
// page=2: Spielereinzelwerte
 
    const __COLUMNINDEX = {
            'Flg'  : 0,
            'Land'  : 1,
            'U'    : 2,
            'Age'  : 3,
            'Skill' : 4,
            'TOR'  : 5,
            'ABW'  : 6,
            'DMI'  : 7,
            'MIT'  : 8,
            'OMI'  : 9,
            'STU'  : 10,
            'Zus'  : 11    // Zusaetze hinter den OptSkills
        };
 
    if (getRows(1) === undefined) {
        __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else {
        return buildOptions(__OPTCONFIG, __OPTSET, {
                                'menuAnchor' : getTable(0, 'div'),
                                'showForm'  : {
                                                  'kennzeichenEnde'      : true,
                                                  'sepStyle'            : true,
                                                  'sepColor'            : true,
                                                  'sepWidth'            : true,
                                                  'saison'              : true,
                                                  'aktuellerZat'        : true,
                                                  'foerderung'          : true,
                                                  'team'                : true,
                                                  'zeigeJahrgang'        : true,
                                                  'zeigeUxx'            : true,
                                                  'zeigeWarnung'        : false,
                                                  'zeigeWarnungMonat'    : false,
                                                  'zeigeWarnungHome'    : false,
                                                  'zeigeWarnungDialog'  : false,
                                                  'zeigeWarnungAufstieg' : false,
                                                  'zeigeWarnungLegende'  : false,
                                                  'zeigeBalken'          : true,
                                                  'absBalken'            : true,
                                                  'zeigeId'              : true,
                                                  'ersetzeAlter'        : true,
                                                  'zeigeAlter'          : true,
                                                  'zeigeQuote'          : true,
                                                  'zeigePosition'        : true,
                                                  'zeigeZatDone'        : true,
                                                  'zeigeZatLeft'        : true,
                                                  'zeigeFixSkills'      : true,
                                                  'zeigeTrainiert'      : true,
                                                  'zeigeAnteilPri'      : true,
                                                  'zeigeAnteilSec'      : true,
                                                  'zeigePrios'          : true,
                                                  'zeigeAufw'            : true,
                                                  'zeigeGeb'            : true,
                                                  'zeigeTal'            : true,
                                                  'anzahlOpti'          : true,
                                                  'anzahlMW'            : true,
                                                  'zeigeTrainiertEnde'  : true,
                                                  'zeigeAnteilPriEnde'  : true,
                                                  'zeigeAnteilSecEnde'  : true,
                                                  'zeigePriosEnde'      : true,
                                                  'zeigeSkillEnde'      : true,
                                                  'anzahlOptiEnde'      : true,
                                                  'anzahlMWEnde'        : true,
                                                  'zatAges'              : true,
                                                  'trainiert'            : true,
                                                  'positions'            : true,
                                                  'skills'              : true,
                                                  'reset'                : true,
                                                  'showForm'            : true
                                              },
                                'formWidth'  : 1
                            }).then(optSet => {
                const __ROWS = getRows(1);
                const __HEADERS = __ROWS[0];
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"


                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 3);
// Verzweige in unterschiedliche Verarbeitungen je nach Wert von page:
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, {
switch (getPageIdFromURL(window.location.href)) {
                                                    'Default'            : true,
    case 0: procHaupt(); break;
                                                    'ersetzeSkills'      : false,
    case 1: procTeamuebersicht(); break;
                                                    'zeigeSkill'        : false
    case 2: procSpielereinzelwerte(); break;
                                                });
     default: break;
 
                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
 
                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
                    if (__ROWS[i].cells.length > 1) {
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
                    } else {
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }
 
                // Format der Trennlinie zwischen den Jahrgaengen...
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
 
                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
                }
            });
     }
 
    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}
}


(() => {
console.log("SCRIPT END");
    (async () => {
        try {
            // URL-Legende:
            // page=0: Managerbuero
            // page=1: Teamuebersicht
            // page=2: Spielereinzelwerte
            // page=3: Opt. Skill
            // page=4: Optionen
 
            // Verzweige in unterschiedliche Verarbeitungen je nach Wert von page:
            switch (getPageIdFromURL(window.location.href, {
                                                              'haupt.php' : 0,  // Ansicht "Haupt" (Managerbuero)
                                                              'ju.php'    : 1  // Ansicht "Jugendteam"
                                                          }, 'page')) {
                case 0  : await procHaupt().catch(defaultCatch); break;
                case 1  : await procTeamuebersicht().catch(defaultCatch); break;
                case 2  : await procSpielereinzelwerte().catch(defaultCatch); break;
                case 3  : await procOptSkill().catch(defaultCatch); break;
                case 4  : await procOptionen().catch(defaultCatch); break;
                default : break;
            }
 
            return 'OK';
        } catch (ex) {
            return defaultCatch(ex);
        }
    })().then(rc => {
            __LOG[1]('SCRIPT END', __DBMOD.Name, '(' + rc + ')');
        })
})();


// *** EOF ***
// *** EOF ***
</pre>
</pre>

Bitte beachte, dass alle Beiträge zu Online-Soccer-Wiki von anderen Mitwirkenden bearbeitet, geändert oder gelöscht werden können. Reiche hier keine Texte ein, falls du nicht willst, dass diese ohne Einschränkung geändert werden können.

Du bestätigst hiermit auch, dass du diese Texte selbst geschrieben hast oder diese von einer gemeinfreien Quelle kopiert hast (weitere Einzelheiten unter Online-Soccer-Wiki:Urheberrechte). ÜBERTRAGE OHNE GENEHMIGUNG KEINE URHEBERRECHTLICH GESCHÜTZTEN INHALTE!

Abbrechen Bearbeitungshilfe (wird in einem neuen Fenster geöffnet)