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
|
# Oldschool Toontown
|
||||||
This repository contains the code for Open Toontown, based on the latest version of Disney's Toontown Online (sv1.0.47.38).
|
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
|
# 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.
|
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`
|
* `minigames: Fix crash when entering the trolley`
|
||||||
* `racing: Fix possible race condition when two racers tied`
|
* `racing: Fix possible race condition when two racers tied`
|
||||||
* `golf: Refix wonky physics once and for all (hopefully)`
|
* `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.soundRun = None
|
||||||
self.soundWalk = None
|
self.soundWalk = None
|
||||||
self.sleepFlag = 0
|
self.sleepFlag = 0
|
||||||
self.noSleep = 0
|
|
||||||
self.isDisguised = 0
|
self.isDisguised = 0
|
||||||
self.movingFlag = 0
|
self.movingFlag = 0
|
||||||
self.swimmingFlag = 0
|
self.swimmingFlag = 0
|
||||||
|
@ -985,7 +984,7 @@ class LocalAvatar(DistributedAvatar.DistributedAvatar, DistributedSmoothNode.Dis
|
||||||
return
|
return
|
||||||
|
|
||||||
def gotoSleep(self):
|
def gotoSleep(self):
|
||||||
if not self.sleepFlag and not self.noSleep:
|
if not self.sleepFlag:
|
||||||
self.b_setAnimState('Sleep', self.animMultiplier)
|
self.b_setAnimState('Sleep', self.animMultiplier)
|
||||||
self.sleepFlag = 1
|
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,
|
message,
|
||||||
[],
|
[],
|
||||||
0])
|
0])
|
||||||
|
base.cr.chatHandler.sendChatMessage(message)
|
||||||
messenger.send('chatUpdate', [message, chatFlags])
|
messenger.send('chatUpdate', [message, chatFlags])
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
@ -652,6 +653,7 @@ class TalkAssistant(DirectObject.DirectObject):
|
||||||
message,
|
message,
|
||||||
[],
|
[],
|
||||||
0], sendToId=receiverAvId)
|
0], sendToId=receiverAvId)
|
||||||
|
base.cr.chatHandler.sendWhisperMessage(message, receiverAvId)
|
||||||
return error
|
return error
|
||||||
|
|
||||||
def sendAccountTalk(self, message, receiverAccount):
|
def sendAccountTalk(self, message, receiverAccount):
|
||||||
|
|
|
@ -418,6 +418,7 @@ class OTPClientRepository(ClientRepositoryBase):
|
||||||
self.centralLogger = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_CENTRAL_LOGGER, 'CentralLogger')
|
self.centralLogger = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_CENTRAL_LOGGER, 'CentralLogger')
|
||||||
if __astron__:
|
if __astron__:
|
||||||
self.astronLoginManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_ASTRON_LOGIN_MANAGER, 'AstronLoginManager')
|
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):
|
def startLeakDetector(self):
|
||||||
if hasattr(self, 'leakDetector'):
|
if hasattr(self, 'leakDetector'):
|
||||||
|
|
|
@ -87,4 +87,6 @@ OTP_ZONE_ID_MANAGEMENT = 2
|
||||||
OTP_ZONE_ID_DISTRICTS = 3
|
OTP_ZONE_ID_DISTRICTS = 3
|
||||||
OTP_ZONE_ID_DISTRICTS_STATS = 4
|
OTP_ZONE_ID_DISTRICTS_STATS = 4
|
||||||
OTP_ZONE_ID_ELEMENTS = 5
|
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
|
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.DistributedObjectGlobalUD import DistributedObjectGlobalUD
|
||||||
from direct.distributed.DistributedObjectUD import DistributedObjectUD
|
from otp.otpbase import OTPGlobals
|
||||||
|
from otp.ai import AIMsgTypes
|
||||||
|
from otp.uberdog.RejectCode import RejectCode
|
||||||
|
|
||||||
class AvatarFriendsManagerUD(DistributedObjectUD):
|
from direct.directnotify.DirectNotifyGlobal import directNotify
|
||||||
notify = DirectNotifyGlobal.directNotify.newCategory('AvatarFriendsManagerUD')
|
|
||||||
|
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.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):
|
# we need something for the AI DC parser to load
|
||||||
notify = DirectNotifyGlobal.directNotify.newCategory('FriendManagerAI')
|
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):
|
def __handleSetAvatar(self):
|
||||||
# Get the client channel.
|
# Get the client channel.
|
||||||
channel = self.loginManager.GetAccountConnectionChannel(self.sender)
|
channel = self.loginManager.GetAccountConnectionChannel(self.sender)
|
||||||
|
|
||||||
# We will first assign a POST_REMOVE that will unload the
|
# We will first assign a POST_REMOVE that will unload the
|
||||||
# avatar in the event of them disconnecting while we are working.
|
# avatar in the event of them disconnecting while we are working.
|
||||||
cleanupDatagram = PyDatagram()
|
cleanupDatagram = PyDatagram()
|
||||||
|
@ -808,7 +808,7 @@ class UnloadAvatarOperation(GameOperation):
|
||||||
|
|
||||||
def __handleUnloadAvatar(self):
|
def __handleUnloadAvatar(self):
|
||||||
channel = self.loginManager.GetAccountConnectionChannel(self.sender)
|
channel = self.loginManager.GetAccountConnectionChannel(self.sender)
|
||||||
|
self.loginManager.air.avatarFriendsManager.avatarOffline(self.avId)
|
||||||
datagram = PyDatagram()
|
datagram = PyDatagram()
|
||||||
datagram.addServerHeader(channel, self.loginManager.air.ourChannel, CLIENTAGENT_CLEAR_POST_REMOVES)
|
datagram.addServerHeader(channel, self.loginManager.air.ourChannel, CLIENTAGENT_CLEAR_POST_REMOVES)
|
||||||
self.loginManager.air.send(datagram)
|
self.loginManager.air.send(datagram)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from panda3d.toontown import *
|
||||||
from otp.ai.AIZoneData import AIZoneDataStore
|
from otp.ai.AIZoneData import AIZoneDataStore
|
||||||
from otp.ai.TimeManagerAI import TimeManagerAI
|
from otp.ai.TimeManagerAI import TimeManagerAI
|
||||||
from otp.distributed.OtpDoGlobals import *
|
from otp.distributed.OtpDoGlobals import *
|
||||||
|
from otp.friends.FriendManagerAI import FriendManagerAI
|
||||||
from toontown.ai.HolidayManagerAI import HolidayManagerAI
|
from toontown.ai.HolidayManagerAI import HolidayManagerAI
|
||||||
from toontown.ai.NewsManagerAI import NewsManagerAI
|
from toontown.ai.NewsManagerAI import NewsManagerAI
|
||||||
from toontown.ai.WelcomeValleyManagerAI import WelcomeValleyManagerAI
|
from toontown.ai.WelcomeValleyManagerAI import WelcomeValleyManagerAI
|
||||||
|
@ -232,6 +233,9 @@ class ToontownAIRepository(ToontownInternalRepository):
|
||||||
self.partyManager = DistributedPartyManagerAI(self)
|
self.partyManager = DistributedPartyManagerAI(self)
|
||||||
self.partyManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
|
self.partyManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
|
||||||
|
|
||||||
|
self.friendManager = FriendManagerAI(self)
|
||||||
|
self.friendManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT)
|
||||||
|
|
||||||
def generateHood(self, hoodConstructor, zoneId):
|
def generateHood(self, hoodConstructor, zoneId):
|
||||||
# Bossbot HQ doesn't use DNA, so we skip over that.
|
# Bossbot HQ doesn't use DNA, so we skip over that.
|
||||||
if zoneId != ToontownGlobals.BossbotHQ:
|
if zoneId != ToontownGlobals.BossbotHQ:
|
||||||
|
|
|
@ -1,5 +1,649 @@
|
||||||
from direct.directnotify import DirectNotifyGlobal
|
from direct.directnotify import DirectNotifyGlobal
|
||||||
from direct.distributed.DistributedObjectAI import DistributedObjectAI
|
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):
|
class EstateManagerAI(DistributedObjectAI):
|
||||||
notify = DirectNotifyGlobal.directNotify.newCategory('EstateManagerAI')
|
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):
|
def handleWord(self, invoker, avId, toon, *args):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class SetHP(MagicWord):
|
class SetHP(MagicWord):
|
||||||
aliases = ["hp", "setlaff", "laff"]
|
aliases = ["hp", "setlaff", "laff"]
|
||||||
desc = "Sets the target's current laff."
|
desc = "Sets the target's current laff."
|
||||||
|
@ -199,6 +200,7 @@ class SetHP(MagicWord):
|
||||||
toon.b_setHp(hp)
|
toon.b_setHp(hp)
|
||||||
return "{}'s laff has been set to {}.".format(toon.getName(), hp)
|
return "{}'s laff has been set to {}.".format(toon.getName(), hp)
|
||||||
|
|
||||||
|
|
||||||
class SetMaxHP(MagicWord):
|
class SetMaxHP(MagicWord):
|
||||||
aliases = ["maxhp", "setmaxlaff", "maxlaff"]
|
aliases = ["maxhp", "setmaxlaff", "maxlaff"]
|
||||||
desc = "Sets the target's max laff."
|
desc = "Sets the target's max laff."
|
||||||
|
@ -217,6 +219,7 @@ class SetMaxHP(MagicWord):
|
||||||
toon.toonUp(maxhp)
|
toon.toonUp(maxhp)
|
||||||
return "{}'s max laff has been set to {}.".format(toon.getName(), maxhp)
|
return "{}'s max laff has been set to {}.".format(toon.getName(), maxhp)
|
||||||
|
|
||||||
|
|
||||||
class ToggleOobe(MagicWord):
|
class ToggleOobe(MagicWord):
|
||||||
aliases = ["oobe"]
|
aliases = ["oobe"]
|
||||||
desc = "Toggles the out of body experience mode, which lets you move the camera freely."
|
desc = "Toggles the out of body experience mode, which lets you move the camera freely."
|
||||||
|
@ -229,6 +232,7 @@ class ToggleOobe(MagicWord):
|
||||||
base.oobe()
|
base.oobe()
|
||||||
return "Oobe mode has been toggled."
|
return "Oobe mode has been toggled."
|
||||||
|
|
||||||
|
|
||||||
class ToggleRun(MagicWord):
|
class ToggleRun(MagicWord):
|
||||||
aliases = ["run"]
|
aliases = ["run"]
|
||||||
desc = "Toggles run mode, which gives you a faster running speed."
|
desc = "Toggles run mode, which gives you a faster running speed."
|
||||||
|
@ -304,6 +308,8 @@ class Inventory(MagicWord):
|
||||||
toon.d_setInventory(toon.inventory.makeNetString())
|
toon.d_setInventory(toon.inventory.makeNetString())
|
||||||
return ("Zeroing inventory for " + toon.getName() + ".")
|
return ("Zeroing inventory for " + toon.getName() + ".")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SetPinkSlips(MagicWord):
|
class SetPinkSlips(MagicWord):
|
||||||
# this command gives the target toon the specified amount of pink slips
|
# this command gives the target toon the specified amount of pink slips
|
||||||
# default is 255
|
# default is 255
|
||||||
|
@ -324,52 +330,7 @@ class AbortMinigame(MagicWord):
|
||||||
|
|
||||||
def handleWord(self, invoker, avId, toon, *args):
|
def handleWord(self, invoker, avId, toon, *args):
|
||||||
messenger.send("minigameAbort")
|
messenger.send("minigameAbort")
|
||||||
return "Requested minigame abort."
|
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."
|
|
||||||
|
|
||||||
class Minigame(MagicWord):
|
class Minigame(MagicWord):
|
||||||
aliases = ["mg"]
|
aliases = ["mg"]
|
||||||
|
@ -555,7 +516,7 @@ class BossBattle(MagicWord):
|
||||||
if not start:
|
if not start:
|
||||||
respText += " in Frolic state"
|
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":
|
elif command == "list":
|
||||||
# List all the ongoing boss battles.
|
# List all the ongoing boss battles.
|
||||||
|
@ -597,7 +558,7 @@ class BossBattle(MagicWord):
|
||||||
return "Index out of range!"
|
return "Index out of range!"
|
||||||
|
|
||||||
boss = AllBossCogs[index]
|
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.
|
# 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
|
# The create command is already described when the invoker is not in a battle. These are the commands
|
||||||
# they can use INSIDE the battle.
|
# 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):
|
def __destroyBoss(self, boss):
|
||||||
bossZone = boss.zoneId
|
bossZone = boss.zoneId
|
||||||
boss.requestDelete()
|
boss.requestDelete()
|
||||||
self.air.deallocateZone(bossZone)
|
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):
|
class Fireworks(MagicWord):
|
||||||
aliases = ["firework"]
|
aliases = ["firework"]
|
||||||
desc = "Starts a firework show."
|
desc = "Starts a firework show."
|
||||||
|
@ -830,4 +675,4 @@ class Fireworks(MagicWord):
|
||||||
# A bit hacky, but better than the old system
|
# A bit hacky, but better than the old system
|
||||||
for item in list(globals().values()):
|
for item in list(globals().values()):
|
||||||
if isinstance(item, type) and issubclass(item, MagicWord):
|
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.wantDynamicShadows = 0
|
||||||
self.exitErrorCode = 0
|
self.exitErrorCode = 0
|
||||||
camera.setPosHpr(0, 0, 0, 0, 0, 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.camLens.setNearFar(ToontownGlobals.DefaultCameraNear, ToontownGlobals.DefaultCameraFar)
|
||||||
self.musicManager.setVolume(0.65)
|
self.musicManager.setVolume(0.65)
|
||||||
self.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor)
|
self.setBackgroundColor(ToontownGlobals.DefaultBackgroundColor)
|
||||||
|
|
|
@ -63,9 +63,10 @@ ConfigVariableDouble('decompressor-step-time').setValue(0.01)
|
||||||
ConfigVariableDouble('extractor-step-time').setValue(0.01)
|
ConfigVariableDouble('extractor-step-time').setValue(0.01)
|
||||||
backgroundNodePath = aspect2d.attachNewNode(backgroundNode, 0)
|
backgroundNodePath = aspect2d.attachNewNode(backgroundNode, 0)
|
||||||
backgroundNodePath.setPos(0.0, 0.0, 0.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('**/fg').setBin('fixed', 20)
|
||||||
backgroundNodePath.find('**/bg').setBin('fixed', 10)
|
backgroundNodePath.find('**/bg').setBin('fixed', 10)
|
||||||
|
backgroundNodePath.find('**/bg').setScale(aspect2d, VBase3(base.getAspectRatio(), 1, 1))
|
||||||
base.graphicsEngine.renderFrame()
|
base.graphicsEngine.renderFrame()
|
||||||
DirectGuiGlobals.setDefaultRolloverSound(base.loader.loadSfx('phase_3/audio/sfx/GUI_rollover.ogg'))
|
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'))
|
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
|
from direct.gui.DirectGui import OnscreenText
|
||||||
serverVersion = ConfigVariableString('server-version', 'no_version_set').value
|
serverVersion = ConfigVariableString('server-version', 'no_version_set').value
|
||||||
print('ToontownStart: serverVersion: ', serverVersion)
|
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)
|
loader.beginBulkLoad('init', TTLocalizer.LoaderLabel, 138, 0, TTLocalizer.TIP_NONE)
|
||||||
from toontown.distributed.ToontownClientRepository import ToontownClientRepository
|
from toontown.distributed.ToontownClientRepository import ToontownClientRepository
|
||||||
cr = ToontownClientRepository(serverVersion, launcher)
|
cr = ToontownClientRepository(serverVersion, launcher)
|
||||||
|
|
|
@ -41,6 +41,7 @@ class ToontownLoadingScreen:
|
||||||
self.waitBar.reparentTo(self.gui)
|
self.waitBar.reparentTo(self.gui)
|
||||||
self.title.reparentTo(self.gui)
|
self.title.reparentTo(self.gui)
|
||||||
self.gui.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
|
self.gui.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
|
||||||
|
self.gui.find('**/bg').setScale(aspect2dp, VBase3(base.getAspectRatio(), 1, 1))
|
||||||
else:
|
else:
|
||||||
self.waitBar.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
|
self.waitBar.reparentTo(aspect2dp, DGG.NO_FADE_SORT_INDEX)
|
||||||
self.title.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__:
|
if __astron__:
|
||||||
# Create our Astron login manager...
|
# Create our Astron login manager...
|
||||||
self.astronLoginManager = self.generateGlobalObject(OTP_DO_ID_ASTRON_LOGIN_MANAGER, 'AstronLoginManager')
|
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%:
|
rem Read the contents of PPYTHON_PATH into %PPYTHON_PATH%:
|
||||||
set /P PPYTHON_PATH=<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
|
%PPYTHON_PATH% -m toontown.launcher.QuickStartLauncher
|
||||||
pause
|
pause
|
||||||
|
GOTO GAME
|
||||||
|
|
Loading…
Reference in a new issue