diff --git a/src/structure/components/com_sportsmanager/admin.php b/src/structure/components/com_sportsmanager/admin.php index 5a52d76..8d97514 100644 --- a/src/structure/components/com_sportsmanager/admin.php +++ b/src/structure/components/com_sportsmanager/admin.php @@ -4553,6 +4553,21 @@ function adminEditTeamspielmodus(): void $row = $rows[0]; } + // Ob unter diesem Spielplan schon Ergebnisse stehen. „Saetze pro Spiel" zu aendern + // rechnet naemlich nichts um — aus sechs Toren werden sechs Saetze, und aus drei + // Saetzen drei Tore. Das laesst sich auch nicht umrechnen (aus „drei gewonnene + // Saetze" folgt keine Torzahl), also wird davor gewarnt statt gehandelt. + if ($row != null) { + $query = "SELECT COUNT(*) AS anzahl" + . "\n FROM #__sportsmanager_teamspiel AS t" + . "\n INNER JOIN #__sportsmanager_begegnung AS b ON b.begegnung_id = t.begegnung_id" + . "\n INNER JOIN #__sportsmanager_team AS m ON m.team_id = b.heim_team_id" + . "\n INNER JOIN #__sportsmanager_veranstaltung AS v ON v.veranstaltung_id = m.veranstaltung_id" + . "\n WHERE v.modus_id = " . (int)$row->teamspiel_modus_id; + $treffer = loadObjectList($db, $query); + $row->hat_ergebnisse = !empty($treffer) && (int)$treffer[0]->anzahl > 0; + } + HTML_sportsmanager_admin::adminEditTeamspielmodus($row); } @@ -4566,10 +4581,204 @@ function adminEditTeamspielmodus(): void $id = $jInput->get('id', 0, 'INT'); $bezeichnung = $db->escape(trim($jInput->get('bezeichnung', '', 'RAW'))); + // Die Auswahl „Saetze pro Spiel" hat drei Eintraege, die Datenbank zwei Felder: der + // dritte Eintrag ist „Mehrere" mit der zusaetzlichen Angabe, dass die Saetze einzeln + // eingetragen werden. punktetyp behaelt damit seine beiden ueberlieferten Werte — es + // ist das Feld, an dem jede fremde Anwendung ablesen muss, ob die Punkte eines Spiels + // Tore oder gewonnene Saetze sind (siehe Datenbankversion 126). $punktetyp = $jInput->get('punktetyp', 0, 'INT'); + $satzergebnisse = $punktetyp == 2 ? 1 : 0; + if ($punktetyp == 2) + $punktetyp = 1; $punkte_sieg_einzel = $jInput->get('punkte_sieg_einzel', 0, 'INT'); $punkte_sieg_doppel_separat = $jInput->get('punkte_sieg_doppel_separat', 0, 'INT'); $punkte_sieg_doppel = $punkte_sieg_doppel_separat ? $jInput->get('punkte_sieg_doppel', 0, 'INT') : $punkte_sieg_einzel; + // Vorsprung (mindestens 1 Tor, sonst waere kein Spiel je entschieden) und Deckelung + // (0 = keine). Beide folgen derselben "Doppel separat"-Schaltung wie die Siegbedingung, + // zu der sie gehoeren. + $punkte_vorsprung_einzel = max(1, $jInput->get('punkte_vorsprung_einzel', 1, 'INT')); + $punkte_vorsprung_doppel = $punkte_sieg_doppel_separat + ? max(1, $jInput->get('punkte_vorsprung_doppel', 1, 'INT')) + : $punkte_vorsprung_einzel; + $punkte_deckel_einzel = max(0, $jInput->get('punkte_deckel_einzel', 0, 'INT')); + $punkte_deckel_doppel = $punkte_sieg_doppel_separat + ? max(0, $jInput->get('punkte_deckel_doppel', 0, 'INT')) + : $punkte_deckel_einzel; + // Eine Deckelung unterhalb der Siegbedingung wuerde das Spiel vor seinem eigenen Ziel + // beenden — das ist keine Konfiguration, sondern ein Zahlendreher. Dann gilt sie nicht. + if ($punkte_deckel_einzel != 0 && $punkte_deckel_einzel < abs($punkte_sieg_einzel)) + $punkte_deckel_einzel = 0; + if ($punkte_deckel_doppel != 0 && $punkte_deckel_doppel < abs($punkte_sieg_doppel)) + $punkte_deckel_doppel = 0; + // Vorsprung und Unentschieden schliessen sich aus: das negative Vorzeichen von + // punkte_sieg_* bedeutet „Remis bei Betrag - 1", und genau dieser Stand ist bei zwei + // Toren Vorsprung kein Endstand, sondern ein laufendes Spiel (5:5 bei Sieg 6). Wird ein + // Vorsprung gefordert, wird deshalb die Variante ohne Unentschieden gespeichert — sonst + // stuende in der Konfiguration zweierlei, das sich widerspricht. + if ($punkte_vorsprung_einzel > 1 && $punkte_sieg_einzel < 0) + $punkte_sieg_einzel = abs($punkte_sieg_einzel); + if ($punkte_vorsprung_doppel > 1 && $punkte_sieg_doppel < 0) + $punkte_sieg_doppel = abs($punkte_sieg_doppel); + // Ohne Siegbedingung („Beliebig") gibt es nichts, worauf sich Vorsprung oder Deckelung + // beziehen koennten. Dann bleiben beide auf ihren neutralen Werten, statt eine Regel zu + // behaupten, die nirgends greift. + // Ohne geforderten Vorsprung endet ein Spiel EXAKT beim Siegwert — dann kann eine + // Grenze nie greifen, und sie zu speichern hiesse, eine Regel zu behaupten, die nichts + // tut (Sven 2026-09-01). Gilt auf jeder Ebene gleich. + if ($punkte_vorsprung_einzel <= 1) $punkte_deckel_einzel = 0; + if ($punkte_vorsprung_doppel <= 1) $punkte_deckel_doppel = 0; + if ($punkte_sieg_einzel == 0) { + $punkte_vorsprung_einzel = 1; + $punkte_deckel_einzel = 0; + } + if ($punkte_sieg_doppel == 0) { + $punkte_vorsprung_doppel = 1; + $punkte_deckel_doppel = 0; + } + // Zweite Ebene: die Regel INNERHALB eines Satzes. Sie ergibt nur dort einen Sinn, wo ein + // Spiel ueberhaupt aus Saetzen besteht — bei „Einer" ist das Spiel der Satz, und die + // Regel steht bereits in punkte_sieg_*. 0 heisst „nicht hinterlegt". + // Vorzeichenbehaftet wie punkte_sieg_*: der Betrag ist der Siegwert, ein negatives + // Vorzeichen erlaubt zusaetzlich ein Unentschieden ein Tor darunter. + $satz_sieg_einzel = $jInput->get('satz_sieg_einzel', 0, 'INT'); + $satz_sieg_doppel = $punkte_sieg_doppel_separat + ? $jInput->get('satz_sieg_doppel', 0, 'INT') + : $satz_sieg_einzel; + $satz_vorsprung_einzel = max(1, $jInput->get('satz_vorsprung_einzel', 1, 'INT')); + $satz_vorsprung_doppel = $punkte_sieg_doppel_separat + ? max(1, $jInput->get('satz_vorsprung_doppel', 1, 'INT')) + : $satz_vorsprung_einzel; + $satz_deckel_einzel = max(0, $jInput->get('satz_deckel_einzel', 0, 'INT')); + $satz_deckel_doppel = $punkte_sieg_doppel_separat + ? max(0, $jInput->get('satz_deckel_doppel', 0, 'INT')) + : $satz_deckel_einzel; + if ($punktetyp == 0) { + $satzergebnisse = 0; + $satz_sieg_einzel = 0; + $satz_sieg_doppel = 0; + $satz_vorsprung_einzel = 1; + $satz_vorsprung_doppel = 1; + $satz_deckel_einzel = 0; + $satz_deckel_doppel = 0; + } + if ($satz_sieg_einzel == 0) { + $satz_vorsprung_einzel = 1; + $satz_deckel_einzel = 0; + } + if ($satz_sieg_doppel == 0) { + $satz_vorsprung_doppel = 1; + $satz_deckel_doppel = 0; + } + // Wie eine Ebene hoeher: eine Grenze unter dem Siegwert ist ein Zahlendreher, und ein + // gefordertes Mindestvorsprung schliesst das Unentschieden aus — bei zwei Toren + // Vorsprung ist ein 5:5 bei „Sieg 6, Remis 5" kein Endstand, sondern ein laufender Satz. + if ($satz_vorsprung_einzel > 1 && $satz_sieg_einzel < 0) + $satz_sieg_einzel = abs($satz_sieg_einzel); + if ($satz_vorsprung_doppel > 1 && $satz_sieg_doppel < 0) + $satz_sieg_doppel = abs($satz_sieg_doppel); + if ($satz_vorsprung_einzel <= 1) $satz_deckel_einzel = 0; + if ($satz_vorsprung_doppel <= 1) $satz_deckel_doppel = 0; + if ($satz_deckel_einzel != 0 && $satz_deckel_einzel < abs($satz_sieg_einzel)) + $satz_deckel_einzel = 0; + if ($satz_deckel_doppel != 0 && $satz_deckel_doppel < abs($satz_sieg_doppel)) + $satz_deckel_doppel = 0; + // Wie viele Saetze ein Spiel hat. Eine Auswahl, zwei Familien: eine feste Zahl (alle + // werden gespielt, positiv) oder Gewinnsaetze (best of 2N-1, negativ). 0 = wie bisher. + $saetze_modus_einzel = $jInput->get('saetze_modus_einzel', 0, 'INT'); + $saetze_modus_doppel = $punkte_sieg_doppel_separat + ? $jInput->get('saetze_modus_doppel', 0, 'INT') + : $saetze_modus_einzel; + // Abweichende Regel fuer den Entscheidungssatz. Den gibt es nur bei Gewinnsaetzen: er + // ist der Satz, der nach N-1 zu N-1 noch aussteht. Bei fester Satzzahl gaebe es keinen, + // und ohne Angabe zur Satzzahl waere nicht bestimmbar, welcher es waere. + $entscheidung_sieg_einzel = $jInput->get('entscheidung_sieg_einzel', 0, 'INT'); + $entscheidung_sieg_doppel = $punkte_sieg_doppel_separat + ? $jInput->get('entscheidung_sieg_doppel', 0, 'INT') + : $entscheidung_sieg_einzel; + $entscheidung_vorsprung_einzel = max(1, $jInput->get('entscheidung_vorsprung_einzel', 1, 'INT')); + $entscheidung_vorsprung_doppel = $punkte_sieg_doppel_separat + ? max(1, $jInput->get('entscheidung_vorsprung_doppel', 1, 'INT')) + : $entscheidung_vorsprung_einzel; + $entscheidung_deckel_einzel = max(0, $jInput->get('entscheidung_deckel_einzel', 0, 'INT')); + $entscheidung_deckel_doppel = $punkte_sieg_doppel_separat + ? max(0, $jInput->get('entscheidung_deckel_doppel', 0, 'INT')) + : $entscheidung_deckel_einzel; + if ($punktetyp == 0) { + $saetze_modus_einzel = 0; + $saetze_modus_doppel = 0; + } + // Die Satzzahl wird ZUSAETZLICH nach punkte_sieg_* gespiegelt — dorthin, wo seit jeher + // steht, bei wievielen Saetzen ein Spiel gewonnen ist. Eine aeltere Programmfassung + // liest damit weiter dieselbe wahre Aussage, obwohl sie die neue Spalte nicht kennt. + // + // Gewinnsaetze sind diese Aussage unmittelbar: N Gewinnsaetze = „gewonnen bei N". + // Aber auch eine feste Satzzahl laesst sich vollstaendig so sagen, denn sie legt fest, + // welche Endstaende es gibt: bei 4 festen Saetzen sind 4:0, 3:1 und 2:2 moeglich, also + // „gewonnen bei 3, Unentschieden bei 2:2" — genau die Bedeutung des negativen + // Vorzeichens. Bei ungerader Satzzahl gibt es kein Unentschieden: 3 feste Saetze heissen + // „gewonnen bei 2". Das ist die Umkehrung der Ueberfuehrung (Datenbankversion 124) und + // deshalb verlustfrei in beide Richtungen. + // + // Fuer punkte_sieg_* = 0 („keine Siegbedingung") bleibt damit nur, was es auch bisher + // meinte: „Beliebig" — keine Angabe zur Satzzahl. + $sieg_aus_satzzahl = function ($modus) { + if ($modus < 0) return -$modus; // N Gewinnsaetze + if ($modus == 0) return null; // keine Angabe + return $modus % 2 == 1 // feste Satzzahl + ? intdiv($modus + 1, 2) // ungerade: kein Remis + : -(intdiv($modus, 2) + 1); // gerade: Remis in der Mitte + }; + $gespiegelt = $sieg_aus_satzzahl($saetze_modus_einzel); + if ($gespiegelt !== null) { + $punkte_sieg_einzel = $gespiegelt; + $punkte_vorsprung_einzel = 1; + $punkte_deckel_einzel = 0; + } + $gespiegelt = $sieg_aus_satzzahl($saetze_modus_doppel); + if ($gespiegelt !== null) { + $punkte_sieg_doppel = $gespiegelt; + $punkte_vorsprung_doppel = 1; + $punkte_deckel_doppel = 0; + } + // Ohne Gewinnsaetze gibt es keinen Entscheidungssatz, und ohne eigene Siegbedingung + // keine Abweichung — dann bleiben die Felder auf ihren neutralen Werten. + if ($saetze_modus_einzel >= 0) $entscheidung_sieg_einzel = 0; + if ($saetze_modus_doppel >= 0) $entscheidung_sieg_doppel = 0; + if ($entscheidung_sieg_einzel == 0) { + $entscheidung_vorsprung_einzel = 1; + $entscheidung_deckel_einzel = 0; + } + if ($entscheidung_sieg_doppel == 0) { + $entscheidung_vorsprung_doppel = 1; + $entscheidung_deckel_doppel = 0; + } + if ($entscheidung_vorsprung_einzel > 1 && $entscheidung_sieg_einzel < 0) + $entscheidung_sieg_einzel = abs($entscheidung_sieg_einzel); + if ($entscheidung_vorsprung_doppel > 1 && $entscheidung_sieg_doppel < 0) + $entscheidung_sieg_doppel = abs($entscheidung_sieg_doppel); + if ($entscheidung_vorsprung_einzel <= 1) $entscheidung_deckel_einzel = 0; + if ($entscheidung_vorsprung_doppel <= 1) $entscheidung_deckel_doppel = 0; + if ($entscheidung_deckel_einzel != 0 && $entscheidung_deckel_einzel < abs($entscheidung_sieg_einzel)) + $entscheidung_deckel_einzel = 0; + if ($entscheidung_deckel_doppel != 0 && $entscheidung_deckel_doppel < abs($entscheidung_sieg_doppel)) + $entscheidung_deckel_doppel = 0; + // Race-Modus: der Stand laeuft ueber die Begegnung durch. + // + // Das Ziel wird NICHT eingegeben — es ergibt sich aus der Spielfolge (Anzahl Spiele mal + // Schritt) und stuende sonst als zweite Wahrheit neben ihr. Mindestvorsprung, Grenze + // und Unentschieden gelten allein fuer das letzte Spiel, also fuer das Ziel. + $zaehlweise = $jInput->get('zaehlweise', 0, 'INT') != 0 ? 1 : 0; + $race_schritt = max(0, $jInput->get('race_schritt', 0, 'INT')); + $race_vorsprung = max(1, $jInput->get('race_vorsprung', 1, 'INT')); + $race_deckel = max(0, $jInput->get('race_deckel', 0, 'INT')); + $race_remis = $jInput->get('race_remis', 0, 'INT') != 0 ? 1 : 0; + if ($zaehlweise == 0 || $race_schritt == 0) { + $zaehlweise = 0; + $race_schritt = 0; + $race_vorsprung = 1; + $race_deckel = 0; + $race_remis = 0; + } $heimtausch = $jInput->get('heimtausch', 0, 'INT'); $spiele_in_spielerstatistik = $jInput->get('spiele_in_spielerstatistik', 0, 'INT'); $status = $jInput->get('status', 0, 'INT'); @@ -4616,13 +4825,59 @@ function adminEditTeamspielmodus(): void if (strlen($verknuepfungen) > 0) $modus .= "|" . $verknuepfungen; + + // Erst hier, weil das Ziel die Anzahl der Spiele braucht: Race setzt „ein Satz je + // Spiel" und „Spielergebnis als Spielpunkte" voraus, und das Ziel wird in die + // Altfelder gespiegelt — dieselbe wahre Aussage, die dort seit jeher steht („das Spiel + // ist bei N Toren gewonnen"), damit eine aeltere Programmfassung nichts Falsches liest. + // Vorsprung und Grenze werden NICHT gespiegelt: sie gelten nur fuer das letzte Spiel, + // waehrend die Altfelder fuer alle gelten wuerden. + if ($zaehlweise == 1) { + $spiele_anzahl = count(array_filter(explode(",", explode("|", $modus)[0]), 'strlen')); + $race_ziel = $spiele_anzahl * $race_schritt; + $punktetyp = 0; + $satzergebnisse = 0; + $saetze_modus_einzel = 0; + $saetze_modus_doppel = 0; + $satz_sieg_einzel = 0; + $satz_sieg_doppel = 0; + $satz_vorsprung_einzel = 1; + $satz_vorsprung_doppel = 1; + $satz_deckel_einzel = 0; + $satz_deckel_doppel = 0; + $entscheidung_sieg_einzel = 0; + $entscheidung_sieg_doppel = 0; + $entscheidung_vorsprung_einzel = 1; + $entscheidung_vorsprung_doppel = 1; + $entscheidung_deckel_einzel = 0; + $entscheidung_deckel_doppel = 0; + $spielpunkte_wertung_einzel = 2; + $spielpunkte_wertung_doppel = 2; + $spielpunkte_bedingung = 0; + $punkte_sieg_einzel = $race_remis ? -$race_ziel : $race_ziel; + $punkte_sieg_doppel = $punkte_sieg_einzel; + $punkte_vorsprung_einzel = 1; + $punkte_vorsprung_doppel = 1; + $punkte_deckel_einzel = 0; + $punkte_deckel_doppel = 0; + // Eine Grenze unter dem Ziel waere ein Zahlendreher — und ohne geforderten + // Vorsprung endet das Rennen ohnehin exakt am Ziel. + if ($race_vorsprung <= 1 || ($race_deckel != 0 && $race_deckel < $race_ziel)) + $race_deckel = 0; + // Vorsprung und Unentschieden schliessen sich aus, wie eine Ebene tiefer auch. + if ($race_vorsprung > 1 && $race_remis) { + $race_remis = 0; + $punkte_sieg_einzel = $race_ziel; + $punkte_sieg_doppel = $race_ziel; + } + } $modus = $db->escape($modus); if ($id == 0) { $query = "INSERT INTO #__sportsmanager_teamspiel_modus" - . "\n (bezeichnung, punktetyp, punkte_sieg_einzel, punkte_sieg_doppel, spielpunkte_wertung_einzel, spielpunkte_wertung_doppel," + . "\n (bezeichnung, punktetyp, satzergebnisse, punkte_sieg_einzel, punkte_sieg_doppel, punkte_vorsprung_einzel, punkte_vorsprung_doppel, punkte_deckel_einzel, punkte_deckel_doppel, satz_sieg_einzel, satz_sieg_doppel, satz_vorsprung_einzel, satz_vorsprung_doppel, satz_deckel_einzel, satz_deckel_doppel, saetze_modus_einzel, saetze_modus_doppel, entscheidung_sieg_einzel, entscheidung_sieg_doppel, entscheidung_vorsprung_einzel, entscheidung_vorsprung_doppel, entscheidung_deckel_einzel, entscheidung_deckel_doppel, zaehlweise, race_schritt, race_vorsprung, race_deckel, race_remis, spielpunkte_wertung_einzel, spielpunkte_wertung_doppel," . "\n spielpunkte_bedingung, spielernamen, heimtausch, modus, spiele_in_spielerstatistik, status)" - . "\n VALUES ('$bezeichnung', '$punktetyp', '$punkte_sieg_einzel', '$punkte_sieg_doppel', '$spielpunkte_wertung_einzel'," + . "\n VALUES ('$bezeichnung', '$punktetyp', '$satzergebnisse', '$punkte_sieg_einzel', '$punkte_sieg_doppel', '$punkte_vorsprung_einzel', '$punkte_vorsprung_doppel', '$punkte_deckel_einzel', '$punkte_deckel_doppel', '$satz_sieg_einzel', '$satz_sieg_doppel', '$satz_vorsprung_einzel', '$satz_vorsprung_doppel', '$satz_deckel_einzel', '$satz_deckel_doppel', '$saetze_modus_einzel', '$saetze_modus_doppel', '$entscheidung_sieg_einzel', '$entscheidung_sieg_doppel', '$entscheidung_vorsprung_einzel', '$entscheidung_vorsprung_doppel', '$entscheidung_deckel_einzel', '$entscheidung_deckel_doppel', '$zaehlweise', '$race_schritt', '$race_vorsprung', '$race_deckel', '$race_remis', '$spielpunkte_wertung_einzel'," . "\n '$spielpunkte_wertung_doppel', '$spielpunkte_bedingung', '$spielernamen', '$heimtausch', '$modus', '$spiele_in_spielerstatistik', '$status');"; $db->setQuery($query); if (!$db->execute()) { @@ -4633,8 +4888,32 @@ function adminEditTeamspielmodus(): void $query = "UPDATE #__sportsmanager_teamspiel_modus" . "\n SET bezeichnung = '$bezeichnung'," . "\n punktetyp = '$punktetyp'," + . "\n satzergebnisse = '$satzergebnisse'," . "\n punkte_sieg_einzel = '$punkte_sieg_einzel'," . "\n punkte_sieg_doppel = '$punkte_sieg_doppel'," + . "\n punkte_vorsprung_einzel = '$punkte_vorsprung_einzel'," + . "\n punkte_vorsprung_doppel = '$punkte_vorsprung_doppel'," + . "\n punkte_deckel_einzel = '$punkte_deckel_einzel'," + . "\n punkte_deckel_doppel = '$punkte_deckel_doppel'," + . "\n satz_sieg_einzel = '$satz_sieg_einzel'," + . "\n satz_sieg_doppel = '$satz_sieg_doppel'," + . "\n satz_vorsprung_einzel = '$satz_vorsprung_einzel'," + . "\n satz_vorsprung_doppel = '$satz_vorsprung_doppel'," + . "\n satz_deckel_einzel = '$satz_deckel_einzel'," + . "\n satz_deckel_doppel = '$satz_deckel_doppel'," + . "\n saetze_modus_einzel = '$saetze_modus_einzel'," + . "\n saetze_modus_doppel = '$saetze_modus_doppel'," + . "\n entscheidung_sieg_einzel = '$entscheidung_sieg_einzel'," + . "\n entscheidung_sieg_doppel = '$entscheidung_sieg_doppel'," + . "\n entscheidung_vorsprung_einzel = '$entscheidung_vorsprung_einzel'," + . "\n entscheidung_vorsprung_doppel = '$entscheidung_vorsprung_doppel'," + . "\n entscheidung_deckel_einzel = '$entscheidung_deckel_einzel'," + . "\n entscheidung_deckel_doppel = '$entscheidung_deckel_doppel'," + . "\n zaehlweise = '$zaehlweise'," + . "\n race_schritt = '$race_schritt'," + . "\n race_vorsprung = '$race_vorsprung'," + . "\n race_deckel = '$race_deckel'," + . "\n race_remis = '$race_remis'," . "\n spielpunkte_wertung_einzel = '$spielpunkte_wertung_einzel'," . "\n spielpunkte_wertung_doppel = '$spielpunkte_wertung_doppel'," . "\n spielpunkte_bedingung = '$spielpunkte_bedingung'," @@ -4695,7 +4974,7 @@ function adminEditTeamspielmodus(): void if (count($rows) < 1) die ("Wrong id"); $teamspiel_modus = $rows[0]; - $query = "INSERT INTO #__sportsmanager_teamspiel_modus (bezeichnung, punktetyp, punkte_sieg_einzel, punkte_sieg_doppel, spielpunkte_wertung_einzel, spielpunkte_wertung_doppel, spielpunkte_bedingung, spielernamen, heimtausch, modus, status) VALUES ('" . $db->escape($teamspiel_modus->bezeichnung) . " (Kopie)', '$teamspiel_modus->punktetyp', '$teamspiel_modus->punkte_sieg_einzel', '$teamspiel_modus->punkte_sieg_doppel', '$teamspiel_modus->spielpunkte_wertung_einzel', '$teamspiel_modus->spielpunkte_wertung_doppel', '$teamspiel_modus->spielpunkte_bedingung', '$teamspiel_modus->spielernamen', '$teamspiel_modus->heimtausch', '$teamspiel_modus->modus', '$teamspiel_modus->status');"; + $query = "INSERT INTO #__sportsmanager_teamspiel_modus (bezeichnung, punktetyp, satzergebnisse, punkte_sieg_einzel, punkte_sieg_doppel, punkte_vorsprung_einzel, punkte_vorsprung_doppel, punkte_deckel_einzel, punkte_deckel_doppel, satz_sieg_einzel, satz_sieg_doppel, satz_vorsprung_einzel, satz_vorsprung_doppel, satz_deckel_einzel, satz_deckel_doppel, saetze_modus_einzel, saetze_modus_doppel, entscheidung_sieg_einzel, entscheidung_sieg_doppel, entscheidung_vorsprung_einzel, entscheidung_vorsprung_doppel, entscheidung_deckel_einzel, entscheidung_deckel_doppel, zaehlweise, race_schritt, race_vorsprung, race_deckel, race_remis, spielpunkte_wertung_einzel, spielpunkte_wertung_doppel, spielpunkte_bedingung, spielernamen, heimtausch, modus, status) VALUES ('" . $db->escape($teamspiel_modus->bezeichnung) . " (Kopie)', '$teamspiel_modus->punktetyp', '$teamspiel_modus->satzergebnisse', '$teamspiel_modus->punkte_sieg_einzel', '$teamspiel_modus->punkte_sieg_doppel', '$teamspiel_modus->punkte_vorsprung_einzel', '$teamspiel_modus->punkte_vorsprung_doppel', '$teamspiel_modus->punkte_deckel_einzel', '$teamspiel_modus->punkte_deckel_doppel', '$teamspiel_modus->satz_sieg_einzel', '$teamspiel_modus->satz_sieg_doppel', '$teamspiel_modus->satz_vorsprung_einzel', '$teamspiel_modus->satz_vorsprung_doppel', '$teamspiel_modus->satz_deckel_einzel', '$teamspiel_modus->satz_deckel_doppel', '$teamspiel_modus->saetze_modus_einzel', '$teamspiel_modus->saetze_modus_doppel', '$teamspiel_modus->entscheidung_sieg_einzel', '$teamspiel_modus->entscheidung_sieg_doppel', '$teamspiel_modus->entscheidung_vorsprung_einzel', '$teamspiel_modus->entscheidung_vorsprung_doppel', '$teamspiel_modus->entscheidung_deckel_einzel', '$teamspiel_modus->entscheidung_deckel_doppel', '$teamspiel_modus->zaehlweise', '$teamspiel_modus->race_schritt', '$teamspiel_modus->race_vorsprung', '$teamspiel_modus->race_deckel', '$teamspiel_modus->race_remis', '$teamspiel_modus->spielpunkte_wertung_einzel', '$teamspiel_modus->spielpunkte_wertung_doppel', '$teamspiel_modus->spielpunkte_bedingung', '$teamspiel_modus->spielernamen', '$teamspiel_modus->heimtausch', '$teamspiel_modus->modus', '$teamspiel_modus->status');"; $db->setQuery($query); if (!$db->execute()) { die($db->stderr(true)); @@ -10938,10 +11217,51 @@ function adminSaveBegegnungSpielplan(): void } else { $spiel_heim_punkte_detailliert = $begegnung_input->get('spiel_' . $spiel_nr . '_heim_punkte_detailliert', array(), 'ARRAY'); $spiel_gast_punkte_detailliert = $begegnung_input->get('spiel_' . $spiel_nr . '_gast_punkte_detailliert', array(), 'ARRAY'); + // Ein Formular mit Satzfeldern schickt sie fuer JEDES Spiel mit, auch fuer die, + // in denen nichts steht. Durchweg leere Satzfelder sind aber keine Aussage ueber + // Saetze — sie sagen „hier wurde nichts eingetragen". Ohne diese Unterscheidung + // gewinnt die leere Satzangabe gegen ein vorhandenes Summenergebnis, und das + // blosse Oeffnen und Speichern einer alten Begegnung loescht sie: gemessen an + // einer Begegnung mit sieben gespeicherten Spielen, von denen nach dem Speichern + // eines uebrig war. + $satzfelder_leer = true; + foreach ($spiel_heim_punkte_detailliert as $wert) { + if ($wert !== '' && $wert !== null) { + $satzfelder_leer = false; + break; + } + } + if ($satzfelder_leer) { + foreach ($spiel_gast_punkte_detailliert as $wert) { + if ($wert !== '' && $wert !== null) { + $satzfelder_leer = false; + break; + } + } + } + if ($satzfelder_leer) { + $spiel_heim_punkte_detailliert = array(); + $spiel_gast_punkte_detailliert = array(); + } if (empty($spiel_heim_punkte_detailliert)) { $spiel_heim_punkte = $begegnung_input->get('spiel_' . $spiel_nr . '_heim_punkte', 0, 'INT'); $spiel_gast_punkte = $begegnung_input->get('spiel_' . $spiel_nr . '_gast_punkte', 0, 'INT'); - $ergebnis_detailliert = $spiel_heim_punkte == 0 && $spiel_gast_punkte == 0 ? '' : ($spiel_heim_punkte . ":" . $spiel_gast_punkte); + // Der gespiegelte Stand („3:1") steht seit jeher in ergebnis_detailliert, + // wenn eine Meldung ohne Satzfelder kommt. Wo der Wettbewerb Ergebnisse JE + // SATZ fuehrt, ist dieses eine Paar aber irrefuehrend: das Feld bedeutet + // dort Saetze, und wer es so liest, sieht einen Satz mit drei Toren und + // rechnet daraus 1:0 statt 3:1 (genau der Fehler, der in unserer eigenen + // App steckte). Ein Altclient, der die Einstellung nicht kennt, meldet + // genau so — deshalb bleibt das Feld dann LEER: der Satzstand steht in + // teamspiel_*_punkte, und die Tore darin sind eben nicht bekannt. + // + // Erfundene Platzhalter („1:0" je gewonnenen Satz) waeren die Alternative + // und sind bewusst verworfen: gespeichert liesse sie niemand mehr von einer + // echten Meldung unterscheiden (Sven 2026-09-03). + $ergebnis_detailliert = ($spiel_heim_punkte == 0 && $spiel_gast_punkte == 0) + || !empty($teamspiel_modus->satzergebnisse) + ? '' + : ($spiel_heim_punkte . ":" . $spiel_gast_punkte); } else { $ergebnis_detailliert = ""; $spiel_heim_punkte = 0; @@ -10997,6 +11317,26 @@ function adminSaveBegegnungSpielplan(): void $spiel_gast_spieler_2_id = $t; } + // Echte Satzergebnisse nicht durch ihren eigenen Spiegel ersetzen. + // + // Stellt jemand einen Wettbewerb von „Mehrere - mit Ergebnis je Satz" zurueck + // auf „nur der Satzstand", bietet das Formular danach nur noch die + // Summenfelder — und ein blosses Oeffnen und Speichern schriebe „3:1" ueber + // ein gespeichertes „6:3 3:6 6:3". Eng gefasst: nur wenn dieser Aufruf gar + // keine Satzfelder mitbringt, der Stand unveraendert bleibt und im Bestand + // mehr als ein Paar steht, bleibt der Detailtext, wie er ist (Sven + // 2026-09-01). + if (isset($spiel_heim_punkte_detailliert) && empty($spiel_heim_punkte_detailliert) + && isset($spiele_nummern[$spiel_nr])) { + $bestand = $spiele_nummern[$spiel_nr]; + if ((int)$bestand->teamspiel_heim_punkte === (int)$spiel_heim_punkte + && (int)$bestand->teamspiel_gast_punkte === (int)$spiel_gast_punkte + && !empty($bestand->ergebnis_detailliert) + && count(explode(" ", trim($bestand->ergebnis_detailliert))) > 1) { + $ergebnis_detailliert = $bestand->ergebnis_detailliert; + } + } + if ($zwischenergebnis && $begegnung->verlegen_aktionen != $anzahl_historie && isset($spiele_nummern[$spiel_nr])) { $spiel = $spiele_nummern[$spiel_nr]; if ($spiel_heim_punkte == 0 && $spiel_gast_punkte == 0) { diff --git a/src/structure/components/com_sportsmanager/database/update.php b/src/structure/components/com_sportsmanager/database/update.php index 3696672..4b0586f 100644 --- a/src/structure/components/com_sportsmanager/database/update.php +++ b/src/structure/components/com_sportsmanager/database/update.php @@ -5787,6 +5787,156 @@ function updateDatabase(): void } } + if ($datenbank_version < 122) { + + // SIEGBEDINGUNGEN vollstaendig abbilden (Issue #304). + // + // Datenbankversion 122, nicht 121: 121 gehoert dem Hall-of-Fame-Zweig (PR #303, + // Issue #300), der die Spalten teamspieler und nicht_ausgespielt bringt. Dieser + // Block muss der LETZTE der Kette bleiben. Die Reihenfolge ist nicht bloss Kosmetik: + // $datenbank_version wird einmal am Anfang gelesen, also laufen beide Bloecke im + // selben Aufruf — aber jeder schreibt am Ende seine eigene Nummer, und die des + // zuletzt gelaufenen bleibt stehen. Stuende dieser Block vor dem 121er, waere die + // gespeicherte Version danach 121, und beim naechsten Seitenaufruf liefe alles + // erneut. + // + // Bisher sagte ein Mannschaftsspielmodus mit einer einzigen Zahl, wann ein Spiel + // endet: punkte_sieg_einzel/_doppel, deren Vorzeichen zusaetzlich ein + // Unentschieden zulaesst. Verbreitete Modi sind damit nicht darstellbar — „6 Tore, + // aber zwei Vorsprung, spaetestens bei 8" —, und wer nach Saetzen spielt, kann die + // Regel des einzelnen Satzes nirgends hinterlegen. Diese Ueberfuehrung ergaenzt + // deshalb drei Ebenen derselben Regel, die Satzzahl, die Angabe ob Saetze einzeln + // eingetragen werden, und eine Zaehlweise. + // + // Alle Vorgabewerte beschreiben GENAU das heutige Verhalten, und keine bestehende + // Spalte wird angetastet. Eine aeltere Programmfassung liest die neuen Spalten + // nicht und rechnet unveraendert weiter; ein Client, der nur die ueberlieferten + // Felder kennt, findet dort dieselbe Auskunft wie vorher. + // + // SPIELEBENE — punkte_vorsprung_* (wie viele klar voraus, 1 = ein Tor genuegt, also + // das heutige Verhalten) und punkte_deckel_* (Wert, bei dem das Spiel unabhaengig + // vom Vorsprung endet, 0 = keine Grenze). + // + // SATZEBENE — satz_sieg_* (0 = nicht hinterlegt, also der heutige Zustand; sonst + // dasselbe Vorzeichen wie punkte_sieg_*: Betrag = Siegwert, negativ erlaubt ein + // Remis ein Tor darunter), satz_vorsprung_*, satz_deckel_*. Die Regel INNERHALB + // eines Satzes hatte bisher keinen Platz: punkte_sieg_* misst dort, je nach + // punktetyp, entweder Tore („Einer") oder gewonnene Saetze („Mehrere"). + // + // SATZZAHL — saetze_modus_*: 0 = wie bisher (allein punkte_sieg_* zaehlt Saetze, + // keine feste Zahl, kein Entscheidungssatz), N > 0 = feste Satzzahl N (alle werden + // gespielt), -N = N Gewinnsaetze (hoechstens 2N-1 Saetze, der letzte ist der + // Entscheidungssatz). Der Editor schreibt die Satzzahl ZUSAETZLICH nach + // punkte_sieg_* zurueck, damit eine aeltere Programmfassung dieselbe — wahre — + // Aussage liest wie bisher: N Gewinnsaetze als N, eine feste Satzzahl als den + // Stand, bei dem sie entschieden ist (4 feste Saetze = „gewonnen bei 3, + // Unentschieden bei 2:2" = -3; 3 feste Saetze = „gewonnen bei 2" = 2). Diese + // Spiegelung ist die Umkehrung der Ueberfuehrung unten und damit verlustfrei. + // + // ENTSCHEIDUNGSSATZ — entscheidung_*: abweichende Regel fuer den Satz, der bei N + // Gewinnsaetzen nach N-1 zu N-1 noch aussteht; sonst gilt satz_*. + // entscheidung_sieg_* = 0 heisst „keine Abweichung" und ist der Bestandswert. + // + // SATZERGEBNISSE — satzergebnisse: werden die Saetze eines Spiels mit ihrem + // jeweiligen Ergebnis eingetragen? Eine EIGENE Spalte und ausdruecklich kein + // dritter Wert in punktetyp: punktetyp ist das einzige Feld, aus dem eine fremde + // Anwendung ablesen muss, ob die Punkte eines Spiels Tore oder gewonnene Saetze + // sind, und es kennt seit jeher genau zwei Werte. Ein dritter haette jede Anwendung + // getroffen, die „1 = Saetze, sonst Tore" schreibt statt „0 = Tore, sonst Saetze" — + // beide Fassungen stehen im Sports Manager selbst, und mindestens ein Client + // (Kickern) ist von hier aus nicht mitzuziehen. So behaelt punktetyp seine + // Bedeutung: die Punkte SIND weiter die gewonnenen Saetze, die feinere Erfassung + // aendert daran nichts, und wer die neue Spalte nicht liest, verpasst das + // Zusaetzliche statt Falsches zu behaupten. + // + // ZAEHLWEISE — zaehlweise/race_*: im Race-Modus laeuft der Stand durch die ganze + // Begegnung, Spiel N endet also, wenn eine Seite N mal race_schritt erreicht. + // Eingetragen wird wie bisher die Differenz des einzelnen Spiels. race_vorsprung, + // race_deckel und race_remis gehoeren zum ZIEL, gelten also nur fuer das letzte + // Spiel der Folge. zaehlweise = 0 ist jeder Wettbewerb, der nichts anderes sagt. + $columns = $db->getTableColumns('#__sportsmanager_teamspiel_modus'); + if (!array_key_exists('satzergebnisse', $columns)) { + $query = "ALTER TABLE `#__sportsmanager_teamspiel_modus`" + . "\n ADD `satzergebnisse` TINYINT(4) NOT NULL DEFAULT '0' AFTER `punktetyp`," + . "\n ADD `punkte_vorsprung_einzel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `punkte_sieg_doppel`," + . "\n ADD `punkte_vorsprung_doppel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `punkte_vorsprung_einzel`," + . "\n ADD `punkte_deckel_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `punkte_vorsprung_doppel`," + . "\n ADD `punkte_deckel_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `punkte_deckel_einzel`," + . "\n ADD `satz_sieg_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `punkte_deckel_doppel`," + . "\n ADD `satz_sieg_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `satz_sieg_einzel`," + . "\n ADD `satz_vorsprung_einzel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `satz_sieg_doppel`," + . "\n ADD `satz_vorsprung_doppel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `satz_vorsprung_einzel`," + . "\n ADD `satz_deckel_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `satz_vorsprung_doppel`," + . "\n ADD `satz_deckel_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `satz_deckel_einzel`," + . "\n ADD `saetze_modus_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `satz_deckel_doppel`," + . "\n ADD `saetze_modus_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `saetze_modus_einzel`," + . "\n ADD `entscheidung_sieg_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `saetze_modus_doppel`," + . "\n ADD `entscheidung_sieg_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `entscheidung_sieg_einzel`," + . "\n ADD `entscheidung_vorsprung_einzel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `entscheidung_sieg_doppel`," + . "\n ADD `entscheidung_vorsprung_doppel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `entscheidung_vorsprung_einzel`," + . "\n ADD `entscheidung_deckel_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `entscheidung_vorsprung_doppel`," + . "\n ADD `entscheidung_deckel_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `entscheidung_deckel_einzel`," + . "\n ADD `zaehlweise` TINYINT(4) NOT NULL DEFAULT '0' AFTER `entscheidung_deckel_doppel`," + . "\n ADD `race_schritt` TINYINT(4) NOT NULL DEFAULT '0' AFTER `zaehlweise`," + . "\n ADD `race_vorsprung` TINYINT(4) NOT NULL DEFAULT '1' AFTER `race_schritt`," + . "\n ADD `race_deckel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `race_vorsprung`," + . "\n ADD `race_remis` TINYINT(4) NOT NULL DEFAULT '0' AFTER `race_deckel`;"; + $db->setQuery($query); + if (!$db->execute()) { + die($db->stderr(true)); + } + } + + // Bestehende „Mehrere"-Modi in die Satzzahl ueberfuehren. + // + // * punkte_sieg_* = 1 → ein Satz, und der wird gespielt. + // * punkte_sieg_* = N (ab 2) → N GEWINNSAETZE. Von den beiden Lesarten, die diese + // Zahl zulaesst (best of 2N-1 oder feste 2N-1 Saetze), ist das die weitaus + // haeufigere; wessen Wettbewerb alle Saetze ausspielt, stellt eine Auswahl um. + // * punkte_sieg_* = -N → FESTE Satzzahl 2(N-1). Das Vorzeichen bedeutet „Remis bei + // N-1 zu N-1 moeglich", und ein Remis setzt voraus, dass alle Saetze gespielt + // werden: bei „Sieg 3, Remis 2:2" sind es vier. + // * 0 bleibt 0 und heisst „Beliebig": da war nie eine Regel. + // + // Auch ungewoehnliche Zahlen werden ueberfuehrt, nicht liegengelassen (Sven + // 2026-09-02). In „Mehrere" MISST punkte_sieg_* gewonnene Saetze — das ist die + // Bedeutung des Feldes, auch wenn ein Wettbewerb sie fuer Tore benutzt hat. Der + // Editor bietet solche Werte nicht zur Wahl an, zeigt den vorhandenen aber als + // eigenen Eintrag: sichtbar, unveraendert speicherbar, und einmal weggestellt + // nicht mehr einstellbar. + // + // Der Race-Modus wird NICHT automatisch erkannt; er ist aus Bestandsdaten nicht + // sicher abzuleiten und wird einmal von Hand gewaehlt. + // + // Nur dort, wo noch nichts gesetzt ist — ein zweiter Lauf aendert nichts. + $query = "UPDATE #__sportsmanager_teamspiel_modus" + . "\n SET saetze_modus_einzel = CASE" + . "\n WHEN punktetyp = 0 THEN 0" + . "\n WHEN punkte_sieg_einzel = 1 THEN 1" + . "\n WHEN punkte_sieg_einzel >= 2 THEN -punkte_sieg_einzel" + . "\n WHEN punkte_sieg_einzel <= -2 THEN 2 * (-punkte_sieg_einzel - 1)" + . "\n ELSE 0 END," + . "\n saetze_modus_doppel = CASE" + . "\n WHEN punktetyp = 0 THEN 0" + . "\n WHEN punkte_sieg_doppel = 1 THEN 1" + . "\n WHEN punkte_sieg_doppel >= 2 THEN -punkte_sieg_doppel" + . "\n WHEN punkte_sieg_doppel <= -2 THEN 2 * (-punkte_sieg_doppel - 1)" + . "\n ELSE 0 END" + . "\n WHERE saetze_modus_einzel = 0 AND saetze_modus_doppel = 0"; + $db->setQuery($query); + if (!$db->execute()) { + die($db->stderr(true)); + } + + $query = "UPDATE #__sportsmanager_einstellungen" + . "\n SET wert = '122'" + . "\n WHERE name = 'datenbank_version'"; + $db->setQuery($query); + if (!$db->execute()) { + die($db->stderr(true)); + } + } + if ($termin_aktionen_email_setzen) { $query = "SELECT aktion_user_id, termin_aktion_id" . "\n FROM #__sportsmanager_termin_aktion"; diff --git a/src/structure/components/com_sportsmanager/views/sportsmanager/view_admin.php b/src/structure/components/com_sportsmanager/views/sportsmanager/view_admin.php index 7467d47..82a1390 100644 --- a/src/structure/components/com_sportsmanager/views/sportsmanager/view_admin.php +++ b/src/structure/components/com_sportsmanager/views/sportsmanager/view_admin.php @@ -4642,22 +4642,278 @@ class HTML_sportsmanager_admin static function adminEditTeamspielmodus($row): void { global $params; + // „Doppel separat" war bisher allein an punkte_sieg_* abzulesen. Seit Vorsprung, + // Deckelung und der Satzregel dazugehoeren, reicht das nicht mehr: zwei Modi mit + // gleicher Siegbedingung, aber verschiedenem Vorsprung im Doppel haetten ihre + // Doppel-Zeilen sonst versteckt — und beim naechsten Speichern waere der + // abweichende Wert stillschweigend ueberschrieben worden. + $doppel_separat = !empty($row) && ( + $row->punkte_sieg_einzel != $row->punkte_sieg_doppel + || ($row->punkte_vorsprung_einzel ?? 1) != ($row->punkte_vorsprung_doppel ?? 1) + || ($row->punkte_deckel_einzel ?? 0) != ($row->punkte_deckel_doppel ?? 0) + || ($row->satz_sieg_einzel ?? 0) != ($row->satz_sieg_doppel ?? 0) + || ($row->satz_vorsprung_einzel ?? 1) != ($row->satz_vorsprung_doppel ?? 1) + || ($row->satz_deckel_einzel ?? 0) != ($row->satz_deckel_doppel ?? 0) + || ($row->saetze_modus_einzel ?? 0) != ($row->saetze_modus_doppel ?? 0) + || ($row->entscheidung_sieg_einzel ?? 0) != ($row->entscheidung_sieg_doppel ?? 0) + || ($row->entscheidung_vorsprung_einzel ?? 1) != ($row->entscheidung_vorsprung_doppel ?? 1) + || ($row->entscheidung_deckel_einzel ?? 0) != ($row->entscheidung_deckel_doppel ?? 0)); + // Die Satzregel gibt es nur, wo ein Spiel aus Saetzen besteht. + $saetze_moeglich = !empty($row) && $row->punktetyp != 0; + // Einen Entscheidungssatz gibt es nur bei Gewinnsaetzen: er ist der Satz, der nach + // N-1 zu N-1 noch aussteht. Bei fester Satzzahl gibt es keinen. + $entscheidung_moeglich = $saetze_moeglich && ($row->saetze_modus_einzel ?? 0) < 0; + $entscheidung_moeglich_doppel = $doppel_separat && !empty($row) + && $row->punktetyp != 0 && ($row->saetze_modus_doppel ?? 0) < 0; + // Wer die Satzzahl waehlt, legt damit die Siegbedingung des Spiels fest — Vorsprung + // und Deckelung auf Spielebene haetten daneben nichts mehr zu sagen. + // Bei „Mehrere" beantwortet die Satzzahl die Frage vollstaendig — „Spiel + // abgeschlossen bei" waere eine zweite Antwort auf dieselbe (Sven 2026-09-01). + // Die Auswahl bleibt im Dokument und sendet weiter ihren Wert, wird aber nicht + // mehr angeboten: ein Altwert ueberlebt dadurch unveraendert, ohne dass man ihn + // neu einstellen koennte. + $eigene_spielregel_verborgen = $saetze_moeglich; + $eigene_spielregel_verborgen_doppel = $saetze_moeglich; + // Im Race-Modus laeuft der Stand ueber die Begegnung durch; die Regeln des + // einzelnen Spiels und des Satzes gibt es dort nicht. + $race_an = !empty($row) && ($row->zaehlweise ?? 0) != 0; + // Die Einheit der SPIELEBENE haengt an punktetyp: Tore bei „Einer", gewonnene + // Saetze bei „Mehrere". Bisher stand dort in beiden Faellen „Punkte" — und genau + // daran war eine Fehlkonfiguration nicht zu erkennen: „42 Punkte" liest sich + // plausibel, „42 Saetze" haette sofort auffallen lassen, dass hier Tore gemeint + // waren (Sven 2026-09-01). + // + // Beide Beschriftungen entstehen serverseitig und reisen als data-Attribut mit; + // beim Umschalten von punktetyp tauscht zeilen_anzeigen() sie ein. So bleibt jede + // Sprache uebersetzt, statt in JavaScript ein Wort zu raten. + $einheit_option = function ($wert, $gewaehlt, $tore, $saetze) use ($saetze_moeglich) { + return ""; + }; + $anzahl_saetze = function ($i) { + return $i . " " . Text::_($i == 1 ? 'COM_SPORTSMANAGER_SET' : 'COM_SPORTSMANAGER_SETS'); + }; ?>
:
+ heim_spielpunkte != 0 || $begegnung->gast_spielpunkte != 0) { - ?> - - - : - - heim_spielpunkte . ":" . $begegnung->gast_spielpunkte; ?> - - kommentar)) { ?> @@ -11770,6 +12693,28 @@ static function adminVerbandsorganMitglieder($rows,$verbandsorgan): void + +   + + + + : + + + + + + @@ -11821,35 +12766,259 @@ static function adminVerbandsorganMitglieder($rows,$verbandsorgan): void $punkte_sieg_doppel = abs($teamspiel_modus->punkte_sieg_doppel); $punkte_unentschieden_einzel = $teamspiel_modus->punkte_sieg_einzel < 0 ? abs($teamspiel_modus->punkte_sieg_einzel + 1) : 0; $punkte_unentschieden_doppel = $teamspiel_modus->punkte_sieg_doppel < 0 ? abs($teamspiel_modus->punkte_sieg_doppel + 1) : 0; - if ($handheld != "yes" && ($punkte_sieg_einzel != 0 || $punkte_sieg_doppel != 0)) { + // Vorsprung und Unentschieden schliessen sich aus (siehe punkte_gegner). Alte + // Daten koennen beides gesetzt haben, wenn ein Vorsprung nachtraeglich kam. + if (max(1, (int)($teamspiel_modus->punkte_vorsprung_einzel ?? 1)) > 1) + $punkte_unentschieden_einzel = 0; + if (max(1, (int)($teamspiel_modus->punkte_vorsprung_doppel ?? 1)) > 1) + $punkte_unentschieden_doppel = 0; + $punkte_vorsprung_einzel = max(1, (int)($teamspiel_modus->punkte_vorsprung_einzel ?? 1)); + $punkte_vorsprung_doppel = max(1, (int)($teamspiel_modus->punkte_vorsprung_doppel ?? 1)); + $punkte_deckel_einzel = max(0, (int)($teamspiel_modus->punkte_deckel_einzel ?? 0)); + $punkte_deckel_doppel = max(0, (int)($teamspiel_modus->punkte_deckel_doppel ?? 0)); + // Zweite Ebene: die Regel im einzelnen Satz. Eingetragen werden einzelne Saetze + // nur bei „Mehrere mit Satzergebnissen" — nur dort greift sie also. + // Eine Mannschaft ohne hinterlegte Spieler braucht keine Spielerauswahl: + // das Feld haette nur den Leereintrag und behauptete eine Wahl, die es nicht + // gibt (Sven 2026-09-02). Gespeichert wird dann genau wie bei einer leeren + // Auswahl — der Server liest die fehlenden Felder mit Vorgabe 0, also + // „kein Spieler", und `$spieler_angegeben` bleibt falsch. + $heim_hat_spieler = !empty($heim_spieler); + $gast_hat_spieler = !empty($gast_spieler); + $satzweise = ($teamspiel_modus->satzergebnisse ?? 0) != 0; + // Race: der Stand laeuft durch, jedes Spiel hat sein eigenes Etappenziel + // (Spiel N endet bei N mal Schritt). Mindestvorsprung, Grenze und ein + // moegliches Unentschieden gelten nur fuer das letzte Spiel — also fuer + // das Ziel selbst. + $race_an = ($teamspiel_modus->zaehlweise ?? 0) != 0 + && ($teamspiel_modus->race_schritt ?? 0) > 0; + $race_schritt = max(0, (int)($teamspiel_modus->race_schritt ?? 0)); + $race_vorsprung = max(1, (int)($teamspiel_modus->race_vorsprung ?? 1)); + $race_deckel = max(0, (int)($teamspiel_modus->race_deckel ?? 0)); + $race_letztes = count($spieltypen); + $race_ziel = $race_letztes * $race_schritt; + $race_remis = ($teamspiel_modus->race_remis ?? 0) != 0 && $race_vorsprung <= 1 + ? $race_ziel - 1 + : 0; + // Je Spiel die Zahl der von Anfang an sichtbaren Satzzeilen; wird in der + // Spiel-Schleife gefuellt und nach dem Formular an JavaScript uebergeben. + $satz_min_je_spiel = array(); + $satz_sieg_einzel = (int)($teamspiel_modus->satz_sieg_einzel ?? 0); + $satz_sieg_doppel = (int)($teamspiel_modus->satz_sieg_doppel ?? 0); + $satz_vorsprung_einzel = max(1, (int)($teamspiel_modus->satz_vorsprung_einzel ?? 1)); + $satz_vorsprung_doppel = max(1, (int)($teamspiel_modus->satz_vorsprung_doppel ?? 1)); + $satz_deckel_einzel = max(0, (int)($teamspiel_modus->satz_deckel_einzel ?? 0)); + $satz_deckel_doppel = max(0, (int)($teamspiel_modus->satz_deckel_doppel ?? 0)); + // Satzzahl: positiv = feste Zahl, negativ = Gewinnsaetze, 0 = keine Angabe. + $saetze_modus_einzel = (int)($teamspiel_modus->saetze_modus_einzel ?? 0); + $saetze_modus_doppel = (int)($teamspiel_modus->saetze_modus_doppel ?? 0); + // Abweichende Regel fuer den Entscheidungssatz; 0 = keine. + $entscheidung_sieg_einzel = (int)($teamspiel_modus->entscheidung_sieg_einzel ?? 0); + $entscheidung_sieg_doppel = (int)($teamspiel_modus->entscheidung_sieg_doppel ?? 0); + $entscheidung_vorsprung_einzel = max(1, (int)($teamspiel_modus->entscheidung_vorsprung_einzel ?? 1)); + $entscheidung_vorsprung_doppel = max(1, (int)($teamspiel_modus->entscheidung_vorsprung_doppel ?? 1)); + $entscheidung_deckel_einzel = max(0, (int)($teamspiel_modus->entscheidung_deckel_einzel ?? 0)); + $entscheidung_deckel_doppel = max(0, (int)($teamspiel_modus->entscheidung_deckel_doppel ?? 0)); + // punkte_gegner brauchen beide Ebenen — das Spiel und der Satz. + if ($handheld != "yes" && ($race_an || $punkte_sieg_einzel != 0 || $punkte_sieg_doppel != 0 + || ($satzweise && ($satz_sieg_einzel != 0 || $satz_sieg_doppel != 0)))) { + // Der Gegenwert zu einem eingegebenen Stand — also die Ergaenzung, die das + // Formular anbietet, wenn nur eine Seite eingetragen ist. + // + // Ergaenzt wird nur, was eindeutig ist: ein Stand UNTERHALB der Siegbedingung + // kann nicht der des Siegers sein, die andere Seite muss also gewonnen haben. + // Was dazu gehoert, ist `sieg`, bei Vorsprung entsprechend mehr, und nie mehr + // als die Deckelung — Beispiel Sieg 6, Vorsprung 2, Deckel 8: zu 5 gehoert 7, + // zu 4 die 6. + // + // Ein Stand, der selbst gewinnen KOENNTE, wird nicht ergaenzt: zu einer 6 bei + // Sieg 6 gehoert entweder 0..4 (dann hat sie gewonnen) oder 8 (dann verloren) — + // das ist geraten, nicht gefolgert. Leerer Rueckgabewert heisst „nichts + // eintragen"; die bisherige Fassung hatte dafuer eine eigene Abbruchbedingung + // auf Gleichheit mit der Siegbedingung. + // + // Das Unentschieden gilt nur OHNE Vorsprung: bei zwei Toren Vorsprung ist ein + // 5:5 bei „Sieg 6, Remis 5" kein Endstand, sondern ein laufendes Spiel. Der + // Editor speichert diese Kombination deshalb gar nicht mehr. + echo "function punkte_gegner(eingabe, sieg, vorsprung, deckel, unentschieden) {\n" + . " var v = Number(eingabe);\n" + . " if (unentschieden != 0 && v == unentschieden)\n" + . " return unentschieden;\n" + . " if (sieg != 0 && v >= sieg)\n" + . " return '';\n" + . " var gegner = v + vorsprung;\n" + . " if (gegner < sieg)\n" + . " gegner = sieg;\n" + . " if (deckel != 0 && gegner > deckel)\n" + . " gegner = deckel;\n" + . " return gegner;\n" + . "}\n"; + if ($race_an) + echo "function race_gegner(nr, feld, schritt, letztes, vorsprung, deckel, remis, seite) {\n" + // Was beide Seiten aus den vorherigen Spielen schon haben. + . " var vorEigen = 0, vorAnder = 0;\n" + . " for (var i = 1; i < nr; i++) {\n" + . " var h = document.adminForm['spiel_' + i + '_heim_punkte'];\n" + . " var g = document.adminForm['spiel_' + i + '_gast_punkte'];\n" + . " var hv = (h != null && h.value != '') ? Number(h.value) : 0;\n" + . " var gv = (g != null && g.value != '') ? Number(g.value) : 0;\n" + . " vorEigen += seite == 'heim' ? hv : gv;\n" + . " vorAnder += seite == 'heim' ? gv : hv;\n" + . " }\n" + . " var ziel = nr * schritt;\n" + . " var eigenLaufend = vorEigen + Number(feld.value);\n" + . " var anderLaufend;\n" + // Nur das letzte Spiel kennt Vorsprung, Grenze und Remis — und die + // gelten fuer den laufenden Stand, nicht fuer die Differenz. + . " if (letztes) {\n" + . " anderLaufend = punkte_gegner(eigenLaufend, ziel, vorsprung, deckel, remis);\n" + . " if (anderLaufend === '') return '';\n" + . " } else {\n" + . " if (eigenLaufend >= ziel) return '';\n" + . " anderLaufend = ziel;\n" + . " }\n" + . " var differenz = anderLaufend - vorAnder;\n" + . " return differenz < 0 ? '' : differenz;\n" + . "}\n"; + // Bei satzweiser Eingabe gibt es keine anfassbaren Summenfelder; dort + // ergaenzt saetze_changed() den Gegenwert, und zwar innerhalb des Satzes. + if (!$satzweise) { echo "function punkte_changed(form) {\n" - . " if (form.value == '' || form.value == " . $punkte_sieg_einzel . ")\n" + . " if (form.value == '')\n" . " return;\n" . " switch(form.name) {\n"; $n = count($spieltypen); for ($i = 1; $i <= $n; $i++) { - if ($spieltypen[$i - 1][0] != "D" && $punkte_sieg_einzel != 0) { + // Bei Race gilt die Etappe fuer den LAUFENDEN Stand, eingetragen wird + // aber die Differenz dieses Spiels — so fuehrt der Sports Manager es + // seit jeher (gemessen 2026-09-01 an 91 Bundesliga-Begegnungen: die + // Summe der Spielwerte IST der Begegnungsstand). Die Ergaenzung muss + // deshalb abziehen, was bis hierher schon auf dem Konto steht. + if ($race_an) { + $letztes = $i == $race_letztes ? 1 : 0; + $args = $i . ", form, " . $race_schritt . ", " . $letztes . ", " + . $race_vorsprung . ", " . $race_deckel . ", " . $race_remis; echo " case 'spiel_" . $i . "_heim_punkte':\n" . " if (document.adminForm.spiel_" . $i . "_gast_punkte.value == '')\n" - . " document.adminForm.spiel_" . $i . "_gast_punkte.value = " . ($punkte_unentschieden_einzel != 0 ? ("form.value == '" . $punkte_unentschieden_einzel . "' ? " . $punkte_unentschieden_einzel . " : " . $punkte_sieg_einzel) : ("'" . $punkte_sieg_einzel . "'")) . ";\n" + . " document.adminForm.spiel_" . $i . "_gast_punkte.value = race_gegner(" . $args . ", 'heim');\n" . " break;\n" . " case 'spiel_" . $i . "_gast_punkte':\n" . " if (document.adminForm.spiel_" . $i . "_heim_punkte.value == '')\n" - . " document.adminForm.spiel_" . $i . "_heim_punkte.value = " . ($punkte_unentschieden_einzel != 0 ? ("form.value == '" . $punkte_unentschieden_einzel . "' ? " . $punkte_unentschieden_einzel . " : " . $punkte_sieg_einzel) : ("'" . $punkte_sieg_einzel . "'")) . ";\n" + . " document.adminForm.spiel_" . $i . "_heim_punkte.value = race_gegner(" . $args . ", 'gast');\n" + . " break;\n"; + } else if ($spieltypen[$i - 1][0] != "D" && $punkte_sieg_einzel != 0) { + echo " case 'spiel_" . $i . "_heim_punkte':\n" + . " if (document.adminForm.spiel_" . $i . "_gast_punkte.value == '')\n" + . " document.adminForm.spiel_" . $i . "_gast_punkte.value = punkte_gegner(form.value, " . $punkte_sieg_einzel . ", " . $punkte_vorsprung_einzel . ", " . $punkte_deckel_einzel . ", " . $punkte_unentschieden_einzel . ");\n" + . " break;\n" + . " case 'spiel_" . $i . "_gast_punkte':\n" + . " if (document.adminForm.spiel_" . $i . "_heim_punkte.value == '')\n" + . " document.adminForm.spiel_" . $i . "_heim_punkte.value = punkte_gegner(form.value, " . $punkte_sieg_einzel . ", " . $punkte_vorsprung_einzel . ", " . $punkte_deckel_einzel . ", " . $punkte_unentschieden_einzel . ");\n" . " break;\n"; } else if ($spieltypen[$i - 1][0] == "D" && $punkte_sieg_doppel != 0) { echo " case 'spiel_" . $i . "_heim_punkte':\n" . " if (document.adminForm.spiel_" . $i . "_gast_punkte.value == '')\n" - . " document.adminForm.spiel_" . $i . "_gast_punkte.value = " . ($punkte_unentschieden_doppel != 0 ? ("form.value == '" . $punkte_unentschieden_doppel . "' ? " . $punkte_unentschieden_doppel . " : " . $punkte_sieg_doppel) : ("'" . $punkte_sieg_doppel . "'")) . ";\n" + . " document.adminForm.spiel_" . $i . "_gast_punkte.value = punkte_gegner(form.value, " . $punkte_sieg_doppel . ", " . $punkte_vorsprung_doppel . ", " . $punkte_deckel_doppel . ", " . $punkte_unentschieden_doppel . ");\n" . " break;\n" . " case 'spiel_" . $i . "_gast_punkte':\n" . " if (document.adminForm.spiel_" . $i . "_heim_punkte.value == '')\n" - . " document.adminForm.spiel_" . $i . "_heim_punkte.value = " . ($punkte_unentschieden_doppel != 0 ? ("form.value == '" . $punkte_unentschieden_doppel . "' ? " . $punkte_unentschieden_doppel . " : " . $punkte_sieg_doppel) : ("'" . $punkte_sieg_doppel . "'")) . ";\n" + . " document.adminForm.spiel_" . $i . "_heim_punkte.value = punkte_gegner(form.value, " . $punkte_sieg_doppel . ", " . $punkte_vorsprung_doppel . ", " . $punkte_deckel_doppel . ", " . $punkte_unentschieden_doppel . ");\n" . " break;\n"; } } echo " }\n" . "}\n"; + } + } + // Der Gesamtstand — und zwar DIE Zahl, die die Begegnung nach aussen + // hat: die Spielpunkte. Liste, Mannschaftsseite und die Kopfzeile der + // Begegnungsdetails zeigen alle heim_spielpunkte:gast_spielpunkte, also + // zeigt das Formular sie auch. Ich hatte hier zuerst die Summe der Tore + // gross und die Spielpunkte in Klammern — bei einem 5er-Spielplan stand da + // „6:4 (Spielpunkte 1:0)", wo die Begegnung 1:0 heisst (Sven 2026-09-02). + // + // Gerechnet wie der Server rechnet (admin.php, + // adminSaveBegegnungSpielplan): je Spiel aus der Wertung — Sieg 1/2/3, + // Unentschieden, oder das Ergebnis selbst. + echo "function gesamtstand_aktualisieren() {\n" + . " var sh = 0, sg = 0, h, g;\n"; + for ($i = 1; $i <= count($spieltypen); $i++) { + $d = $spieltypen[$i - 1][0] == "D"; + $w = $d ? $spielpunkte_wertung_doppel : $spielpunkte_wertung_einzel; + echo " h = document.adminForm['spiel_" . $i . "_heim_punkte'];\n" + . " g = document.adminForm['spiel_" . $i . "_gast_punkte'];\n" + . " h = (h != null && h.value != '') ? Number(h.value) : 0;\n" + . " g = (g != null && g.value != '') ? Number(g.value) : 0;\n"; + if ($w == 2) { + echo " sh += h; sg += g;\n"; + } else { + $sieg = $w == 0 ? 2 : ($w == 1 ? 3 : 1); + $remis = $w <= 1 ? 1 : 0; + echo " if (h > g) sh += " . $sieg . ";\n" + . " else if (g > h) sg += " . $sieg . ";\n" + . " else if (h > 0) { sh += " . $remis . "; sg += " . $remis . "; }\n"; + } + } + echo " var feld = document.getElementById('gesamtstand');\n" + . " if (feld != null) feld.textContent = sh + ':' + sg;\n" + . "}\n"; + if ($satzweise) { + // Ein Satz ist eingetragen, also: den Gegenwert IM SATZ ergaenzen, die + // gewonnenen Saetze neu zaehlen und in die versteckten Summenfelder + // schreiben. Gezaehlt wird nach derselben Regel wie auf dem Server + // (adminSaveBegegnungSpielplan): ein Satz zaehlt fuer die Seite mit den + // meisten Toren, ein 0:0 zaehlt fuer niemanden. + echo "function saetze_changed(nr, feld, sieg, vorsprung, deckel, unentschieden) {\n" + . " var heim = document.getElementsByName('spiel_' + nr + '_heim_punkte_detailliert[]');\n" + . " var gast = document.getElementsByName('spiel_' + nr + '_gast_punkte_detailliert[]');\n" + . " if (feld != null && sieg != 0) {\n" + . " var i = Number(feld.getAttribute('data-satz'));\n" + . " var eigen = feld.getAttribute('data-seite') == 'heim' ? heim[i] : gast[i];\n" + . " var ander = feld.getAttribute('data-seite') == 'heim' ? gast[i] : heim[i];\n" + . " if (eigen.value != '' && ander.value == '')\n" + . " ander.value = punkte_gegner(eigen.value, sieg, vorsprung, deckel, unentschieden);\n" + . " }\n" + . " var saetze_heim = 0;\n" + . " var saetze_gast = 0;\n" + . " for (var i = 0; i < heim.length; i++) {\n" + . " var h = heim[i].value == '' ? 0 : Number(heim[i].value);\n" + . " var g = gast[i].value == '' ? 0 : Number(gast[i].value);\n" + . " if (h > g) saetze_heim++;\n" + . " else if (g > h) saetze_gast++;\n" + . " }\n" + . " var leer = saetze_heim == 0 && saetze_gast == 0;\n" + . " document.adminForm['spiel_' + nr + '_heim_punkte'].value = leer ? '' : saetze_heim;\n" + . " document.adminForm['spiel_' + nr + '_gast_punkte'].value = leer ? '' : saetze_gast;\n" + . " var anzeige = document.getElementById('saetze_' + nr);\n" + . " if (anzeige != null) anzeige.innerHTML = leer ? '' : (saetze_heim + ':' + saetze_gast);\n" + . " satzzeilen_anzeigen(nr);\n" + . " gesamtstand_aktualisieren();\n" + . "}\n" + // Gestaffelt zeigen: sichtbar ist die Mindestzahl, und dahinter genau + // eine leere Zeile — nicht die volle Hoechstzahl. Sonst stuenden bei + // sieben Spielen zu fuenf Saetzen siebzig Auswahlfelder da, von denen + // die meisten leer bleiben. + // + // Alle Zeilen bleiben im Dokument und werden mitgesendet; leere + // Satzpaare ueberspringt der Server ohnehin. Verborgen wird also nur, + // was man sieht, nicht was gespeichert wird. + . "function satzzeilen_anzeigen(nr) {\n" + . " var min = satz_min_je_spiel[nr];\n" + . " if (min == null) return;\n" + . " var heim = document.getElementsByName('spiel_' + nr + '_heim_punkte_detailliert[]');\n" + . " var gast = document.getElementsByName('spiel_' + nr + '_gast_punkte_detailliert[]');\n" + . " var gefuellt = 0;\n" + . " for (var i = 0; i < heim.length; i++) {\n" + . " var a = heim[i].value == '' ? 0 : Number(heim[i].value);\n" + . " var b = gast[i].value == '' ? 0 : Number(gast[i].value);\n" + . " if (a != 0 || b != 0) gefuellt = i + 1;\n" + . " }\n" + . " var sichtbar = Math.max(min, Math.min(heim.length, gefuellt + 1));\n" + . " for (var i = 0; i < heim.length; i++) {\n" + . " var zeile = document.getElementById('satz_' + nr + '_' + i);\n" + . " if (zeile != null) zeile.style.display = i < sichtbar ? '' : 'none';\n" + . " }\n" + . "}\n"; } if ($handheld != "yes") { echo "function spieler_changed(form) {\n" @@ -11972,28 +13141,45 @@ static function adminVerbandsorganMitglieder($rows,$verbandsorgan): void . " var punkte_gast;\n"; $n = count($spieltypen); for ($i = 1; $i <= $n; $i++) { + // Geprueft wird nur, was es auch gibt: hat eine Mannschaft keine + // hinterlegten Spieler, steht dort kein Auswahlfeld — und eine + // Forderung nach Namen, die niemand erfuellen kann, wuerde das + // Melden verhindern statt es zu ordnen. + if (!$heim_hat_spieler && !$gast_hat_spieler) + continue; if ($spielernamen == 1) { if ($spieltypen[$i - 1][0] != "D") continue; echo " punkte_heim = document.adminForm.spiel_" . $i . "_heim_punkte.value != '' ? document.adminForm.spiel_" . $i . "_heim_punkte.value : 0;\n" . " punkte_gast = document.adminForm.spiel_" . $i . "_gast_punkte.value != '' ? document.adminForm.spiel_" . $i . "_gast_punkte.value : 0;\n" - . " if (punkte_heim != 0 || punkte_gast != 0) {\n" - . " if ((document.adminForm.spiel_" . $i . "_heim_spieler_1_id.value == 0 && document.adminForm.spiel_" . $i . "_heim_spieler_2_id.value != 0) || (document.adminForm.spiel_" . $i . "_heim_spieler_1_id.value != 0 && document.adminForm.spiel_" . $i . "_heim_spieler_2_id.value == 0))\n" - . " return false;\n" - . " if ((document.adminForm.spiel_" . $i . "_gast_spieler_1_id.value == 0 && document.adminForm.spiel_" . $i . "_gast_spieler_2_id.value != 0) || (document.adminForm.spiel_" . $i . "_gast_spieler_1_id.value != 0 && document.adminForm.spiel_" . $i . "_gast_spieler_2_id.value == 0))\n" - . " return false;\n" - . " }\n"; + . " if (punkte_heim != 0 || punkte_gast != 0) {\n"; + if ($heim_hat_spieler) + echo " if ((document.adminForm.spiel_" . $i . "_heim_spieler_1_id.value == 0 && document.adminForm.spiel_" . $i . "_heim_spieler_2_id.value != 0) || (document.adminForm.spiel_" . $i . "_heim_spieler_1_id.value != 0 && document.adminForm.spiel_" . $i . "_heim_spieler_2_id.value == 0))\n" + . " return false;\n"; + if ($gast_hat_spieler) + echo " if ((document.adminForm.spiel_" . $i . "_gast_spieler_1_id.value == 0 && document.adminForm.spiel_" . $i . "_gast_spieler_2_id.value != 0) || (document.adminForm.spiel_" . $i . "_gast_spieler_1_id.value != 0 && document.adminForm.spiel_" . $i . "_gast_spieler_2_id.value == 0))\n" + . " return false;\n"; + echo " }\n"; } else { if ($spieltypen[$i - 1][0] != "D" && $spieltypen[$i - 1][0] != "E") continue; echo " punkte_heim = document.adminForm.spiel_" . $i . "_heim_punkte.value != '' ? document.adminForm.spiel_" . $i . "_heim_punkte.value : 0;\n" . " punkte_gast = document.adminForm.spiel_" . $i . "_gast_punkte.value != '' ? document.adminForm.spiel_" . $i . "_gast_punkte.value : 0;\n" - . " if (punkte_heim != 0 || punkte_gast != 0) {\n" - . " if (document.adminForm.spiel_" . $i . "_heim_spieler_1_id.value == 0 || document.adminForm.spiel_" . $i . "_gast_spieler_1_id.value == 0)\n" - . " return false;\n"; - if ($spieltypen[$i - 1][0] == "D") - echo " if (document.adminForm.spiel_" . $i . "_heim_spieler_2_id.value == 0 || document.adminForm.spiel_" . $i . "_gast_spieler_2_id.value == 0)\n" + . " if (punkte_heim != 0 || punkte_gast != 0) {\n"; + if ($heim_hat_spieler) + echo " if (document.adminForm.spiel_" . $i . "_heim_spieler_1_id.value == 0)\n" . " return false;\n"; + if ($gast_hat_spieler) + echo " if (document.adminForm.spiel_" . $i . "_gast_spieler_1_id.value == 0)\n" + . " return false;\n"; + if ($spieltypen[$i - 1][0] == "D") { + if ($heim_hat_spieler) + echo " if (document.adminForm.spiel_" . $i . "_heim_spieler_2_id.value == 0)\n" + . " return false;\n"; + if ($gast_hat_spieler) + echo " if (document.adminForm.spiel_" . $i . "_gast_spieler_2_id.value == 0)\n" + . " return false;\n"; + } echo " }\n"; } } @@ -12030,6 +13216,7 @@ static function adminVerbandsorganMitglieder($rows,$verbandsorgan): void + + + + + + + + + @@ -12100,7 +13295,7 @@ static function adminVerbandsorganMitglieder($rows,$verbandsorgan): void , , , );" + aria-label=""> + + " . $i . ""; + } + ?> + + + + + + + + + teamspiel_heim_punkte != 0 || $spiel->teamspiel_gast_punkte != 0); + ?> + + + + + : + teamspiel_heim_punkte . ":" . (int)$spiel->teamspiel_gast_punkte) + : ''; ?> + + + @@ -12195,6 +13554,19 @@ static function adminVerbandsorganMitglieder($rows,$verbandsorgan): void value="1"> + gesamtstand_aktualisieren();\n"; + if ($satzweise && !empty($satz_min_je_spiel)) { + // Erst hier, weil die Mindestzahlen in der Spiel-Schleife entstehen. Der Aufruf + // beim Laden bringt Anfangszustand und Regel auf denselben Stand — ein bereits + // gespeichertes Fuenf-Satz-Spiel zeigt so alle fuenf Zeilen, ein leeres drei. + echo "\n"; + } + ?>   Begegnung ist die Partie zweier Mannschaften. Sie besteht aus mehreren Spielen: Einzel und Doppel, in der Reihenfolge der Spielfolge. Jedes Spiel bringt Spielpunkte für die Begegnung. Ein Spiel besteht aus einem Satz („Einer") oder aus mehreren. In einem Satz fallen Tore. Die Einstellungen unten sind in dieser Reihenfolge geordnet: von der Begegnung über das Spiel zum Satz." +COM_SPORTSMANAGER_RESULT_AS_GAME_POINTS="Ohne Umrechnung – Tore bzw. Sätze sind die Spielpunkte" +COM_SPORTSMANAGER_COUNTING="Zählweise" +COM_SPORTSMANAGER_COUNTING_PER_GAME="Je Spiel – jedes Spiel beginnt bei 0:0" +COM_SPORTSMANAGER_COUNTING_RACE="Race-Modus – die Tore addieren sich über alle Spiele" +COM_SPORTSMANAGER_RACE_STEP="Schritt je Spiel" +COM_SPORTSMANAGER_RACE_TARGET="Ziel" +COM_SPORTSMANAGER_RACE_TARGET_FORMULA="Anzahl der Spiele aus der Spielfolge × Schritt je Spiel" +COM_SPORTSMANAGER_RACE_TARGET_CURRENT="derzeit %1$d Spiele × %2$d = %3$d Tore" +COM_SPORTSMANAGER_RACE_MARGIN="Mindestvorsprung am Ziel" +COM_SPORTSMANAGER_RACE_CAP="Spätestens gewonnen bei" +COM_SPORTSMANAGER_RACE_DRAW="Unentschieden am Ziel" +COM_SPORTSMANAGER_RACE_DRAW_NO="Nein" +COM_SPORTSMANAGER_RACE_DRAW_YES="Ja – bei einem Tor unter dem Ziel" +COM_SPORTSMANAGER_RACE_HELP="Der Stand läuft über die ganze Begegnung durch: Spiel 1 endet beim Schritt, Spiel 2 beim doppelten Schritt, und so fort bis zum Ziel. Mindestvorsprung und Grenze gelten nur für das letzte Spiel. Der Race-Modus setzt „Spielergebnis als Spielpunkte" und einen Satz je Spiel voraus – beides wird mitgesetzt." +COM_SPORTSMANAGER_ADD_GAME="Spiel hinzufügen" +COM_SPORTSMANAGER_ADD_LINK="Verknüpfung hinzufügen" +COM_SPORTSMANAGER_REMOVE_GAME="Dieses Spiel entfernen" +COM_SPORTSMANAGER_REMOVE_LINK="Diese Verknüpfung entfernen" +COM_SPORTSMANAGER_LINK_NUMBER="Verknüpfung Nr. %d" +COM_SPORTSMANAGER_GROUP_SEQUENCE="Spielfolge" +COM_SPORTSMANAGER_GAMES_IN_STATISTIK_FIRST_ONE="nur das erste Spiel" +COM_SPORTSMANAGER_GAMES_IN_STATISTIK_FIRST="nur die ersten %d Spiele" +COM_SPORTSMANAGER_POINT_TYPE_CHANGED_WARNING="Für diesen Mannschaftsspielplan sind bereits Ergebnisse eingetragen. Die gespeicherten Zahlen werden NICHT umgerechnet – sie bedeuten danach etwas anderes (Tore statt Sätze bzw. umgekehrt). Trotzdem speichern?" +COM_SPORTSMANAGER_RACE_RUNNING="Stand" +COM_SPORTSMANAGER_TOTAL_SCORE="Gesamtstand" diff --git a/src/structure/language/en-GB/en-GB.com_sportsmanager.ini b/src/structure/language/en-GB/en-GB.com_sportsmanager.ini index 5e1c898..df2f8d8 100644 --- a/src/structure/language/en-GB/en-GB.com_sportsmanager.ini +++ b/src/structure/language/en-GB/en-GB.com_sportsmanager.ini @@ -490,7 +490,7 @@ COM_SPORTSMANAGER_GAME_DOUBLES2="Double games" COM_SPORTSMANAGER_GAME_DOUBLES_IDENTICAL="Doubles game identical" COM_SPORTSMANAGER_GAME_DOUBLES_SEPARATELY="Doubles game seperately" COM_SPORTSMANAGER_ONE="One" -COM_SPORTSMANAGER_MULTIPLE="Multiple" +COM_SPORTSMANAGER_MULTIPLE="Multiple – set score only" COM_SPORTSMANAGER_HOME_EXCHANGE="Home exchange" COM_SPORTSMANAGER_NOT_ALLOWED="Not allowed" COM_SPORTSMANAGER_ALLOWED="Allowed" @@ -1124,4 +1124,52 @@ COM_SPORTSMANAGER_MATCH_REPORT_CORRECTED="Match report corrected" COM_SPORTSMANAGER_MIN_MATCHES="Min count matches" COM_SPORTSMANAGER_SELECT_ALL="All" COM_SPORTSMANAGER_MAIL_SEND_ERROR="The notification e-mail for the result could not be sent." -COM_SPORTSMANAGER_TERMIN_MAIL_SEND_ERROR="At least one notification e-mail for the appointment could not be sent." \ No newline at end of file +COM_SPORTSMANAGER_TERMIN_MAIL_SEND_ERROR="At least one notification e-mail for the appointment could not be sent." +COM_SPORTSMANAGER_GAME_MARGIN="Minimum margin" +COM_SPORTSMANAGER_GAME_CAP="Won at the latest at" +COM_SPORTSMANAGER_GAME_CAP_NONE="No limit" +COM_SPORTSMANAGER_NO_MARGIN="None" +COM_SPORTSMANAGER_ONE_GOAL="1 goal" +COM_SPORTSMANAGER_NUMBER_GOALS="%d goals" +COM_SPORTSMANAGER_MULTIPLE_WITH_SETS="Multiple – with each set's result" +COM_SPORTSMANAGER_SET_NUMBER="Set %d" +COM_SPORTSMANAGER_SET_COUNT="Sets per game" +COM_SPORTSMANAGER_GROUP_GENERAL="General" +COM_SPORTSMANAGER_GROUP_GAME="Game" +COM_SPORTSMANAGER_GROUP_SET="Set" +COM_SPORTSMANAGER_GROUP_MATCH="Match" +COM_SPORTSMANAGER_GROUP_PLAYERS="Players and statistics" +COM_SPORTSMANAGER_DOUBLES_SETTINGS="Doubles games" +COM_SPORTSMANAGER_ONE_FIXED_SET="1 set (played out)" +COM_SPORTSMANAGER_NUMBER_FIXED_SETS="%d sets (all played out)" +COM_SPORTSMANAGER_NUMBER_WINNING_SETS="Best of %d wins" +COM_SPORTSMANAGER_DECIDING_SET="Deciding set" +COM_SPORTSMANAGER_DECIDING_SET_SAME="As the other sets" +COM_SPORTSMANAGER_SETS_PER_GAME_HELP="With “each set’s result” the goals of the individual sets are recorded too and appear in the match report; without it only the set score remains." +COM_SPORTSMANAGER_SET_COUNT_HELP="These settings describe the competition; they never reject an entry. Evaluations and apps use them to tell a game still running from one already decided." +COM_SPORTSMANAGER_MODE_GLOSSARY="A match is the fixture between two teams. It consists of several games – singles and doubles in the order of the game sequence – and each game yields game points for the match. A game consists of one set (“One”) or of several. Within a set, goals are scored. The settings below follow that order: from the match through the game down to the set." +COM_SPORTSMANAGER_RESULT_AS_GAME_POINTS="Without conversion – goals or sets are the game points" +COM_SPORTSMANAGER_COUNTING="Counting" +COM_SPORTSMANAGER_COUNTING_PER_GAME="Per game – every game starts at 0:0" +COM_SPORTSMANAGER_COUNTING_RACE="Race – goals add up across all games" +COM_SPORTSMANAGER_RACE_STEP="Step per game" +COM_SPORTSMANAGER_RACE_TARGET="Target" +COM_SPORTSMANAGER_RACE_TARGET_FORMULA="Number of games in the sequence × step per game" +COM_SPORTSMANAGER_RACE_TARGET_CURRENT="currently %1$d games × %2$d = %3$d goals" +COM_SPORTSMANAGER_RACE_MARGIN="Minimum margin at the target" +COM_SPORTSMANAGER_RACE_CAP="Won at the latest at" +COM_SPORTSMANAGER_RACE_DRAW="Draw at the target" +COM_SPORTSMANAGER_RACE_DRAW_NO="No" +COM_SPORTSMANAGER_RACE_DRAW_YES="Yes – one goal below the target" +COM_SPORTSMANAGER_RACE_HELP="The score runs through the whole match: game 1 ends at the step, game 2 at twice the step, and so on up to the target. Minimum margin and limit apply to the last game only. Race requires “game result as game points” and one set per game – both are set along with it." +COM_SPORTSMANAGER_ADD_GAME="Add a game" +COM_SPORTSMANAGER_ADD_LINK="Add a link" +COM_SPORTSMANAGER_REMOVE_GAME="Remove this game" +COM_SPORTSMANAGER_REMOVE_LINK="Remove this link" +COM_SPORTSMANAGER_LINK_NUMBER="Link no. %d" +COM_SPORTSMANAGER_GROUP_SEQUENCE="Game sequence" +COM_SPORTSMANAGER_GAMES_IN_STATISTIK_FIRST_ONE="the first game only" +COM_SPORTSMANAGER_GAMES_IN_STATISTIK_FIRST="the first %d games only" +COM_SPORTSMANAGER_POINT_TYPE_CHANGED_WARNING="Results have already been entered for this team schedule. The stored numbers are NOT converted – afterwards they mean something else (goals instead of sets, or the other way round). Save anyway?" +COM_SPORTSMANAGER_RACE_RUNNING="Score" +COM_SPORTSMANAGER_TOTAL_SCORE="Total score" diff --git a/src/structure/script.php b/src/structure/script.php index 3f2e4e6..089a1b6 100644 --- a/src/structure/script.php +++ b/src/structure/script.php @@ -790,8 +790,32 @@ return new class () implements InstallerScriptInterface . "\n `teamspiel_modus_id` int(11) NOT NULL AUTO_INCREMENT," . "\n `bezeichnung` varchar(50) NOT NULL DEFAULT ''," . "\n `punktetyp` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `satzergebnisse` tinyint(4) NOT NULL DEFAULT '0'," . "\n `punkte_sieg_einzel` tinyint(4) NOT NULL DEFAULT '0'," . "\n `punkte_sieg_doppel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `punkte_vorsprung_einzel` tinyint(4) NOT NULL DEFAULT '1'," + . "\n `punkte_vorsprung_doppel` tinyint(4) NOT NULL DEFAULT '1'," + . "\n `punkte_deckel_einzel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `punkte_deckel_doppel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `satz_sieg_einzel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `satz_sieg_doppel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `satz_vorsprung_einzel` tinyint(4) NOT NULL DEFAULT '1'," + . "\n `satz_vorsprung_doppel` tinyint(4) NOT NULL DEFAULT '1'," + . "\n `satz_deckel_einzel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `satz_deckel_doppel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `saetze_modus_einzel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `saetze_modus_doppel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `entscheidung_sieg_einzel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `entscheidung_sieg_doppel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `entscheidung_vorsprung_einzel` tinyint(4) NOT NULL DEFAULT '1'," + . "\n `entscheidung_vorsprung_doppel` tinyint(4) NOT NULL DEFAULT '1'," + . "\n `entscheidung_deckel_einzel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `entscheidung_deckel_doppel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `zaehlweise` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `race_schritt` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `race_vorsprung` tinyint(4) NOT NULL DEFAULT '1'," + . "\n `race_deckel` tinyint(4) NOT NULL DEFAULT '0'," + . "\n `race_remis` tinyint(4) NOT NULL DEFAULT '0'," . "\n `spielpunkte_wertung_einzel` tinyint(4) NOT NULL DEFAULT '0'," . "\n `spielpunkte_wertung_doppel` tinyint(4) NOT NULL DEFAULT '0'," . "\n `spielpunkte_bedingung` tinyint(4) NOT NULL DEFAULT '0'," @@ -1283,7 +1307,7 @@ return new class () implements InstallerScriptInterface $db->setQuery( $query ); if (!$db->execute()) { die($db->stderr(true)); } - $query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'datenbank_version', wert = '121';"; + $query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'datenbank_version', wert = '122';"; $db->setQuery( $query ); if (!$db->execute()) { die($db->stderr(true)); }