historical/toontown-classic.git/toontown/friends/TTOffFriendsManagerUD.py
2024-01-16 11:20:27 -06:00

479 lines
19 KiB
Python

import functools
import json
import time
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
from direct.distributed.PyDatagram import *
from direct.fsm.FSM import FSM
class GetAvatarInfoOperation(FSM):
def __init__(self, mgr, senderId, avId, callback):
FSM.__init__(self, 'GetAvatarInfoOperation')
self.mgr = mgr
self.senderId = senderId
self.avId = avId
self.callback = callback
self.isPet = False
def start(self):
self.demand('GetAvatarInfo')
def enterGetAvatarInfo(self):
self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, self.avId, self.__gotAvatarInfo)
def __gotAvatarInfo(self, dclass, fields):
if dclass not in (
self.mgr.air.dclassesByName['DistributedToonUD'], self.mgr.air.dclassesByName['DistributedPetAI']):
self.demand('Failure', 'Invalid dclass for avId %d' % self.avId)
return
self.isPet = dclass == self.mgr.air.dclassesByName['DistributedPetAI']
self.fields = fields
self.fields['avId'] = self.avId
self.demand('Finished')
def enterFinished(self):
if not self.isPet:
self.mgr.avBasicInfoCache[self.avId] = {
'expire': time.time() + config.GetInt('friend-detail-cache-expire', 3600),
'avInfo': [self.avId, self.fields['setName'][0], self.fields['setDNAString'][0],
self.fields['setPetId'][0]]}
self.callback(success=True, avId=self.senderId, fields=self.fields, isPet=self.isPet)
def enterFailure(self, reason):
self.mgr.notify.warning(reason)
self.callback(success=False, avId=None, fields=None, isPet=False)
class GetFriendsListOperation(FSM):
def __init__(self, mgr, avId, callback):
FSM.__init__(self, 'GetFriendsListOperation')
self.mgr = mgr
self.avId = avId
self.callback = callback
self.friendsDetails = []
self.iterated = 0
self.operations = {}
self.onlineFriends = []
def start(self):
self.demand('GetFriendsList')
def enterGetFriendsList(self):
self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, self.avId, self.__gotFriendsList)
def __gotFriendsList(self, dclass, fields):
if self.state != 'GetFriendsList':
self.demand('Failure', '__gotFriendsList called when looking for friends list, avId %d' % self.avId)
return
if dclass != self.mgr.air.dclassesByName['DistributedToonUD']:
self.demand('Failure', 'Invalid dclass for avId %d' % self.avId)
return
self.friendsList = fields['setFriendsList'][0]
self.demand('GetFriendDetails')
def enterGetFriendDetails(self):
if len(self.friendsList) <= 0:
self.callback(success=False, avId=self.avId, friendsDetails=None, onlineFriends=None)
return
for friendId, trueFriend in self.friendsList:
details = self.mgr.avBasicInfoCache.get(friendId)
if details:
expire = details.get('expire')
avInfo = details.get('avInfo')
if expire and avInfo:
if expire > time.time():
self.friendsDetails.append(avInfo)
self.iterated += 1
self.__testFinished()
continue
else:
del self.mgr.avBasicInfoCache[friendId]
newOperation = GetAvatarInfoOperation(self.mgr, self.avId, friendId, self.__gotAvatarInfo)
newOperation.start()
self.operations[friendId] = newOperation
def __gotAvatarInfo(self, success, avId, fields, isPet):
if fields['avId'] in self.operations:
del self.operations[fields['avId']]
if not success:
self.demand('Failure', '__gotAvatarInfo received unsuccessful callback, avId=%d' % self.avId)
return
if self.state != 'GetFriendDetails':
self.demand('Failure', '__gotAvatarInfo while not looking for friends details, avId=%d' % self.avId)
return
if avId != self.avId:
self.demand('Failure',
'__gotAvatarInfo response for wrong requester. wrongId=%d, rightId=%d' % (self.avId, avId))
return
self.iterated += 1
self.friendsDetails.append(
[fields['avId'], fields['setName'][0], fields['setDNAString'][0], fields['setPetId'][0]])
self.__testFinished()
def __testFinished(self):
if self.iterated >= len(self.friendsList) and len(self.operations) == 0:
self.demand('CheckFriendsOnline')
def enterCheckFriendsOnline(self):
self.iterated = 0
for friendId, trueFriend in self.friendsList:
self.mgr.air.getActivated(friendId, self.__gotActivatedResp)
def __gotActivatedResp(self, avId, activated):
self.iterated += 1
if activated:
self.onlineFriends.append(avId)
if self.iterated == len(self.friendsList):
self.demand('Finished')
def enterFinished(self):
self.callback(success=True, avId=self.avId, friendsDetails=self.friendsDetails,
onlineFriends=self.onlineFriends)
def enterFailure(self, reason):
self.mgr.notify.warning(reason)
self.callback(success=False, avId=self.avId, friendsDetails=None, onlineFriends=None)
class UpdateAvatarFieldOperation(FSM):
def __init__(self, mgr, senderId, avId, callback):
FSM.__init__(self, 'UpdateAvatarFieldOperation')
self.mgr = mgr
self.senderId = senderId
self.avId = avId
self.callback = callback
self.field = None
self.value = None
def start(self, field, value):
self.field = field
self.value = value
self.demand('GetAvatarOnline')
def enterGetAvatarOnline(self):
self.mgr.air.getActivated(self.avId, self.__avatarOnlineResp)
def __avatarOnlineResp(self, avId, activated):
if self.state != 'GetAvatarOnline':
self.demand('Failure', 'Received __avatarOnlineResp while not in GetAvatarOnline state.')
return
self.online = activated
self.demand('UpdateAvatarField')
def enterUpdateAvatarField(self):
if self.online:
dg = self.mgr.air.dclassesByName['DistributedToonUD'].aiFormatUpdate(self.field, self.avId, self.avId,
self.mgr.air.ourChannel, [self.value])
self.mgr.air.send(dg)
else:
self.mgr.air.dbInterface.updateObject(self.mgr.air.dbId, self.avId,
self.mgr.air.dclassesByName['DistributedToonUD'],
{self.field: [self.value]})
self.demand('Finished')
def enterFinished(self):
self.callback(success=True, avId=self.senderId, online=self.online)
def enterFailure(self, reason):
self.mgr.notify.warning(reason)
self.callback(success=False)
class TTOffFriendsManagerUD(DistributedObjectGlobalUD):
notify = DirectNotifyGlobal.directNotify.newCategory('TTOffFriendsManagerUD')
def __init__(self, air):
DistributedObjectGlobalUD.__init__(self, air)
self.operations = {}
self.avBasicInfoCache = {}
def deleteOperation(self, avId):
operation = self.operations.get(avId)
if not operation:
self.notify.debug('%s tried to delete non-existent operation!' % avId)
return
if operation.state != 'Off':
operation.demand('Off')
del self.operations[avId]
def getFriendsListRequest(self):
avId = self.air.getAvatarIdFromSender()
if not avId:
return
if avId in self.operations:
return
newOperation = GetFriendsListOperation(self, avId, self.__gotFriendsList)
newOperation.start()
self.operations[avId] = newOperation
def __gotFriendsList(self, success, avId, friendsDetails, onlineFriends):
self.deleteOperation(avId)
if not success:
return
self.sendUpdateToAvatarId(avId, 'friendsListRequestResp', [friendsDetails])
for friendId in onlineFriends:
self.sendUpdateToAvatarId(avId, 'friendOnline', [friendId, 0, 0])
def getAvatarDetails(self, avId):
senderId = self.air.getAvatarIdFromSender()
if not senderId:
return
if senderId in self.operations:
return
newOperation = GetAvatarInfoOperation(self, senderId, avId, self.__gotAvatarDetails)
newOperation.start()
self.operations[senderId] = newOperation
def __gotAvatarDetails(self, success, avId, fields, isPet):
self.deleteOperation(avId)
if not success:
return
if isPet:
details = [
['setTraitSeed', fields['setTraitSeed'][0]],
['setSafeZone', fields['setSafeZone'][0]],
['setLastSeenTimestamp', fields.get('setLastSeenTimestamp', [0])[0]],
['setHead', fields['setHead'][0]],
['setEars', fields['setEars'][0]],
['setNose', fields['setNose'][0]],
['setTail', fields['setTail'][0]],
['setBodyTexture', fields['setBodyTexture'][0]],
['setColor', fields['setColor'][0]],
['setColorScale', fields['setColorScale'][0]],
['setEyeColor', fields['setEyeColor'][0]],
['setGender', fields['setGender'][0]],
['setOwnerId', fields['setOwnerId'][0]],
['setPetName', fields['setPetName'][0]]
]
else:
details = [
['setName', fields['setName'][0]],
['setExperience', fields['setExperience'][0]],
['setTrackAccess', fields['setTrackAccess'][0]],
['setTrackBonusLevel', fields['setTrackBonusLevel'][0]],
['setInventory', fields['setInventory'][0]],
['setHp', fields['setHp'][0]],
['setMaxHp', fields['setMaxHp'][0]],
['setDefaultShard', fields['setDefaultShard'][0]],
['setLastHood', fields['setLastHood'][0]],
['setDNAString', fields['setDNAString'][0].encode('base64')],
['setMailboxContents', fields['setMailboxContents'][0].encode('base64')],
['setAwardMailboxContents', fields['setAwardMailboxContents'][0].encode('base64')],
['setGiftSchedule', fields['setGiftSchedule'][0].encode('base64')],
['setDeliverySchedule', fields['setDeliverySchedule'][0].encode('base64')],
['setAwardSchedule', fields['setAwardSchedule'][0].encode('base64')],
['setHat', fields['setHat'][0], fields['setHat'][1], fields['setHat'][2]],
['setGlasses', fields['setGlasses'][0], fields['setGlasses'][1], fields['setGlasses'][2]],
['setBackpack', fields['setBackpack'][0], fields['setBackpack'][1], fields['setBackpack'][2]],
['setShoes', fields['setShoes'][0], fields['setShoes'][1], fields['setShoes'][2]],
['setHatList', fields['setHatList'][0]],
['setGlassesList', fields['setGlassesList'][0]],
['setBackpackList', fields['setBackpackList'][0]],
['setShoesList', fields['setShoesList'][0]],
['setCustomMessages', fields['setCustomMessages'][0]],
['setEmoteAccess', fields['setEmoteAccess'][0]],
['setClothesTopsList', fields['setClothesTopsList'][0]],
['setClothesBottomsList', fields['setClothesBottomsList'][0]],
['setPetTrickPhrases', fields['setPetTrickPhrases'][0]]
]
self.sendUpdateToAvatarId(avId, 'avatarDetailsResp', [fields['avId'], json.dumps(details)])
def removeFriend(self, friendId):
avId = self.air.getAvatarIdFromSender()
if not avId:
return
if avId in self.operations:
return
newOperation = GetAvatarInfoOperation(self, avId, avId,
functools.partial(self.__handleRemoveFriend, friendId=friendId))
newOperation.start()
self.operations[avId] = newOperation
def __handleRemoveFriend(self, success, avId, fields, isPet, friendId=None, final=False):
self.deleteOperation(avId)
if not (success and friendId):
return
if fields['avId'] not in [avId, friendId]:
self.notify.warning('__handleRemoveFriend received wrong Toon fields from DB, avId=%d' % avId)
return
friendsList = fields['setFriendsList'][0]
searchId = avId if final else friendId
for index, friend in enumerate(friendsList):
if friend[0] == searchId:
del friendsList[index]
break
newOperation = UpdateAvatarFieldOperation(self, avId, friendId if final else avId,
functools.partial(self.__handleFriendRemoved, friendId=friendId,
final=final))
newOperation.start('setFriendsList', friendsList)
self.operations[avId] = newOperation
def __handleFriendRemoved(self, success, avId, online=False, friendId=None, final=False):
self.deleteOperation(avId)
if not (success and friendId):
return
if not final:
newOperation = GetAvatarInfoOperation(self, avId, friendId,
functools.partial(self.__handleRemoveFriend, friendId=friendId,
final=True))
newOperation.start()
self.operations[avId] = newOperation
else:
if online:
dg = self.air.dclassesByName['DistributedToonUD'].aiFormatUpdate('friendsNotify', friendId, friendId,
self.air.ourChannel, [avId, 1])
self.air.send(dg)
def comingOnline(self, avId, friends):
for friendId in friends:
self.air.getActivated(friendId, functools.partial(self.__comingOnlineFriendOnline, otherId=avId))
def __comingOnlineFriendOnline(self, avId, activated, otherId=None):
if not (otherId and activated):
return
# Declare our avatar to their friend.
dg = PyDatagram()
dg.addServerHeader(self.GetPuppetConnectionChannel(avId), self.air.ourChannel, CLIENTAGENT_DECLARE_OBJECT)
dg.addUint32(otherId)
dg.addUint16(self.air.dclassesByName['DistributedToonUD'].getNumber())
self.air.send(dg)
# Declare their friend to our avatar.
dg = PyDatagram()
dg.addServerHeader(self.GetPuppetConnectionChannel(otherId), self.air.ourChannel, CLIENTAGENT_DECLARE_OBJECT)
dg.addUint32(avId)
dg.addUint16(self.air.dclassesByName['DistributedToonUD'].getNumber())
self.air.send(dg)
# Tell the client that their friend is online.
self.sendUpdateToAvatarId(avId, 'friendOnline', [otherId, 0, 0])
def goingOffline(self, avId):
newOperation = GetAvatarInfoOperation(self, avId, avId, self.__handleGoingOffline)
newOperation.start()
def __handleGoingOffline(self, success, avId, fields, isPet):
if not success:
return
for friendId, trueFriend in fields['setFriendsList'][0]:
self.air.getActivated(friendId, functools.partial(self.__handleGoneOffline, otherId=avId,
accId=fields['setDISLid'][0]))
def __handleGoneOffline(self, avId, activated, otherId=None, accId=None):
if not (otherId and activated and accId):
return
# Undeclare to the friend.
dg = PyDatagram()
dg.addServerHeader(self.GetPuppetConnectionChannel(avId), self.air.ourChannel, CLIENTAGENT_UNDECLARE_OBJECT)
dg.addUint32(otherId)
self.air.send(dg)
# Undeclare to the now-offline avId.
dg = PyDatagram()
dg.addServerHeader(self.GetAccountConnectionChannel(accId), self.air.ourChannel, CLIENTAGENT_UNDECLARE_OBJECT)
dg.addUint32(avId)
self.air.send(dg)
# Tell them they're offline.
self.sendUpdateToAvatarId(avId, 'friendOffline', [otherId])
def clearList(self, avId):
newOperation = GetAvatarInfoOperation(self, avId, avId, self.__handleClearListGotFriendsList)
newOperation.start()
self.operations[avId] = newOperation
def __handleClearListGotFriendsList(self, success, avId, fields, isPet):
if not success:
return
if avId != fields['avId']:
return
friendIds = fields['setFriendsList'][0][:]
friendIds.append((avId, 1))
if friendIds[0][0] == avId:
return
newOperation = GetAvatarInfoOperation(self, avId, friendIds[0][0],
functools.partial(self.__handleClearListGotFriendData,
friendIds=friendIds[1:]))
newOperation.start()
self.operations[avId] = newOperation
def __handleClearListGotFriendData(self, success, avId, fields, isPet, friendIds=[]):
self.deleteOperation(avId)
if not success:
if friendIds:
newOperation = GetAvatarInfoOperation(self, avId, friendIds[0][0],
functools.partial(self.__handleClearListGotFriendData,
friendIds=friendIds[1:]))
newOperation.start()
self.operations[avId] = newOperation
else:
return
friendsIds = fields['setFriendsList'][0][:]
if avId == fields['avId']:
friendsIds = []
else:
for friend in friendsIds:
if friend[0] == avId:
friendsIds.remove(friend)
newOperation = UpdateAvatarFieldOperation(self, avId, fields['avId'],
functools.partial(self.__handleClearListUpdatedAvatarField,
friendId=fields['avId'], friendIds=friendIds[1:]))
newOperation.start('setFriendsList', friendsIds)
self.operations[avId] = newOperation
def __handleClearListUpdatedAvatarField(self, success, avId, online=False, friendId=None, friendIds=[]):
self.deleteOperation(avId)
if success and online:
dg = self.air.dclassesByName['DistributedToonUD'].aiFormatUpdate('friendsNotify', friendId, friendId,
self.air.ourChannel, [avId, 1])
self.air.send(dg)
if not friendIds:
return
newOperation = GetAvatarInfoOperation(self, avId, friendIds[0][0],
functools.partial(self.__handleClearListGotFriendData,
friendIds=friendIds[1:]))
newOperation.start()
self.operations[avId] = newOperation