480 lines
19 KiB
Python
480 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
|