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'); + }; ?>