diff --git a/etc/toon.dc b/etc/toon.dc index 184ca91..ccaebef 100755 --- a/etc/toon.dc +++ b/etc/toon.dc @@ -2874,6 +2874,7 @@ dclass ToontownFriendsManager : DistributedObject { makeFriendsResponse(uint32, uint32, uint8, uint32) airecv; removeFriend(uint32) clsend; requestSecret(uint32); + requestSecretResponse(uint8, string, uint32) airecv; }; dclass TTSpeedchatRelay : SpeedchatRelay { diff --git a/otp/friends/FriendManagerAI.py b/otp/friends/FriendManagerAI.py index ab4a1e0..3321330 100644 --- a/otp/friends/FriendManagerAI.py +++ b/otp/friends/FriendManagerAI.py @@ -50,9 +50,11 @@ class FriendManagerAI(DistributedObjectGlobalAI): # will be sent in response to secret requests to the database, # via the AIR. self.accept("makeFriendsReply", self.makeFriendsReply) + self.accept("requestSecretReply", self.requestSecretReply) def delete(self): self.ignore("makeFriendsReply") + self.ignore("requestSecretReply") DistributedObjectGlobalAI.delete(self) ### Messages sent from inviter client to AI @@ -409,3 +411,18 @@ class FriendManagerAI(DistributedObjectGlobalAI): avatar.d_friendsNotify(invite.inviterId, 2) self.clearInvite(invite) + + def requestSecretReply(self, result, secret, requesterId): + self.down_requestSecretResponse(requesterId, result, secret) + + def down_requestSecretResponse(self, recipient, result, secret): + """requestSecret(self, int8 result, string secret) + + Sent by the AI to the client in response to requestSecret(). + result is one of: + + 0 - Too many secrets outstanding. Try again later. + 1 - Success. The new secret is supplied. + + """ + self.sendUpdateToAvatarId(recipient, 'requestSecretResponse', [result, secret]) \ No newline at end of file diff --git a/otp/friends/FriendSecret.py b/otp/friends/FriendSecret.py index 0b01bd2..3eaefc8 100644 --- a/otp/friends/FriendSecret.py +++ b/otp/friends/FriendSecret.py @@ -1,59 +1,67 @@ -from panda3d.core import * -from panda3d.otp import * -from direct.gui.DirectGui import * from direct.directnotify import DirectNotifyGlobal from direct.fsm import StateData -from otp.otpbase import OTPLocalizer +from direct.gui.DirectGui import * +from panda3d.core import * +from panda3d.otp import NametagGlobals + from otp.otpbase import OTPGlobals +from otp.otpbase import OTPLocalizer from otp.uberdog import RejectCode + globalFriendSecret = None AccountSecret = 0 AvatarSecret = 1 BothSecrets = 2 -def showFriendSecret(secretType = AvatarSecret): + +def showFriendSecret(secretType=AvatarSecret): + """ + Added optional parameter to offer the choice between account and avatar friend tokens. + """ + # A module function to open the global secret panel. global globalFriendSecret if not base.cr.isPaid(): + # bring up teaser panel chatMgr = base.localAvatar.chatMgr - chatMgr.fsm.request('trueFriendTeaserPanel') + chatMgr.fsm.request("trueFriendTeaserPanel") elif not base.cr.isParentPasswordSet(): + # non-COPPA gamecard user: prompt to leave game and set parent password chatMgr = base.localAvatar.chatMgr - if base.cr.productName in ['DisneyOnline-AP', - 'DisneyOnline-UK', - 'JP', - 'DE', - 'BR', - 'FR']: + if base.cr.productName in ['DisneyOnline-AP', 'DisneyOnline-UK', 'JP', 'DE', 'BR', 'FR']: chatMgr = base.localAvatar.chatMgr if not base.cr.isPaid(): - chatMgr.fsm.request('unpaidChatWarning') + chatMgr.fsm.request("unpaidChatWarning") else: chatMgr.paidNoParentPassword = 1 - chatMgr.fsm.request('unpaidChatWarning') + chatMgr.fsm.request("unpaidChatWarning") else: chatMgr.paidNoParentPassword = 1 - chatMgr.fsm.request('noSecretChatAtAll') + chatMgr.fsm.request("noSecretChatAtAll") # bring up dialog for true friends # open chat with true friends + elif not base.cr.allowSecretChat(): + # If we're paid but have not yet activated secrets, pop up the + # dialog to do this. chatMgr = base.localAvatar.chatMgr - if base.cr.productName in ['DisneyOnline-AP', - 'DisneyOnline-UK', - 'JP', - 'DE', - 'BR', - 'FR']: + if base.cr.productName in ['DisneyOnline-AP', 'DisneyOnline-UK', 'JP', 'DE', 'BR', 'FR']: chatMgr = base.localAvatar.chatMgr if not base.cr.isPaid(): - chatMgr.fsm.request('unpaidChatWarning') + chatMgr.fsm.request("unpaidChatWarning") else: chatMgr.paidNoParentPassword = 1 - chatMgr.fsm.request('unpaidChatWarning') + chatMgr.fsm.request("unpaidChatWarning") else: - chatMgr.fsm.request('noSecretChatAtAll') + # we no longer offer the ability to enable chat in game + # chatMgr.fsm.request("noSecretChatWarning") + chatMgr.fsm.request("noSecretChatAtAll") elif base.cr.needParentPasswordForSecretChat(): + # If we're paid but have the flag set to require the Parent Password to be + # entered to get or use a Secret Friends password, pop up the + # dialog to get the Parent Password. unloadFriendSecret() globalFriendSecret = FriendSecretNeedsParentLogin(secretType) globalFriendSecret.enter() else: + # Otherwise, actually open the secrets panel. openFriendSecret(secretType) @@ -63,53 +71,60 @@ def openFriendSecret(secretType): globalFriendSecret.unload() globalFriendSecret = FriendSecret(secretType) globalFriendSecret.enter() - return def hideFriendSecret(): + # A module function to close the global secret panel if it is open. if globalFriendSecret != None: globalFriendSecret.exit() - return def unloadFriendSecret(): + # A module function to completely unload the global secret panel. global globalFriendSecret if globalFriendSecret != None: globalFriendSecret.unload() globalFriendSecret = None - return class FriendSecretNeedsParentLogin(StateData.StateData): - notify = DirectNotifyGlobal.directNotify.newCategory('FriendSecretNeedsParentLogin') + notify = DirectNotifyGlobal.directNotify.newCategory("FriendSecretNeedsParentLogin") def __init__(self, secretType): - StateData.StateData.__init__(self, 'friend-secret-needs-parent-login-done') + StateData.StateData.__init__(self, "friend-secret-needs-parent-login-done") self.dialog = None self.secretType = secretType - return def enter(self): StateData.StateData.enter(self) - base.localAvatar.chatMgr.fsm.request('otherDialog') + base.localAvatar.chatMgr.fsm.request("otherDialog") + # Pop up a dialog indicating the parent account needs to login + # in order to get to the Secret Friends chat: if self.dialog == None: - guiButton = loader.loadModel('phase_3/models/gui/quit_button') + guiButton = loader.loadModel("phase_3/models/gui/quit_button") buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') - nameBalloon = loader.loadModel('phase_3/models/props/chatbox_input') - optionsButtonImage = (guiButton.find('**/QuitBtn_UP'), guiButton.find('**/QuitBtn_DN'), guiButton.find('**/QuitBtn_RLVR')) - okButtonImage = (buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr')) - cancelButtonImage = (buttons.find('**/CloseBtn_UP'), buttons.find('**/CloseBtn_DN'), buttons.find('**/CloseBtn_Rllvr')) + nameBalloon = loader.loadModel("phase_3/models/props/chatbox_input") + optionsButtonImage = ( + guiButton.find("**/QuitBtn_UP"), guiButton.find("**/QuitBtn_DN"), guiButton.find("**/QuitBtn_RLVR")) + okButtonImage = ( + buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr')) + cancelButtonImage = ( + buttons.find('**/CloseBtn_UP'), buttons.find('**/CloseBtn_DN'), buttons.find('**/CloseBtn_Rllvr')) + + # The Castillian (and all foreign) version relies on a seperate parent + # password system. Omit the the password entry field and cancel button. withParentAccount = False try: withParentAccount = base.cr.withParentAccount except: - self.notify.warning('withParentAccount not found in base.cr') + self.notify.warning("withParentAccount not found in base.cr") + pass if withParentAccount: okPos = (-0.22, 0.0, -0.5) textPos = (0, 0.25) okCommand = self.__handleOKWithParentAccount - elif base.cr.productName != 'Terra-DMC': + elif base.cr.productName != "Terra-DMC": okPos = (-0.22, 0.0, -0.5) textPos = (0, 0.25) okCommand = self.__oldHandleOK @@ -118,24 +133,48 @@ class FriendSecretNeedsParentLogin(StateData.StateData): okPos = (0, 0, -0.35) textPos = (0, 0.125) okCommand = self.__handleCancel - self.dialog = DirectFrame(parent=aspect2dp, pos=(0.0, 0.1, 0.2), relief=None, image=DGG.getDefaultDialogGeom(), image_color=OTPGlobals.GlobalDialogColor, image_scale=(1.4, 1.0, 1.25), image_pos=(0, 0, -0.1), text=OTPLocalizer.FriendSecretNeedsParentLoginWarning, text_wordwrap=21.5, text_scale=0.055, text_pos=textPos, textMayChange=1) - DirectButton(self.dialog, image=okButtonImage, relief=None, text=OTPLocalizer.FriendSecretNeedsPasswordWarningOK, text_scale=0.05, text_pos=(0.0, -0.1), textMayChange=0, pos=okPos, command=okCommand) - DirectLabel(parent=self.dialog, relief=None, pos=(0, 0, 0.35), text=OTPLocalizer.FriendSecretNeedsPasswordWarningTitle, textMayChange=0, text_scale=0.08) - if base.cr.productName != 'Terra-DMC': - self.usernameLabel = DirectLabel(parent=self.dialog, relief=None, pos=(-0.07, 0.0, -0.1), text=OTPLocalizer.ParentLogin, text_scale=0.06, text_align=TextNode.ARight, textMayChange=0) - self.usernameEntry = DirectEntry(parent=self.dialog, relief=None, image=nameBalloon, image1_color=(0.8, 0.8, 0.8, 1.0), scale=0.064, pos=(0.0, 0.0, -0.1), width=OTPGlobals.maxLoginWidth, numLines=1, focus=1, cursorKeys=1, obscured=1, command=self.__handleUsername) - self.passwordLabel = DirectLabel(parent=self.dialog, relief=None, pos=(-0.02, 0.0, -0.3), text=OTPLocalizer.ParentPassword, text_scale=0.06, text_align=TextNode.ARight, textMayChange=0) - self.passwordEntry = DirectEntry(parent=self.dialog, relief=None, image=nameBalloon, image1_color=(0.8, 0.8, 0.8, 1.0), scale=0.064, pos=(0.04, 0.0, -0.3), width=OTPGlobals.maxLoginWidth, numLines=1, focus=1, cursorKeys=1, obscured=1, command=okCommand) - DirectButton(self.dialog, image=cancelButtonImage, relief=None, text=OTPLocalizer.FriendSecretNeedsPasswordWarningCancel, text_scale=0.05, text_pos=(0.0, -0.1), textMayChange=1, pos=(0.2, 0.0, -0.5), command=self.__handleCancel) + + # make the common gui elements + self.dialog = DirectFrame(parent=aspect2dp, pos=(0.0, 0.1, 0.2), relief=None, + image=DGG.getDefaultDialogGeom(), image_color=OTPGlobals.GlobalDialogColor, + image_scale=(1.4, 1.0, 1.25), image_pos=(0, 0, -0.1), + text=OTPLocalizer.FriendSecretNeedsParentLoginWarning, text_wordwrap=21.5, text_scale=0.055, + text_pos=textPos, textMayChange=1) + DirectButton(self.dialog, image=okButtonImage, relief=None, + text=OTPLocalizer.FriendSecretNeedsPasswordWarningOK, text_scale=0.05, text_pos=(0.0, -0.1), + textMayChange=0, pos=okPos, command=okCommand) + DirectLabel(parent=self.dialog, relief=None, pos=(0, 0, 0.35), + text=OTPLocalizer.FriendSecretNeedsPasswordWarningTitle, textMayChange=0, text_scale=0.08) + + # if not foreign, make the domestic only password entry elements + if base.cr.productName != "Terra-DMC": + self.usernameLabel = DirectLabel(parent=self.dialog, relief=None, pos=(-0.07, 0.0, -0.1), + text=OTPLocalizer.ParentLogin, text_scale=0.06, text_align=TextNode.ARight, textMayChange=0) + self.usernameEntry = DirectEntry(parent=self.dialog, relief=None, image=nameBalloon, + image1_color=(0.8, 0.8, 0.8, 1.0), scale=0.064, pos=(0.0, 0.0, -0.1), + width=OTPGlobals.maxLoginWidth, numLines=1, focus=1, cursorKeys=1, obscured=1, + command=self.__handleUsername) + self.passwordLabel = DirectLabel(parent=self.dialog, relief=None, pos=(-0.02, 0.0, -0.3), + text=OTPLocalizer.ParentPassword, text_scale=0.06, text_align=TextNode.ARight, textMayChange=0) + self.passwordEntry = DirectEntry(parent=self.dialog, relief=None, image=nameBalloon, + image1_color=(0.8, 0.8, 0.8, 1.0), scale=0.064, pos=(0.04, 0.0, -0.3), + width=OTPGlobals.maxLoginWidth, numLines=1, focus=1, cursorKeys=1, obscured=1, command=okCommand) + DirectButton(self.dialog, image=cancelButtonImage, relief=None, + text=OTPLocalizer.FriendSecretNeedsPasswordWarningCancel, text_scale=0.05, text_pos=(0.0, -0.1), + textMayChange=1, pos=(0.2, 0.0, -0.5), command=self.__handleCancel) + + # hide the parent login as it's not valid yet if withParentAccount: self.usernameEntry.enterText('') self.usernameEntry['focus'] = 1 self.passwordEntry.enterText('') + else: self.usernameEntry.hide() self.usernameLabel.hide() self.passwordEntry['focus'] = 1 self.passwordEntry.enterText('') + guiButton.removeNode() buttons.removeNode() nameBalloon.removeNode() @@ -147,8 +186,8 @@ class FriendSecretNeedsParentLogin(StateData.StateData): elif self.passwordEntry: self.passwordEntry['focus'] = 1 self.passwordEntry.enterText('') + self.dialog.show() - return def exit(self): self.ignoreAll() @@ -156,11 +195,11 @@ class FriendSecretNeedsParentLogin(StateData.StateData): self.dialog.destroy() self.dialog = None if self.isEntered: - base.localAvatar.chatMgr.fsm.request('mainMenu') + base.localAvatar.chatMgr.fsm.request("mainMenu") StateData.StateData.exit(self) - return def __handleUsername(self, *args): + # activate password entry if self.passwordEntry: self.passwordEntry['focus'] = 1 self.passwordEntry.enterText('') @@ -168,17 +207,29 @@ class FriendSecretNeedsParentLogin(StateData.StateData): def __handleOKWithParentAccount(self, *args): username = self.usernameEntry.get() password = self.passwordEntry.get() + # we need to store these in base.cr for __enterSecret base.cr.parentUsername = username base.cr.parentPassword = password tt = base.cr.loginInterface - okflag, message = tt.authenticateParentUsernameAndPassword(localAvatar.DISLid, base.cr.password, username, password) + try: + DISLIdFromLogin = base.cr.DISLIdFromLogin + except: + DISLIdFromLogin = 0 + if DISLIdFromLogin and (DISLIdFromLogin != localAvatar.DISLid): + # we only expect to have 1 DISLId per player, we're screwed if this happens + self.notify.error( + "Mismatched DISLIds, fromLogin=%s, localAvatar.dislId=%s" % (DISLIdFromLogin, localAvatar.DISLid)) + okflag, message = tt.authenticateParentUsernameAndPassword(localAvatar.DISLid, base.cr.password, username, + password) if okflag: self.exit() openFriendSecret(self.secretType) elif message: - base.localAvatar.chatMgr.fsm.request('problemActivatingChat') - base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % message + # Error connecting. + base.localAvatar.chatMgr.fsm.request("problemActivatingChat") + base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % (message) else: + # Wrong password. self.dialog['text'] = OTPLocalizer.FriendSecretNeedsPasswordWarningWrongPassword self.passwordEntry['focus'] = 1 self.passwordEntry.enterText('') @@ -186,6 +237,7 @@ class FriendSecretNeedsParentLogin(StateData.StateData): def __oldHandleOK(self, *args): username = self.usernameEntry.get() password = self.passwordEntry.get() + # we need to store these in base.cr for __enterSecret base.cr.parentUsername = username base.cr.parentPassword = password tt = base.cr.loginInterface @@ -194,59 +246,83 @@ class FriendSecretNeedsParentLogin(StateData.StateData): self.exit() openFriendSecret(self.secretType) elif message: - base.localAvatar.chatMgr.fsm.request('problemActivatingChat') - base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % message + # Error connecting. + base.localAvatar.chatMgr.fsm.request("problemActivatingChat") + base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % (message) else: + # Wrong password. self.dialog['text'] = OTPLocalizer.FriendSecretNeedsPasswordWarningWrongPassword self.passwordEntry['focus'] = 1 self.passwordEntry.enterText('') def __handleOK(self, *args): + base.cr.parentUsername = self.usernameEntry.get() base.cr.parentPassword = self.passwordEntry.get() + # we don't have a request for this yet, so pretend we are submitting a limited secret base.cr.playerFriendsManager.sendRequestUseLimitedSecret('', base.cr.parentUsername, base.cr.parentPassword) self.accept(OTPGlobals.PlayerFriendRejectUseSecretEvent, self.__handleParentLogin) + # we won't get an answer yet, so spoof a return code + # self.__handleParentLogin(0) + # that doesn't work, just immediately close the panel self.exit() def __handleParentLogin(self, reason): + # don't know reason codes yet, spoof something if reason == 0: self.exit() openFriendSecret(self.secretType) elif reason == 1: + # Wrong username self.dialog['text'] = OTPLocalizer.FriendSecretNeedsPasswordWarningWrongUsername self.usernameEntry['focus'] = 1 self.usernameEntry.enterText('') elif reason == 2: + # Wrong password self.dialog['text'] = OTPLocalizer.FriendSecretNeedsPasswordWarningWrongPassword self.passwordEntry['focus'] = 1 self.passwordEntry.enterText('') else: - base.localAvatar.chatMgr.fsm.request('problemActivatingChat') - base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % message + # Error connecting (fall through case) + base.localAvatar.chatMgr.fsm.request("problemActivatingChat") + base.localAvatar.chatMgr.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % (message) def __handleCancel(self): self.exit() class FriendSecret(DirectFrame, StateData.StateData): - notify = DirectNotifyGlobal.directNotify.newCategory('FriendSecret') + """ + This is a panel that allows the user to manage 'secrets' that is, + code words that identify people who are actually friends in real + life (and thus are entitled to use full chat with each other). + + This panel is the place the user will come to either get a new + 'secret' to give to someone else, or to enter the 'secret' they + got from someone. + """ + notify = DirectNotifyGlobal.directNotify.newCategory("FriendSecret") def __init__(self, secretType): - DirectFrame.__init__(self, parent=aspect2dp, pos=(0, 0, 0.3), relief=None, image=DGG.getDefaultDialogGeom(), image_scale=(1.6, 1, 1.4), image_pos=(0, 0, -0.05), image_color=OTPGlobals.GlobalDialogColor, borderWidth=(0.01, 0.01)) - StateData.StateData.__init__(self, 'friend-secret-done') + DirectFrame.__init__(self, parent=aspect2dp, pos=(0, 0, 0.30), relief=None, image=DGG.getDefaultDialogGeom(), + image_scale=(1.6, 1, 1.4), image_pos=(0, 0, -0.05), + image_color=OTPGlobals.GlobalDialogColor, borderWidth=(0.01, 0.01), ) + StateData.StateData.__init__(self, "friend-secret-done") self.initialiseoptions(FriendSecret) self.prefix = OTPGlobals.getDefaultProductPrefix() + # what types of secret friends will we offer self.secretType = secretType - self.notify.debug('### secretType = %s' % self.secretType) + self.notify.debug("### secretType = %s" % self.secretType) + # what type of secret friend has the user chosen self.requestedSecretType = secretType - self.notify.debug('### requestedSecretType = %s' % self.requestedSecretType) - return + self.notify.debug("### requestedSecretType = %s" % self.requestedSecretType) def unload(self): if self.isLoaded == 0: return None self.isLoaded = 0 self.exit() + del self.introText del self.getSecret del self.enterSecretText @@ -259,34 +335,48 @@ class FriendSecret(DirectFrame, StateData.StateData): del self.accountButton DirectFrame.destroy(self) self.ignore('clientCleanup') - return None def load(self): if self.isLoaded == 1: return None self.isLoaded = 1 - self.introText = DirectLabel(parent=self, relief=None, pos=(0, 0, 0.4), scale=0.05, text=OTPLocalizer.FriendSecretIntro, text_fg=(0, 0, 0, 1), text_wordwrap=30) + + self.introText = DirectLabel(parent=self, relief=None, pos=(0, 0, 0.4), scale=0.05, + text=OTPLocalizer.FriendSecretIntro, text_fg=(0, 0, 0, 1), text_wordwrap=30, ) self.introText.hide() - guiButton = loader.loadModel('phase_3/models/gui/quit_button') - self.getSecret = DirectButton(parent=self, relief=None, pos=(0, 0, -0.11), image=(guiButton.find('**/QuitBtn_UP'), guiButton.find('**/QuitBtn_DN'), guiButton.find('**/QuitBtn_RLVR')), image_scale=OTPLocalizer.FSgetSecret, text=OTPLocalizer.FriendSecretGetSecret, text_scale=OTPLocalizer.FSgetSecretButton, text_pos=(0, -0.02), command=self.__determineSecret) + + guiButton = loader.loadModel("phase_3/models/gui/quit_button") + + self.getSecret = DirectButton(parent=self, relief=None, pos=(0, 0, -0.11), image=( + guiButton.find("**/QuitBtn_UP"), guiButton.find("**/QuitBtn_DN"), guiButton.find("**/QuitBtn_RLVR"),), + image_scale=OTPLocalizer.FSgetSecret, text=OTPLocalizer.FriendSecretGetSecret, + text_scale=OTPLocalizer.FSgetSecretButton, text_pos=(0, -0.02), command=self.__determineSecret, ) self.getSecret.hide() - self.enterSecretText = DirectLabel(parent=self, relief=None, pos=OTPLocalizer.FSenterSecretTextPos, scale=0.05, text=OTPLocalizer.FriendSecretEnterSecret, text_fg=(0, 0, 0, 1), text_wordwrap=30) + + self.enterSecretText = DirectLabel(parent=self, relief=None, pos=OTPLocalizer.FSenterSecretTextPos, scale=0.05, + text=OTPLocalizer.FriendSecretEnterSecret, text_fg=(0, 0, 0, 1), text_wordwrap=30, ) self.enterSecretText.hide() - self.enterSecret = DirectEntry(parent=self, relief=DGG.SUNKEN, scale=0.06, pos=(-0.6, 0, -0.38), frameColor=(0.8, 0.8, 0.5, 1), borderWidth=(0.1, 0.1), numLines=1, width=20, frameSize=(-0.4, - 20.4, - -0.4, - 1.1), command=self.__enterSecret) + + self.enterSecret = DirectEntry(parent=self, relief=DGG.SUNKEN, scale=0.06, pos=(-0.60, 0, -0.38), + frameColor=(0.8, 0.8, 0.5, 1), borderWidth=(0.1, 0.1), numLines=1, width=20, + frameSize=(-0.4, 20.4, -0.4, 1.1), command=self.__enterSecret) + + # This fixes a little problem with the initial frame size. self.enterSecret.resetFrameSize() self.enterSecret.hide() - self.ok1 = DirectButton(parent=self, relief=None, image=(guiButton.find('**/QuitBtn_UP'), guiButton.find('**/QuitBtn_DN'), guiButton.find('**/QuitBtn_RLVR')), image_scale=OTPLocalizer.FSok1, text=OTPLocalizer.FriendSecretEnter, text_scale=0.06, text_pos=(0, -0.02), pos=(0, 0, -0.5), command=self.__ok1) + + self.ok1 = DirectButton(parent=self, relief=None, image=( + guiButton.find("**/QuitBtn_UP"), guiButton.find("**/QuitBtn_DN"), guiButton.find("**/QuitBtn_RLVR"),), + image_scale=OTPLocalizer.FSok1, text=OTPLocalizer.FriendSecretEnter, text_scale=0.06, text_pos=(0, -0.02), + pos=(0, 0, -0.5), command=self.__ok1, ) self.ok1.hide() - if base.cr.productName in ['JP', - 'DE', - 'BR', - 'FR']: + if base.cr.productName in ['JP', 'DE', 'BR', 'FR']: + # There should be no 'change secret friends options' button in + # the UK version. + # the only thing we do to self.changeOptions is show and hide it; + # avoid adding a bunch of 'if' statements class ShowHide: - def show(self): pass @@ -294,16 +384,32 @@ class FriendSecret(DirectFrame, StateData.StateData): pass self.changeOptions = ShowHide() - self.ok2 = DirectButton(parent=self, relief=None, image=(guiButton.find('**/QuitBtn_UP'), guiButton.find('**/QuitBtn_DN'), guiButton.find('**/QuitBtn_RLVR')), image_scale=OTPLocalizer.FSok2, text=OTPLocalizer.FriendSecretOK, text_scale=0.06, text_pos=(0, -0.02), pos=(0, 0, -0.57), command=self.__ok2) + + self.ok2 = DirectButton(parent=self, relief=None, image=( + guiButton.find("**/QuitBtn_UP"), guiButton.find("**/QuitBtn_DN"), guiButton.find("**/QuitBtn_RLVR"),), + image_scale=OTPLocalizer.FSok2, text=OTPLocalizer.FriendSecretOK, text_scale=0.06, text_pos=(0, -0.02), + pos=(0, 0, -0.57), command=self.__ok2, ) self.ok2.hide() - self.cancel = DirectButton(parent=self, relief=None, text=OTPLocalizer.FriendSecretCancel, image=(guiButton.find('**/QuitBtn_UP'), guiButton.find('**/QuitBtn_DN'), guiButton.find('**/QuitBtn_RLVR')), image_scale=OTPLocalizer.FScancel, text_scale=0.06, text_pos=(0, -0.02), pos=(0, 0, -0.57), command=self.__cancel) + + self.cancel = DirectButton(parent=self, relief=None, text=OTPLocalizer.FriendSecretCancel, image=( + guiButton.find("**/QuitBtn_UP"), guiButton.find("**/QuitBtn_DN"), guiButton.find("**/QuitBtn_RLVR"),), + image_scale=OTPLocalizer.FScancel, text_scale=0.06, text_pos=(0, -0.02), pos=(0, 0, -0.57), + command=self.__cancel, ) self.cancel.hide() - self.nextText = DirectLabel(parent=self, relief=None, pos=(0, 0, 0.3), scale=0.06, text='', text_scale=OTPLocalizer.FSnextText, text_fg=(0, 0, 0, 1), text_wordwrap=25.5) + + self.nextText = DirectLabel(parent=self, relief=None, pos=(0, 0, 0.30), scale=0.06, text="", + text_scale=OTPLocalizer.FSnextText, text_fg=(0, 0, 0, 1), text_wordwrap=25.5, ) self.nextText.hide() - self.secretText = DirectLabel(parent=self, relief=None, pos=(0, 0, -0.42), scale=0.1, text='', text_fg=(0, 0, 0, 1), text_wordwrap=30) + + self.secretText = DirectLabel(parent=self, relief=None, pos=(0, 0, -0.42), scale=0.1, text="", + text_fg=(0, 0, 0, 1), text_wordwrap=30, ) self.secretText.hide() + guiButton.removeNode() + + # moved into it's own method to make overloading easier self.makeFriendTypeButtons() + self.accept('clientCleanup', self.__handleCleanup) self.accept('walkDone', self.__handleStop) @@ -314,59 +420,100 @@ class FriendSecret(DirectFrame, StateData.StateData): self.unload() def makeFriendTypeButtons(self): + # NOTE: this uses generic text, if you want game-specific text, please inherit and override + # (see $TOONTOWN/src/friends/ToontownFriendSecret.py for an example) buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') - self.avatarButton = DirectButton(self, image=(buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr')), relief=None, text=OTPLocalizer.FriendSecretDetermineSecretAvatar, text_scale=0.07, text_pos=(0.0, -0.1), pos=(-0.35, 0.0, -0.05), command=self.__handleAvatar) - avatarText = DirectLabel(parent=self, relief=None, pos=Vec3(0.35, 0, -0.3), text=OTPLocalizer.FriendSecretDetermineSecretAvatarRollover, text_fg=(0, 0, 0, 1), text_pos=(0, 0), text_scale=0.055, text_align=TextNode.ACenter) + + # make an avatar friend + self.avatarButton = DirectButton(self, image=( + buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief=None, text=OTPLocalizer.FriendSecretDetermineSecretAvatar, text_scale=0.07, text_pos=(0.0, -0.1), + pos=(-0.35, 0.0, -0.05), command=self.__handleAvatar) + # make a label to show avatar friend desc on rollover + avatarText = DirectLabel(parent=self, relief=None, pos=Vec3(0.35, 0, -0.3), + text=OTPLocalizer.FriendSecretDetermineSecretAvatarRollover, text_fg=(0, 0, 0, 1), text_pos=(0, 0), + text_scale=0.055, text_align=TextNode.ACenter, ) avatarText.reparentTo(self.avatarButton.stateNodePath[2]) self.avatarButton.hide() - self.accountButton = DirectButton(self, image=(buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr')), relief=None, text=OTPLocalizer.FriendSecretDetermineSecretAccount, text_scale=0.07, text_pos=(0.0, -0.1), pos=(0.35, 0.0, -0.05), command=self.__handleAccount) - accountText = DirectLabel(parent=self, relief=None, pos=Vec3(-0.35, 0, -0.3), text=OTPLocalizer.FriendSecretDetermineSecretAccountRollover, text_fg=(0, 0, 0, 1), text_pos=(0, 0), text_scale=0.055, text_align=TextNode.ACenter) + + # make an account friend + self.accountButton = DirectButton(self, image=( + buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief=None, text=OTPLocalizer.FriendSecretDetermineSecretAccount, text_scale=0.07, text_pos=(0.0, -0.1), + pos=(0.35, 0.0, -0.05), command=self.__handleAccount) + # make a label to show feature desc on rollover + accountText = DirectLabel(parent=self, relief=None, pos=Vec3(-0.35, 0, -0.3), + text=OTPLocalizer.FriendSecretDetermineSecretAccountRollover, text_fg=(0, 0, 0, 1), text_pos=(0, 0), + text_scale=0.055, text_align=TextNode.ACenter, ) accountText.reparentTo(self.accountButton.stateNodePath[2]) self.accountButton.hide() + buttons.removeNode() - return def enter(self): if self.isEntered == 1: return self.isEntered = 1 + # Use isLoaded to avoid redundant loading if self.isLoaded == 0: self.load() + self.show() + + # Set us up on the first page. self.introText.show() self.getSecret.show() self.enterSecretText.show() self.enterSecret.show() self.ok1.show() + self.ok2.hide() self.cancel.hide() self.nextText.hide() self.secretText.hide() - base.localAvatar.chatMgr.fsm.request('otherDialog') + + # While the entry's on the screen, we have to turn off the + # background focus on the normal chat entry. Otherwise, + # keypresses would start chatting! + base.localAvatar.chatMgr.fsm.request("otherDialog") + + # And now we can set the focus on our entry. self.enterSecret['focus'] = 1 + + # The secret panel uses up a lot of space onscreen; in + # particular, it will hide most of the chat balloons. Force + # these to go to the margins. NametagGlobals.setOnscreenChatForced(1) def exit(self): if self.isEntered == 0: return self.isEntered = 0 + + # Restore the normal chat behavior. NametagGlobals.setOnscreenChatForced(0) + self.__cleanupFirstPage() self.ignoreAll() self.accept('clientCleanup', self.unload) self.hide() def __determineSecret(self): + # If we support both types of secrets... if self.secretType == BothSecrets: + # ask the player what type they want self.__cleanupFirstPage() self.ok1.hide() + # NOTE: this uses generic text, if you want game-specific text, please inherit and override + # (see $TOONTOWN/src/friends/ToontownFriendSecret.py for an example) self.nextText['text'] = OTPLocalizer.FriendSecretDetermineSecret - self.nextText.setPos(0, 0, 0.3) + self.nextText.setPos(0, 0, 0.30) self.nextText.show() self.avatarButton.show() self.accountButton.show() self.cancel.show() else: + # or just get the secret self.__getSecret() def __handleAvatar(self): @@ -383,39 +530,36 @@ class FriendSecret(DirectFrame, StateData.StateData): def __getSecret(self): self.__cleanupFirstPage() self.nextText['text'] = OTPLocalizer.FriendSecretGettingSecret - self.nextText.setPos(0, 0, 0.3) + self.nextText.setPos(0, 0, 0.30) self.nextText.show() self.avatarButton.hide() self.accountButton.hide() self.ok1.hide() self.cancel.show() - if self.requestedSecretType == AvatarSecret: - if not base.cr.friendManager: - self.notify.warning('No FriendManager available.') - self.exit() - return - base.cr.friendManager.up_requestSecret() - self.accept('requestSecretResponse', self.__gotAvatarSecret) - else: - if base.cr.needParentPasswordForSecretChat(): - self.notify.info('### requestLimitedSecret') - base.cr.playerFriendsManager.sendRequestLimitedSecret(base.cr.parentUsername, base.cr.parentPassword) - else: - base.cr.playerFriendsManager.sendRequestUnlimitedSecret() - self.notify.info('### requestUnlimitedSecret') - self.accept(OTPGlobals.PlayerFriendNewSecretEvent, self.__gotAccountSecret) - self.accept(OTPGlobals.PlayerFriendRejectNewSecretEvent, self.__rejectAccountSecret) + # If we don't have a FriendManager, something's badly wrong. + # Most likely we're running in a development environment + # without an AI client. + if not base.cr.friendManager: + self.notify.warning("No FriendManager available.") + self.exit() + return + base.cr.friendManager.up_requestSecret() + self.accept('requestSecretResponse', + self.__gotAvatarSecret) # else: # if base.cr.needParentPasswordForSecretChat(): # self.notify.info("### requestLimitedSecret") # base.cr.playerFriendsManager.sendRequestLimitedSecret(base.cr.parentUsername, base.cr.parentPassword) # else: # base.cr.playerFriendsManager.sendRequestUnlimitedSecret() # self.notify.info("### requestUnlimitedSecret") # self.accept(OTPGlobals.PlayerFriendNewSecretEvent, self.__gotAccountSecret) # self.accept(OTPGlobals.PlayerFriendRejectNewSecretEvent, self.__rejectAccountSecret) def __gotAvatarSecret(self, result, secret): self.ignore('requestSecretResponse') + if result == 1: self.nextText['text'] = OTPLocalizer.FriendSecretGotSecret self.nextText.setPos(*OTPLocalizer.FSgotSecretPos) - if self.prefix: - self.secretText['text'] = self.prefix + ' ' + secret - else: - self.secretText['text'] = secret + # always append the prefix + self.secretText['text'] = self.prefix + ' ' + secret + elif result == 2: + self.nextText['text'] = OTPLocalizer.FriendSecretTooManyFriends + else: + # Oops, too many secrets. self.nextText['text'] = OTPLocalizer.FriendSecretTooMany self.nextText.show() self.secretText.show() @@ -423,133 +567,148 @@ class FriendSecret(DirectFrame, StateData.StateData): self.ok1.hide() self.ok2.show() - def __gotAccountSecret(self, secret): - self.ignore(OTPGlobals.PlayerFriendNewSecretEvent) - self.ignore(OTPGlobals.PlayerFriendRejectNewSecretEvent) - self.nextText['text'] = OTPLocalizer.FriendSecretGotSecret - self.nextText.setPos(0, 0, 0.47) - self.secretText['text'] = secret - self.nextText.show() - self.secretText.show() - self.cancel.hide() - self.ok1.hide() - self.ok2.show() - - def __rejectAccountSecret(self, reason): - print('## rejectAccountSecret: reason = ', reason) - self.ignore(OTPGlobals.PlayerFriendNewSecretEvent) - self.ignore(OTPGlobals.PlayerFriendRejectNewSecretEvent) - self.nextText['text'] = OTPLocalizer.FriendSecretTooMany - self.nextText.show() - self.secretText.show() - self.cancel.hide() - self.ok1.hide() - self.ok2.show() - def __enterSecret(self, secret): - self.enterSecret.set('') + # Empty the entry for next time. + self.enterSecret.set("") + secret = secret.strip() if not secret: + # If the secret is empty, it just means to close down + # the dialog. self.exit() return + + # If we don't have a FriendManager, something's badly wrong. + # Most likely we're running in a development environment + # without an AI client. if not base.cr.friendManager: - self.notify.warning('No FriendManager available.') + self.notify.warning("No FriendManager available.") self.exit() return + self.__cleanupFirstPage() + + # The client can now prepend a product prefix to all secrets - strip this off if present if self.prefix: if secret[0:2] == self.prefix: secret = secret[3:] - self.notify.info('### use TT secret') + self.notify.info("### use TT secret") self.accept('submitSecretResponse', self.__enteredSecret) base.cr.friendManager.up_submitSecret(secret) - else: - self.accept(OTPGlobals.PlayerFriendUpdateEvent, self.__useAccountSecret) - self.accept(OTPGlobals.PlayerFriendRejectUseSecretEvent, self.__rejectUseAccountSecret) - if base.cr.needParentPasswordForSecretChat(): - self.notify.info('### useLimitedSecret') - base.cr.playerFriendsManager.sendRequestUseLimitedSecret(secret, base.cr.parentUsername, base.cr.parentPassword) - else: - self.notify.info('### useUnlimitedSecret') - base.cr.playerFriendsManager.sendRequestUseUnlimitedSecret(secret) + self.nextText['text'] = OTPLocalizer.FriendSecretTryingSecret - self.nextText.setPos(0, 0, 0.3) + self.nextText.setPos(0, 0, 0.30) self.nextText.show() self.ok1.hide() self.cancel.show() def __enteredSecret(self, result, avId): self.ignore('submitSecretResponse') + if result == 1: + # We made it! handle = base.cr.identifyAvatar(avId) if handle != None: - self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretSuccess % handle.getName() + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretSuccess % (handle.getName()) else: + # Shoot. We just made friends with someone, but we + # don't know who it is yet. We'll have to ask the + # server who this is, and wait for the response before + # we can continue. self.accept('friendsMapComplete', self.__nowFriends, [avId]) ready = base.cr.fillUpFriendsMap() if ready: self.__nowFriends(avId) return + elif result == 0: + # Unknown secret. self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretUnknown + elif result == 2: + # Friends list full. handle = base.cr.identifyAvatar(avId) if handle != None: - self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretFull % handle.getName() + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretFull % (handle.getName()) else: self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretFullNoName + elif result == 3: + # Self match. self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretSelf + elif result == 4: - self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretWrongProduct % self.prefix + # This doesn't look like our secret friend code - wrong prefix + self.nextText['text'] = OTPLocalizer.FriendSecretEnteredSecretWrongProduct % (self.prefix) + self.nextText.show() self.cancel.hide() self.ok1.hide() self.ok2.show() - return def __useAccountSecret(self, avId, friendInfo): self.ignore(OTPGlobals.PlayerFriendUpdateEvent) self.ignore(OTPGlobals.PlayerFriendRejectUseSecretEvent) + + # todo - pass an avId? self.__enteredSecret(1, 0) def __rejectUseAccountSecret(self, reason): - print('## rejectUseAccountSecret: reason = ', reason) + print("## rejectUseAccountSecret: reason = ", reason) self.ignore(OTPGlobals.PlayerFriendUpdateEvent) self.ignore(OTPGlobals.PlayerFriendRejectUseSecretEvent) - if reason == RejectCode.RejectCode.FRIENDS_LIST_FULL: + + if (reason == RejectCode.RejectCode.FRIENDS_LIST_FULL): self.__enteredSecret(2, 0) - elif reason == RejectCode.RejectCode.ALREADY_FRIENDS_WITH_SELF: + elif (reason == RejectCode.RejectCode.ALREADY_FRIENDS_WITH_SELF): self.__enteredSecret(3, 0) else: + # todo: this shouldn't be the fall thru self.__enteredSecret(0, 0) def __nowFriends(self, avId): + # This is called only from enteredSecret(), after the friend + # transaction has completed. This will be called only if the + # client didn't know the identity of the friend at the time, + # but it should know now. + self.ignore('friendsMapComplete') + handle = base.cr.identifyAvatar(avId) if handle != None: - self.nextText['text'] = OTPLocalizer.FriendSecretNowFriends % handle.getName() + self.nextText['text'] = OTPLocalizer.FriendSecretNowFriends % (handle.getName()) else: + # This really shouldn't be possible. self.nextText['text'] = OTPLocalizer.FriendSecretNowFriendsNoName + self.nextText.show() self.cancel.hide() self.ok1.hide() self.ok2.show() - return def __ok1(self): + # Clicking "ok" from the front screen is the same thing as + # pressing Enter in the entry. secret = self.enterSecret.get() self.__enterSecret(secret) def __ok2(self): + # Clicking "ok" from a finished screen just makes the thing go + # away. self.exit() def __cancel(self): + # Clicking "cancel" makes the panel close too. self.exit() def __cleanupFirstPage(self): + # Removes all the widgets etc. that were created for the + # welcome page, except the introText. + self.introText.hide() self.getSecret.hide() self.enterSecretText.hide() self.enterSecret.hide() - base.localAvatar.chatMgr.fsm.request('mainMenu') + + # Restore the background focus on the chat entry. + base.localAvatar.chatMgr.fsm.request("mainMenu") diff --git a/toontown/friends/ToontownFriendSecret.py b/toontown/friends/ToontownFriendSecret.py index 491a7ce..f7dc1a8 100644 --- a/toontown/friends/ToontownFriendSecret.py +++ b/toontown/friends/ToontownFriendSecret.py @@ -3,9 +3,13 @@ from direct.gui.DirectGui import * from direct.directnotify import DirectNotifyGlobal from otp.otpbase import OTPLocalizer from toontown.toonbase import TTLocalizer + +# This file overrides key methods in FriendSecret to add Toontown specific text to the OTP buttons + from otp.friends.FriendSecret import AccountSecret from otp.friends.FriendSecret import AvatarSecret from otp.friends.FriendSecret import BothSecrets + from otp.friends import FriendSecret from otp.friends.FriendSecret import globalFriendSecret from otp.friends.FriendSecret import showFriendSecret @@ -18,41 +22,94 @@ def openFriendSecret(secretType): globalFriendSecret.unload() globalFriendSecret = ToontownFriendSecret(secretType) globalFriendSecret.enter() - return - +# there is no way to overload module level methods, so hammer it in FriendSecret.openFriendSecret = openFriendSecret class ToontownFriendSecret(FriendSecret.FriendSecret): - notify = DirectNotifyGlobal.directNotify.newCategory('ToontownFriendSecret') + + notify = DirectNotifyGlobal.directNotify.newCategory("ToontownFriendSecret") def __init__(self, secretType): + #secretType is specefied in toon/ToonAvatarPanel.py-> __handleSecrets(self): FriendSecret.FriendSecret.__init__(self, secretType) self.initialiseoptions(ToontownFriendSecret) def makeFriendTypeButtons(self): + assert self.notify.debugCall() + buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui') - self.avatarButton = DirectButton(parent=self, image=(buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr')), relief=None, text=TTLocalizer.FriendInviterToon, text_scale=0.07, text_pos=(0.0, -0.1), pos=(-0.35, 0.0, -0.05), command=self._FriendSecret__handleAvatar) - avatarText = DirectLabel(parent=self, relief=None, pos=Vec3(0.35, 0, -0.3), text=TTLocalizer.FriendInviterToonFriendInfo, text_fg=(0, 0, 0, 1), text_pos=(0, 0), text_scale=0.055, text_align=TextNode.ACenter) + + # make an avatar friend + self.avatarButton = DirectButton( + parent = self, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = TTLocalizer.FriendInviterToon, + text_scale = 0.07, + text_pos = (0.0, -0.1), + pos = (-0.35, 0.0, -0.05), + command = self._FriendSecret__handleAvatar + ) + # make a label to show avatar friend desc on rollover + avatarText = DirectLabel( + parent = self, + relief = None, + pos = Vec3(0.35, 0, -0.3), + text = TTLocalizer.FriendInviterToonFriendInfo, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0), + text_scale = 0.055, + text_align = TextNode.ACenter, + ) avatarText.reparentTo(self.avatarButton.stateNodePath[2]) self.avatarButton.hide() - self.accountButton = DirectButton(parent=self, image=(buttons.find('**/ChtBx_OKBtn_UP'), buttons.find('**/ChtBx_OKBtn_DN'), buttons.find('**/ChtBx_OKBtn_Rllvr')), relief=None, text=TTLocalizer.FriendInviterPlayer, text_scale=0.07, text_pos=(0.0, -0.1), pos=(0.35, 0.0, -0.05), command=self._FriendSecret__handleAccount) - accountText = DirectLabel(parent=self, relief=None, pos=Vec3(-0.35, 0, -0.3), text=TTLocalizer.FriendInviterPlayerFriendInfo, text_fg=(0, 0, 0, 1), text_pos=(0, 0), text_scale=0.055, text_align=TextNode.ACenter) + + # make an account friend + self.accountButton = DirectButton( + parent = self, + image = (buttons.find('**/ChtBx_OKBtn_UP'), + buttons.find('**/ChtBx_OKBtn_DN'), + buttons.find('**/ChtBx_OKBtn_Rllvr')), + relief = None, + text = TTLocalizer.FriendInviterPlayer, + text_scale = 0.07, + text_pos = (0.0, -0.1), + pos = (0.35, 0.0, -0.05), + command = self._FriendSecret__handleAccount + ) + # make a label to show feature desc on rollover + accountText = DirectLabel( + parent = self, + relief = None, + pos = Vec3(-0.35, 0, -0.3), + text = TTLocalizer.FriendInviterPlayerFriendInfo, + text_fg = (0, 0, 0, 1), + text_pos = (0, 0), + text_scale = 0.055, + text_align = TextNode.ACenter, + ) accountText.reparentTo(self.accountButton.stateNodePath[2]) self.accountButton.hide() + buttons.removeNode() - return def __determineSecret(self): + assert self.notify.debugCall() + # If we support both types of secrets... if self.secretType == BothSecrets: + # ask the player what type they want self._FriendSecret__cleanupFirstPage() self.ok1.hide() self.changeOptions.hide() self.nextText['text'] = TTLocalizer.FriendInviterBegin - self.nextText.setPos(0, 0, 0.3) + self.nextText.setPos(0, 0, 0.30) self.nextText.show() self.avatarButton.show() self.accountButton.show() self.cancel.show() else: + # or just get the secret self._FriendSecret__getSecret() diff --git a/toontown/friends/ToontownFriendsManagerAI.py b/toontown/friends/ToontownFriendsManagerAI.py index a068cc7..7a73003 100644 --- a/toontown/friends/ToontownFriendsManagerAI.py +++ b/toontown/friends/ToontownFriendsManagerAI.py @@ -22,5 +22,8 @@ class ToontownFriendsManagerAI(DistributedObjectGlobalAI): messenger.send("makeFriendsReply", [result, context]) + def requestSecretResponse(self, result, secret, avId): + messenger.send("requestSecretReply", [result, secret, avId]) + def sendRequestSecret(self, requesterId): self.sendUpdate('requestSecret', [requesterId]) diff --git a/toontown/friends/ToontownFriendsManagerUD.py b/toontown/friends/ToontownFriendsManagerUD.py index 91830eb..42f6402 100644 --- a/toontown/friends/ToontownFriendsManagerUD.py +++ b/toontown/friends/ToontownFriendsManagerUD.py @@ -3,26 +3,52 @@ from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobal from direct.distributed.PyDatagram import * from otp.otpbase import OTPGlobals - - +import random +import string +import json class FriendsOperation: + """ + Base class for all friend-related operations. + """ def __init__(self, friendsManager, sender): + """ + Initialize the FriendsOperation. + + :param friendsManager: The friends manager instance. + :param sender: The sender's ID. + """ self.friendsManager = friendsManager self.sender = sender def _handleDone(self): - # TODO - pass + """ + Handle the completion of the operation. + """ + self.friendsManager.operations.remove(self) def _handleError(self, error): - # TODO - pass + """ + Handle an error during the operation. + + :param error: The error message. + """ + self.friendsManager.notify.warning(error) + self.friendsManager.operations.remove(self) class GetFriendsListOperation(FriendsOperation): + """ + Operation to retrieve the friends list of a sender. + """ def __init__(self, friendsManager, sender): + """ + Initialize the GetFriendsListOperation. + + :param friendsManager: The friends manager instance. + :param sender: The sender's ID. + """ FriendsOperation.__init__(self, friendsManager, sender) self.friendsList = None self.tempFriendsList = None @@ -30,6 +56,9 @@ class GetFriendsListOperation(FriendsOperation): self.currentFriendIdx = None def start(self): + """ + Start the operation to retrieve the friends list. + """ self.friendsList = [] self.tempFriendsList = [] self.onlineFriends = [] @@ -38,6 +67,12 @@ class GetFriendsListOperation(FriendsOperation): self.__handleSenderRetrieved) def __handleSenderRetrieved(self, dclass, fields): + """ + Handle the retrieval of the sender's data. + + :param dclass: The data class of the sender. + :param fields: The fields of the sender. + """ if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']: self._handleError('Retrieved sender is not a DistributedToonUD!') return @@ -51,6 +86,12 @@ class GetFriendsListOperation(FriendsOperation): self.__handleFriendRetrieved) def __handleFriendRetrieved(self, dclass, fields): + """ + Handle the retrieval of a friend's data. + + :param dclass: The data class of the friend. + :param fields: The fields of the friend. + """ if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']: self._handleError('Retrieved friend is not a DistributedToonUD!') return @@ -67,11 +108,20 @@ class GetFriendsListOperation(FriendsOperation): self.__handleFriendRetrieved) def __checkFriendsOnline(self): + """ + Check which friends are currently online. + """ self.currentFriendIdx = 0 for friendDetails in self.friendsList: self.friendsManager.air.getActivated(friendDetails[0], self.__gotActivatedResp) def __gotActivatedResp(self, avId, activated): + """ + Handle the response of checking if a friend is activated. + + :param avId: The avatar ID. + :param activated: Whether the friend is activated. + """ self.currentFriendIdx += 1 if activated: self.onlineFriends.append(avId) @@ -80,34 +130,67 @@ class GetFriendsListOperation(FriendsOperation): self._handleDone() def __sendFriendsList(self, success): + """ + Send the friends list to the sender. + + :param success: Whether the operation was successful. + """ self.friendsManager.sendUpdateToAvatarId(self.sender, 'getFriendsListResponse', [success, self.friendsList if success else []]) for friendId in self.onlineFriends: self.friendsManager.sendFriendOnline(self.sender, friendId, 0, 1) def _handleDone(self): + """ + Handle the completion of the operation. + """ self.__sendFriendsList(True) FriendsOperation._handleDone(self) def _handleError(self, error): + """ + Handle an error during the operation. + + :param error: The error message. + """ self.__sendFriendsList(False) FriendsOperation._handleError(self, error) class GetAvatarDetailsOperation(FriendsOperation): + """ + Operation to retrieve the details of an avatar. + """ def __init__(self, friendsManager, sender): + """ + Initialize the GetAvatarDetailsOperation. + + :param friendsManager: The friends manager instance. + :param sender: The sender's ID. + """ FriendsOperation.__init__(self, friendsManager, sender) self.avId = None self.dclass = None self.fields = None def start(self, avId): + """ + Start the operation to retrieve the avatar details. + + :param avId: The avatar ID. + """ self.avId = avId self.fields = {} self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, avId, self.__handleAvatarRetrieved) def __handleAvatarRetrieved(self, dclass, fields): + """ + Handle the retrieval of the avatar's data. + + :param dclass: The data class of the avatar. + :param fields: The fields of the avatar. + """ if dclass not in (self.friendsManager.air.dclassesByName['DistributedToonUD'], self.friendsManager.air.dclassesByName['DistributedPetAI']): self._handleError('Retrieved avatar is not a DistributedToonUD or DistributedPetAI!') @@ -119,6 +202,13 @@ class GetAvatarDetailsOperation(FriendsOperation): self._handleDone() def __packAvatarDetails(self, dclass, fields): + """ + Pack the avatar details into a byte array. + + :param dclass: The data class of the avatar. + :param fields: The fields of the avatar. + :return: The packed avatar details. + """ # Pack required fields. fieldPacker = DCPacker() for i in range(dclass.getNumInheritedFields()): @@ -140,6 +230,11 @@ class GetAvatarDetailsOperation(FriendsOperation): return fieldPacker.getBytes() def __sendAvatarDetails(self, success): + """ + Send the avatar details to the sender. + + :param success: Whether the operation was successful. + """ datagram = PyDatagram() datagram.addUint32(self.fields['avId']) # avId datagram.addUint8(0 if success else 1) # returnCode @@ -150,17 +245,33 @@ class GetAvatarDetailsOperation(FriendsOperation): self.friendsManager.sendUpdateToAvatarId(self.sender, 'getAvatarDetailsResponse', [datagram.getMessage()]) def _handleDone(self): + """ + Handle the completion of the operation. + """ self.__sendAvatarDetails(True) FriendsOperation._handleDone(self) def _handleError(self, error): + """ + Handle an error during the operation. + + :param error: The error message. + """ self.__sendAvatarDetails(False) FriendsOperation._handleError(self, error) class MakeFriendsOperation(FriendsOperation): + """ + Operation to make two avatars friends. + """ def __init__(self, friendsManager): + """ + Initialize the MakeFriendsOperation. + + :param friendsManager: The friends manager instance. + """ FriendsOperation.__init__(self, friendsManager, None) self.avatarAId = None self.avatarBId = None @@ -172,6 +283,14 @@ class MakeFriendsOperation(FriendsOperation): self.avatarBFriendsList = None def start(self, avatarAId, avatarBId, flags, context): + """ + Start the operation to make two avatars friends. + + :param avatarAId: The first avatar's ID. + :param avatarBId: The second avatar's ID. + :param flags: The friendship flags. + :param context: The context of the operation. + """ self.avatarAId = avatarAId self.avatarBId = avatarBId self.flags = flags @@ -181,14 +300,34 @@ class MakeFriendsOperation(FriendsOperation): self.friendsManager.air.getActivated(self.avatarAId, self.__gotActivatedAvatarA) def __handleActivatedResp(self, avId, activated): + """ + Handle the response of checking if an avatar is activated. + + :param avId: The avatar ID. + :param activated: Whether the avatar is activated. + """ if activated: self.onlineToons.append(avId) def __gotActivatedAvatarA(self, avId, activated): + """ + Handle the response of checking if the first avatar is activated. + + :param avId: The avatar ID. + :param activated: Whether the avatar is activated. + """ self.__handleActivatedResp(avId, activated) self.friendsManager.air.getActivated(self.avatarBId, self.__gotActivatedAvatarB) def __handleMakeFriends(self, dclass, fields, friendId): + """ + Handle the process of making friends. + + :param dclass: The data class of the avatar. + :param fields: The fields of the avatar. + :param friendId: The friend's ID. + :return: A tuple indicating success and the updated friends list. + """ if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']: self._handleError('Retrieved avatar is not a DistributedToonUD!') return False, [] @@ -207,6 +346,12 @@ class MakeFriendsOperation(FriendsOperation): return True, friendsList def __handleAvatarARetrieved(self, dclass, fields): + """ + Handle the retrieval of the first avatar's data. + + :param dclass: The data class of the avatar. + :param fields: The fields of the avatar. + """ success, avatarAFriendsList = self.__handleMakeFriends(dclass, fields, self.avatarBId) if success: self.avatarAFriendsList = avatarAFriendsList @@ -214,17 +359,35 @@ class MakeFriendsOperation(FriendsOperation): self.__handleAvatarBRetrieved) def __gotActivatedAvatarB(self, avId, activated): + """ + Handle the response of checking if the second avatar is activated. + + :param avId: The avatar ID. + :param activated: Whether the avatar is activated. + """ self.__handleActivatedResp(avId, activated) self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.avatarAId, self.__handleAvatarARetrieved) def __handleAvatarBRetrieved(self, dclass, fields): + """ + Handle the retrieval of the second avatar's data. + + :param dclass: The data class of the avatar. + :param fields: The fields of the avatar. + """ success, avatarBFriendsList = self.__handleMakeFriends(dclass, fields, self.avatarAId) if success: self.avatarBFriendsList = avatarBFriendsList self._handleDone() def __handleSetFriendsList(self, avId, friendsList): + """ + Handle setting the friends list for an avatar. + + :param avId: The avatar ID. + :param friendsList: The updated friends list. + """ if avId in self.onlineToons: self.friendsManager.sendUpdateToAvatar(avId, 'setFriendsList', [friendsList]) else: @@ -234,6 +397,9 @@ class MakeFriendsOperation(FriendsOperation): {'setFriendsList': [friendsList]}) def _handleDone(self): + """ + Handle the completion of the operation. + """ self.resultCode = 1 if self.avatarAFriendsList is not None and self.avatarBFriendsList is not None: self.__handleSetFriendsList(self.avatarAId, self.avatarAFriendsList) @@ -247,14 +413,28 @@ class MakeFriendsOperation(FriendsOperation): FriendsOperation._handleDone(self) def _handleError(self, error): + """ + Handle an error during the operation. + + :param error: The error message. + """ self.resultCode = 0 self.friendsManager.sendMakeFriendsResponse(self.avatarAId, self.avatarBId, self.resultCode, self.context) FriendsOperation._handleError(self, error) class RemoveFriendOperation(FriendsOperation): + """ + Operation to remove a friend from the sender's friends list. + """ def __init__(self, friendsManager, sender): + """ + Initialize the RemoveFriendOperation. + + :param friendsManager: The friends manager instance. + :param sender: The sender's ID. + """ FriendsOperation.__init__(self, friendsManager, sender) self.friendId = None self.onlineToons = None @@ -262,24 +442,55 @@ class RemoveFriendOperation(FriendsOperation): self.friendFriendsList = None def start(self, friendId): + """ + Start the operation to remove a friend. + + :param friendId: The friend's ID. + """ self.friendId = friendId self.onlineToons = [] self.friendsManager.air.getActivated(self.sender, self.__gotSenderActivated) def __handleActivatedResp(self, avId, activated): + """ + Handle the response of checking if an avatar is activated. + + :param avId: The avatar ID. + :param activated: Whether the avatar is activated. + """ if activated: self.onlineToons.append(avId) def __gotSenderActivated(self, avId, activated): + """ + Handle the response of checking if the sender is activated. + + :param avId: The avatar ID. + :param activated: Whether the avatar is activated. + """ self.__handleActivatedResp(avId, activated) self.friendsManager.air.getActivated(self.friendId, self.__gotFriendActivated) def __gotFriendActivated(self, avId, activated): + """ + Handle the response of checking if the friend is activated. + + :param avId: The avatar ID. + :param activated: Whether the avatar is activated. + """ self.__handleActivatedResp(avId, activated) self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.sender, self.__handleSenderRetrieved) def __handleRemoveFriend(self, dclass, fields, friendId): + """ + Handle the process of removing a friend. + + :param dclass: The data class of the avatar. + :param fields: The fields of the avatar. + :param friendId: The friend's ID. + :return: A tuple indicating success and the updated friends list. + """ if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']: self._handleError('Retrieved sender is not a DistributedToonUD!') return False, [] @@ -299,6 +510,12 @@ class RemoveFriendOperation(FriendsOperation): return False, [] def __handleSenderRetrieved(self, dclass, fields): + """ + Handle the retrieval of the sender's data. + + :param dclass: The data class of the sender. + :param fields: The fields of the sender. + """ success, senderFriendsList = self.__handleRemoveFriend(dclass, fields, self.friendId) if success: self.senderFriendsList = senderFriendsList @@ -306,12 +523,24 @@ class RemoveFriendOperation(FriendsOperation): self.__handleFriendRetrieved) def __handleFriendRetrieved(self, dclass, fields): + """ + Handle the retrieval of the friend's data. + + :param dclass: The data class of the friend. + :param fields: The fields of the friend. + """ success, friendFriendsList = self.__handleRemoveFriend(dclass, fields, self.sender) if success: self.friendFriendsList = friendFriendsList self._handleDone() def __handleSetFriendsList(self, avId, friendsList): + """ + Handle setting the friends list for an avatar. + + :param avId: The avatar ID. + :param friendsList: The updated friends list. + """ if avId in self.onlineToons: self.friendsManager.sendUpdateToAvatar(avId, 'setFriendsList', [friendsList]) else: @@ -321,6 +550,9 @@ class RemoveFriendOperation(FriendsOperation): {'setFriendsList': [friendsList]}) def _handleDone(self): + """ + Handle the completion of the operation. + """ if self.senderFriendsList is not None and self.friendFriendsList is not None: self.__handleSetFriendsList(self.sender, self.senderFriendsList) self.__handleSetFriendsList(self.friendId, self.friendFriendsList) @@ -336,24 +568,47 @@ class RemoveFriendOperation(FriendsOperation): class ComingOnlineOperation(FriendsOperation): + """ + Operation to handle an avatar coming online. + """ def __init__(self, friendsManager): + """ + Initialize the ComingOnlineOperation. + + :param friendsManager: The friends manager instance. + """ FriendsOperation.__init__(self, friendsManager, None) self.avId = None self.friendsList = None self.currentFriendIdx = None def start(self, avId, friendsList): + """ + Start the operation to handle an avatar coming online. + + :param avId: The avatar ID. + :param friendsList: The friends list of the avatar. + """ self.avId = avId self.friendsList = friendsList self.__checkFriendsOnline() def __checkFriendsOnline(self): + """ + Check which friends are currently online. + """ self.currentFriendIdx = 0 for friendId in self.friendsList: self.friendsManager.air.getActivated(friendId, self.__gotFriendActivated) def __gotFriendActivated(self, avId, activated): + """ + Handle the response of checking if a friend is activated. + + :param avId: The avatar ID. + :param activated: Whether the friend is activated. + """ self.currentFriendIdx += 1 if activated: self.friendsManager.declareObject(avId, self.avId) @@ -365,8 +620,16 @@ class ComingOnlineOperation(FriendsOperation): class GoingOfflineOperation(FriendsOperation): + """ + Operation to handle an avatar going offline. + """ def __init__(self, friendsManager): + """ + Initialize the GoingOfflineOperation. + + :param friendsManager: The friends manager instance. + """ FriendsOperation.__init__(self, friendsManager, None) self.avId = None self.friendsList = None @@ -374,12 +637,23 @@ class GoingOfflineOperation(FriendsOperation): self.currentFriendIdx = None def start(self, avId): + """ + Start the operation to handle an avatar going offline. + + :param avId: The avatar ID. + """ self.avId = avId self.friendsList = [] self.accId = 0 self.friendsManager.air.dbInterface.queryObject(self.friendsManager.air.dbId, self.avId, self.__handleAvatarRetrieved) def __handleAvatarRetrieved(self, dclass, fields): + """ + Handle the retrieval of the avatar's data. + + :param dclass: The data class of the avatar. + :param fields: The fields of the avatar. + """ if dclass != self.friendsManager.air.dclassesByName['DistributedToonUD']: self._handleError('Retrieved avatar is not a DistributedToonUD!') return @@ -389,11 +663,20 @@ class GoingOfflineOperation(FriendsOperation): self.__checkFriendsOnline() def __checkFriendsOnline(self): + """ + Check which friends are currently online. + """ self.currentFriendIdx = 0 for friendId, _ in self.friendsList: self.friendsManager.air.getActivated(friendId, self.__gotFriendActivated) def __gotFriendActivated(self, avId, activated): + """ + Handle the response of checking if a friend is activated. + + :param avId: The avatar ID. + :param activated: Whether the friend is activated. + """ self.currentFriendIdx += 1 if activated: self.friendsManager.undeclareObject(avId, self.avId) @@ -403,19 +686,97 @@ class GoingOfflineOperation(FriendsOperation): if self.currentFriendIdx >= len(self.friendsList): self._handleDone() +class GenerateSecretOperation(FriendsOperation): + """ + Operation to generate a secret. + """ + def __init__(self, friendsManager): + """ + Initialize the GenerateSecretOperation. + + :param friendsManager: The friends manager instance. + :param requesterId: The requester's ID. + """ + FriendsOperation.__init__(self, friendsManager, None) + self.secret = None + self.requesterId = None + + def start(self, requesterId): + """ + Start the operation to generate a secret. + Format of secret: + TT 3 random letters/numbers 3 random letters/numbers + """ + self.requesterId = requesterId + # first check if friends list is full + + # check if too many secrets + # TODO + self.secret = '' + for i in range(3): + self.secret += random.choice(string.ascii_letters + string.digits) + self.secret += ' ' + for i in range(3): + self.secret += random.choice(string.ascii_letters + string.digits) + + self.friendsManager.secrets[self.secret] = requesterId + self._handleDone() + + def _handleDone(self): + """ + Handle the completion of the operation. + """ + FriendsOperation._handleDone(self) + # send the response to the requester that it was a success, the result and the secret + self.friendsManager.sendRequestSecretResponse(self.requesterId, 1, self.secret) + + + def _handleError(self, error): + """ + Handle an error during the operation. + + :param error: The error message. + """ + FriendsOperation._handleError(self, error) + # send the response to the requester that it was a failure, the result and the secret + self.friendsManager.sendRequestSecretResponse(self.requesterId, 0, '') + class ToontownFriendsManagerUD(DistributedObjectGlobalUD): + """ + The main friends manager class for Toontown. + """ notify = DirectNotifyGlobal.directNotify.newCategory('ToontownFriendsManagerUD') def __init__(self, air): + """ + Initialize the ToontownFriendsManagerUD. + + :param air: The AI repository instance. + """ DistributedObjectGlobalUD.__init__(self, air) + self.secret = None self.operations = [] - self.secrets = [] + self.secrets = {} def sendMakeFriendsResponse(self, avatarAId, avatarBId, result, context): + """ + Send the response for making friends. + + :param avatarAId: The first avatar's ID. + :param avatarBId: The second avatar's ID. + :param result: The result of the operation. + :param context: The context of the operation. + """ self.sendUpdate('makeFriendsResponse', [avatarAId, avatarBId, result, context]) def declareObject(self, doId, objId): + """ + Declare an object to the client. + + :param doId: The distributed object ID. + :param objId: The object ID. + """ datagram = PyDatagram() datagram.addServerHeader(self.GetPuppetConnectionChannel(doId), self.air.ourChannel, CLIENTAGENT_DECLARE_OBJECT) datagram.addUint32(objId) @@ -423,6 +784,13 @@ class ToontownFriendsManagerUD(DistributedObjectGlobalUD): self.air.send(datagram) def undeclareObject(self, doId, objId, isAccount=False): + """ + Undeclare an object to the client. + + :param doId: The distributed object ID. + :param objId: The object ID. + :param isAccount: Whether the object is an account. + """ datagram = PyDatagram() if isAccount: datagram.addServerHeader(self.GetAccountConnectionChannel(doId), self.air.ourChannel, @@ -435,12 +803,33 @@ class ToontownFriendsManagerUD(DistributedObjectGlobalUD): self.air.send(datagram) def sendFriendOnline(self, avId, friendId, commonChatFlags, whitelistChatFlags): + """ + Notify that a friend has come online. + + :param avId: The avatar ID. + :param friendId: The friend's ID. + :param commonChatFlags: The common chat flags. + :param whitelistChatFlags: The whitelist chat flags. + """ self.sendUpdateToAvatarId(avId, 'friendOnline', [friendId, commonChatFlags, whitelistChatFlags]) def sendFriendOffline(self, avId, friendId): + """ + Notify that a friend has gone offline. + + :param avId: The avatar ID. + :param friendId: The friend's ID. + """ self.sendUpdateToAvatarId(avId, 'friendOffline', [friendId]) def sendUpdateToAvatar(self, avId, fieldName, args=[]): + """ + Send an update to an avatar. + + :param avId: The avatar ID. + :param fieldName: The field name to update. + :param args: The arguments for the update. + """ dclass = self.air.dclassesByName['DistributedToonUD'] if not dclass: return @@ -453,6 +842,12 @@ class ToontownFriendsManagerUD(DistributedObjectGlobalUD): self.air.send(datagram) def runSenderOperation(self, operationType, *args): + """ + Run an operation initiated by the sender. + + :param operationType: The type of operation. + :param args: The arguments for the operation. + """ sender = self.air.getAvatarIdFromSender() if not sender: return @@ -462,27 +857,84 @@ class ToontownFriendsManagerUD(DistributedObjectGlobalUD): newOperation.start(*args) def runServerOperation(self, operationType, *args): + """ + Run an operation initiated by the server. + + :param operationType: The type of operation. + :param args: The arguments for the operation. + """ newOperation = operationType(self) self.operations.append(newOperation) newOperation.start(*args) def getFriendsListRequest(self): + """ + Handle a request to get the friends list. + """ self.runSenderOperation(GetFriendsListOperation) def getAvatarDetailsRequest(self, avId): + """ + Handle a request to get avatar details. + + :param avId: The avatar ID. + """ self.runSenderOperation(GetAvatarDetailsOperation, avId) def makeFriends(self, avatarAId, avatarBId, flags, context): + """ + Handle a request to make two avatars friends. + + :param avatarAId: The first avatar's ID. + :param avatarBId: The second avatar's ID. + :param flags: The friendship flags. + :param context: The context of the operation. + """ self.runServerOperation(MakeFriendsOperation, avatarAId, avatarBId, flags, context) def removeFriend(self, friendId): + """ + Handle a request to remove a friend. + + :param friendId: The friend's ID. + """ self.runSenderOperation(RemoveFriendOperation, friendId) + def requestSecret(self, requesterId): + """ + Handle a request to generate a secret. + + """ + self.runServerOperation(GenerateSecretOperation, requesterId) + def comingOnline(self, avId, friendsList): + """ + Handle a request for an avatar coming online. + + :param avId: The avatar ID. + :param friendsList: The friends list of the avatar. + """ self.runServerOperation(ComingOnlineOperation, avId, friendsList) def goingOffline(self, avId): + """ + Handle a request for an avatar going offline. + + :param avId: The avatar ID. + """ self.runServerOperation(GoingOfflineOperation, avId) - def requestSecret(self, requesterId): - print('requestSecret') + def sendRequestSecretResponse(self, requesterId, success, secret): + """ + Send a response to a secret request. + + :param requesterId: The requester's ID. + :param success: Whether the operation was successful. + :param secret: The generated secret. + """ + self.sendUpdate('requestSecretResponse', [success, secret, requesterId]) + + + + + \ No newline at end of file