oldschool-toontown/otp/friends/AvatarFriendsManagerUD.py

388 lines
18 KiB
Python
Raw Normal View History

from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
from otp.otpbase import OTPGlobals
from otp.ai import AIMsgTypes
from otp.uberdog.RejectCode import RejectCode
2019-11-08 22:55:55 -06:00
from direct.directnotify.DirectNotifyGlobal import directNotify
from otp.friends.AvatarFriendInfo import AvatarFriendInfo
class AvatarFriendsManagerUD(DistributedObjectGlobalUD):
"""
The Avatar Friends Manager is a global object.
This object handles client requests on avatar-level (as opposed to player-level) friends.
See Also:
"otp/src/friends/AvatarFriendsManager.py"
"otp/src/friends/PlayerFriendsManager.py"
"pirates/src/friends/PiratesFriendsList.py"
"otp/src/configfiles/otp.dc"
"pirates/src/configfiles/pirates.dc"
"""
notify = directNotify.newCategory('AvatarFriendsManagerUD')
def __init__(self, air):
assert self.notify.debugCall()
DistributedObjectGlobalUD.__init__(self, air)
self.DBuser = config.GetString("mysql-user", "ud_rw")
self.DBpassword = config.GetString("mysql-password", "r3adwr1te")
self.DBhost = config.GetString("avatarfriends-db-host","localhost")
self.DBport = config.GetInt("avatarfriends-db-port",3306)
self.DBname = config.GetString("avatarfriends-db-name","avatar_friends")
self.friends = None
self.friendsList = None
self.wantMysql = config.GetBool('want-mysql-db', 0)
if self.wantMysql:
from otp.friends.AvatarFriendsDB import AvatarFriendsDB
self.db = AvatarFriendsDB(host=self.DBhost,
port=self.DBport,
user=self.DBuser,
password=self.DBpassword,
dbname=self.DBname)
else:
# lets use the astron db or openotp db
if __astron__:
self.db = self.air.dbInterface
else:
# TODO
self.db = None
self.avatarId2FriendsList = {}
self.avatarId2Invitations = {}
self.avatarId2Unvitations = {} #an unvitation is a rejected (but not retracted) invitation
#self.avatarId2Name = {}
self.avatarId2Info = {}
self.asyncRequests = {}
self.isAvatarOnline = {}
def announceGenerate(self):
assert self.notify.debugCall()
#self.accept("avatarOnline", self.avatarOnline, [])
self.accept("avatarOnlinePlusAccountInfo", self.avatarOnlinePlusAccountInfo, [])
self.accept("avatarOffline", self.avatarOffline, [])
DistributedObjectGlobalUD.announceGenerate(self)
self.sendUpdateToChannel(
AIMsgTypes.CHANNEL_CLIENT_BROADCAST, "online", [])
self.sendUpdateToChannel(
AIMsgTypes.OTP_CHANNEL_AI_AND_UD_BROADCAST, "online", [])
def delete(self):
assert self.notify.debugCall()
self.ignoreAll()
for i in list(self.asyncRequests.values()):
i.delete()
DistributedObjectGlobalUD.delete(self)
#----------------------------------
def avatarOnlinePlusAccountInfo(self,avatarId,accountId,playerName,
playerNameApproved,openChatEnabled,
createFriendsWithChat,chatCodeCreation):
assert self.notify.debugCall()
assert avatarId
self.notify.debug("avatarOnlinePlusAccountInfo")
if avatarId in self.isAvatarOnline:
assert self.notify.debug(
"\n\nWe got a duplicate avatar online notice %s"%(avatarId,))
if avatarId and avatarId not in self.isAvatarOnline:
self.isAvatarOnline[avatarId]=True
self.avatarId2Info[avatarId] = AvatarFriendInfo(avatarName=str(avatarId),
playerName = playerName,
playerId = accountId,
onlineYesNo=1,
openChatEnabledYesNo=openChatEnabled,)
# Get my friends list from the SQL DB
if self.wantMysql:
self.friends = self.db.getFriends(avatarId)
else:
self.db.queryObject(self.air.dbId, avatarId, self.__gotFriendsList)
self.avatarId2FriendsList[avatarId]=self.friends
if not hasattr(self.friends, "keys"): #check for error
self.notify.warning("self.db.getFriends(avatarId) has no keys %s" % (self.friends))
return
# Callback function for asynchronous avatar name fetch
def setName(avatarId, avatarId2info, friends, context, name):
if avatarId in avatarId2info:
avatarId2info[avatarId].avatarName = name[0]
for friendId in friends:
if friendId in self.isAvatarOnline:
if (friendId in self.avatarId2FriendsList) and (avatarId in self.avatarId2FriendsList[friendId]):
self.sendUpdateToAvatarId(friendId,"updateAvatarFriend",
[avatarId,self.getFriendView(friendId,avatarId)])
self.sendExtraUpdates(friendId,avatarId)
# Get my friends' info to me
for friend in list(self.friends.keys()):
friendId = friend
if friendId not in self.isAvatarOnline:
if friendId not in self.avatarId2Info:
self.avatarId2Info[friendId] = AvatarFriendInfo()
#fetch this friend's name from the gameDB since we don't have it yet
context=self.air.allocateContext()
dclassName="DistributedAvatarUD"
self.air.contextToClassName[context]=dclassName
self.acceptOnce(
"doFieldResponse-%s"%context,setName,[friendId,self.avatarId2Info,[avatarId,]])
self.air.queryObjectField(dclassName,"setName",friendId,context)
else:
#print "AFMUD warning: info entry found for offline friend"
self.sendUpdateToAvatarId(avatarId,"updateAvatarFriend",[friendId,self.getFriendView(avatarId,friendId)])
self.sendExtraUpdates(avatarId,friendId)
else:
assert friendId in self.avatarId2Info
self.sendUpdateToAvatarId(avatarId,"updateAvatarFriend",[friendId,self.getFriendView(avatarId,friendId)])
self.sendExtraUpdates(avatarId,friendId)
# Get my info to my friends
context=self.air.allocateContext()
dclassName="DistributedAvatarUD"
self.air.contextToClassName[context]=dclassName
self.acceptOnce(
"doFieldResponse-%s"%(context,),
setName, [avatarId, self.avatarId2Info, list(self.friends.keys())])
self.air.queryObjectField(
dclassName, "setName", avatarId, context)
def __gotFriendsList(self, dclass, fields):
self.friends = fields.get('setFriendsList', [])
self.friendsList = self.friends
def getFriendView(self, viewerId, friendId):
info = self.avatarId2Info[friendId]
assert viewerId in self.avatarId2FriendsList, "avatarId2FriendsList has no key %d" % viewerId
assert friendId in self.avatarId2FriendsList[viewerId], "avatarId2FriendsList[%d] has no key %d" % (viewerId, friendId)
info.openChatFriendshipYesNo = self.avatarId2FriendsList[viewerId][friendId]
if info.openChatFriendshipYesNo or \
(info.openChatEnabledYesNo and \
self.avatarId2Info[viewerId].openChatEnabledYesNo):
info.understandableYesNo = 1
else:
info.understandableYesNo = 0
return info
def sendExtraUpdates(self,destId,aboutId):
pass
@report(types = ['args'], dConfigParam = 'orphanedavatar')
def avatarOffline(self, avatarId):
"""
Is called from handleAvatarUsage when the avatar leaves the game.
Also is called from DistributedAvatarManagerUD when it detects
an orphaned avatar in the world.
"""
assert self.notify.debugCall()
self.isAvatarOnline.pop(avatarId,None)
if avatarId in self.avatarId2Info:
self.avatarId2Info[avatarId].onlineYesNo = 0
if avatarId:
friendsList = self.avatarId2FriendsList.get(avatarId, None)
if friendsList is not None and avatarId in self.avatarId2Info:
for friend in friendsList:
self.sendUpdateToAvatarId(
friend, "updateAvatarFriend", [avatarId,self.avatarId2Info[avatarId]])
invitations = self.avatarId2Invitations.pop(avatarId, [])
for invitee in invitations:
self.sendUpdateToAvatarId(
invitee, "retractInvite", [avatarId])
self.avatarId2FriendsList.pop(avatarId,None)
self.avatarId2Info.pop(avatarId,None)
#----------------------------------------------------------------------
# Functions called by the client
def requestInvite(self, otherAvatarId):
avatarId = self.air.getAvatarIdFromSender()
assert self.notify.debugCall("avatarId:%s"%(avatarId,))
invitations = self.avatarId2Invitations.setdefault(avatarId, [])
othersInvitations = self.avatarId2Invitations.setdefault(
otherAvatarId, [])
friendsList = self.avatarId2FriendsList.get(avatarId)
otherFriendsList = self.avatarId2FriendsList.get(otherAvatarId)
def reject(reason):
self.sendUpdateToAvatarId(
avatarId, "rejectInvite", [otherAvatarId, reason])
#clear unvitations
unvitations = self.avatarId2Unvitations.setdefault(avatarId, [])
if otherAvatarId in unvitations:
unvitations.remove(otherAvatarId)
if friendsList is None:
reject(RejectCode.FRIENDS_LIST_NOT_HANDY)
elif otherFriendsList is None:
reject(RejectCode.INVITEE_NOT_ONLINE)
elif avatarId in self.avatarId2Unvitations.setdefault(otherAvatarId, []): #check for unvitation
reject(RejectCode.INVITATION_DECLINED)
elif otherAvatarId in invitations:
reject(RejectCode.ALREADY_INVITED)
elif avatarId == otherAvatarId:
reject(RejectCode.ALREADY_FRIENDS_WITH_SELF)
elif otherAvatarId in friendsList:
reject(RejectCode.ALREADY_YOUR_FRIEND)
elif avatarId in otherFriendsList:
reject(RejectCode.ALREADY_YOUR_FRIEND)
self.notify.error(
"Friends lists out of sync %s %s"%(avatarId, otherAvatarId))
#should be adding player friends list?
elif (len(friendsList)
+ 0
> OTPGlobals.MaxFriends):
reject(RejectCode.FRIENDS_LIST_FULL)
#should be adding player friends list?
elif (len(otherFriendsList)
+ 0
> OTPGlobals.MaxFriends):
reject(RejectCode.OTHER_FRIENDS_LIST_FULL)
elif avatarId in othersInvitations:
othersInvitations.remove(avatarId)
assert otherAvatarId not in invitations
self.avatarId2FriendsList[avatarId][otherAvatarId] = 0
if otherAvatarId in self.avatarId2FriendsList:
self.avatarId2FriendsList[otherAvatarId][avatarId] = 0
#update the friends database
try:
if self.wantMysql:
self.db.addFriendship(avatarId,otherAvatarId)
else:
if __astron__:
# update setFriendsList field in the distributedtoon dclass to include the new friend
friendsList = self.avatarId2FriendsList[avatarId]
friendsList.append(otherAvatarId)
self.db.updateObject(self.air.dbId, avatarId, self.air.dclassesByName['DistributedToonUD'], {'setFriendsList': [friendsList]} )
else:
# TODO openotp: update setFriendsList field in the distributedtoon dclass to include the new friend
pass
except:
pass #HACK for testing
self.air.writeServerEvent('friendAccept', avatarId, '%s' % otherAvatarId)
#tell them they're friends and give presence info, includes online status
self.sendUpdateToAvatarId(otherAvatarId,"updateAvatarFriend",[avatarId,self.getFriendView(otherAvatarId,avatarId)])
self.sendUpdateToAvatarId(avatarId,"updateAvatarFriend",[otherAvatarId,self.getFriendView(avatarId,otherAvatarId)])
self.sendExtraUpdates(avatarId,otherAvatarId)
self.sendExtraUpdates(otherAvatarId,avatarId)
else:
invitations.append(otherAvatarId)
# Tell the other guy we're inviting him!
self.air.writeServerEvent('friendInvite', avatarId, '%s' % otherAvatarId)
self.sendUpdateToAvatarId(avatarId, "friendConsidering", [otherAvatarId])
self.sendUpdateToAvatarId(otherAvatarId, "invitationFrom", [avatarId,self.avatarId2Info[avatarId].avatarName])
def requestRemove(self, otherAvatarId):
"""
Call this function if you want to retract an invitation you've
made, or to decline an invitation from otherAvatarId, or to
remove an existing friend from your friends list.
otherAvatarId may be online or offline.
"""
avatarId = self.air.getAvatarIdFromSender()
self.air.writeServerEvent('friendRemove', avatarId, '%s' % otherAvatarId)
self.friendsList = self.avatarId2FriendsList.get(avatarId,None)
if self.friendsList is None:
if self.wantMysql:
self.friendsList = self.db.getFriends(avatarId)
self.avatarId2FriendsList[avatarId] = self.friendsList
else:
if __astron__:
self.db.queryObject(self.air.dbId, avatarId, self.__gotFriendsList)
self.avatarId2FriendsList[avatarId] = self.friendsList
else:
# TODO openotp: queryObject
pass
assert self.notify.debugCall("avatarId:%s"%(avatarId,))
def reject(reason):
self.sendUpdateToAvatarId(
avatarId, "rejectRemove", [otherAvatarId, reason])
invitations = self.avatarId2Invitations.setdefault(avatarId, [])
if otherAvatarId in invitations:
# The other avatar was only invited and had not yet accepted
self.sendUpdateToAvatarId(otherAvatarId, "retractInvite", [avatarId])
invitations.remove(otherAvatarId)
assert otherAvatarId not in invitations
assert otherAvatarId not in friendsList
return
else: # create an unvitation
unvitations = self.avatarId2Unvitations.setdefault(avatarId, [])
if otherAvatarId in unvitations:
pass
else:
unvitations.append(otherAvatarId)
othersInvitations = self.avatarId2Invitations.setdefault(otherAvatarId, [])
if avatarId in othersInvitations:
# I was only invited and had not yet accepted
self.sendUpdateToAvatarId(
otherAvatarId, "rejectInvite",
[avatarId, RejectCode.INVITATION_DECLINED])
othersInvitations.remove(avatarId)
assert avatarId not in othersInvitations
assert otherAvatarId not in friendsList
return
if otherAvatarId not in friendsList:
reject(RejectCode.ALREADY_NOT_YOUR_FRIEND)
else:
if avatarId in self.avatarId2FriendsList:
self.avatarId2FriendsList[avatarId].pop(otherAvatarId,None)
if otherAvatarId in self.avatarId2FriendsList:
self.avatarId2FriendsList[otherAvatarId].pop(avatarId,None)
if self.wantMysql:
self.db.removeFriendship(avatarId,otherAvatarId)
else:
if __astron__:
# update setFriendsList field in the distributedtoon dclass to remove the friend
friendsList = self.avatarId2FriendsList[avatarId]
friendsList.remove(otherAvatarId)
self.db.updateObject(self.air.dbId, avatarId, self.air.dclassesByName['DistributedToonUD'], {'setFriendsList': [friendsList]} )
else:
# TODO openotp: update setFriendsList field in the distributedtoon dclass to remove the friend
pass
self.sendUpdateToAvatarId(avatarId,"removeAvatarFriend",[otherAvatarId])
self.sendUpdateToAvatarId(otherAvatarId,"removeAvatarFriend",[avatarId])
def updateAvatarName(self, avatarId, avatarName):
if avatarId in self.avatarId2Info:
self.avatarId2Info[avatarId].avatarName = avatarName
friends = self.avatarId2FriendsList.get(avatarId,[])
for friendId in friends:
if friendId in self.isAvatarOnline:
self.sendUpdateToAvatarId(friendId,"updateAvatarFriend",
[avatarId,self.getFriendView(friendId,avatarId)])
self.sendExtraUpdates(friendId,avatarId)