Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified TSH.exe
Binary file not shown.
82 changes: 81 additions & 1 deletion assets/characters.json
Original file line number Diff line number Diff line change
Expand Up @@ -101850,6 +101850,84 @@
],
"characterId": null,
"expand": []
},
{
"id": 2343,
"name": "Infernal Host",
"isCommon": true,
"videogameId": 50435,
"externalIdMap": null,
"images": [
{
"id": 6178894,
"width": 200,
"height": 200,
"ratio": 1,
"type": "icon",
"url": "https://images.start.gg/images/character/2343/image-6a924acaeb55765597982b4ec8ccd965.png?ehk=KyucVEAHBGQP5a2ztmiwJdYNPibzdHsZN7CAP%2F9xbbk%3D&ehkOptimized=SYz7LncC0KR9aBqkyhXrrT8GJ3MQanXHNu%2Fds%2BXuUw8%3D",
"isOriginal": true,
"entity": null,
"entityId": null,
"uploadedBy": null,
"createdAt": null,
"updatedAt": null
},
{
"id": 6178895,
"width": 200,
"height": 200,
"ratio": 1,
"type": "stockIcon",
"url": "https://images.start.gg/images/character/2343/image-9f6c1cb933c0db19da4359a3f8ce0930.png?ehk=s2fnwZfmjY05T316FSX804IUQV4UuT6v3XEArsPCgj8%3D&ehkOptimized=aufF8XRx1GFtLA3GDkVvYwgBHfK4RYrQxzFHD01vukY%3D",
"isOriginal": true,
"entity": null,
"entityId": null,
"uploadedBy": null,
"createdAt": null,
"updatedAt": null
}
],
"characterId": null,
"expand": []
},
{
"id": 2344,
"name": "Vanguard",
"isCommon": true,
"videogameId": 50435,
"externalIdMap": null,
"images": [
{
"id": 6178897,
"width": 200,
"height": 200,
"ratio": 1,
"type": "icon",
"url": "https://images.start.gg/images/character/2344/image-25052384ad79679f2614090e9d8bf563.png?ehk=%2F%2BioQFt2hk9bBf%2Bec8zcdJ6gprwve3tP%2B4BCKellD%2Bg%3D&ehkOptimized=FTfbnOmG%2FNV2VO%2BzguN6o6JNq5eSS0dLmVeqtbX4ikk%3D",
"isOriginal": true,
"entity": null,
"entityId": null,
"uploadedBy": null,
"createdAt": null,
"updatedAt": null
},
{
"id": 6178898,
"width": 200,
"height": 200,
"ratio": 1,
"type": "stockIcon",
"url": "https://images.start.gg/images/character/2344/image-5ac9babde33ca82b162acc980414927a.png?ehk=IxWnSuZHjKV22cSC%2F8mPC23vm0upJt1UvRB3Zzd28Vw%3D&ehkOptimized=oKpQD5e6L%2Fyw7ebpgHM2Zqd7%2FcYpsad7eJOVCLzTUR8%3D",
"isOriginal": true,
"entity": null,
"entityId": null,
"uploadedBy": null,
"createdAt": null,
"updatedAt": null
}
],
"characterId": null,
"expand": []
}
]
},
Expand Down Expand Up @@ -103958,7 +104036,9 @@
2339,
2340,
2341,
2342
2342,
2343,
2344
],
"resultEntity": "character",
"actionRecords": []
Expand Down
2 changes: 1 addition & 1 deletion assets/contributors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ JinchanJ
Kekwel
TwilCynder
ladyisatis
Lunki51
ashleygruver
Lunki51
cash12121
JuandePL
Eric Iniguez
Expand Down
4,997 changes: 4,996 additions & 1 deletion assets/rulesets.json

Large diffs are not rendered by default.

156 changes: 156 additions & 0 deletions src/ScoreManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@


# The current score of the match, as well as individual games, are managed by the ScoreManager ; the Scoreboard widget owns a ScoreManager instance
# When the score changes in the ScoreManager, it is reflected in the state (by OnScoreChanged)
# When the score is changed in the UI, instead of directly changing the state, the scoreboard widget informs the ScoreManager of the change (via OnScoreUIChanged)
# When an external source attemps to change the score (auto-update or stage strike app), instead of directly changing the value if the UI,
# we call ScoreManager.CommandScoreChange, which changes the score in the UI.
# Note that CommandScoreChange calls OnScoreChanged to update the state with the new data, then updates the UI, which calls OnScoreUIChanged, which calls OnScoreChanged a second time.
# This is because there doesn't seem to be a safe way to make a difference between a code-induced change and an user-made change in the UI. I'm starting to hate Qt.
# (It isn't a problem anyway, as OnScoreChanged will check for differences and see that there isn't any -> doing nothing)

# The ScoreManager *can* track individual games, when the information it receives allows it to ; but it may as well stop tracking them.

# Example 1 : The score is manually changed in the UI
# - ScoreManager.OnScoreUIChanged is called with the new score
# - Calls ScoreManager.OnScoreChanged
# - Change self.score
# - If the difference with the previous score is 1 (or -1), add a game (with no info) or remove one
# - If the difference was greater, forget the games, we are now in score only mode
# - The state is updated

# Example 2 : data is received from start.gg, with chars and stage :
# - ScoreManager.CommandScoreChange is called with all the data. It updates self.games with it, as well as the score; OnScoreChanged is called, updates the state
# - ScoreManager.CommandScoreChange also changes the values in the score widgets
# - This (unfortunately) triggers the valueChanged signal, which calls ScoreManager.OnScoreChanged (see TSHScoreboardWidget.py:356)
# - ScoreManager.OnScoreChanged sees that nothing changed since the last time we changed the score (as that was during the CommandScoreChange call)

# REGARDING THE STAGE STRIKE WEB APP (SSWA)
# It should be possible to do what we want to do here without really touching the way the SSWA works, but i think this is a good occasion to try doing so.
# My proposal is :
# - When the Web server tries to update the score :
# - IF the "allow sswa to change score setting" is true, call ScoreManager.ReportGame directly (without using any method of TSHScoreboardWidget)
# - In that case, IF the new "keep score updated in the sswa" setting is also true, send a message to the sswa with the new score.
# - Add a "Sync SSWA", which forces the synchronization. The information sent here include the score but also who won last (useful for stage strike).
# If that information is not known (we are in score-only mode or the last game has no winner), the user is prompted for it before the sync message is sent.
# - Currently, from what i understand, the SSWA doesn't know the exact order of previous games (it knows who won on which stages, but not in which order). We should change that
# - Maybe when we receive an update from the SSWA and the data it holds doesn't match ours (different scores, or same score but different stages), a warning should be displayed,
# with the option of overwriting the ScoreManager's data OR to sync the stage strike app instead

from .StateManager import StateManager


class ScoreManager:
def __init__(self, parent) -> None:
self.scoreboardNumber = parent.scoreboardNumber
self.parent = parent
self.games = []
self.Reset()

def Reset(self):
self.games = []
self.score = [0, 0]

def Swap(self):
temp = self.score[0]
self.score[0] = self.score[1]
self.score[1] = temp

for game in self.games:
if game and game["winner"]:
game["winner"] = 1 - game["winner"]


def OnScoreUIChanged(self, team, value):
if self.score[team] == value:
#If the new value is already the one we know, nothing to do (it's most likely because the change was done by the ScoreManager itself)
return
self.score[team] = value
StateManager.BlockSaving()
self.OnScoreChanged(team)
StateManager.ReleaseSaving()

#if team is given we only update the score for that team in the state
def OnScoreChanged(self, team = -1):
if team > -1:
#update just the score for the given team
StateManager.Set(f"score.{self.scoreboardNumber}.team.${team + 1}.score", self.score[team])
pass
else:
#Update EVERYTHING in the state
pass

def OnGamesChanged(self):

#recalculate the score
#only if it changed :
StateManager.BlockSaving()
self.OnScoreChanged()
self.UpdateScoreUI()
StateManager.ReleaseSaving()

def ReportGame(self, winner, p1Char, p2Char, stage):
#if we are in score only mode, we just increment the score
#else :

self.games.append({
winner: winner,
p1Char: p1Char,
p2Char: p2Char,
stage: stage
})
self.OnGamesChanged()

# We probably received data from startgg
def ChangeGames(self, games):
#Process the games data
self.OnGamesChanged()

def CommandScoreChange(self, team, change):
self.score[team] += change
self.UpdateScoreUI(team)
pass

def UpdateScoreUI(self, team = -1):
if team < 0:
self.UpdateScoreUI(0)
self.UpdateScoreUI(1)
else:
self.parent.SetScore(team, self.score[team])

def EditGamesUI(self):
#Opens the UI allowing the user to edit each game.
#I still don't know if the modifications take effect as you do them or when pressing a Save button (like on start.gg) (we need to think about performances tho)
pass

def ReportGameUI(self):
#Opens the UI allowing the user to report a game
#display a warning if we are in score only mode
self.ReportGame(None, None, None, None)
pass



#FRENCH VERSION OF THE NOTES (SAME AS THE BEGINNING OF THIS FILE I JUST COULDNT BRING MYSELF TO DELETE THEM BECAUSE SOME DETAILS MAY HAVE BEEN LOST IN TRANSLATION)
# Le score du match,ainsi que les games, sont contenu dans le ScoreManager, propriété du Scoreboard.
# Pour l'appli stage strike :
# - Quand elle envoie un "x won", on ajoute une game avec les infos données (stage en gros)
# - Quand l'appli envoie une update de score, elle demande le score actuel, pour se mettre à jour (setting pour désactiver ça)
# - En + du setting actuel qui autorise l'appli stage strike à changer le score, un setting qui détemine si mettre le score à jour sur TSh le change dans l'appli.
# UI additionnelle :
# - Bouton "report game" : affiche un dialogue permettant de report une game (who won, chars, stage, final game ?). Report une game déclenche une modif startgg
# - Bouton "games" : affiche un dialogue listant les games, et permettant de les éditer
# - Bouton "modif startgg" (potentiellement dans l'UI de gestion des games ?) : update le score sur startgg
# - Peut être boutons x won dans le scoreboard directement ?
# - Bouton "update web app score" pour forcer une update du score sur l'app
# Pour le scoreboard :
# SOLUTION 1 : Changer le score du ScoreManager sauvegarde le score dans le state ;
# Quand le score est modifié depuis le scoreboard, il modifie le ScoreManager : calcul de la différence avec le score précédent du joueur concerné
# -> Si positif, ajout de x games (sans infos)
# -> Si négatif, retrait des x dernières games
# SOLUTION 2 : Changer le contenu des spinners change le state ; changer le ScoreManager change le contenu des spinners (donc changement du state)

# Autre possibilité :
# Mode "score only", où on ne connait pas les games individuelles.
# - On entre dans ce mode si on change le score manuellement dans les spinners
# - Si on entre dans l'interface des games dans ce mode, on a juste un bouton qui propose de remettre le score à une valeur qui correspond aux games
34 changes: 24 additions & 10 deletions src/TSHScoreboardWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from qtpy.QtCore import *
from qtpy import uic
from typing import List
from .ScoreManager import ScoreManager

from src.TSHSelectSetWindow import TSHSelectSetWindow

Expand Down Expand Up @@ -37,6 +38,8 @@ def __init__(self, scoreboardNumber=1, *args):

self.scoreboardNumber = scoreboardNumber

self.scoreManager = ScoreManager(self)

StateManager.Set(f"score.{self.scoreboardNumber}", {})
StateManager.Set(f"score.{self.scoreboardNumber}.last_sets.1", {})
StateManager.Set(f"score.{self.scoreboardNumber}.last_sets.2", {})
Expand Down Expand Up @@ -349,7 +352,13 @@ def __init__(self, scoreboardNumber=1, *args):
)
self.scoreColumn.findChild(QSpinBox, "best_of").valueChanged.emit(0)

self.scoreContainers = [
self.scoreColumn.findChild(QSpinBox, "score_left"),
self.scoreColumn.findChild(QSpinBox, "score_right")
]

self.scoreColumn.findChild(QSpinBox, "score_left").valueChanged.connect(
#lambda value: self.scoreManager.OnScoreChanged(1, value)
lambda value: StateManager.Set(
f"score.{self.scoreboardNumber}.team.1.score", value)
)
Expand Down Expand Up @@ -420,6 +429,11 @@ def __init__(self, scoreboardNumber=1, *args):
if self.scoreboardNumber > 1:
self.UpdateStreamButton()

def SetScore(self, team, score):
container = self.scoreContainers[team]
if container:
container.setValue(score)

def ExportTeamLogo(self, team, value):
if os.path.exists(f"./user_data/team_logo/{value.lower()}.png"):
StateManager.Set(f"score.{self.scoreboardNumber}.team.{team}.logo",
Expand Down Expand Up @@ -629,8 +643,8 @@ def SwapTeams(self):
StateManager.ReleaseSaving()

def ResetScore(self):
self.scoreColumn.findChild(QSpinBox, "score_left").setValue(0)
self.scoreColumn.findChild(QSpinBox, "score_right").setValue(0)
self.SetScore(0, 0)
self.SetScore(1, 0)

def AutoUpdate(self, data):
TSHTournamentDataProvider.instance.GetMatch(
Expand Down Expand Up @@ -765,12 +779,9 @@ def LoadUserSetOptionsClicked(self):
# Change <team>(0, 1) score by <change>(+X, -X)
def CommandScoreChange(self, team: int, change: int):
if team in (0, 1):
scoreContainers = [
self.scoreColumn.findChild(QSpinBox, "score_left"),
self.scoreColumn.findChild(QSpinBox, "score_right")
]
scoreContainers[team].setValue(
scoreContainers[team].value()+change)

self.SetScore(team,
self.scoreContainers[team].value()+change)

def CommandClearAll(self):
for t, team in enumerate([self.team1playerWidgets, self.team2playerWidgets]):
Expand All @@ -782,8 +793,8 @@ def ClearScore(self):
c.setCurrentText("")
c.lineEdit().editingFinished.emit()

self.scoreColumn.findChild(QSpinBox, "score_left").setValue(0)
self.scoreColumn.findChild(QSpinBox, "score_right").setValue(0)
self.SetScore(0)
self.SetScore(0)

self.team1column.findChild(QCheckBox, "losers").setChecked(False)
self.team2column.findChild(QCheckBox, "losers").setChecked(False)
Expand All @@ -792,6 +803,9 @@ def ClearScore(self):
def ChangeSetData(self, data):
StateManager.BlockSaving()

# STATEMANAGER PR : much work to do here. A lot of lines need to be changed to call ScoreManager instead of changing the State or the UI;
# It should also be considered that this method will most likely no longer be called when receiving data from the stage strike app

try:
if data.get("round_name"):
self.scoreColumn.findChild(
Expand Down
Loading