frontends (quick_frontend, primitivus): fixes room games:
- add quick_frontend.quick_games for registering the signals and registering the UI classes
- rename the signals handlers to fit the convention (e.g.: tarotGameScoreHandler)
- rename card_game to game_tarot, quick_card_game to quick_game_tarot, CardGame to TarotGame
This commit is contained in:
parent
5ef7999fa7
commit
e597a804d9
4 changed files with 183 additions and 198 deletions
|
|
@ -26,7 +26,7 @@ from sat.tools.misc import TriggerManager
|
|||
|
||||
from sat_frontends.tools import jid
|
||||
from sat_frontends.quick_frontend.quick_widgets import QuickWidgetsManager
|
||||
from sat_frontends.quick_frontend import quick_chat
|
||||
from sat_frontends.quick_frontend import quick_chat, quick_games
|
||||
from sat_frontends.quick_frontend.constants import Const as C
|
||||
|
||||
import sys
|
||||
|
|
@ -252,25 +252,14 @@ class QuickApp(object):
|
|||
self.registerSignal("roomUserLeft", iface="plugin")
|
||||
self.registerSignal("roomUserChangedNick", iface="plugin")
|
||||
self.registerSignal("roomNewSubject", iface="plugin")
|
||||
self.registerSignal("tarotGameStarted", iface="plugin")
|
||||
self.registerSignal("tarotGameNew", iface="plugin")
|
||||
self.registerSignal("tarotGameChooseContrat", iface="plugin")
|
||||
self.registerSignal("tarotGameShowCards", iface="plugin")
|
||||
self.registerSignal("tarotGameYourTurn", iface="plugin")
|
||||
self.registerSignal("tarotGameScore", iface="plugin")
|
||||
self.registerSignal("tarotGameCardsPlayed", iface="plugin")
|
||||
self.registerSignal("tarotGameInvalidCards", iface="plugin")
|
||||
self.registerSignal("quizGameStarted", iface="plugin")
|
||||
self.registerSignal("quizGameNew", iface="plugin")
|
||||
self.registerSignal("quizGameQuestion", iface="plugin")
|
||||
self.registerSignal("quizGamePlayerBuzzed", iface="plugin")
|
||||
self.registerSignal("quizGamePlayerSays", iface="plugin")
|
||||
self.registerSignal("quizGameAnswerResult", iface="plugin")
|
||||
self.registerSignal("quizGameTimerExpired", iface="plugin")
|
||||
self.registerSignal("quizGameTimerRestarted", iface="plugin")
|
||||
self.registerSignal("chatStateReceived", iface="plugin")
|
||||
self.registerSignal("personalEvent", iface="plugin")
|
||||
|
||||
# FIXME: do it dynamically
|
||||
quick_games.Tarot.registerSignals(self)
|
||||
quick_games.Quiz.registerSignals(self)
|
||||
quick_games.Radiocol.registerSignals(self)
|
||||
|
||||
self.current_action_ids = set() # FIXME: to be removed
|
||||
self.current_action_ids_cb = {} # FIXME: to be removed
|
||||
self.media_dir = self.bridge.getConfig('', 'media_dir')
|
||||
|
|
@ -576,107 +565,6 @@ class QuickApp(object):
|
|||
chat_widget.setSubject(subject)
|
||||
log.debug("new subject for room [%(room_jid)s]: %(subject)s" % {'room_jid': room_jid, "subject": subject})
|
||||
|
||||
def tarotGameStartedHandler(self, room_jid_s, referee, players, profile):
|
||||
log.debug(_("Tarot Game Started \o/"))
|
||||
room_jid = jid.JID(room_jid_s)
|
||||
chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
|
||||
chat_widget.startGame("Tarot", referee, players)
|
||||
log.debug("new Tarot game started by [%(referee)s] in room [%(room_jid)s] with %(players)s" % {'referee': referee, 'room_jid': room_jid, 'players': [str(player) for player in players]})
|
||||
|
||||
def tarotGameNewHandler(self, room_jid_s, hand, profile):
|
||||
log.debug(_("New Tarot Game"))
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Tarot").newGame(hand)
|
||||
|
||||
def tarotGameChooseContratHandler(self, room_jid_s, xml_data, profile):
|
||||
"""Called when the player has to select his contrat"""
|
||||
log.debug(_("Tarot: need to select a contrat"))
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Tarot").chooseContrat(xml_data)
|
||||
|
||||
def tarotGameShowCardsHandler(self, room_jid_s, game_stage, cards, data, profile):
|
||||
log.debug(_("Show cards"))
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Tarot").showCards(game_stage, cards, data)
|
||||
|
||||
def tarotGameYourTurnHandler(self, room_jid_s, profile):
|
||||
log.debug(_("My turn to play"))
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Tarot").myTurn()
|
||||
|
||||
def tarotGameScoreHandler(self, room_jid_s, xml_data, winners, loosers, profile):
|
||||
"""Called when the game is finished and the score are updated"""
|
||||
log.debug(_("Tarot: score received"))
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Tarot").showScores(xml_data, winners, loosers)
|
||||
|
||||
def tarotGameCardsPlayedHandler(self, room_jid_s, player, cards, profile):
|
||||
log.debug(_("Card(s) played (%(player)s): %(cards)s") % {"player": player, "cards": cards})
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Tarot").cardsPlayed(player, cards)
|
||||
|
||||
def tarotGameInvalidCardsHandler(self, room_jid_s, phase, played_cards, invalid_cards, profile):
|
||||
log.debug(_("Cards played are not valid: %s") % invalid_cards)
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Tarot").invalidCards(phase, played_cards, invalid_cards)
|
||||
|
||||
def quizGameStartedHandler(self, room_jid_s, referee, players, profile):
|
||||
log.debug(_("Quiz Game Started \o/"))
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.startGame("Quiz", referee, players)
|
||||
log.debug(_("new Quiz game started by [%(referee)s] in room [%(room_jid)s] with %(players)s") % {'referee': referee, 'room_jid': room_jid_s, 'players': [str(player) for player in players]})
|
||||
|
||||
def quizGameNewHandler(self, room_jid_s, data, profile):
|
||||
log.debug(_("New Quiz Game"))
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Quiz").quizGameNewHandler(data)
|
||||
|
||||
def quizGameQuestionHandler(self, room_jid_s, question_id, question, timer, profile):
|
||||
"""Called when a new question is asked"""
|
||||
log.debug(_(u"Quiz: new question: %s") % question)
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Quiz").quizGameQuestionHandler(question_id, question, timer)
|
||||
|
||||
def quizGamePlayerBuzzedHandler(self, room_jid_s, player, pause, profile):
|
||||
"""Called when a player pushed the buzzer"""
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Quiz").quizGamePlayerBuzzedHandler(player, pause)
|
||||
|
||||
def quizGamePlayerSaysHandler(self, room_jid_s, player, text, delay, profile):
|
||||
"""Called when a player say something"""
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Quiz").quizGamePlayerSaysHandler(player, text, delay)
|
||||
|
||||
def quizGameAnswerResultHandler(self, room_jid_s, player, good_answer, score, profile):
|
||||
"""Called when a player say something"""
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Quiz").quizGameAnswerResultHandler(player, good_answer, score)
|
||||
|
||||
def quizGameTimerExpiredHandler(self, room_jid_s, profile):
|
||||
"""Called when nobody answered the question in time"""
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Quiz").quizGameTimerExpiredHandler()
|
||||
|
||||
def quizGameTimerRestartedHandler(self, room_jid_s, time_left, profile):
|
||||
"""Called when the question is not answered, and we still have time"""
|
||||
chat_widget = self.widgets.getWidget(quick_chat.QuickChat, jid.JID(room_jid_s), profile)
|
||||
if chat_widget:
|
||||
chat_widget.getGame("Quiz").quizGameTimerRestartedHandler(time_left)
|
||||
|
||||
def chatStateReceivedHandler(self, from_jid_s, state, profile):
|
||||
"""Called when a new chat state is received.
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
self.id = "" # FIXME: to be removed
|
||||
self.nick = None
|
||||
self.occupants = set()
|
||||
self.games = {}
|
||||
|
||||
def __str__(self):
|
||||
return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile)
|
||||
|
|
@ -227,16 +228,6 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def startGame(self, game_type, referee, players):
|
||||
"""Configure the chat window to start a game"""
|
||||
#No need to raise an error as game are not mandatory
|
||||
log.warning(_('startGame is not implemented in this frontend'))
|
||||
|
||||
def getGame(self, game_type):
|
||||
"""Return class managing the game type"""
|
||||
#No need to raise an error as game are not mandatory
|
||||
log.warning(_('getGame is not implemented in this frontend'))
|
||||
|
||||
def updateChatState(self, from_jid, state):
|
||||
"""Set the chat state (XEP-0085) of the contact.
|
||||
|
||||
|
|
@ -244,4 +235,19 @@ class QuickChat(quick_widgets.QuickWidget):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def addGamePanel(self, widget):
|
||||
"""Insert a game panel to this Chat dialog.
|
||||
|
||||
@param widget (Widget): the game panel
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def removeGamePanel(self, widget):
|
||||
"""Remove the game panel from this Chat dialog.
|
||||
|
||||
@param widget (Widget): the game panel
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
quick_widgets.register(QuickChat)
|
||||
|
|
|
|||
158
frontends/src/quick_frontend/quick_game_tarot.py
Normal file
158
frontends/src/quick_frontend/quick_game_tarot.py
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# helper class for making a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
from sat_frontends.tools.jid import JID
|
||||
|
||||
|
||||
class QuickTarotGame(object):
|
||||
|
||||
def __init__(self, parent, referee, players):
|
||||
self._autoplay = None #XXX: use 0 to activate fake play, None else
|
||||
self.parent = parent
|
||||
self.referee = referee
|
||||
self.players = players
|
||||
self.played = {}
|
||||
for player in players:
|
||||
self.played[player] = None
|
||||
self.player_nick = parent.nick
|
||||
self.bottom_nick = unicode(self.player_nick)
|
||||
idx = self.players.index(self.player_nick)
|
||||
idx = (idx + 1) % len(self.players)
|
||||
self.right_nick = unicode(self.players[idx])
|
||||
idx = (idx + 1) % len(self.players)
|
||||
self.top_nick = unicode(self.players[idx])
|
||||
idx = (idx + 1) % len(self.players)
|
||||
self.left_nick = unicode(self.players[idx])
|
||||
self.bottom_nick = unicode(self.player_nick)
|
||||
self.selected = [] #Card choosed by the player (e.g. during ecart)
|
||||
self.hand_size = 13 #number of cards in a hand
|
||||
self.hand = []
|
||||
self.to_show = []
|
||||
self.state = None
|
||||
|
||||
def resetRound(self):
|
||||
"""Reset the game's variables to be reatty to start the next round"""
|
||||
del self.selected[:]
|
||||
del self.hand[:]
|
||||
del self.to_show[:]
|
||||
self.state = None
|
||||
for pl in self.played:
|
||||
self.played[pl] = None
|
||||
|
||||
def getPlayerLocation(self, nick):
|
||||
"""return player location (top,bottom,left or right)"""
|
||||
for location in ['top','left','bottom','right']:
|
||||
if getattr(self,'%s_nick' % location) == nick:
|
||||
return location
|
||||
assert(False)
|
||||
|
||||
def loadCards(self):
|
||||
"""Load all the cards in memory
|
||||
@param dir: directory where the PNG files are"""
|
||||
self.cards={}
|
||||
self.deck=[]
|
||||
self.cards["atout"]={} #As Tarot is a french game, it's more handy & logical to keep french names
|
||||
self.cards["pique"]={} #spade
|
||||
self.cards["coeur"]={} #heart
|
||||
self.cards["carreau"]={} #diamond
|
||||
self.cards["trefle"]={} #club
|
||||
|
||||
def tarotGameNewHandler(self, hand):
|
||||
"""Start a new game, with given hand"""
|
||||
assert (len(self.hand) == 0)
|
||||
for suit, value in hand:
|
||||
self.hand.append(self.cards[suit, value])
|
||||
self.hand.sort()
|
||||
self.state = "init"
|
||||
|
||||
def tarotGameChooseContratHandler(self, xml_data):
|
||||
"""Called when the player as to select his contrat
|
||||
@param xml_data: SàT xml representation of the form"""
|
||||
raise NotImplementedError
|
||||
|
||||
def tarotGameShowCardsHandler(self, game_stage, cards, data):
|
||||
"""Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
|
||||
self.to_show = []
|
||||
for suit, value in cards:
|
||||
self.to_show.append(self.cards[suit, value])
|
||||
if game_stage == "chien" and data['attaquant'] == self.player_nick:
|
||||
self.state = "wait_for_ecart"
|
||||
else:
|
||||
self.state = "chien"
|
||||
|
||||
def tarotGameYourTurnHandler(self):
|
||||
"""Called when we have to play :)"""
|
||||
if self.state == "chien":
|
||||
self.to_show = []
|
||||
self.state = "play"
|
||||
self.__fakePlay()
|
||||
|
||||
def __fakePlay(self):
|
||||
"""Convenience method for stupid autoplay
|
||||
/!\ don't forgot to comment any interactive dialog for invalid card"""
|
||||
if self._autoplay == None:
|
||||
return
|
||||
if self._autoplay >= len(self.hand):
|
||||
self._autoplay = 0
|
||||
card = self.hand[self._autoplay]
|
||||
self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], self.parent.profile)
|
||||
del self.hand[self._autoplay]
|
||||
self.state = "wait"
|
||||
self._autoplay+=1
|
||||
|
||||
def tarotGameScoreHandler(self, xml_data, winners, loosers):
|
||||
"""Called at the end of a game
|
||||
@param xml_data: SàT xml representation of the scores
|
||||
@param winners: list of winners' nicks
|
||||
@param loosers: list of loosers' nicks"""
|
||||
raise NotImplementedError
|
||||
|
||||
def tarotGameCardsPlayedHandler(self, player, cards):
|
||||
"""A card has been played by player"""
|
||||
if self.to_show:
|
||||
self.to_show = []
|
||||
pl_cards = []
|
||||
if self.played[player] != None: #FIXME
|
||||
for pl in self.played:
|
||||
self.played[pl] = None
|
||||
for suit, value in cards:
|
||||
pl_cards.append(self.cards[suit, value])
|
||||
self.played[player] = pl_cards[0]
|
||||
|
||||
def tarotGameInvalidCardsHandler(self, phase, played_cards, invalid_cards):
|
||||
"""Invalid cards have been played
|
||||
@param phase: phase of the game
|
||||
@param played_cards: all the cards played
|
||||
@param invalid_cards: cards which are invalid"""
|
||||
|
||||
if phase == "play":
|
||||
self.state = "play"
|
||||
elif phase == "ecart":
|
||||
self.state = "ecart"
|
||||
else:
|
||||
log.error ('INTERNAL ERROR: unmanaged game phase')
|
||||
|
||||
for suit, value in played_cards:
|
||||
self.hand.append(self.cards[suit, value])
|
||||
|
||||
self.hand.sort()
|
||||
self.__fakePlay()
|
||||
|
||||
106
frontends/src/quick_frontend/quick_games.py
Normal file
106
frontends/src/quick_frontend/quick_games.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# helper class for making a SAT frontend
|
||||
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from sat.core.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
from sat.core.i18n import _
|
||||
|
||||
from sat_frontends.tools import jid
|
||||
from sat_frontends.quick_frontend.constants import Const as C
|
||||
|
||||
import quick_chat
|
||||
|
||||
|
||||
class RoomGame(object):
|
||||
_game_name = None
|
||||
_signal_prefix = None
|
||||
_signal_suffixes = None
|
||||
|
||||
@classmethod
|
||||
def registerSignals(cls, host):
|
||||
|
||||
def make_handler(suffix, signal):
|
||||
def handler(*args):
|
||||
if suffix in ("Started", "Players"):
|
||||
return cls.startedHandler(host, suffix, *args)
|
||||
return cls.genericHandler(host, signal, *args)
|
||||
return handler
|
||||
|
||||
for suffix in cls._signal_suffixes:
|
||||
signal = cls._signal_prefix + suffix
|
||||
host.registerSignal(signal, handler=make_handler(suffix, signal), iface="plugin")
|
||||
|
||||
@classmethod
|
||||
def startedHandler(cls, host, suffix, *args):
|
||||
room_jid, args, profile = jid.JID(args[0]), args[1:-1], args[-1]
|
||||
referee, players, args = args[0], args[1], args[2:]
|
||||
chat_widget = host.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
|
||||
|
||||
# self.occupants_panel.updateSpecials(players, SYMBOLS[self._game_name.lower()]) # FIXME
|
||||
if suffix == "Players" or chat_widget.nick not in players:
|
||||
return # waiting for other players to join, or not playing
|
||||
if cls._game_name in chat_widget.games:
|
||||
return # game panel is already there
|
||||
real_class = host.widgets.getRealClass(cls)
|
||||
if real_class == cls:
|
||||
host.showDialog(_(u"A {game} activity between {players} has been started, but you couldn't take part because your client doesn't support it.").format(game=cls._game_name, players=', '.join(players)),
|
||||
_(u"{game} Game").format(game=cls._game_name))
|
||||
return
|
||||
panel = real_class(chat_widget, referee, players, *args)
|
||||
chat_widget.games[cls._game_name] = panel
|
||||
chat_widget.addGamePanel(panel)
|
||||
|
||||
@classmethod
|
||||
def genericHandler(cls, host, signal, *args):
|
||||
room_jid, args, profile = jid.JID(args[0]), args[1:-1], args[-1]
|
||||
chat_widget = host.widgets.getWidget(quick_chat.QuickChat, room_jid, profile)
|
||||
if chat_widget:
|
||||
try:
|
||||
game_panel = chat_widget.games[cls._game_name]
|
||||
except KeyError:
|
||||
log.error("TODO: better game synchronisation - received signal %s but no panel is found" % signal)
|
||||
return
|
||||
else:
|
||||
getattr(game_panel, "%sHandler" % signal)(*args)
|
||||
|
||||
|
||||
class Tarot(RoomGame):
|
||||
_game_name = "Tarot"
|
||||
_signal_prefix = "tarotGame"
|
||||
_signal_suffixes = ("Started", "Players", "New", "ChooseContrat",
|
||||
"ShowCards", "YourTurn", "Score", "CardsPlayed",
|
||||
"InvalidCards",
|
||||
)
|
||||
|
||||
|
||||
class Quiz(RoomGame):
|
||||
_game_name = "Quiz"
|
||||
_signal_prefix = "quizGame"
|
||||
_signal_suffixes = ("Started", "New", "Question", "PlayerBuzzed",
|
||||
"PlayerSays", "AnswerResult", "TimerExpired",
|
||||
"TimerRestarted",
|
||||
)
|
||||
|
||||
|
||||
class Radiocol(RoomGame):
|
||||
_game_name = "Radiocol"
|
||||
_signal_prefix = "radiocol"
|
||||
_signal_suffixes = ("Started", "Players", "SongRejected", "Preload",
|
||||
"Play", "NoUpload", "UploadOk")
|
||||
Loading…
Reference in a new issue