add widescreen patch, fix chat/friends.
This commit is contained in:
parent
a5ecbb8b1e
commit
4fbb08316c
23 changed files with 5463 additions and 183 deletions
10
README.md
10
README.md
|
@ -1,5 +1,5 @@
|
|||
# Open Toontown
|
||||
This repository contains the code for Open Toontown, based on the latest version of Disney's Toontown Online (sv1.0.47.38).
|
||||
# Oldschool Toontown
|
||||
This repository contains the code for Open Toontown, plus some added features from (at the time of adding) bleeding-edge commits. It is based on the latest version of Disney's Toontown Online (sv1.0.47.38).
|
||||
|
||||
# Setup
|
||||
After cloning the repository, you will need to clone the [resources](https://github.com/open-toontown/resources) repository inside the directory where you cloned the source repo.
|
||||
|
@ -47,3 +47,9 @@ How you commit changes is your choice, but please include what you did and a bas
|
|||
* `minigames: Fix crash when entering the trolley`
|
||||
* `racing: Fix possible race condition when two racers tied`
|
||||
* `golf: Refix wonky physics once and for all (hopefully)`
|
||||
|
||||
# Known issues...
|
||||
|
||||
* Estates don't work.
|
||||
* Fishing doesn't work.
|
||||
* Parties don't work.
|
5
otp/avatar/DistributedPlayerUD.py
Normal file
5
otp/avatar/DistributedPlayerUD.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from otp.avatar.DistributedAvatarUD import DistributedAvatarUD
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
|
||||
class DistributedPlayerUD(DistributedAvatarUD):
|
||||
notify = directNotify.newCategory('DistributedPlayerUD')
|
|
@ -61,7 +61,6 @@ class LocalAvatar(DistributedAvatar.DistributedAvatar, DistributedSmoothNode.Dis
|
|||
self.soundRun = None
|
||||
self.soundWalk = None
|
||||
self.sleepFlag = 0
|
||||
self.noSleep = 0
|
||||
self.isDisguised = 0
|
||||
self.movingFlag = 0
|
||||
self.swimmingFlag = 0
|
||||
|
@ -985,7 +984,7 @@ class LocalAvatar(DistributedAvatar.DistributedAvatar, DistributedSmoothNode.Dis
|
|||
return
|
||||
|
||||
def gotoSleep(self):
|
||||
if not self.sleepFlag and not self.noSleep:
|
||||
if not self.sleepFlag:
|
||||
self.b_setAnimState('Sleep', self.animMultiplier)
|
||||
self.sleepFlag = 1
|
||||
|
||||
|
|
31
otp/chat/ChatHandler.py
Normal file
31
otp/chat/ChatHandler.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal
|
||||
|
||||
class ChatHandler(DistributedObjectGlobal):
|
||||
"""
|
||||
The purpose of this class is to handle chat messages from the client to the
|
||||
uberdog to filter out unwanted words. Then send it through the server.
|
||||
"""
|
||||
|
||||
notify = directNotify.newCategory('ChatRouter')
|
||||
|
||||
def sendChatMessage(self, message):
|
||||
"""
|
||||
|
||||
|
||||
send a chat message to the uberdog
|
||||
|
||||
Args:
|
||||
message (string): the message to send that was typed in by the user
|
||||
"""
|
||||
self.sendUpdate('chatMessage', [message])
|
||||
|
||||
def sendWhisperMessage(self, message, receiverAvId):
|
||||
"""
|
||||
send a whisper message to the uberdog
|
||||
|
||||
Args:
|
||||
message (string): the message to send that was typed in by the user
|
||||
receiverAvId (int): the avatar id of the person to send the message to
|
||||
"""
|
||||
self.sendUpdate('whisperMessage', [message, receiverAvId])
|
76
otp/chat/ChatHandlerUD.py
Normal file
76
otp/chat/ChatHandlerUD.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
|
||||
from toontown.chat.TTWhiteList import TTWhiteList
|
||||
|
||||
whiteList = TTWhiteList()
|
||||
|
||||
class ChatHandlerUD(DistributedObjectGlobalUD):
|
||||
"""
|
||||
The purpose of this class is to handle chat messages from the client to the
|
||||
uberdog to filter out unwanted words. Then send it through the server.
|
||||
"""
|
||||
notify = directNotify.newCategory('ChatRouterUD')
|
||||
|
||||
def filterWhitelist(self, message):
|
||||
"""
|
||||
|
||||
this function filters out words that are not in the whitelist
|
||||
|
||||
Args:
|
||||
message (string): the original message to filter
|
||||
|
||||
Returns:
|
||||
mods (string): the filtered message
|
||||
"""
|
||||
words = message.split(' ')
|
||||
offset = 0
|
||||
mods = []
|
||||
|
||||
for word in words:
|
||||
if not whiteList.isWord(word):
|
||||
mods.append((offset, offset + len(word) - 1))
|
||||
|
||||
offset += len(word) + 1
|
||||
|
||||
return mods
|
||||
|
||||
def chatMessage(self, message):
|
||||
"""
|
||||
send a chat message through the server
|
||||
|
||||
Args:
|
||||
message (string): the message to send that was typed in by the user
|
||||
"""
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
|
||||
if not avId:
|
||||
return
|
||||
|
||||
channel = avId
|
||||
|
||||
mods = self.filterWhitelist(message)
|
||||
|
||||
do = self.air.dclassesByName['DistributedPlayerUD']
|
||||
args = [avId, 0, '', message, mods, 0]
|
||||
datagram = do.aiFormatUpdate('setTalk', avId, channel, self.air.ourChannel, args)
|
||||
self.air.send(datagram)
|
||||
|
||||
def whisperMessage(self, message, receiverAvId):
|
||||
"""
|
||||
send a whisper message through the server
|
||||
|
||||
Args:
|
||||
message (string): the message to send that was typed in by the user
|
||||
receiverAvId (int): the avatar id of the person to send the message to
|
||||
"""
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
|
||||
if not avId:
|
||||
return
|
||||
|
||||
mods = self.filterWhitelist(message)
|
||||
|
||||
do = self.air.dclassesByName['DistributedPlayerUD']
|
||||
args = [avId, 0, '', message, mods, 0]
|
||||
datagram = do.aiFormatUpdate('setTalkWhisper', receiverAvId, receiverAvId, self.air.ourChannel, args)
|
||||
self.air.send(datagram)
|
752
otp/chat/TalkAssistant - Copy.py
Normal file
752
otp/chat/TalkAssistant - Copy.py
Normal file
|
@ -0,0 +1,752 @@
|
|||
import sys
|
||||
from direct.showbase import DirectObject
|
||||
from otp.otpbase import OTPLocalizer
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from otp.otpbase import OTPGlobals
|
||||
from otp.speedchat import SCDecoders
|
||||
from panda3d.core import *
|
||||
from otp.chat.TalkMessage import TalkMessage
|
||||
from otp.chat.TalkHandle import TalkHandle
|
||||
import time
|
||||
from otp.chat.TalkGlobals import *
|
||||
from otp.chat.ChatGlobals import *
|
||||
from panda3d.otp import CFSpeech, CFTimeout, CFThought
|
||||
ThoughtPrefix = '.'
|
||||
|
||||
class TalkAssistant(DirectObject.DirectObject):
|
||||
ExecNamespace = None
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('TalkAssistant')
|
||||
execChat = ConfigVariableBool('exec-chat', 0).value
|
||||
|
||||
def __init__(self):
|
||||
self.logWhispers = 1
|
||||
self.whiteList = None
|
||||
self.clearHistory()
|
||||
self.zeroTimeDay = time.time()
|
||||
self.zeroTimeGame = globalClock.getRealTime()
|
||||
self.floodThreshold = 10.0
|
||||
self.useWhiteListFilter = ConfigVariableBool('white-list-filter-openchat', 0).value
|
||||
self.lastWhisperDoId = None
|
||||
self.lastWhisperPlayerId = None
|
||||
self.lastWhisper = None
|
||||
self.SCDecoder = SCDecoders
|
||||
return
|
||||
|
||||
def clearHistory(self):
|
||||
self.historyComplete = []
|
||||
self.historyOpen = []
|
||||
self.historyUpdates = []
|
||||
self.historyGuild = []
|
||||
self.historyByDoId = {}
|
||||
self.historyByDISLId = {}
|
||||
self.floodDataByDoId = {}
|
||||
self.spamDictByDoId = {}
|
||||
self.labelGuild = OTPLocalizer.TalkGuild
|
||||
self.handleDict = {}
|
||||
self.messageCount = 0
|
||||
self.shownWhiteListWarning = 0
|
||||
|
||||
def delete(self):
|
||||
self.ignoreAll()
|
||||
self.clearHistory()
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def countMessage(self):
|
||||
self.messageCount += 1
|
||||
return self.messageCount - 1
|
||||
|
||||
def getOpenText(self, numLines, startPoint = 0):
|
||||
return self.historyOpen[startPoint:startPoint + numLines]
|
||||
|
||||
def getSizeOpenText(self):
|
||||
return len(self.historyOpen)
|
||||
|
||||
def getCompleteText(self, numLines, startPoint = 0):
|
||||
return self.historyComplete[startPoint:startPoint + numLines]
|
||||
|
||||
def getCompleteTextFromRecent(self, numLines, startPoint = 0):
|
||||
start = len(self.historyComplete) - startPoint
|
||||
if start < 0:
|
||||
start = 0
|
||||
backStart = max(start - numLines, 0)
|
||||
text = self.historyComplete[backStart:start]
|
||||
text.reverse()
|
||||
return text
|
||||
|
||||
def getAllCompleteText(self):
|
||||
return self.historyComplete
|
||||
|
||||
def getAllHistory(self):
|
||||
return self.historyComplete
|
||||
|
||||
def getSizeCompleteText(self):
|
||||
return len(self.historyComplete)
|
||||
|
||||
def getHandle(self, doId):
|
||||
return self.handleDict.get(doId)
|
||||
|
||||
def doWhiteListWarning(self):
|
||||
pass
|
||||
|
||||
def addToHistoryDoId(self, message, doId, scrubbed = 0):
|
||||
if message.getTalkType() == TALK_WHISPER and doId != localAvatar.doId:
|
||||
self.lastWhisperDoId = doId
|
||||
self.lastWhisper = self.lastWhisperDoId
|
||||
if doId not in self.historyByDoId:
|
||||
self.historyByDoId[doId] = []
|
||||
self.historyByDoId[doId].append(message)
|
||||
if not self.shownWhiteListWarning and scrubbed and doId == localAvatar.doId:
|
||||
self.doWhiteListWarning()
|
||||
self.shownWhiteListWarning = 1
|
||||
if doId not in self.floodDataByDoId:
|
||||
self.floodDataByDoId[doId] = [0.0, self.stampTime(), message]
|
||||
else:
|
||||
oldTime = self.floodDataByDoId[doId][1]
|
||||
newTime = self.stampTime()
|
||||
timeDiff = newTime - oldTime
|
||||
oldRating = self.floodDataByDoId[doId][0]
|
||||
contentMult = 1.0
|
||||
if len(message.getBody()) < 6:
|
||||
contentMult += 0.2 * float(6 - len(message.getBody()))
|
||||
if self.floodDataByDoId[doId][2].getBody() == message.getBody():
|
||||
contentMult += 1.0
|
||||
floodRating = max(0, 3.0 * contentMult + oldRating - timeDiff)
|
||||
self.floodDataByDoId[doId] = [floodRating, self.stampTime(), message]
|
||||
if floodRating > self.floodThreshold:
|
||||
if oldRating < self.floodThreshold:
|
||||
self.floodDataByDoId[doId] = [floodRating + 3.0, self.stampTime(), message]
|
||||
return 1
|
||||
else:
|
||||
self.floodDataByDoId[doId] = [oldRating - timeDiff, self.stampTime(), message]
|
||||
return 2
|
||||
return 0
|
||||
|
||||
def addToHistoryDISLId(self, message, dISLId, scrubbed = 0):
|
||||
if message.getTalkType() == TALK_ACCOUNT and dISLId != base.cr.accountDetailRecord.playerAccountId:
|
||||
self.lastWhisperPlayerId = dISLId
|
||||
self.lastWhisper = self.lastWhisperPlayerId
|
||||
if dISLId not in self.historyByDISLId:
|
||||
self.historyByDISLId[dISLId] = []
|
||||
self.historyByDISLId[dISLId].append(message)
|
||||
|
||||
def addHandle(self, doId, message):
|
||||
if doId == localAvatar.doId:
|
||||
return
|
||||
handle = self.handleDict.get(doId)
|
||||
if not handle:
|
||||
handle = TalkHandle(doId, message)
|
||||
self.handleDict[doId] = handle
|
||||
else:
|
||||
handle.addMessageInfo(message)
|
||||
|
||||
def stampTime(self):
|
||||
return globalClock.getRealTime() - self.zeroTimeGame
|
||||
|
||||
def findName(self, id, isPlayer = 0):
|
||||
if isPlayer:
|
||||
return self.findPlayerName(id)
|
||||
else:
|
||||
return self.findAvatarName(id)
|
||||
|
||||
def findAvatarName(self, id):
|
||||
info = base.cr.identifyAvatar(id)
|
||||
if info:
|
||||
return info.getName()
|
||||
else:
|
||||
return ''
|
||||
|
||||
def findPlayerName(self, id):
|
||||
info = base.cr.playerFriendsManager.getFriendInfo(id)
|
||||
if info:
|
||||
return info.playerName
|
||||
else:
|
||||
return ''
|
||||
|
||||
def whiteListFilterMessage(self, text):
|
||||
if not self.useWhiteListFilter:
|
||||
return text
|
||||
elif not base.whiteList:
|
||||
return 'no list'
|
||||
words = text.split(' ')
|
||||
newwords = []
|
||||
for word in words:
|
||||
if word == '' or base.whiteList.isWord(word):
|
||||
newwords.append(word)
|
||||
else:
|
||||
newwords.append(base.whiteList.defaultWord)
|
||||
|
||||
newText = ' '.join(newwords)
|
||||
return newText
|
||||
|
||||
def colorMessageByWhiteListFilter(self, text):
|
||||
if not base.whiteList:
|
||||
return text
|
||||
words = text.split(' ')
|
||||
newwords = []
|
||||
for word in words:
|
||||
if word == '' or base.whiteList.isWord(word):
|
||||
newwords.append(word)
|
||||
else:
|
||||
newwords.append('\x01WLRed\x01' + word + '\x02')
|
||||
|
||||
newText = ' '.join(newwords)
|
||||
return newText
|
||||
|
||||
def executeSlashCommand(self, text):
|
||||
pass
|
||||
|
||||
def executeGMCommand(self, text):
|
||||
pass
|
||||
|
||||
def isThought(self, message):
|
||||
if not message:
|
||||
return 0
|
||||
elif len(message) == 0:
|
||||
return 0
|
||||
elif message.find(ThoughtPrefix, 0, len(ThoughtPrefix)) >= 0:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def removeThoughtPrefix(self, message):
|
||||
if self.isThought(message):
|
||||
return message[len(ThoughtPrefix):]
|
||||
else:
|
||||
return message
|
||||
|
||||
def fillWithTestText(self):
|
||||
hold = self.floodThreshold
|
||||
self.floodThreshold = 1000.0
|
||||
self.receiveOpenTalk(1001, 'Bob the Ghost', None, None, 'Hello from the machine')
|
||||
self.receiveOpenTalk(1001, 'Bob the Ghost', None, None, 'More text for ya!')
|
||||
self.receiveOpenTalk(1001, 'Bob the Ghost', None, None, 'Hope this makes life easier')
|
||||
self.receiveOpenTalk(1002, 'Doug the Spirit', None, None, 'Now we need some longer text that will spill over onto two lines')
|
||||
self.receiveOpenTalk(1002, 'Doug the Spirit', None, None, 'Maybe I will tell you')
|
||||
self.receiveOpenTalk(1001, 'Bob the Ghost', None, None, 'If you are seeing this text it is because you are cool')
|
||||
self.receiveOpenTalk(1002, 'Doug the Spirit', None, None, "That's right, there is no need to call tech support")
|
||||
self.receiveOpenTalk(localAvatar.doId, localAvatar.getName, None, None, "Okay I won't call tech support, because I am cool")
|
||||
self.receiveGMTalk(1003, 'God of Text', None, None, 'Good because I have seen it already')
|
||||
self.floodThreshold = hold
|
||||
return
|
||||
|
||||
def printHistoryComplete(self):
|
||||
print('HISTORY COMPLETE')
|
||||
for message in self.historyComplete:
|
||||
print('%s %s %s\n%s\n' % (message.getTimeStamp(),
|
||||
message.getSenderAvatarName(),
|
||||
message.getSenderAccountName(),
|
||||
message.getBody()))
|
||||
|
||||
def importExecNamespace(self):
|
||||
pass
|
||||
|
||||
def execMessage(self, message):
|
||||
print('execMessage %s' % message)
|
||||
if not TalkAssistant.ExecNamespace:
|
||||
TalkAssistant.ExecNamespace = {}
|
||||
exec('from panda3d.core import *', globals(), self.ExecNamespace)
|
||||
self.importExecNamespace()
|
||||
try:
|
||||
if not __debug__ or __execWarnings__:
|
||||
print('EXECWARNING TalkAssistant eval: %s' % message)
|
||||
printStack()
|
||||
return str(eval(message, globals(), TalkAssistant.ExecNamespace))
|
||||
except SyntaxError:
|
||||
try:
|
||||
if not __debug__ or __execWarnings__:
|
||||
print('EXECWARNING TalkAssistant exec: %s' % message)
|
||||
printStack()
|
||||
exec(message, globals(), TalkAssistant.ExecNamespace)
|
||||
return 'ok'
|
||||
except:
|
||||
exception = sys.exc_info()[0]
|
||||
extraInfo = sys.exc_info()[1]
|
||||
if extraInfo:
|
||||
return str(extraInfo)
|
||||
else:
|
||||
return str(exception)
|
||||
|
||||
except:
|
||||
exception = sys.exc_info()[0]
|
||||
extraInfo = sys.exc_info()[1]
|
||||
if extraInfo:
|
||||
return str(extraInfo)
|
||||
else:
|
||||
return str(exception)
|
||||
|
||||
def checkOpenTypedChat(self):
|
||||
if base.localAvatar.commonChatFlags & OTPGlobals.CommonChat:
|
||||
return True
|
||||
return False
|
||||
|
||||
def checkAnyTypedChat(self):
|
||||
if base.localAvatar.commonChatFlags & OTPGlobals.CommonChat:
|
||||
return True
|
||||
if base.localAvatar.canChat():
|
||||
return True
|
||||
return False
|
||||
|
||||
def checkOpenSpeedChat(self):
|
||||
return True
|
||||
|
||||
def checkWhisperTypedChatAvatar(self, avatarId):
|
||||
remoteAvatar = base.cr.doId2do.get(avatarId)
|
||||
if remoteAvatar:
|
||||
if remoteAvatar.isUnderstandable():
|
||||
return True
|
||||
if base.localAvatar.commonChatFlags & OTPGlobals.SuperChat:
|
||||
return True
|
||||
remoteAvatarOrHandleOrInfo = base.cr.identifyAvatar(avatarId)
|
||||
if remoteAvatarOrHandleOrInfo and hasattr(remoteAvatarOrHandleOrInfo, 'isUnderstandable'):
|
||||
if remoteAvatarOrHandleOrInfo.isUnderstandable():
|
||||
return True
|
||||
info = base.cr.playerFriendsManager.findPlayerInfoFromAvId(avatarId)
|
||||
if info:
|
||||
if info.understandableYesNo:
|
||||
return True
|
||||
info = base.cr.avatarFriendsManager.getFriendInfo(avatarId)
|
||||
if info:
|
||||
if info.understandableYesNo:
|
||||
return True
|
||||
if base.cr.getFriendFlags(avatarId) & OTPGlobals.FriendChat:
|
||||
return True
|
||||
return False
|
||||
|
||||
def checkWhisperSpeedChatAvatar(self, avatarId):
|
||||
return True
|
||||
|
||||
def checkWhisperTypedChatPlayer(self, playerId):
|
||||
info = base.cr.playerFriendsManager.getFriendInfo(playerId)
|
||||
if info:
|
||||
if info.understandableYesNo:
|
||||
return True
|
||||
return False
|
||||
|
||||
def checkWhisperSpeedChatPlayer(self, playerId):
|
||||
if base.cr.playerFriendsManager.isPlayerFriend(playerId):
|
||||
return True
|
||||
return False
|
||||
|
||||
def checkOpenSpeedChat(self):
|
||||
return True
|
||||
|
||||
def checkWhisperSpeedChatAvatar(self, avatarId):
|
||||
return True
|
||||
|
||||
def checkWhisperSpeedChatPlayer(self, playerId):
|
||||
if base.cr.playerFriendsManager.isPlayerFriend(playerId):
|
||||
return True
|
||||
return False
|
||||
|
||||
def checkGuildTypedChat(self):
|
||||
if localAvatar.guildId:
|
||||
return True
|
||||
return False
|
||||
|
||||
def checkGuildSpeedChat(self):
|
||||
if localAvatar.guildId:
|
||||
return True
|
||||
return False
|
||||
|
||||
def receiveOpenTalk(self, senderAvId, avatarName, accountId, accountName, message, scrubbed = 0):
|
||||
error = None
|
||||
if not avatarName and senderAvId:
|
||||
localAvatar.sendUpdate('logSuspiciousEvent', ['receiveOpenTalk: invalid avatar name (%s)' % senderAvId])
|
||||
avatarName = self.findAvatarName(senderAvId)
|
||||
if not accountName and accountId:
|
||||
accountName = self.findPlayerName(accountId)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, senderAvId, avatarName, accountId, accountName, None, None, None, None, TALK_OPEN, None)
|
||||
if senderAvId != localAvatar.doId:
|
||||
self.addHandle(senderAvId, newMessage)
|
||||
reject = 0
|
||||
if senderAvId:
|
||||
reject = self.addToHistoryDoId(newMessage, senderAvId, scrubbed)
|
||||
if accountId:
|
||||
self.addToHistoryDISLId(newMessage, accountId)
|
||||
if reject == 1:
|
||||
newMessage.setBody(OTPLocalizer.AntiSpamInChat)
|
||||
if reject != 2:
|
||||
isSpam = self.spamDictByDoId.get(senderAvId) and reject
|
||||
if not isSpam:
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyOpen.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
if newMessage.getBody() == OTPLocalizer.AntiSpamInChat:
|
||||
self.spamDictByDoId[senderAvId] = 1
|
||||
else:
|
||||
self.spamDictByDoId[senderAvId] = 0
|
||||
return error
|
||||
|
||||
def receiveWhisperTalk(self, avatarId, avatarName, accountId, accountName, toId, toName, message, scrubbed = 0):
|
||||
error = None
|
||||
print('receiveWhisperTalk %s %s %s %s %s' % (avatarId,
|
||||
avatarName,
|
||||
accountId,
|
||||
accountName,
|
||||
message))
|
||||
if not avatarName and avatarId:
|
||||
avatarName = self.findAvatarName(avatarId)
|
||||
if not accountName and accountId:
|
||||
accountName = self.findPlayerName(accountId)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, avatarId, avatarName, accountId, accountName, toId, toName, None, None, TALK_WHISPER, None)
|
||||
if avatarId == localAvatar.doId:
|
||||
self.addHandle(toId, newMessage)
|
||||
else:
|
||||
self.addHandle(avatarId, newMessage)
|
||||
self.historyComplete.append(newMessage)
|
||||
if avatarId:
|
||||
self.addToHistoryDoId(newMessage, avatarId, scrubbed)
|
||||
if accountId:
|
||||
self.addToHistoryDISLId(newMessage, accountId)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveAccountTalk(self, avatarId, avatarName, accountId, accountName, toId, toName, message, scrubbed = 0):
|
||||
if not accountName and base.cr.playerFriendsManager.playerId2Info.get(accountId):
|
||||
accountName = base.cr.playerFriendsManager.playerId2Info.get(accountId).playerName
|
||||
error = None
|
||||
if not avatarName and avatarId:
|
||||
avatarName = self.findAvatarName(avatarId)
|
||||
if not accountName and accountId:
|
||||
accountName = self.findPlayerName(accountId)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, avatarId, avatarName, accountId, accountName, None, None, toId, toName, TALK_ACCOUNT, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
if avatarId:
|
||||
self.addToHistoryDoId(newMessage, avatarId, scrubbed)
|
||||
if accountId:
|
||||
self.addToHistoryDISLId(newMessage, accountId, scrubbed)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveGuildTalk(self, senderAvId, fromAC, avatarName, message, scrubbed = 0):
|
||||
error = None
|
||||
if not self.isThought(message):
|
||||
accountName = self.findName(fromAC, 1)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, senderAvId, avatarName, fromAC, accountName, None, None, None, None, TALK_GUILD, None)
|
||||
reject = self.addToHistoryDoId(newMessage, senderAvId)
|
||||
if reject == 1:
|
||||
newMessage.setBody(OTPLocalizer.AntiSpamInChat)
|
||||
if reject != 2:
|
||||
isSpam = self.spamDictByDoId.get(senderAvId) and reject
|
||||
if not isSpam:
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyGuild.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
if newMessage.getBody() == OTPLocalizer.AntiSpamInChat:
|
||||
self.spamDictByDoId[senderAvId] = 1
|
||||
else:
|
||||
self.spamDictByDoId[senderAvId] = 0
|
||||
return error
|
||||
|
||||
def receiveGMTalk(self, avatarId, avatarName, accountId, accountName, message, scrubbed = 0):
|
||||
error = None
|
||||
if not avatarName and avatarId:
|
||||
avatarName = self.findAvatarName(avatarId)
|
||||
if not accountName and accountId:
|
||||
accountName = self.findPlayerName(accountId)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, avatarId, avatarName, accountId, accountName, None, None, None, None, TALK_GM, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyOpen.append(newMessage)
|
||||
if avatarId:
|
||||
self.addToHistoryDoId(newMessage, avatarId)
|
||||
if accountId:
|
||||
self.addToHistoryDISLId(newMessage, accountId)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveThought(self, avatarId, avatarName, accountId, accountName, message, scrubbed = 0):
|
||||
error = None
|
||||
if not avatarName and avatarId:
|
||||
avatarName = self.findAvatarName(avatarId)
|
||||
if not accountName and accountId:
|
||||
accountName = self.findPlayerName(accountId)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, avatarId, avatarName, accountId, accountName, None, None, None, None, AVATAR_THOUGHT, None)
|
||||
if avatarId != localAvatar.doId:
|
||||
self.addHandle(avatarId, newMessage)
|
||||
reject = 0
|
||||
if avatarId:
|
||||
reject = self.addToHistoryDoId(newMessage, avatarId, scrubbed)
|
||||
if accountId:
|
||||
self.addToHistoryDISLId(newMessage, accountId)
|
||||
if reject == 1:
|
||||
newMessage.setBody(OTPLocalizer.AntiSpamInChat)
|
||||
if reject != 2:
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyOpen.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveGameMessage(self, message):
|
||||
error = None
|
||||
if not self.isThought(message):
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, None, None, None, None, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, INFO_GAME, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyUpdates.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveSystemMessage(self, message):
|
||||
error = None
|
||||
if not self.isThought(message):
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, None, None, None, None, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, INFO_SYSTEM, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyUpdates.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveDeveloperMessage(self, message):
|
||||
error = None
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, None, None, None, None, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, INFO_DEV, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyUpdates.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveGuildMessage(self, message, senderAvId, senderName):
|
||||
error = None
|
||||
if not self.isThought(message):
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, senderAvId, senderName, None, None, None, None, None, None, TALK_GUILD, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyGuild.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveGuildUpdateMessage(self, message, senderId, senderName, receiverId, receiverName, extraInfo = None):
|
||||
error = None
|
||||
if not self.isThought(message):
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, senderId, senderName, None, None, receiverId, receiverName, None, None, INFO_GUILD, extraInfo)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyGuild.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveFriendUpdate(self, friendId, friendName, isOnline):
|
||||
if isOnline:
|
||||
onlineMessage = OTPLocalizer.FriendOnline
|
||||
else:
|
||||
onlineMessage = OTPLocalizer.FriendOffline
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), onlineMessage, friendId, friendName, None, None, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, UPDATE_FRIEND, None)
|
||||
self.addHandle(friendId, newMessage)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyUpdates.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return
|
||||
|
||||
def receiveFriendAccountUpdate(self, friendId, friendName, isOnline):
|
||||
if isOnline:
|
||||
onlineMessage = OTPLocalizer.FriendOnline
|
||||
else:
|
||||
onlineMessage = OTPLocalizer.FriendOffline
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), onlineMessage, None, None, friendId, friendName, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, UPDATE_FRIEND, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyUpdates.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return
|
||||
|
||||
def receiveGuildUpdate(self, memberId, memberName, isOnline):
|
||||
if base.cr.identifyFriend(memberId) is None:
|
||||
if isOnline:
|
||||
onlineMessage = OTPLocalizer.GuildMemberOnline
|
||||
else:
|
||||
onlineMessage = OTPLocalizer.GuildMemberOffline
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), onlineMessage, memberId, memberName, None, None, None, None, None, None, UPDATE_GUILD, None)
|
||||
self.addHandle(memberId, newMessage)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyUpdates.append(newMessage)
|
||||
self.historyGuild.append(newMessage)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return
|
||||
|
||||
def receiveOpenSpeedChat(self, type, messageIndex, senderAvId, name = None):
|
||||
error = None
|
||||
if not name and senderAvId:
|
||||
name = self.findName(senderAvId, 0)
|
||||
if type == SPEEDCHAT_NORMAL:
|
||||
message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex)
|
||||
elif type == SPEEDCHAT_EMOTE:
|
||||
message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, name)
|
||||
elif type == SPEEDCHAT_CUSTOM:
|
||||
message = self.SCDecoder.decodeSCCustomMsg(messageIndex)
|
||||
if message in (None, ''):
|
||||
return
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, senderAvId, name, None, None, None, None, None, None, TALK_OPEN, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyOpen.append(newMessage)
|
||||
self.addToHistoryDoId(newMessage, senderAvId)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receiveAvatarWhisperSpeedChat(self, type, messageIndex, senderAvId, name = None):
|
||||
error = None
|
||||
if not name and senderAvId:
|
||||
name = self.findName(senderAvId, 0)
|
||||
if type == SPEEDCHAT_NORMAL:
|
||||
message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex)
|
||||
elif type == SPEEDCHAT_EMOTE:
|
||||
message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, name)
|
||||
elif type == SPEEDCHAT_CUSTOM:
|
||||
message = self.SCDecoder.decodeSCCustomMsg(messageIndex)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, senderAvId, name, None, None, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, TALK_WHISPER, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyOpen.append(newMessage)
|
||||
self.addToHistoryDoId(newMessage, senderAvId)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def receivePlayerWhisperSpeedChat(self, type, messageIndex, senderAvId, name = None):
|
||||
error = None
|
||||
if not name and senderAvId:
|
||||
name = self.findName(senderAvId, 1)
|
||||
if type == SPEEDCHAT_NORMAL:
|
||||
message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex)
|
||||
elif type == SPEEDCHAT_EMOTE:
|
||||
message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, name)
|
||||
elif type == SPEEDCHAT_CUSTOM:
|
||||
message = self.SCDecoder.decodeSCCustomMsg(messageIndex)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, None, None, senderAvId, name, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, TALK_WHISPER, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.historyOpen.append(newMessage)
|
||||
self.addToHistoryDISLId(newMessage, senderAvId)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def sendOpenTalk(self, message):
|
||||
error = None
|
||||
if base.cr.magicWordManager and base.cr.wantMagicWords and len(message) > 0 and message[0] == base.cr.magicWordManager.chatPrefix:
|
||||
messenger.send('magicWord', [message])
|
||||
self.receiveDeveloperMessage(message)
|
||||
else:
|
||||
chatFlags = CFSpeech | CFTimeout
|
||||
if self.isThought(message):
|
||||
chatFlags = CFThought
|
||||
base.localAvatar.sendUpdate('setTalk', [0,
|
||||
0,
|
||||
'',
|
||||
message,
|
||||
[],
|
||||
0])
|
||||
messenger.send('chatUpdate', [message, chatFlags])
|
||||
return error
|
||||
|
||||
def sendWhisperTalk(self, message, receiverAvId):
|
||||
error = None
|
||||
receiver = base.cr.doId2do.get(receiverAvId)
|
||||
if receiver:
|
||||
receiver.sendUpdate('setTalkWhisper', [0,
|
||||
0,
|
||||
'',
|
||||
message,
|
||||
[],
|
||||
0])
|
||||
else:
|
||||
receiver = base.cr.identifyAvatar(receiverAvId)
|
||||
if receiver:
|
||||
base.localAvatar.sendUpdate('setTalkWhisper', [0,
|
||||
0,
|
||||
'',
|
||||
message,
|
||||
[],
|
||||
0], sendToId=receiverAvId)
|
||||
return error
|
||||
|
||||
def sendAccountTalk(self, message, receiverAccount):
|
||||
error = None
|
||||
base.cr.playerFriendsManager.sendUpdate('setTalkAccount', [receiverAccount,
|
||||
0,
|
||||
'',
|
||||
message,
|
||||
[],
|
||||
0])
|
||||
return error
|
||||
|
||||
def sendGuildTalk(self, message):
|
||||
error = None
|
||||
if self.checkGuildTypedChat():
|
||||
base.cr.guildManager.sendTalk(message)
|
||||
else:
|
||||
print('Guild chat error')
|
||||
error = ERROR_NO_GUILD_CHAT
|
||||
return error
|
||||
|
||||
def sendOpenSpeedChat(self, type, messageIndex):
|
||||
error = None
|
||||
if type == SPEEDCHAT_NORMAL:
|
||||
messenger.send(SCChatEvent)
|
||||
messenger.send('chatUpdateSC', [messageIndex])
|
||||
base.localAvatar.b_setSC(messageIndex)
|
||||
elif type == SPEEDCHAT_EMOTE:
|
||||
messenger.send('chatUpdateSCEmote', [messageIndex])
|
||||
messenger.send(SCEmoteChatEvent)
|
||||
base.localAvatar.b_setSCEmote(messageIndex)
|
||||
elif type == SPEEDCHAT_CUSTOM:
|
||||
messenger.send('chatUpdateSCCustom', [messageIndex])
|
||||
messenger.send(SCCustomChatEvent)
|
||||
base.localAvatar.b_setSCCustom(messageIndex)
|
||||
return error
|
||||
|
||||
def sendAvatarWhisperSpeedChat(self, type, messageIndex, receiverId):
|
||||
error = None
|
||||
if type == SPEEDCHAT_NORMAL:
|
||||
base.localAvatar.whisperSCTo(messageIndex, receiverId, 0)
|
||||
message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex)
|
||||
elif type == SPEEDCHAT_EMOTE:
|
||||
base.localAvatar.whisperSCEmoteTo(messageIndex, receiverId, 0)
|
||||
message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, localAvatar.getName())
|
||||
elif type == SPEEDCHAT_CUSTOM:
|
||||
base.localAvatar.whisperSCCustomTo(messageIndex, receiverId, 0)
|
||||
message = self.SCDecoder.decodeSCCustomMsg(messageIndex)
|
||||
if self.logWhispers:
|
||||
avatarName = None
|
||||
accountId = None
|
||||
avatar = base.cr.identifyAvatar(receiverId)
|
||||
if avatar:
|
||||
avatarName = avatar.getName()
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, receiverId, avatarName, None, None, TALK_WHISPER, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.addToHistoryDoId(newMessage, localAvatar.doId)
|
||||
self.addToHistoryDISLId(newMessage, base.cr.accountDetailRecord.playerAccountId)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def sendPlayerWhisperSpeedChat(self, type, messageIndex, receiverId):
|
||||
error = None
|
||||
if type == SPEEDCHAT_NORMAL:
|
||||
base.cr.speedchatRelay.sendSpeedchat(receiverId, messageIndex)
|
||||
message = self.SCDecoder.decodeSCStaticTextMsg(messageIndex)
|
||||
elif type == SPEEDCHAT_EMOTE:
|
||||
base.cr.speedchatRelay.sendSpeedchatEmote(receiverId, messageIndex)
|
||||
message = self.SCDecoder.decodeSCEmoteWhisperMsg(messageIndex, localAvatar.getName())
|
||||
return
|
||||
elif type == SPEEDCHAT_CUSTOM:
|
||||
base.cr.speedchatRelay.sendSpeedchatCustom(receiverId, messageIndex)
|
||||
message = self.SCDecoder.decodeSCCustomMsg(messageIndex)
|
||||
if self.logWhispers:
|
||||
receiverName = self.findName(receiverId, 1)
|
||||
newMessage = TalkMessage(self.countMessage(), self.stampTime(), message, localAvatar.doId, localAvatar.getName(), localAvatar.DISLid, localAvatar.DISLname, None, None, receiverId, receiverName, TALK_ACCOUNT, None)
|
||||
self.historyComplete.append(newMessage)
|
||||
self.addToHistoryDoId(newMessage, localAvatar.doId)
|
||||
self.addToHistoryDISLId(newMessage, base.cr.accountDetailRecord.playerAccountId)
|
||||
messenger.send('NewOpenMessage', [newMessage])
|
||||
return error
|
||||
|
||||
def sendGuildSpeedChat(self, type, msgIndex):
|
||||
error = None
|
||||
if self.checkGuildSpeedChat():
|
||||
base.cr.guildManager.sendSC(msgIndex)
|
||||
else:
|
||||
print('Guild Speedchat error')
|
||||
error = ERROR_NO_GUILD_CHAT
|
||||
return error
|
||||
|
||||
def getWhisperReplyId(self):
|
||||
if self.lastWhisper:
|
||||
toPlayer = 0
|
||||
if self.lastWhisper == self.lastWhisperPlayerId:
|
||||
toPlayer = 1
|
||||
return (self.lastWhisper, toPlayer)
|
||||
return (0, 0)
|
|
@ -630,6 +630,7 @@ class TalkAssistant(DirectObject.DirectObject):
|
|||
message,
|
||||
[],
|
||||
0])
|
||||
base.cr.chatHandler.sendChatMessage(message)
|
||||
messenger.send('chatUpdate', [message, chatFlags])
|
||||
return error
|
||||
|
||||
|
@ -652,6 +653,7 @@ class TalkAssistant(DirectObject.DirectObject):
|
|||
message,
|
||||
[],
|
||||
0], sendToId=receiverAvId)
|
||||
base.cr.chatHandler.sendWhisperMessage(message, receiverAvId)
|
||||
return error
|
||||
|
||||
def sendAccountTalk(self, message, receiverAccount):
|
||||
|
|
|
@ -418,6 +418,7 @@ class OTPClientRepository(ClientRepositoryBase):
|
|||
self.centralLogger = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_CENTRAL_LOGGER, 'CentralLogger')
|
||||
if __astron__:
|
||||
self.astronLoginManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_ASTRON_LOGIN_MANAGER, 'AstronLoginManager')
|
||||
self.chatHandler = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_CHAT_ROUTER, 'ChatHandler')
|
||||
|
||||
def startLeakDetector(self):
|
||||
if hasattr(self, 'leakDetector'):
|
||||
|
|
|
@ -87,4 +87,6 @@ OTP_ZONE_ID_MANAGEMENT = 2
|
|||
OTP_ZONE_ID_DISTRICTS = 3
|
||||
OTP_ZONE_ID_DISTRICTS_STATS = 4
|
||||
OTP_ZONE_ID_ELEMENTS = 5
|
||||
OTP_DO_ID_CHAT_HANDLER = 4681
|
||||
OTP_DO_ID_CHAT_ROUTER = OTP_DO_ID_CHAT_HANDLER
|
||||
OTP_NET_MESSENGER_CHANNEL = (OTP_DO_ID_UBER_DOG << 32) + OTP_ZONE_ID_MANAGEMENT
|
||||
|
|
175
otp/friends/AvatarFriendsDB.py
Normal file
175
otp/friends/AvatarFriendsDB.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
import MySQLdb
|
||||
import MySQLdb.constants.CR
|
||||
#import MySQLdb
|
||||
import datetime
|
||||
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||
from otp.distributed import OtpDoGlobals
|
||||
from otp.uberdog.DBInterface import DBInterface
|
||||
|
||||
SERVER_GONE_ERROR = MySQLdb.constants.CR.SERVER_GONE_ERROR
|
||||
SERVER_LOST = MySQLdb.constants.CR.SERVER_LOST
|
||||
|
||||
class AvatarFriendsDB(DBInterface):
|
||||
"""
|
||||
DB wrapper class for avatar friends! All SQL code for avatar friends should be in here.
|
||||
"""
|
||||
notify = directNotify.newCategory('AvatarFriendsDB')
|
||||
|
||||
def __init__(self,host,port,user,password,dbname):
|
||||
self.sqlAvailable = True
|
||||
if not self.sqlAvailable:
|
||||
return
|
||||
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.dbname = self.processDBName(dbname)
|
||||
try:
|
||||
self.db = MySQLdb.connect(host=host,
|
||||
port=port,
|
||||
user=user,
|
||||
password=password)
|
||||
except MySQLdb.OperationalError as e:
|
||||
if __debug__:
|
||||
self.notify.warning("Failed to connect to MySQL at %s:%d. Avatar friends DB is disabled."%(host,port))
|
||||
self.sqlAvailable = 0
|
||||
uber.sqlAvailable = 0
|
||||
return
|
||||
|
||||
if __debug__:
|
||||
self.notify.info("Connected to avatar friends MySQL db at %s:%d."%(host,port))
|
||||
|
||||
#temp hack for initial dev, create DB structure if it doesn't exist already
|
||||
cursor = self.db.cursor()
|
||||
try:
|
||||
cursor.execute("CREATE DATABASE `%s`"%self.dbname)
|
||||
if __debug__:
|
||||
self.notify.info("Database '%s' did not exist, created a new one!"%self.dbname)
|
||||
except MySQLdb.ProgrammingError as e:
|
||||
pass
|
||||
|
||||
cursor.execute("USE `%s`"%self.dbname)
|
||||
if __debug__:
|
||||
self.notify.debug("Using database '%s'"%self.dbname)
|
||||
|
||||
try:
|
||||
cursor.execute("""
|
||||
CREATE TABLE `avatarfriends` (
|
||||
`friendId1` int(32) UNSIGNED NOT NULL,
|
||||
`friendId2` int(32) UNSIGNED NOT NULL,
|
||||
`openChatYesNo` tinyint(1) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`friendId1`,`friendId2`),
|
||||
KEY `idxFriend1` (`friendId1`),
|
||||
KEY `idxFriend2` (`friendId2`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
||||
""")
|
||||
if __debug__:
|
||||
self.notify.info("Table avatarfriends did not exist, created a new one!")
|
||||
except MySQLdb.OperationalError as e:
|
||||
pass
|
||||
|
||||
def reconnect(self):
|
||||
if __debug__:
|
||||
self.notify.debug("MySQL server was missing, attempting to reconnect.")
|
||||
try: self.db.close()
|
||||
except: pass
|
||||
self.db = MySQLdb.connect(host=self.host,
|
||||
port=self.port,
|
||||
user=self.user,
|
||||
password=self.password)
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute("USE `%s`"%self.dbname)
|
||||
if __debug__:
|
||||
self.notify.debug("Reconnected to MySQL server at %s:%d."%(self.host,self.port))
|
||||
|
||||
def disconnect(self):
|
||||
if not self.sqlAvailable:
|
||||
return
|
||||
self.db.close()
|
||||
self.db = None
|
||||
|
||||
def getFriends(self,avatarId):
|
||||
if not self.sqlAvailable:
|
||||
return []
|
||||
|
||||
cursor = MySQLdb.cursors.DictCursor(self.db)
|
||||
try:
|
||||
cursor.execute("SELECT * FROM avatarfriends WHERE friendId1=%s OR friendId2=%s",(avatarId,avatarId))
|
||||
except MySQLdb.OperationalError as e:
|
||||
if e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST:
|
||||
self.reconnect()
|
||||
cursor = MySQLdb.cursors.DictCursor(self.db)
|
||||
cursor.execute("SELECT * FROM avatarfriends WHERE friendId1=%s OR friendId2=%s",(avatarId,avatarId))
|
||||
else:
|
||||
raise e
|
||||
|
||||
friends = cursor.fetchall()
|
||||
|
||||
cleanfriends = {}
|
||||
for f in friends:
|
||||
if f['friendId1'] == avatarId:
|
||||
cleanfriends[f['friendId2']] = f['openChatYesNo']
|
||||
else:
|
||||
cleanfriends[f['friendId1']] = f['openChatYesNo']
|
||||
return cleanfriends
|
||||
|
||||
def addFriendship(self,avatarId1,avatarId2,openChat=0):
|
||||
if not self.sqlAvailable:
|
||||
return
|
||||
cursor = MySQLdb.cursors.DictCursor(self.db)
|
||||
try:
|
||||
if avatarId1 < avatarId2:
|
||||
cursor.execute("INSERT INTO avatarfriends (friendId1,friendId2,openChatYesNo) VALUES (%s,%s,%s)",(avatarId1,avatarId2,openChat))
|
||||
else:
|
||||
cursor.execute("INSERT INTO avatarfriends (friendId1,friendId2,openChatYesNo) VALUES (%s,%s,%s)",(avatarId2,avatarId1,openChat))
|
||||
except MySQLdb.OperationalError as e:
|
||||
if e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST:
|
||||
self.reconnect()
|
||||
cursor = MySQLdb.cursors.DictCursor(self.db)
|
||||
if avatarId1 < avatarId2:
|
||||
cursor.execute("INSERT INTO avatarfriends (friendId1,friendId2,openChatYesNo) VALUES (%s,%s,%s)",(avatarId1,avatarId2,openChat))
|
||||
else:
|
||||
cursor.execute("INSERT INTO avatarfriends (friendId1,friendId2,openChatYesNo) VALUES (%s,%s,%s)",(avatarId2,avatarId1,openChat))
|
||||
else:
|
||||
raise e
|
||||
|
||||
self.db.commit()
|
||||
|
||||
def removeFriendship(self,avatarId1,avatarId2):
|
||||
if not self.sqlAvailable:
|
||||
return
|
||||
cursor = MySQLdb.cursors.DictCursor(self.db)
|
||||
try:
|
||||
if avatarId1 < avatarId2:
|
||||
cursor.execute("DELETE FROM avatarfriends where friendId1=%s AND friendId2=%s",(avatarId1,avatarId2))
|
||||
else:
|
||||
cursor.execute("DELETE FROM avatarfriends where friendId1=%s AND friendId2=%s",(avatarId2,avatarId1))
|
||||
except MySQLdb.OperationalError as e:
|
||||
if e[0] == SERVER_GONE_ERROR or e[0] == SERVER_LOST: # 'Lost connection to MySQL server during query'
|
||||
self.reconnect()
|
||||
cursor = MySQLdb.cursors.DictCursor(self.db)
|
||||
if avatarId1 < avatarId2:
|
||||
cursor.execute("DELETE FROM avatarfriends where friendId1=%s AND friendId2=%s",(avatarId1,avatarId2))
|
||||
else:
|
||||
cursor.execute("DELETE FROM avatarfriends where friendId1=%s AND friendId2=%s",(avatarId2,avatarId1))
|
||||
else:
|
||||
raise e
|
||||
|
||||
self.db.commit()
|
||||
|
||||
#for debugging only
|
||||
def dumpFriendsTable(self):
|
||||
assert self.db,"Tried to call dumpFriendsTable when DB was closed."
|
||||
cursor = MySQLdb.cursors.DictCursor(self.db)
|
||||
cursor.execute("SELECT * FROM avatarfriends")
|
||||
return cursor.fetchallDict()
|
||||
|
||||
#for debugging only
|
||||
def clearFriendsTable(self):
|
||||
assert self.db,"Tried to call clearFriendsTable when DB was closed."
|
||||
cursor = MySQLdb.cursors.DictCursor(self.db)
|
||||
cursor.execute("TRUNCATE TABLE avatarfriends")
|
||||
self.db.commit()
|
||||
|
||||
|
9
otp/friends/AvatarFriendsDB.sql
Normal file
9
otp/friends/AvatarFriendsDB.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE `avatarfriends` (
|
||||
`friendId1` int(32) NOT NULL,
|
||||
`friendId2` int(32) NOT NULL,
|
||||
`openChatYesNo` tinyint(1) NOT NULL default '0',
|
||||
PRIMARY KEY (`friendId1`,`friendId2`),
|
||||
KEY `idxFriend1` (`friendId1`),
|
||||
KEY `idxFriend2` (`friendId2`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
|
@ -1,5 +1,387 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectUD import DistributedObjectUD
|
||||
from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
|
||||
from otp.otpbase import OTPGlobals
|
||||
from otp.ai import AIMsgTypes
|
||||
from otp.uberdog.RejectCode import RejectCode
|
||||
|
||||
class AvatarFriendsManagerUD(DistributedObjectUD):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('AvatarFriendsManagerUD')
|
||||
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)
|
||||
|
|
|
@ -1,5 +1,752 @@
|
|||
from otp.ai.AIBaseGlobal import *
|
||||
from pandac.PandaModules import *
|
||||
from direct.distributed import DistributedObjectAI
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from direct.distributed.PyDatagram import *
|
||||
from otp.avatar import DistributedAvatarAI
|
||||
from otp.otpbase import OTPGlobals
|
||||
import datetime
|
||||
import json
|
||||
import random
|
||||
import os
|
||||
# all of this is commented out because the friend manager was moved
|
||||
# to the OTP server
|
||||
# yeah which we don't have access too lul so lets uncomment it out
|
||||
|
||||
class FriendManagerAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('FriendManagerAI')
|
||||
# we need something for the AI DC parser to load
|
||||
class FriendManagerAI(DistributedObjectAI.DistributedObjectAI):
|
||||
# These structures record the invitations currently being handled.
|
||||
nextContext = 0
|
||||
invites = {}
|
||||
inviters = {}
|
||||
invitees = {}
|
||||
|
||||
# This is the length of time, in seconds, to sit on a secret guess
|
||||
# before processing it. This serves to make it difficult to guess
|
||||
# passwords at random.
|
||||
SecretDelay = 1.0
|
||||
|
||||
# This is the length of time that should elapse before we start to
|
||||
# forget who has declined friendships from whom.
|
||||
DeclineFriendshipTimeout = 600.0
|
||||
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory("FriendManagerAI")
|
||||
friendDataFolder = simbase.config.GetString('server-data-folder', 'dependencies/backups/')
|
||||
|
||||
# This subclass is used to record currently outstanding
|
||||
# in-the-game invitation requests.
|
||||
class Invite:
|
||||
def __init__(self, context, inviterId, inviteeId):
|
||||
self.context = context
|
||||
self.inviterId = inviterId
|
||||
self.inviteeId = inviteeId
|
||||
self.inviter = None
|
||||
self.invitee = None
|
||||
self.inviteeKnows = 0
|
||||
self.sendSpecialResponse = 0
|
||||
|
||||
def __init__(self, air):
|
||||
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
|
||||
|
||||
# We maintain two maps of toons who have declined
|
||||
# friendships. We add entries to map1, and every ten
|
||||
# minutes, we roll map1 into map2. This provides a
|
||||
# timeout of ten to twenty minutes for a particular
|
||||
# rejection, and also prevents the maps from growing very
|
||||
# large in memory.
|
||||
self.declineFriends1 = {}
|
||||
self.declineFriends2 = {}
|
||||
self.lastRollTime = 0
|
||||
self.filename = self.getFilename()
|
||||
self.trueFriendCodes = self.loadTrueFriendCodes()
|
||||
self.district = str(self.air.districtId)
|
||||
|
||||
taskMgr.add(self.trueFriendCodesTask, 'truefriend-codes-clear-task')
|
||||
|
||||
|
||||
def generate(self):
|
||||
DistributedObjectAI.DistributedObjectAI.generate(self)
|
||||
|
||||
# The FriendManagerAI always listens for these events, which
|
||||
# will be sent in response to secret requests to the database,
|
||||
# via the AIR.
|
||||
self.accept("makeFriendsReply", self.makeFriendsReply)
|
||||
self.accept("requestSecretReply", self.__requestSecretReply)
|
||||
self.accept("submitSecretReply", self.__submitSecretReply)
|
||||
|
||||
def delete(self):
|
||||
self.ignore("makeFriendsReply")
|
||||
self.ignore("requestSecretReply")
|
||||
self.ignore("submitSecretReply")
|
||||
|
||||
# Clean up all outstanding secret request tasks.
|
||||
taskMgr.removeTasksMatching("secret-*")
|
||||
|
||||
DistributedObjectAI.DistributedObjectAI.delete(self)
|
||||
|
||||
|
||||
# Messages sent from inviter client to AI
|
||||
|
||||
def friendQuery(self, inviteeId):
|
||||
"""friendQuery(self, int inviterId, int inviteeId)
|
||||
|
||||
Sent by the inviter to the AI to initiate a friendship
|
||||
request.
|
||||
"""
|
||||
inviterId = self.air.getAvatarIdFromSender()
|
||||
invitee = self.air.doId2do.get(inviteeId)
|
||||
|
||||
# see if the inviteeId is valid
|
||||
if not invitee:
|
||||
self.air.writeServerEvent('suspicious', inviteeId, 'FriendManagerAI.friendQuery not on list')
|
||||
return
|
||||
|
||||
self.notify.debug("AI: friendQuery(%d, %d)" % (inviterId, inviteeId))
|
||||
self.newInvite(inviterId, inviteeId)
|
||||
|
||||
def cancelFriendQuery(self, context):
|
||||
"""cancelFriendQuery(self, int context)
|
||||
|
||||
Sent by the inviter to the AI to cancel a pending friendship
|
||||
request.
|
||||
"""
|
||||
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
self.notify.debug("AI: cancelFriendQuery(%d)" % (context))
|
||||
|
||||
try:
|
||||
invite = FriendManagerAI.invites[context]
|
||||
except:
|
||||
# The client might legitimately try to cancel a context
|
||||
# that has already been cancelled.
|
||||
#self.air.writeServerEvent('suspicious', avId, 'FriendManagerAI.cancelFriendQuery unknown context')
|
||||
#FriendManagerAI.notify.warning('Message for unknown context ' + `context`)
|
||||
return
|
||||
|
||||
self.cancelInvite(invite)
|
||||
|
||||
|
||||
# Messages sent from invitee client to AI
|
||||
|
||||
def inviteeFriendConsidering(self, response, context):
|
||||
"""inviteeFriendConsidering(self, int response, int context)
|
||||
|
||||
Sent by the invitee to the AI to indicate whether the invitee
|
||||
is able to consider the request right now.
|
||||
|
||||
The responses are:
|
||||
0 - no
|
||||
1 - yes
|
||||
4 - the invitee is ignoring you.
|
||||
"""
|
||||
self.notify.debug("AI: inviteeFriendConsidering(%d, %d)" % (response, context))
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
|
||||
try:
|
||||
invite = FriendManagerAI.invites[context]
|
||||
except:
|
||||
self.air.writeServerEvent('suspicious', avId, 'FriendManagerAI.inviteeFriendConsidering unknown context')
|
||||
FriendManagerAI.notify.warning('Message for unknown context ' + context)
|
||||
return
|
||||
|
||||
if response == 1:
|
||||
self.inviteeAvailable(invite)
|
||||
else:
|
||||
self.inviteeUnavailable(invite, response)
|
||||
|
||||
def inviteeFriendResponse(self, yesNoMaybe, context):
|
||||
"""inviteeFriendResponse(self, int yesNoMaybe, int context)
|
||||
|
||||
Sent by the invitee to the AI, following an affirmative
|
||||
response in inviteeFriendConsidering, to indicate whether or
|
||||
not the user decided to accept the friendship.
|
||||
"""
|
||||
|
||||
self.notify.debug("AI: inviteeFriendResponse(%d, %d)" % (yesNoMaybe, context))
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
if not avId:
|
||||
return
|
||||
try:
|
||||
invite = FriendManagerAI.invites[context]
|
||||
except:
|
||||
self.air.writeServerEvent('suspicious', avId, 'FriendManagerAI.inviteeFriendResponse unknown context')
|
||||
FriendManagerAI.notify.warning('Message for unknown context ' + context)
|
||||
return
|
||||
|
||||
if yesNoMaybe == 1:
|
||||
invitee = invite.inviteeId
|
||||
if invitee in self.air.doId2do:
|
||||
invitee = self.air.doId2do[invitee]
|
||||
else:
|
||||
del FriendManagerAI.invites[context]
|
||||
return
|
||||
inviter = invite.inviterId
|
||||
if not (invitee and inviter):
|
||||
#probably logged off before a response
|
||||
return
|
||||
if inviter in self.air.doId2do:
|
||||
inviter = self.air.doId2do[inviter]
|
||||
else:
|
||||
del FriendManagerAI.invites[context]
|
||||
return
|
||||
dg = PyDatagram()
|
||||
dg.addServerHeader(self.GetPuppetConnectionChannel(invitee.getDoId()), self.air.ourChannel,
|
||||
CLIENTAGENT_DECLARE_OBJECT)
|
||||
dg.addUint32(inviter.getDoId())
|
||||
dg.addUint16(self.air.dclassesByName['DistributedToonAI'].getNumber())
|
||||
self.air.send(dg)
|
||||
|
||||
dg = PyDatagram()
|
||||
dg.addServerHeader(self.GetPuppetConnectionChannel(inviter.getDoId()), self.air.ourChannel,
|
||||
CLIENTAGENT_DECLARE_OBJECT)
|
||||
dg.addUint32(invitee.getDoId())
|
||||
dg.addUint16(self.air.dclassesByName['DistributedToonAI'].getNumber())
|
||||
self.air.send(dg)
|
||||
invitee.extendFriendsList(inviter.getDoId(), 0)
|
||||
inviter.extendFriendsList(invitee.getDoId(), 0)
|
||||
invitee.d_setFriendsList(invitee.getFriendsList())
|
||||
inviter.d_setFriendsList(inviter.getFriendsList())
|
||||
|
||||
del FriendManagerAI.invites[context]
|
||||
# else:
|
||||
# self.noFriends(invite, yesNoMaybe)
|
||||
|
||||
def inviteeAcknowledgeCancel(self, context):
|
||||
"""inviteeAcknowledgeCancel(self, int context)
|
||||
|
||||
Sent by the invitee to the AI, in response to an
|
||||
inviteeCancelFriendQuery message. This simply acknowledges
|
||||
receipt of the message and tells the AI that it is safe to
|
||||
clean up the context.
|
||||
"""
|
||||
|
||||
self.notify.debug("AI: inviteeAcknowledgeCancel(%d)" % (context))
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
|
||||
try:
|
||||
invite = FriendManagerAI.invites[context]
|
||||
except:
|
||||
# The client might legitimately try to cancel a context
|
||||
# that has already been cancelled.
|
||||
#self.air.writeServerEvent('suspicious', avId, 'FriendManagerAI.inviteeAcknowledgeCancel unknown context')
|
||||
#FriendManagerAI.notify.warning('Message for unknown context ' + `context`)
|
||||
return
|
||||
|
||||
self.clearInvite(invite)
|
||||
|
||||
|
||||
# Messages sent from AI to inviter client
|
||||
|
||||
def down_friendConsidering(self, recipient, yesNoAlready, context):
|
||||
"""friendConsidering(self, DistributedObject recipient,
|
||||
int yesNoAlready, int context)
|
||||
|
||||
Sent by the AI to the inviter client to indicate whether the
|
||||
invitee is able to consider the request right now.
|
||||
|
||||
The responses are:
|
||||
# 0 - the invitee is busy
|
||||
# 2 - the invitee is already your friend
|
||||
# 3 - the invitee is yourself
|
||||
# 4 - the invitee is ignoring you.
|
||||
# 6 - the invitee not accepting friends
|
||||
"""
|
||||
|
||||
self.sendUpdateToAvatarId(recipient, "friendConsidering", [yesNoAlready, context])
|
||||
self.notify.debug("AI: friendConsidering(%d, %d)" % (yesNoAlready, context))
|
||||
|
||||
def down_friendResponse(self, recipient, yesNoMaybe, context):
|
||||
"""friendResponse(self, DistributedOBject recipient,
|
||||
int yesNoMaybe, int context)
|
||||
|
||||
Sent by the AI to the inviter client, following an affirmitive
|
||||
response in friendConsidering, to indicate whether or not the
|
||||
user decided to accept the friendship.
|
||||
"""
|
||||
|
||||
self.sendUpdateToAvatarId(recipient, "friendResponse", [yesNoMaybe, context])
|
||||
self.notify.debug("AI: friendResponse(%d, %d)" % (yesNoMaybe, context))
|
||||
|
||||
|
||||
|
||||
# Messages sent from AI to invitee client
|
||||
|
||||
def down_inviteeFriendQuery(self, recipient, inviterId, inviterName,
|
||||
inviterDna, context):
|
||||
"""inviteeFriendQuery(self, DistributedObject recipient,
|
||||
int inviterId, string inviterName,
|
||||
AvatarDNA inviterDna, int context)
|
||||
|
||||
Sent by the AI to the invitee client to initiate a friendship
|
||||
request from the indiciated inviter. The invitee client
|
||||
should respond immediately with inviteeFriendConsidering, to
|
||||
indicate whether the invitee is able to consider the
|
||||
invitation right now.
|
||||
"""
|
||||
|
||||
self.sendUpdateToAvatarId(recipient, "inviteeFriendQuery",
|
||||
[inviterId, inviterName,
|
||||
inviterDna.makeNetString(), context])
|
||||
self.notify.debug("AI: inviteeFriendQuery(%d, %s, dna, %d)" % (inviterId, inviterName, context))
|
||||
|
||||
def down_inviteeCancelFriendQuery(self, recipient, context):
|
||||
"""inviteeCancelFriendQuery(self, DistributedObject recipient,
|
||||
int context)
|
||||
|
||||
Sent by the AI to the invitee client to initiate that the
|
||||
inviter has rescinded his/her previous invitation by clicking
|
||||
the cancel button.
|
||||
"""
|
||||
|
||||
self.sendUpdateToAvatarId(recipient, "inviteeCancelFriendQuery", [context])
|
||||
self.notify.debug("AI: inviteeCancelFriendQuery(%d)" % (context))
|
||||
|
||||
|
||||
def getFilename(self):
|
||||
if not hasattr(self, 'district'):
|
||||
self.district = str(self.air.districtId)
|
||||
|
||||
return '{0}{1}{2}{3}.json'.format(self.friendDataFolder, 'trueFriendCodes/', 'trueFriendCodes_', self.district)
|
||||
|
||||
def loadTrueFriendCodes(self):
|
||||
try:
|
||||
trueFriendCodesFile = open(self.filename, 'r')
|
||||
trueFriendsCodesData = json.load(trueFriendCodesFile)
|
||||
return trueFriendsCodesData
|
||||
except:
|
||||
return {}
|
||||
# Messages involving secrets
|
||||
|
||||
def deleteSecret(self, secret):
|
||||
#delete secrets in true friend codes
|
||||
if secret in self.trueFriendCodes:
|
||||
del self.trueFriendCodes[secret]
|
||||
self.updateTrueFriendCodesFile()
|
||||
|
||||
def updateTrueFriendCodesFile(self):
|
||||
#update the file for true friend codes
|
||||
if not os.path.exists(os.path.dirname(self.filename)):
|
||||
os.makedirs(os.path.dirname(self.filename))
|
||||
trueFriendCodesFile = open(self.filename, 'w')
|
||||
trueFriendCodesFile.seek(0)
|
||||
json.dump(self.trueFriendCodes, trueFriendCodesFile)
|
||||
trueFriendCodesFile.close()
|
||||
|
||||
def trueFriendCodesTask(self, task):
|
||||
for trueFriendCode in list(self.trueFriendCodes.keys()):
|
||||
trueFriendCodeInfo = self.trueFriendCodes[trueFriendCode] #get info about the code
|
||||
trueFriendCodeDay = trueFriendCodeInfo[1] #get the day of the code
|
||||
today = datetime.datetime.now().day
|
||||
if trueFriendCodeDay + 2 == today:
|
||||
#true friend code is 2 days old remove it
|
||||
self.deleteSecret(trueFriendCode)
|
||||
return task.again
|
||||
|
||||
|
||||
def requestSecret(self):
|
||||
"""requestSecret(self)
|
||||
|
||||
Sent by the client to the AI to request a new "secret" for the
|
||||
user.
|
||||
"""
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
av = self.air.doId2do.get(avId)
|
||||
if not av:
|
||||
self.notify.warning('no av from avid: {0}'.format(avId))
|
||||
return
|
||||
#if friendslist is less than the friends limit
|
||||
if len(av.getFriendsList()) < OTPGlobals.MaxFriends:
|
||||
#get current day
|
||||
day = datetime.datetime.now().day
|
||||
#cretae a true friend code
|
||||
trueFriendCode = self.createTrueFriendCode()
|
||||
#set the information up
|
||||
self.trueFriendCodes[trueFriendCode] = (avId, day)
|
||||
#update the file to include the true friend code
|
||||
self.updateTrueFriendCodesFile()
|
||||
self.down_requestSecretResponse(avId, 1, trueFriendCode)
|
||||
self.air.writeServerEvent('true-friend-code-requested', avId=avId, trueFriendCode=trueFriendCode)
|
||||
else:
|
||||
self.down_requestSecretResponse(avId, 2, "")
|
||||
#self.air.requestSecret(avId)
|
||||
|
||||
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.
|
||||
2 - Too many friends
|
||||
"""
|
||||
self.sendUpdateToAvatarId(recipient, 'requestSecretResponse', [result, secret])
|
||||
|
||||
def createTrueFriendCode(self):
|
||||
chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||
|
||||
def randomChar():
|
||||
return random.choice(chars)
|
||||
|
||||
trueFriendCode = '{0}{1}{2} {3}{4}{5}'.format(randomChar(), randomChar(), randomChar(), randomChar(), randomChar(), randomChar())
|
||||
return trueFriendCode
|
||||
|
||||
def submitSecret(self, secret):
|
||||
"""submitSecret(self, string secret)
|
||||
|
||||
Sent by the client to the AI to submit a "secret" typed in by
|
||||
the user.
|
||||
"""
|
||||
avId = self.air.getAvatarIdFromSender()
|
||||
av = self.air.doId2do.get(avId)
|
||||
if not av:
|
||||
self.notify.warning('submitSecret: no av ')
|
||||
return
|
||||
secretInfo = self.trueFriendCodes.get(secret)
|
||||
if not secretInfo:
|
||||
self.down_submitSecretResponse(avId, 0, 0)
|
||||
return
|
||||
friendId = secretInfo[0]
|
||||
friend = self.air.doId2do.get(friendId)
|
||||
if av:
|
||||
if friend:
|
||||
if avId == friendId:
|
||||
self.down_submitSecretResponse(avId, 3, 0)
|
||||
self.deleteSecret(secret)
|
||||
elif len(friend.getFriendsList()) < OTPGlobals.MaxFriends or len(
|
||||
av.getFriendsList()) < OTPGlobals.MaxFriends:
|
||||
dg = PyDatagram()
|
||||
dg.addServerHeader(self.GetPuppetConnectionChannel(friendId), self.air.ourChannel,
|
||||
CLIENTAGENT_DECLARE_OBJECT)
|
||||
dg.addUint32(avId)
|
||||
dg.addUint16(self.air.dclassesByName['DistributedToonAI'].getNumber())
|
||||
self.air.send(dg)
|
||||
|
||||
dg = PyDatagram()
|
||||
dg.addServerHeader(self.GetPuppetConnectionChannel(avId), self.air.ourChannel,
|
||||
CLIENTAGENT_DECLARE_OBJECT)
|
||||
dg.addUint32(friendId)
|
||||
dg.addUint16(self.air.dclassesByName['DistributedToonAI'].getNumber())
|
||||
self.air.send(dg)
|
||||
|
||||
friend.extendFriendsList(avId, 1)
|
||||
av.extendFriendsList(friendId, 1)
|
||||
|
||||
friend.d_setFriendsList(friend.getFriendsList())
|
||||
av.d_setFriendsList(av.getFriendsList())
|
||||
av.addTrueFriend(friendId)
|
||||
self.down_submitSecretResponse(avId, 1, friendId)
|
||||
self.deleteSecret(secret)
|
||||
else:
|
||||
#friends list is full
|
||||
self.down_submitSecretResponse(avId, 2, friendId)
|
||||
else:
|
||||
#offline friend
|
||||
def handleAvatar(dclass, fields):
|
||||
if dclass != self.air.dclassesByName['DistributedToonAI']:
|
||||
return
|
||||
|
||||
newFriendsList = []
|
||||
oldFriendsList = fields['setFriendsList'][0]
|
||||
if len(oldFriendsList) >= OTPGlobals.MaxFriends:
|
||||
self.d_submitSecretResponse(avId, 2, friendId)
|
||||
return
|
||||
|
||||
for i in oldFriendsList:
|
||||
newFriendsList.append(i)
|
||||
|
||||
newFriendsList.append((avId, 1))
|
||||
self.air.dbInterface.updateObject(self.air.dbId, friendId,
|
||||
self.air.dclassesByName['DistributedToonAI'],
|
||||
{'setFriendsList': [newFriendsList]})
|
||||
av.extendFriendsList(friendId, 1)
|
||||
av.d_setFriendsList(av.getFriendsList())
|
||||
self.d_submitSecretResponse(avId, 1, friendId)
|
||||
self.deleteSecret(secret)
|
||||
|
||||
self.air.dbInterface.queryObject(self.air.dbId, friendId, handleAvatar)
|
||||
|
||||
self.air.writeServerEvent('truefriend-code-submitted', avId=avId, friendId=friendId, trueFriendCode=secret)
|
||||
|
||||
# We have to sit on this request for a few seconds before
|
||||
# processing it. This delay is solely to discourage password
|
||||
# guessing.
|
||||
# taskName = "secret-" + str(avId)
|
||||
# taskMgr.remove(taskName)
|
||||
#if FriendManagerAI.SecretDelay:
|
||||
# taskMgr.doMethodLater(FriendManagerAI.SecretDelay,
|
||||
# self.continueSubmission,
|
||||
#taskName,
|
||||
#extraArgs = (avId, secret))
|
||||
# else:
|
||||
# No delay
|
||||
# self.continueSubmission(avId, secret)
|
||||
|
||||
def continueSubmission(self, avId, secret):
|
||||
"""continueSubmission(self, avId, secret)
|
||||
Finishes the work of submitSecret, a short time later.
|
||||
"""
|
||||
self.air.submitSecret(avId, secret)
|
||||
|
||||
def down_submitSecretResponse(self, recipient, result, avId):
|
||||
"""submitSecret(self, int8 result, int32 avId)
|
||||
|
||||
Sent by the AI to the client in response to submitSecret().
|
||||
result is one of:
|
||||
|
||||
0 - Failure. The secret is unknown or has timed out.
|
||||
1 - Success. You are now friends with the indicated avId.
|
||||
2 - Failure. One of the avatars has too many friends already.
|
||||
3 - Failure. You just used up your own secret.
|
||||
|
||||
"""
|
||||
self.sendUpdateToAvatarId(recipient, 'submitSecretResponse', [result, avId])
|
||||
|
||||
|
||||
# Support methods
|
||||
|
||||
def newInvite(self, inviterId, inviteeId):
|
||||
context = FriendManagerAI.nextContext
|
||||
FriendManagerAI.nextContext += 1
|
||||
|
||||
invite = FriendManagerAI.Invite(context, inviterId, inviteeId)
|
||||
FriendManagerAI.invites[context] = invite
|
||||
|
||||
# If the invitee has previously (recently) declined a
|
||||
# friendship from this inviter, don't ask again.
|
||||
previous = self.__previousResponse(inviteeId, inviterId)
|
||||
if previous != None:
|
||||
self.inviteeUnavailable(invite, previous + 10)
|
||||
return
|
||||
|
||||
# If the invitee is presently being invited by someone else,
|
||||
# we don't even have to bother him.
|
||||
if inviteeId in FriendManagerAI.invitees:
|
||||
self.inviteeUnavailable(invite, 0)
|
||||
return
|
||||
|
||||
if invite.inviterId == invite.inviteeId:
|
||||
# You can't be friends with yourself.
|
||||
self.inviteeUnavailable(invite, 3)
|
||||
return
|
||||
|
||||
# If the inviter is already involved in some other context,
|
||||
# that one is now void.
|
||||
if inviterId in FriendManagerAI.inviters:
|
||||
self.cancelInvite(FriendManagerAI.inviters[inviterId])
|
||||
|
||||
FriendManagerAI.inviters[inviterId] = invite
|
||||
FriendManagerAI.invitees[inviteeId] = invite
|
||||
|
||||
#self.air.queryObject(inviteeId, self.gotInvitee, invite)
|
||||
#self.air.queryObject(inviterId, self.gotInviter, invite)
|
||||
invite.inviter = self.air.doId2do.get(inviterId)
|
||||
invite.invitee = self.air.doId2do.get(inviteeId)
|
||||
if invite.inviter and invite.invitee:
|
||||
self.beginInvite(invite)
|
||||
|
||||
|
||||
# def gotInviter(self, handle, invite):
|
||||
# if not invite.inviter:
|
||||
# invite.inviter = handle
|
||||
# if invite.invitee:
|
||||
# self.beginInvite(invite)
|
||||
|
||||
# def gotInvitee(self, handle, invite):
|
||||
# if not invite.invitee:
|
||||
# invite.invitee = handle
|
||||
# if invite.inviter:
|
||||
# self.beginInvite(invite)
|
||||
|
||||
def beginInvite(self, invite):
|
||||
# Ask the invitee if he is available to consider being
|
||||
# someone's friend--that is, that he's not busy playing a
|
||||
# minigame or something.
|
||||
|
||||
invite.inviteeKnows = 1
|
||||
self.down_inviteeFriendQuery(invite.inviteeId, invite.inviterId,
|
||||
invite.inviter.getName(),
|
||||
invite.inviter.dna, invite.context)
|
||||
|
||||
def inviteeUnavailable(self, invite, code):
|
||||
# Cannot make the request for one of these reasons:
|
||||
#
|
||||
# 0 - the invitee is busy
|
||||
# 2 - the invitee is already your friend
|
||||
# 3 - the invitee is yourself
|
||||
# 4 - the invitee is ignoring you.
|
||||
# 6 - the invitee not accepting friends
|
||||
self.down_friendConsidering(invite.inviterId, code, invite.context)
|
||||
|
||||
# That ends the invitation.
|
||||
self.clearInvite(invite)
|
||||
|
||||
def inviteeAvailable(self, invite):
|
||||
# The invitee is considering our friendship request.
|
||||
self.down_friendConsidering(invite.inviterId, 1, invite.context)
|
||||
|
||||
|
||||
def noFriends(self, invite, yesNoMaybe):
|
||||
# The invitee declined to make friends.
|
||||
#
|
||||
# 0 - no
|
||||
# 2 - unable to answer; e.g. entered a minigame or something.
|
||||
# 3 - the invitee has too many friends already.
|
||||
|
||||
if yesNoMaybe == 0 or yesNoMaybe == 3:
|
||||
# The user explictly said no or has too many friends.
|
||||
# Disallow this guy from asking again for the next ten
|
||||
# minutes or so.
|
||||
|
||||
if invite.inviteeId not in self.declineFriends1:
|
||||
self.declineFriends1[invite.inviteeId] = {}
|
||||
self.declineFriends1[invite.inviteeId][invite.inviterId] = yesNoMaybe
|
||||
|
||||
self.down_friendResponse(invite.inviterId, yesNoMaybe, invite.context)
|
||||
self.clearInvite(invite)
|
||||
|
||||
def makeFriends(self, invite):
|
||||
# The invitee agreed to make friends.
|
||||
self.air.makeFriends(invite.inviteeId, invite.inviterId, 0,
|
||||
invite.context)
|
||||
self.down_friendResponse(invite.inviterId, 1, invite.context)
|
||||
# The reply will clear the context out when it comes in.
|
||||
|
||||
def makeSpecialFriends(self, requesterId, avId):
|
||||
# The "requester" has typed in a codeword that successfully
|
||||
# matches a friend in the world. Attempt to make the
|
||||
# friendship (with chat permission), and send back a code
|
||||
# indicating success or failure.
|
||||
|
||||
# Get a special Invite structure just for this purpose.
|
||||
context = FriendManagerAI.nextContext
|
||||
FriendManagerAI.nextContext += 1
|
||||
|
||||
invite = FriendManagerAI.Invite(context, requesterId, avId)
|
||||
FriendManagerAI.invites[context] = invite
|
||||
|
||||
invite.sendSpecialResponse = 1
|
||||
|
||||
self.air.makeFriends(invite.inviteeId, invite.inviterId, 1,
|
||||
invite.context)
|
||||
|
||||
def clearInvite(self, invite):
|
||||
try:
|
||||
del FriendManagerAI.invites[invite.context]
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
del FriendManagerAI.inviters[invite.inviterId]
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
del FriendManagerAI.invitees[invite.inviteeId]
|
||||
except:
|
||||
pass
|
||||
|
||||
def cancelInvite(self, invite):
|
||||
if invite.inviteeKnows:
|
||||
self.down_inviteeCancelFriendQuery(invite.inviteeId, invite.context)
|
||||
|
||||
invite.inviteeKnows = 0
|
||||
|
||||
|
||||
def makeFriendsReply(self, result, context):
|
||||
try:
|
||||
invite = FriendManagerAI.invites[context]
|
||||
except:
|
||||
FriendManagerAI.notify.warning('Message for unknown context ' + context)
|
||||
return
|
||||
|
||||
if result:
|
||||
# By now, the server has OK'ed the friends transaction.
|
||||
# Update our internal bookkeeping so we remember who's
|
||||
# friends with whom. This is mainly useful for correct
|
||||
# accounting of the make-a-friend quest.
|
||||
invitee = self.air.doId2do.get(invite.inviteeId)
|
||||
inviter = self.air.doId2do.get(invite.inviterId)
|
||||
if invitee != None:
|
||||
invitee.extendFriendsList(invite.inviterId, invite.sendSpecialResponse)
|
||||
self.air.questManager.toonMadeFriend(invitee, inviter)
|
||||
|
||||
#inviter = self.air.doId2do.get(invite.inviterId)
|
||||
if inviter != None:
|
||||
inviter.extendFriendsList(invite.inviteeId, invite.sendSpecialResponse)
|
||||
self.air.questManager.toonMadeFriend(inviter, invitee)
|
||||
|
||||
if invite.sendSpecialResponse:
|
||||
# If this flag is set, the "invite" was generated via the
|
||||
# codeword system, instead of through the normal path. In
|
||||
# this case, we need to send the acknowledgement back to
|
||||
# the client.
|
||||
|
||||
if result:
|
||||
# Success! Send a result code of 1.
|
||||
result = 1
|
||||
else:
|
||||
# Failure, some friends list problem. Result code of 2.
|
||||
result = 2
|
||||
|
||||
self.down_submitSecretResponse(invite.inviterId, result,
|
||||
invite.inviteeId)
|
||||
|
||||
# Also send a notification to the other avatar, if he's on.
|
||||
avatar = DistributedAvatarAI.DistributedAvatarAI(self.air)
|
||||
avatar.doId = invite.inviteeId
|
||||
avatar.d_friendsNotify(invite.inviterId, 2)
|
||||
|
||||
self.clearInvite(invite)
|
||||
|
||||
|
||||
|
||||
def __requestSecretReply(self, result, secret, requesterId):
|
||||
self.notify.debug("request secret result = %d, secret = '%s', requesterId = %d" % (result, secret, requesterId))
|
||||
self.down_requestSecretResponse(requesterId, result, secret)
|
||||
|
||||
|
||||
def __submitSecretReply(self, result, secret, requesterId, avId):
|
||||
self.notify.debug("submit secret result = %d, secret = '%s', requesterId = %d, avId = %d" % (result, secret, requesterId, avId))
|
||||
if result == 1:
|
||||
# We successfully matched the secret, so we should do a
|
||||
# few more sanity checks and then try to make friends.
|
||||
if avId == requesterId:
|
||||
# This is the requester's own secret!
|
||||
result = 3
|
||||
else:
|
||||
# Ok, make us special friends.
|
||||
self.makeSpecialFriends(requesterId, avId)
|
||||
|
||||
# In this case, the response gets sent after the
|
||||
# friends transaction completes.
|
||||
return
|
||||
|
||||
self.down_submitSecretResponse(requesterId, result, avId)
|
||||
|
||||
def __previousResponse(self, inviteeId, inviterId):
|
||||
# Return the previous rejection code if this invitee has
|
||||
# previously (recently) declined a friendship from this
|
||||
# inviter, or None if there was no previous rejection.
|
||||
|
||||
now = globalClock.getRealTime()
|
||||
if now - self.lastRollTime >= self.DeclineFriendshipTimeout:
|
||||
self.declineFriends2 = self.declineFriends1
|
||||
self.declineFriends1 = {}
|
||||
|
||||
# Now, is the invitee/inviter combination present in either
|
||||
# map?
|
||||
previous = None
|
||||
if inviteeId in self.declineFriends1:
|
||||
previous = self.declineFriends1[inviteeId].get(inviterId)
|
||||
if previous != None:
|
||||
return previous
|
||||
|
||||
if inviteeId in self.declineFriends2:
|
||||
previous = self.declineFriends2[inviteeId].get(inviterId)
|
||||
if previous != None:
|
||||
return previous
|
||||
|
||||
# Nope, go ahead and ask.
|
||||
return None
|
|
@ -760,7 +760,7 @@ class LoadAvatarOperation(AvatarOperation):
|
|||
def __handleSetAvatar(self):
|
||||
# Get the client channel.
|
||||
channel = self.loginManager.GetAccountConnectionChannel(self.sender)
|
||||
|
||||
|
||||
# We will first assign a POST_REMOVE that will unload the
|
||||
# avatar in the event of them disconnecting while we are working.
|
||||
cleanupDatagram = PyDatagram()
|
||||
|
@ -808,7 +808,7 @@ class UnloadAvatarOperation(GameOperation):
|
|||
|
||||
def __handleUnloadAvatar(self):
|
||||
channel = self.loginManager.GetAccountConnectionChannel(self.sender)
|
||||
|
||||
self.loginManager.air.avatarFriendsManager.avatarOffline(self.avId)
|
||||
datagram = PyDatagram()
|
||||
datagram.addServerHeader(channel, self.loginManager.air.ourChannel, CLIENTAGENT_CLEAR_POST_REMOVES)
|
||||
self.loginManager.air.send(datagram)
|
||||
|
|
|
@ -5,6 +5,7 @@ from panda3d.toontown import *
|
|||
from otp.ai.AIZoneData import AIZoneDataStore
|
||||
from otp.ai.TimeManagerAI import TimeManagerAI
|
||||
from otp.distributed.OtpDoGlobals import *
|
||||
from otp.friends.FriendManagerAI import FriendManagerAI
|
||||
from toontown.ai.HolidayManagerAI import HolidayManagerAI
|
||||
from toontown.ai.NewsManagerAI import NewsManagerAI
|
||||
from toontown.ai.WelcomeValleyManagerAI import WelcomeValleyManagerAI
|
||||
|
@ -232,6 +233,9 @@ class ToontownAIRepository(ToontownInternalRepository):
|
|||
self.partyManager = DistributedPartyManagerAI(self)
|
||||
self.partyManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
|
||||
|
||||
self.friendManager = FriendManagerAI(self)
|
||||
self.friendManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
|
||||
|
||||
def generateHood(self, hoodConstructor, zoneId):
|
||||
# Bossbot HQ doesn't use DNA, so we skip over that.
|
||||
if zoneId != ToontownGlobals.BossbotHQ:
|
||||
|
|
|
@ -1,5 +1,649 @@
|
|||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
import functools
|
||||
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
||||
from direct.fsm.FSM import FSM
|
||||
|
||||
from toontown.estate import HouseGlobals
|
||||
from toontown.estate.DistributedHouseAI import DistributedHouseAI
|
||||
from toontown.toon import ToonDNA
|
||||
|
||||
|
||||
class LoadHouseOperation(FSM):
|
||||
def __init__(self, mgr, estate, index, avatar, callback):
|
||||
FSM.__init__(self, 'LoadHouseOperation')
|
||||
self.mgr = mgr
|
||||
self.estate = estate
|
||||
self.index = index
|
||||
self.avatar = avatar
|
||||
self.callback = callback
|
||||
self.done = False
|
||||
self.houseId = None
|
||||
self.house = None
|
||||
self.gender = None
|
||||
|
||||
def start(self):
|
||||
# We have a few different cases here:
|
||||
if self.avatar is None:
|
||||
# Case #1: There isn't an avatar in that estate slot. Make a blank house.
|
||||
# Because this state completes so fast, we'll use taskMgr to delay
|
||||
# it until the next iteration. This solves reentrancy problems.
|
||||
taskMgr.doMethodLater(0.0, self.demand, 'makeBlankHouse-%s' % id(self), extraArgs=['MakeBlankHouse'])
|
||||
return
|
||||
|
||||
style = ToonDNA.ToonDNA()
|
||||
style.makeFromNetString(self.avatar.get('setDNAString')[0])
|
||||
self.houseId = self.avatar.get('setHouseId', [0])[0]
|
||||
self.gender = style.gender
|
||||
if self.houseId == 0:
|
||||
# Case #2: There is an avatar, but no setHouseId. Make a new house:
|
||||
self.demand('CreateHouse')
|
||||
else:
|
||||
# Case #3: Avatar with a setHouseId. Load it:
|
||||
self.demand('LoadHouse')
|
||||
|
||||
def enterMakeBlankHouse(self):
|
||||
self.house = DistributedHouseAI(self.mgr.air)
|
||||
self.house.setHousePos(self.index)
|
||||
self.house.setColor(self.index)
|
||||
self.house.generateWithRequired(self.estate.zoneId)
|
||||
self.estate.houses[self.index] = self.house
|
||||
self.demand('Off')
|
||||
|
||||
def enterCreateHouse(self):
|
||||
self.mgr.air.dbInterface.createObject(self.mgr.air.dbId, self.mgr.air.dclassesByName['DistributedHouseAI'],
|
||||
{'setName': [self.avatar['setName'][0]],
|
||||
'setAvatarId': [self.avatar['avId']]}, self.__handleHouseCreated)
|
||||
|
||||
def __handleHouseCreated(self, houseId):
|
||||
if self.state != 'CreateHouse':
|
||||
# This operation was likely aborted.
|
||||
return
|
||||
|
||||
# Update the avatar's houseId:
|
||||
av = self.mgr.air.doId2do.get(self.avatar['avId'])
|
||||
if av:
|
||||
av.b_setHouseId(houseId)
|
||||
else:
|
||||
self.mgr.air.dbInterface.updateObject(self.mgr.air.dbId, self.avatar['avId'],
|
||||
self.mgr.air.dclassesByName['DistributedToonAI'],
|
||||
{'setHouseId': [houseId]})
|
||||
|
||||
self.houseId = houseId
|
||||
self.demand('LoadHouse')
|
||||
|
||||
def enterLoadHouse(self):
|
||||
# Activate the house:
|
||||
self.mgr.air.sendActivate(self.houseId, self.mgr.air.districtId, self.estate.zoneId,
|
||||
self.mgr.air.dclassesByName['DistributedHouseAI'],
|
||||
{'setHousePos': [self.index],
|
||||
'setColor': [self.index],
|
||||
'setName': [self.avatar['setName'][0]],
|
||||
'setAvatarId': [self.avatar['avId']]})
|
||||
|
||||
# Wait for the house to generate:
|
||||
self.acceptOnce('generate-%d' % self.houseId, self.__handleHouseGenerated)
|
||||
|
||||
def __handleHouseGenerated(self, house):
|
||||
# The house will need to be able to reference
|
||||
# the estate for setting up gardens, so:
|
||||
house.estate = self.estate
|
||||
|
||||
# Initialize our interior:
|
||||
house.interior.gender = self.gender
|
||||
house.interior.start()
|
||||
|
||||
self.house = house
|
||||
self.estate.houses[self.index] = self.house
|
||||
if config.GetBool('want-gardening', False):
|
||||
# Initialize our garden:
|
||||
self.house.createGardenManager()
|
||||
|
||||
self.demand('Off')
|
||||
|
||||
def exitLoadHouse(self):
|
||||
self.ignore('generate-%d' % self.houseId)
|
||||
|
||||
def enterOff(self):
|
||||
self.done = True
|
||||
self.callback(self.house)
|
||||
|
||||
|
||||
class LoadEstateOperation(FSM):
|
||||
def __init__(self, mgr, callback):
|
||||
FSM.__init__(self, 'LoadEstateOperation')
|
||||
self.mgr = mgr
|
||||
self.callback = callback
|
||||
self.estate = None
|
||||
self.accId = None
|
||||
self.zoneId = None
|
||||
self.avIds = None
|
||||
self.avatars = None
|
||||
self.houseOperations = None
|
||||
self.petOperations = None
|
||||
|
||||
def start(self, accId, zoneId):
|
||||
self.accId = accId
|
||||
self.zoneId = zoneId
|
||||
self.demand('QueryAccount')
|
||||
|
||||
def enterQueryAccount(self):
|
||||
self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, self.accId, self.__handleQueryAccount)
|
||||
|
||||
def __handleQueryAccount(self, dclass, fields):
|
||||
if self.state != 'QueryAccount':
|
||||
# This operation was likely aborted.
|
||||
return
|
||||
|
||||
if dclass != self.mgr.air.dclassesByName['AccountAI']:
|
||||
self.mgr.notify.warning('Account %d has non-account dclass %d!' % (self.accId, dclass))
|
||||
self.demand('Failure')
|
||||
return
|
||||
|
||||
self.accFields = fields
|
||||
self.estateId = fields.get('ESTATE_ID', 0)
|
||||
self.demand('QueryAvatars')
|
||||
|
||||
def enterQueryAvatars(self):
|
||||
self.avIds = self.accFields.get('ACCOUNT_AV_SET', [0] * 6)
|
||||
self.avatars = {}
|
||||
for index, avId in enumerate(self.avIds):
|
||||
if avId == 0:
|
||||
self.avatars[index] = None
|
||||
continue
|
||||
|
||||
self.mgr.air.dbInterface.queryObject(self.mgr.air.dbId, avId,
|
||||
functools.partial(self.__handleQueryAvatar, index=index))
|
||||
|
||||
def __handleQueryAvatar(self, dclass, fields, index):
|
||||
if self.state != 'QueryAvatars':
|
||||
# This operation was likely aborted.
|
||||
return
|
||||
|
||||
if dclass != self.mgr.air.dclassesByName['DistributedToonAI']:
|
||||
self.mgr.notify.warning(
|
||||
'Account %d has avatar %d with non-Toon dclass %d!' % (self.accId, self.avIds[index], dclass))
|
||||
self.demand('Failure')
|
||||
return
|
||||
|
||||
fields['avId'] = self.avIds[index]
|
||||
self.avatars[index] = fields
|
||||
if len(self.avatars) == 6:
|
||||
self.__gotAllAvatars()
|
||||
|
||||
def __gotAllAvatars(self):
|
||||
# We have all of our avatars, so now we can handle the estate.
|
||||
if self.estateId:
|
||||
# We already have an estate, so let's load that:
|
||||
self.demand('LoadEstate')
|
||||
else:
|
||||
# We don't yet have an estate, so let's make one:
|
||||
self.demand('CreateEstate')
|
||||
|
||||
def enterCreateEstate(self):
|
||||
# Create a blank estate object:
|
||||
self.mgr.air.dbInterface.createObject(self.mgr.air.dbId, self.mgr.air.dclassesByName['DistributedEstateAI'], {},
|
||||
self.__handleEstateCreated)
|
||||
|
||||
def __handleEstateCreated(self, estateId):
|
||||
if self.state != 'CreateEstate':
|
||||
# This operation was likely aborted.
|
||||
return
|
||||
|
||||
self.estateId = estateId
|
||||
|
||||
# Store the new estate object on our account:
|
||||
self.mgr.air.dbInterface.updateObject(self.mgr.air.dbId, self.accId, self.mgr.air.dclassesByName['AccountAI'],
|
||||
{'ESTATE_ID': estateId})
|
||||
|
||||
self.demand('LoadEstate')
|
||||
|
||||
def enterLoadEstate(self):
|
||||
# Set the estate fields:
|
||||
fields = {'setSlot%dToonId' % i: (avId,) for i, avId in enumerate(self.avIds)}
|
||||
|
||||
# Activate the estate:
|
||||
self.mgr.air.sendActivate(self.estateId, self.mgr.air.districtId, self.zoneId,
|
||||
self.mgr.air.dclassesByName['DistributedEstateAI'], fields)
|
||||
|
||||
# Wait for the estate to generate:
|
||||
self.acceptOnce('generate-%d' % self.estateId, self.__handleEstateGenerated)
|
||||
|
||||
def __handleEstateGenerated(self, estate):
|
||||
# Get the estate:
|
||||
self.estate = estate
|
||||
|
||||
# For keeping track of pets in this estate:
|
||||
self.estate.pets = []
|
||||
|
||||
# Map the owner to the estate:
|
||||
ownerId = self.mgr.getOwnerFromZone(self.estate.zoneId)
|
||||
owner = self.mgr.air.doId2do.get(ownerId)
|
||||
if owner:
|
||||
self.mgr.toon2estate[owner] = self.estate
|
||||
|
||||
# Set the estate's ID list:
|
||||
self.estate.b_setIdList(self.avIds)
|
||||
|
||||
# Load houses:
|
||||
self.demand('LoadHouses')
|
||||
|
||||
def exitLoadEstate(self):
|
||||
self.ignore('generate-%d' % self.estateId)
|
||||
|
||||
def enterLoadHouses(self):
|
||||
self.houseOperations = []
|
||||
for houseIndex in xrange(6):
|
||||
houseOperation = LoadHouseOperation(self.mgr, self.estate, houseIndex, self.avatars[houseIndex],
|
||||
self.__handleHouseLoaded)
|
||||
self.houseOperations.append(houseOperation)
|
||||
houseOperation.start()
|
||||
|
||||
def __handleHouseLoaded(self, house):
|
||||
if self.state != 'LoadHouses':
|
||||
# We aren't loading houses, so we probably got cancelled. Therefore,
|
||||
# the only sensible thing to do is simply destroy the house.
|
||||
house.requestDelete()
|
||||
return
|
||||
|
||||
# A house operation just finished! Let's see if all of them are done:
|
||||
if all(houseOperation.done for houseOperation in self.houseOperations):
|
||||
# Load our pets:
|
||||
self.demand('LoadPets')
|
||||
|
||||
def enterLoadPets(self):
|
||||
self.petOperations = []
|
||||
for houseIndex in xrange(6):
|
||||
av = self.avatars[houseIndex]
|
||||
if av and av['setPetId'][0] != 0:
|
||||
petOperation = LoadPetOperation(self.mgr, self.estate, av, self.__handlePetLoaded)
|
||||
self.petOperations.append(petOperation)
|
||||
petOperation.start()
|
||||
|
||||
if not self.petOperations:
|
||||
taskMgr.doMethodLater(0, lambda: self.demand('Finished'), 'no-pets', extraArgs=[])
|
||||
|
||||
def __handlePetLoaded(self, pet):
|
||||
if self.state != 'LoadPets':
|
||||
pet.requestDelete()
|
||||
return
|
||||
|
||||
# A pet operation just finished! Let's see if all of them are done:
|
||||
if all(petOperation.done for petOperation in self.petOperations):
|
||||
self.demand('Finished')
|
||||
|
||||
def enterFinished(self):
|
||||
self.petOperations = []
|
||||
self.callback(True)
|
||||
|
||||
def enterFailure(self):
|
||||
self.cancel()
|
||||
self.callback(False)
|
||||
|
||||
def cancel(self):
|
||||
if self.estate:
|
||||
self.estate.destroy()
|
||||
self.estate = None
|
||||
|
||||
self.demand('Off')
|
||||
|
||||
|
||||
class LoadPetOperation(FSM):
|
||||
def __init__(self, mgr, estate, toon, callback):
|
||||
FSM.__init__(self, 'LoadPetFSM')
|
||||
self.mgr = mgr
|
||||
self.estate = estate
|
||||
self.toon = toon
|
||||
self.callback = callback
|
||||
self.done = False
|
||||
self.petId = 0
|
||||
|
||||
def start(self):
|
||||
if type(self.toon) == dict:
|
||||
self.petId = self.toon['setPetId'][0]
|
||||
else:
|
||||
self.petId = self.toon.getPetId()
|
||||
|
||||
if self.petId not in self.mgr.air.doId2do:
|
||||
self.mgr.air.sendActivate(self.petId, self.mgr.air.districtId, self.estate.zoneId)
|
||||
self.acceptOnce('generate-%d' % self.petId, self.__generated)
|
||||
else:
|
||||
self.__generated(self.mgr.air.doId2do[self.petId])
|
||||
|
||||
def __generated(self, pet):
|
||||
self.pet = pet
|
||||
self.estate.pets.append(pet)
|
||||
self.demand('Off')
|
||||
|
||||
def enterOff(self):
|
||||
self.ignore('generate-%d' % self.petId)
|
||||
self.done = True
|
||||
self.callback(self.pet)
|
||||
|
||||
|
||||
class EstateManagerAI(DistributedObjectAI):
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('EstateManagerAI')
|
||||
|
||||
def __init__(self, air):
|
||||
DistributedObjectAI.__init__(self, air)
|
||||
self.toon2estate = {}
|
||||
self.estate = {}
|
||||
self.estate2toons = {}
|
||||
self.estate2timeout = {}
|
||||
self.zone2toons = {}
|
||||
self.zone2owner = {}
|
||||
self.petOperations = []
|
||||
|
||||
def getEstateZone(self, avId, name):
|
||||
# Thank you name, very cool!
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
accId = self.air.getAccountIdFromSender()
|
||||
senderAv = self.air.doId2do.get(senderId)
|
||||
if not senderAv:
|
||||
self.air.writeServerEvent('suspicious', senderId, 'Sent getEstateZone() but not on district!')
|
||||
return
|
||||
|
||||
# If an avId has been provided, then the sender wants to visit a friend.
|
||||
# In this case, we do not need to load the estate, we only need to check
|
||||
# to see if it already exists.
|
||||
if avId and avId != senderId:
|
||||
av = self.air.doId2do.get(avId)
|
||||
if av and av.dclass == self.air.dclassesByName['DistributedToonAI']:
|
||||
estate = self.toon2estate.get(av)
|
||||
if estate:
|
||||
# Found an estate!
|
||||
avId = estate.owner.doId
|
||||
zoneId = estate.zoneId
|
||||
self._mapToEstate(senderAv, estate)
|
||||
|
||||
# In case the sender is teleporting from their estate
|
||||
# to another estate, we want to unload their estate.
|
||||
self._unloadEstate(senderAv)
|
||||
|
||||
if senderAv and senderAv.getPetId() != 0:
|
||||
pet = self.air.doId2do.get(senderAv.getPetId())
|
||||
if pet:
|
||||
self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadPet,
|
||||
extraArgs=[estate, senderAv])
|
||||
pet.requestDelete()
|
||||
else:
|
||||
self.__handleLoadPet(estate, senderAv)
|
||||
|
||||
# Now we want to send the sender to the estate.
|
||||
if hasattr(senderAv, 'enterEstate'):
|
||||
senderAv.enterEstate(avId, zoneId)
|
||||
|
||||
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [avId, zoneId])
|
||||
|
||||
# We weren't able to find the given avId at an estate, that's pretty sad.
|
||||
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [0, 0])
|
||||
return
|
||||
|
||||
# Otherwise, the sender wants to go to their own estate.
|
||||
estate = getattr(senderAv, 'estate', None)
|
||||
if estate:
|
||||
# The sender already has an estate loaded, so let's send them there.
|
||||
self._mapToEstate(senderAv, senderAv.estate)
|
||||
|
||||
if senderAv and senderAv.getPetId() != 0:
|
||||
pet = self.air.doId2do.get(senderAv.getPetId())
|
||||
if pet:
|
||||
self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadPet,
|
||||
extraArgs=[estate, senderAv])
|
||||
pet.requestDelete()
|
||||
else:
|
||||
self.__handleLoadPet(estate, senderAv)
|
||||
|
||||
if hasattr(senderAv, 'enterEstate'):
|
||||
senderAv.enterEstate(senderId, estate.zoneId)
|
||||
|
||||
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [senderId, estate.zoneId])
|
||||
|
||||
# If a timeout is active, cancel it:
|
||||
if estate in self.estate2timeout:
|
||||
self.estate2timeout[estate].remove()
|
||||
del self.estate2timeout[estate]
|
||||
|
||||
return
|
||||
|
||||
if getattr(senderAv, 'loadEstateOperation', None):
|
||||
# We already have a loading operation underway; ignore this second
|
||||
# request since the first operation will setEstateZone() when it
|
||||
# finishes anyway.
|
||||
return
|
||||
|
||||
zoneId = self.air.allocateZone()
|
||||
self.zone2owner[zoneId] = avId
|
||||
|
||||
def estateLoaded(success):
|
||||
if success:
|
||||
senderAv.estate = senderAv.loadEstateOperation.estate
|
||||
senderAv.estate.owner = senderAv
|
||||
self._mapToEstate(senderAv, senderAv.estate)
|
||||
if hasattr(senderAv, 'enterEstate'):
|
||||
senderAv.enterEstate(senderId, zoneId)
|
||||
|
||||
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [senderId, zoneId])
|
||||
else:
|
||||
# Estate loading failed. Sad!
|
||||
self.sendUpdateToAvatarId(senderId, 'setEstateZone', [0, 0])
|
||||
|
||||
# Might as well free up the zoneId as well.
|
||||
self.air.deallocateZone(zoneId)
|
||||
del self.zone2owner[zoneId]
|
||||
|
||||
senderAv.loadEstateOperation = None
|
||||
|
||||
self.acceptOnce(self.air.getAvatarExitEvent(senderAv.doId), self.__handleUnexpectedExit, extraArgs=[senderAv])
|
||||
|
||||
if senderAv and senderAv.getPetId() != 0:
|
||||
pet = self.air.doId2do.get(senderAv.getPetId())
|
||||
if pet:
|
||||
self.acceptOnce(self.air.getAvatarExitEvent(senderAv.getPetId()), self.__handleLoadEstate,
|
||||
extraArgs=[senderAv, estateLoaded, accId, zoneId])
|
||||
pet.requestDelete()
|
||||
return
|
||||
|
||||
self.__handleLoadEstate(senderAv, estateLoaded, accId, zoneId)
|
||||
|
||||
def __handleUnexpectedExit(self, senderAv):
|
||||
self._unmapFromEstate(senderAv)
|
||||
self._unloadEstate(senderAv)
|
||||
|
||||
def exitEstate(self):
|
||||
senderId = self.air.getAvatarIdFromSender()
|
||||
senderAv = self.air.doId2do.get(senderId)
|
||||
if not senderAv:
|
||||
self.air.writeServerEvent('suspicious', senderId, 'Sent exitEstate() but not on district!')
|
||||
return
|
||||
|
||||
self._unmapFromEstate(senderAv)
|
||||
self._unloadEstate(senderAv)
|
||||
|
||||
def removeFriend(self, ownerId, avId):
|
||||
if not (ownerId or avId):
|
||||
return
|
||||
|
||||
owner = self.air.doId2do.get(ownerId)
|
||||
if not owner:
|
||||
return
|
||||
|
||||
friend = self.air.doId2do.get(avId)
|
||||
if not friend:
|
||||
return
|
||||
|
||||
estate = self.estate.get(ownerId)
|
||||
if not estate:
|
||||
return
|
||||
|
||||
if ownerId not in estate.getIdList():
|
||||
return
|
||||
|
||||
toons = self.estate2toons.get(estate, [])
|
||||
if owner not in toons and friend not in toons:
|
||||
return
|
||||
|
||||
friendInList = False
|
||||
for friendPair in owner.getFriendsList():
|
||||
if type(friendPair) == tuple:
|
||||
friendId = friendPair[0]
|
||||
else:
|
||||
friendId = friendPair
|
||||
|
||||
if friendId == avId:
|
||||
friendInList = True
|
||||
break
|
||||
|
||||
if not friendInList:
|
||||
self.sendUpdateToAvatarId(friend.doId, 'sendAvToPlayground', [friend.doId, 1])
|
||||
|
||||
def _unloadEstate(self, av):
|
||||
if getattr(av, 'estate', None):
|
||||
estate = av.estate
|
||||
if estate not in self.estate2timeout:
|
||||
self.estate2timeout[estate] = taskMgr.doMethodLater(HouseGlobals.BOOT_GRACE_PERIOD, self._cleanupEstate,
|
||||
estate.uniqueName('unload-estate'),
|
||||
extraArgs=[estate])
|
||||
|
||||
# Send warning:
|
||||
self._sendToonsToPlayground(av.estate, 0)
|
||||
|
||||
if getattr(av, 'loadEstateOperation', None):
|
||||
self.air.deallocateZone(av.loadEstateOperation.zoneId)
|
||||
av.loadEstateOperation.cancel()
|
||||
av.loadEstateOperation = None
|
||||
|
||||
if av and hasattr(av, 'exitEstate') and hasattr(av, 'isInEstate') and av.isInEstate():
|
||||
av.exitEstate()
|
||||
|
||||
if av and av.getPetId() != 0:
|
||||
self.ignore(self.air.getAvatarExitEvent(av.getPetId()))
|
||||
pet = self.air.doId2do.get(av.getPetId())
|
||||
if pet:
|
||||
pet.requestDelete()
|
||||
|
||||
self.ignore(self.air.getAvatarExitEvent(av.doId))
|
||||
|
||||
def _mapToEstate(self, av, estate):
|
||||
self._unmapFromEstate(av)
|
||||
self.estate[av.doId] = estate
|
||||
self.estate2toons.setdefault(estate, []).append(av)
|
||||
if av not in self.toon2estate:
|
||||
self.toon2estate[av] = estate
|
||||
|
||||
self.zone2toons.setdefault(estate.zoneId, []).append(av.doId)
|
||||
|
||||
def _unmapFromEstate(self, av):
|
||||
estate = self.toon2estate.get(av)
|
||||
if not estate:
|
||||
return
|
||||
|
||||
try:
|
||||
del self.estate[av.doId]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
del self.toon2estate[av]
|
||||
try:
|
||||
self.estate2toons[estate].remove(av)
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
try:
|
||||
self.zone2toons[estate.zoneId].remove(av.doId)
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
def _cleanupEstate(self, estate):
|
||||
# Boot all avatars from estate:
|
||||
self._sendToonsToPlayground(estate, 1)
|
||||
|
||||
# Clean up avatar <-> estate mappings:
|
||||
for av in self.estate2toons.get(estate, []):
|
||||
try:
|
||||
del self.estate[av.doId]
|
||||
del self.toon2estate[av]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
del self.estate2toons[estate]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
del self.zone2toons[estate.zoneId]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Clean up timeout, if it exists:
|
||||
if estate in self.estate2timeout:
|
||||
del self.estate2timeout[estate]
|
||||
|
||||
# Destroy estate and unmap from owner:
|
||||
estate.destroy()
|
||||
estate.owner.estate = None
|
||||
|
||||
# Destroy pets:
|
||||
for pet in estate.pets:
|
||||
pet.requestDelete()
|
||||
|
||||
estate.pets = []
|
||||
|
||||
# Free estate's zone:
|
||||
self.air.deallocateZone(estate.zoneId)
|
||||
del self.zone2owner[estate.zoneId]
|
||||
|
||||
def _sendToonsToPlayground(self, estate, reason):
|
||||
for toon in self.estate2toons.get(estate, []):
|
||||
self.sendUpdateToAvatarId(toon.doId, 'sendAvToPlayground', [toon.doId, reason])
|
||||
|
||||
def getEstateZones(self, ownerId):
|
||||
toon = self.air.doId2do.get(ownerId)
|
||||
if not toon:
|
||||
return []
|
||||
|
||||
estate = self.toon2estate.get(toon)
|
||||
if not estate:
|
||||
return []
|
||||
|
||||
return [estate.zoneId]
|
||||
|
||||
def getEstateHouseZones(self, ownerId):
|
||||
houseZones = []
|
||||
toon = self.air.doId2do.get(ownerId)
|
||||
if not toon:
|
||||
return houseZones
|
||||
|
||||
estate = self.toon2estate.get(toon)
|
||||
if not estate:
|
||||
return houseZones
|
||||
|
||||
houses = estate.houses
|
||||
for house in houses:
|
||||
houseZones.append(house.interiorZone)
|
||||
|
||||
return houseZones
|
||||
|
||||
def _lookupEstate(self, toon):
|
||||
return self.toon2estate.get(toon)
|
||||
|
||||
def getOwnerFromZone(self, zoneId):
|
||||
return self.zone2owner.get(zoneId, 0)
|
||||
|
||||
def __handleLoadPet(self, estate, av):
|
||||
petOperation = LoadPetOperation(self, estate, av, self.__handlePetLoaded)
|
||||
self.petOperations.append(petOperation)
|
||||
petOperation.start()
|
||||
|
||||
def __handlePetLoaded(self, _):
|
||||
# A pet operation just finished! Let's see if all of them are done:
|
||||
if all(petOperation.done for petOperation in self.petOperations):
|
||||
self.petOperations = []
|
||||
|
||||
def __handleLoadEstate(self, av, callback, accId, zoneId):
|
||||
self._unmapFromEstate(av)
|
||||
av.loadEstateOperation = LoadEstateOperation(self, callback)
|
||||
av.loadEstateOperation.start(accId, zoneId)
|
|
@ -176,6 +176,7 @@ class MagicWord(DirectObject):
|
|||
def handleWord(self, invoker, avId, toon, *args):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class SetHP(MagicWord):
|
||||
aliases = ["hp", "setlaff", "laff"]
|
||||
desc = "Sets the target's current laff."
|
||||
|
@ -199,6 +200,7 @@ class SetHP(MagicWord):
|
|||
toon.b_setHp(hp)
|
||||
return "{}'s laff has been set to {}.".format(toon.getName(), hp)
|
||||
|
||||
|
||||
class SetMaxHP(MagicWord):
|
||||
aliases = ["maxhp", "setmaxlaff", "maxlaff"]
|
||||
desc = "Sets the target's max laff."
|
||||
|
@ -217,6 +219,7 @@ class SetMaxHP(MagicWord):
|
|||
toon.toonUp(maxhp)
|
||||
return "{}'s max laff has been set to {}.".format(toon.getName(), maxhp)
|
||||
|
||||
|
||||
class ToggleOobe(MagicWord):
|
||||
aliases = ["oobe"]
|
||||
desc = "Toggles the out of body experience mode, which lets you move the camera freely."
|
||||
|
@ -229,6 +232,7 @@ class ToggleOobe(MagicWord):
|
|||
base.oobe()
|
||||
return "Oobe mode has been toggled."
|
||||
|
||||
|
||||
class ToggleRun(MagicWord):
|
||||
aliases = ["run"]
|
||||
desc = "Toggles run mode, which gives you a faster running speed."
|
||||
|
@ -304,6 +308,8 @@ class Inventory(MagicWord):
|
|||
toon.d_setInventory(toon.inventory.makeNetString())
|
||||
return ("Zeroing inventory for " + toon.getName() + ".")
|
||||
|
||||
|
||||
|
||||
class SetPinkSlips(MagicWord):
|
||||
# this command gives the target toon the specified amount of pink slips
|
||||
# default is 255
|
||||
|
@ -324,52 +330,7 @@ class AbortMinigame(MagicWord):
|
|||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
messenger.send("minigameAbort")
|
||||
return "Requested minigame abort."
|
||||
|
||||
class SkipMiniGolfHole(MagicWord):
|
||||
aliases = ["skipgolfhole", "skipgolf", "skiphole"]
|
||||
desc = "Skips the current golf hole."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
arguments = []
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI
|
||||
course = None
|
||||
for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it.
|
||||
if isinstance(do, DistributedGolfCourseAI):
|
||||
if invoker.doId in do.avIdList:
|
||||
course = do
|
||||
break
|
||||
if not course:
|
||||
return "You aren't in a golf course!"
|
||||
|
||||
if course.isPlayingLastHole(): # If the Toon is on the final hole, calling holeOver() will softlock, so instead we move onto the reward screen.
|
||||
course.demand('WaitReward')
|
||||
else:
|
||||
course.holeOver()
|
||||
|
||||
return "Skipped the current hole."
|
||||
|
||||
class AbortGolfCourse(MagicWord):
|
||||
aliases = ["abortminigolf", "abortgolf", "abortcourse", "leavegolf", "leavecourse"]
|
||||
desc = "Aborts the current golf course."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
arguments = []
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI
|
||||
course = None
|
||||
for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it.
|
||||
if isinstance(do, DistributedGolfCourseAI):
|
||||
if invoker.doId in do.avIdList:
|
||||
course = do
|
||||
break
|
||||
if not course:
|
||||
return "You aren't in a golf course!"
|
||||
|
||||
course.setCourseAbort()
|
||||
|
||||
return "Aborted golf course."
|
||||
return "Requested minigame abort."
|
||||
|
||||
class Minigame(MagicWord):
|
||||
aliases = ["mg"]
|
||||
|
@ -555,7 +516,7 @@ class BossBattle(MagicWord):
|
|||
if not start:
|
||||
respText += " in Frolic state"
|
||||
|
||||
return respText + ", teleporting...", toon.doId, ["cogHQLoader", "cogHQBossBattle", "movie" if start else "teleportIn", boss.getHoodId(), boss.zoneId, 0]
|
||||
return respText + ", teleporting...", ["cogHQLoader", "cogHQBossBattle", "movie" if start else "teleportIn", boss.getHoodId(), boss.zoneId, 0]
|
||||
|
||||
elif command == "list":
|
||||
# List all the ongoing boss battles.
|
||||
|
@ -597,7 +558,7 @@ class BossBattle(MagicWord):
|
|||
return "Index out of range!"
|
||||
|
||||
boss = AllBossCogs[index]
|
||||
return "Teleporting to boss battle...", toon.doId, ["cogHQLoader", "cogHQBossBattle", "", boss.getHoodId(), boss.zoneId, 0]
|
||||
return "Teleporting to boss battle...", ["cogHQLoader", "cogHQBossBattle", "", boss.getHoodId(), boss.zoneId, 0]
|
||||
|
||||
|
||||
# The following commands needs the invoker to be in a boss battle.
|
||||
|
@ -636,129 +597,13 @@ class BossBattle(MagicWord):
|
|||
|
||||
# The create command is already described when the invoker is not in a battle. These are the commands
|
||||
# they can use INSIDE the battle.
|
||||
return f"Unknown command: \"{command}\". Valid commands: \"start\", \"stop\", \"skip\", \"final\", \"kill\"."
|
||||
return respText + f"Unknown command: \"{command}\". Valid commands: \"start\", \"stop\", \"skip\", \"final\", \"kill\"."
|
||||
|
||||
def __destroyBoss(self, boss):
|
||||
bossZone = boss.zoneId
|
||||
boss.requestDelete()
|
||||
self.air.deallocateZone(bossZone)
|
||||
|
||||
class GlobalTeleport(MagicWord):
|
||||
aliases = ["globaltp", "tpaccess"]
|
||||
desc = "Enables teleport access to all zones."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
from toontown.toonbase import ToontownGlobals
|
||||
toon.b_setHoodsVisited(ToontownGlobals.HoodsForTeleportAll)
|
||||
toon.b_setTeleportAccess(ToontownGlobals.HoodsForTeleportAll)
|
||||
return f"Enabled teleport access to all zones for {toon.getName()}."
|
||||
|
||||
class Teleport(MagicWord):
|
||||
aliases = ["tp", "goto"]
|
||||
desc = "Teleport to a specified zone."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
arguments = [("zoneName", str, False, '')]
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
from toontown.hood import ZoneUtil
|
||||
from toontown.toonbase import ToontownGlobals
|
||||
zoneName = args[0]
|
||||
|
||||
# Can add stuff like streets to this too if you wanted, but if you do you'll want it to be a valid zone on that street. eg: 2100 is invalid, but any value 2101 to 2156 is fine.
|
||||
# so if you wanted to add a silly street key, theroetically you could do something like this: 'sillystreet': ToontownGlobals.SillyStreet +1,
|
||||
zoneName2Id = {'ttc': ToontownGlobals.ToontownCentral,
|
||||
'dd': ToontownGlobals.DonaldsDock,
|
||||
'dg': ToontownGlobals.DaisyGardens,
|
||||
'mml': ToontownGlobals.MinniesMelodyland,
|
||||
'tb': ToontownGlobals.TheBrrrgh,
|
||||
'ddl': ToontownGlobals.DonaldsDreamland,
|
||||
'gs': ToontownGlobals.GoofySpeedway,
|
||||
'oz': ToontownGlobals.OutdoorZone,
|
||||
'aa': ToontownGlobals.OutdoorZone,
|
||||
'gz': ToontownGlobals.GolfZone,
|
||||
'sbhq': ToontownGlobals.SellbotHQ,
|
||||
'factory': ToontownGlobals.SellbotFactoryExt,
|
||||
'cbhq': ToontownGlobals.CashbotHQ,
|
||||
'lbhq': ToontownGlobals.LawbotHQ,
|
||||
'bbhq': ToontownGlobals.BossbotHQ}
|
||||
|
||||
try:
|
||||
zone = zoneName2Id[zoneName]
|
||||
except KeyError:
|
||||
return "Unknown zone name!"
|
||||
|
||||
return f"Requested to teleport {toon.getName()} to zone {zone}.", toon.doId, [ZoneUtil.getBranchLoaderName(zone), ZoneUtil.getToonWhereName(zone), "", ZoneUtil.getHoodId(zone), zone, 0]
|
||||
|
||||
class ToggleSleep(MagicWord):
|
||||
aliases = ["sleep", "nosleep", "neversleep", "togglesleeping", "insomnia"]
|
||||
desc = "Toggles sleeping for the target."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
toon.d_toggleSleep()
|
||||
return f"Toggled sleeping for {toon.getName()}."
|
||||
|
||||
class ToggleImmortal(MagicWord):
|
||||
aliases = ["immortal", "invincible", "invulnerable"]
|
||||
desc = "Toggle immortal mode. This makes the Toon immune to damage."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
toon.setImmortalMode(not toon.immortalMode)
|
||||
return f"Toggled immortal mode for {toon.getName()}"
|
||||
|
||||
class ToggleGhost(MagicWord):
|
||||
aliases = ["ghost", "invisible", "spy"]
|
||||
desc = "Toggle ghost mode."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
# 1 is for the attic, 2 enables you to see yourself other ghost toons. 0 is off.
|
||||
toon.b_setGhostMode(2 if not toon.ghostMode else 0) # As it's primarily for moderation purposes, we set it to 2 here, or 0 if it's already on.
|
||||
return f"Toggled ghost mode for {toon.getName()}"
|
||||
|
||||
class SetGM(MagicWord):
|
||||
aliases = ["icon", "seticon", "gm", "gmicon", "setgmicon"]
|
||||
desc = "Sets the GM icon on the target."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
arguments = [("iconRequest", int, False, 0),]
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
from toontown.toonbase import TTLocalizer
|
||||
|
||||
iconRequest = args[0]
|
||||
if iconRequest > len(TTLocalizer.GM_NAMES) or iconRequest < 0:
|
||||
return "Invalid GM icon ID!"
|
||||
|
||||
toon.b_setGM(0) # Reset it first, otherwise the Toon keeps the old icon, but the name still changes.
|
||||
toon.b_setGM(iconRequest)
|
||||
return f"GM icon set to {iconRequest} for {toon.getName()}"
|
||||
|
||||
class SetMaxCarry(MagicWord):
|
||||
aliases = ["gagpouch", "pouch", "gagcapacity"]
|
||||
desc = "Set a Toon's gag pouch size."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
arguments = [("pouchSize", int, True)]
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
pouchSize = args[0]
|
||||
|
||||
if pouchSize > 255 or pouchSize < 0:
|
||||
return "Specified pouch size must be between 1 and 255."
|
||||
|
||||
toon.b_setMaxCarry(pouchSize)
|
||||
return f"Set gag pouch size to {pouchSize} for {toon.getName()}"
|
||||
|
||||
class ToggleInstantKill(MagicWord):
|
||||
aliases = ["instantkill", "instakill"]
|
||||
desc = "Toggle the ability to instantly kill a Cog with any gag."
|
||||
execLocation = MagicWordConfig.EXEC_LOC_SERVER
|
||||
|
||||
def handleWord(self, invoker, avId, toon, *args):
|
||||
toon.setInstantKillMode(not toon.instantKillMode)
|
||||
return f"Toggled instant-kill mode for {toon.getName()}"
|
||||
|
||||
class Fireworks(MagicWord):
|
||||
aliases = ["firework"]
|
||||
desc = "Starts a firework show."
|
||||
|
@ -830,4 +675,4 @@ class Fireworks(MagicWord):
|
|||
# A bit hacky, but better than the old system
|
||||
for item in list(globals().values()):
|
||||
if isinstance(item, type) and issubclass(item, MagicWord):
|
||||
i = item()
|
||||
i = item()
|
2594
toontown/toon/DistributedToon - Copy.py
Normal file
2594
toontown/toon/DistributedToon - Copy.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -55,7 +55,7 @@ class ToonBase(OTPBase.OTPBase):
|
|||
self.wantDynamicShadows = 0
|
||||
self.exitErrorCode = 0
|
||||
camera.setPosHpr(0, 0, 0, 0, 0, 0)
|
||||
self.camLens.setFov(ToontownGlobals.DefaultCameraFov)
|
||||
self.camLens.setMinFov(ToontownGlobals.DefaultCameraFov / (4. / 3.))
|
||||
self.camLens.setNearFar(ToontownGlobals.DefaultCameraNear, ToontownGlobals.DefaultCameraFar)
|
||||
self.musicManager.setVolume(0.65)
|
||||
self.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor)
|
||||
|
|
|
@ -63,9 +63,10 @@ ConfigVariableDouble('decompressor-step-time').setValue(0.01)
|
|||
ConfigVariableDouble('extractor-step-time').setValue(0.01)
|
||||
backgroundNodePath = aspect2d.attachNewNode(backgroundNode, 0)
|
||||
backgroundNodePath.setPos(0.0, 0.0, 0.0)
|
||||
backgroundNodePath.setScale(render2d, VBase3(1))
|
||||
backgroundNodePath.setScale(aspect2d, VBase3(1.33, 1, 1))
|
||||
backgroundNodePath.find('**/fg').setBin('fixed', 20)
|
||||
backgroundNodePath.find('**/bg').setBin('fixed', 10)
|
||||
backgroundNodePath.find('**/bg').setScale(aspect2d, VBase3(base.getAspectRatio(), 1, 1))
|
||||
base.graphicsEngine.renderFrame()
|
||||
DirectGuiGlobals.setDefaultRolloverSound(base.loader.loadSfx('phase_3/audio/sfx/GUI_rollover.ogg'))
|
||||
DirectGuiGlobals.setDefaultClickSound(base.loader.loadSfx('phase_3/audio/sfx/GUI_create_toon_fwd.ogg'))
|
||||
|
@ -89,7 +90,7 @@ else:
|
|||
from direct.gui.DirectGui import OnscreenText
|
||||
serverVersion = ConfigVariableString('server-version', 'no_version_set').value
|
||||
print('ToontownStart: serverVersion: ', serverVersion)
|
||||
version = OnscreenText(serverVersion, pos=(-1.3, -0.975), scale=0.06, fg=Vec4(0, 0, 1, 0.6), align=TextNode.ALeft)
|
||||
version = OnscreenText(serverVersion, parent=base.a2dBottomLeft, pos=(0.033, 0.025), scale=0.06, fg=Vec4(0, 0, 1, 0.6), align=TextNode.ALeft)
|
||||
loader.beginBulkLoad('init', TTLocalizer.LoaderLabel, 138, 0, TTLocalizer.TIP_NONE)
|
||||
from toontown.distributed.ToontownClientRepository import ToontownClientRepository
|
||||
cr = ToontownClientRepository(serverVersion, launcher)
|
||||
|
|
|
@ -41,6 +41,7 @@ class ToontownLoadingScreen:
|
|||
self.waitBar.reparentTo(self.gui)
|
||||
self.title.reparentTo(self.gui)
|
||||
self.gui.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
|
||||
self.gui.find('**/bg').setScale(aspect2dp, VBase3(base.getAspectRatio(), 1, 1))
|
||||
else:
|
||||
self.waitBar.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
|
||||
self.title.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
|
||||
|
|
|
@ -45,3 +45,5 @@ class ToontownUDRepository(ToontownInternalRepository):
|
|||
if __astron__:
|
||||
# Create our Astron login manager...
|
||||
self.astronLoginManager = self.generateGlobalObject(OTP_DO_ID_ASTRON_LOGIN_MANAGER, 'AstronLoginManager')
|
||||
self.chatHandler = self.generateGlobalObject(OTP_DO_ID_CHAT_ROUTER, 'ChatHandler')
|
||||
self.avatarFriendsManager = self.generateGlobalObject(OTP_DO_ID_AVATAR_FRIENDS_MANAGER, 'AvatarFriendsManager')
|
||||
|
|
|
@ -5,7 +5,9 @@ cd..
|
|||
rem Read the contents of PPYTHON_PATH into %PPYTHON_PATH%:
|
||||
set /P PPYTHON_PATH=<PPYTHON_PATH
|
||||
|
||||
set LOGIN_TOKEN=dev
|
||||
set /P LOGIN_TOKEN="Login password: "
|
||||
|
||||
:GAME
|
||||
%PPYTHON_PATH% -m toontown.launcher.QuickStartLauncher
|
||||
pause
|
||||
GOTO GAME
|
||||
|
|
Loading…
Reference in a new issue