Friends: True friend codes can now be generated

This commit is contained in:
DarthM 2025-01-28 01:47:30 -05:00
parent a9eb3c2ea0
commit 05d73cb4fb
6 changed files with 863 additions and 174 deletions

View File

@ -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 {

View File

@ -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])

View File

@ -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")

View File

@ -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()

View File

@ -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])

View File

@ -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])