From aaafd22c6db7e83c9441e39da133d46ee864b973 Mon Sep 17 00:00:00 2001 From: Open Toontown <57279094+opentoontown@users.noreply.github.com> Date: Fri, 16 Dec 2022 23:44:17 -0500 Subject: [PATCH] ai: Use Disney's ToontownAIRepository --- otp/ai/AIDistrict.py | 482 +++ otp/ai/AIRepository.py | 1913 +++++++++ otp/ai/GarbageLeakServerEventAggregatorAI.py | 89 + toontown/ai/AIStart.py | 10 +- toontown/ai/ToontownAIRepository.py | 1431 +++++-- toontown/ai/ToontownMagicWordManagerAI.py | 3617 ++++++++++++++++++ 6 files changed, 7117 insertions(+), 425 deletions(-) create mode 100644 otp/ai/AIDistrict.py create mode 100644 otp/ai/AIRepository.py create mode 100644 otp/ai/GarbageLeakServerEventAggregatorAI.py create mode 100644 toontown/ai/ToontownMagicWordManagerAI.py diff --git a/otp/ai/AIDistrict.py b/otp/ai/AIDistrict.py new file mode 100644 index 0000000..ca0b9ab --- /dev/null +++ b/otp/ai/AIDistrict.py @@ -0,0 +1,482 @@ +from pandac.PandaModules import * +from otp.otpbase import OTPGlobals +from .AIMsgTypes import * +from direct.directnotify import DirectNotifyGlobal +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.task import Task +from direct.distributed import ParentMgr +from otp.ai.AIRepository import AIRepository +from otp.ai.AIZoneData import AIZoneData, AIZoneDataStore +import sys +import os +import copy +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator + +if __debug__: + import pdb + +class AIDistrict(AIRepository): + notify = DirectNotifyGlobal.directNotify.newCategory("AIDistrict") + + def __init__( + self, mdip, mdport, esip, esport, dcFileNames, + districtId, districtName, districtType, serverId, + minChannel, maxChannel, dcSuffix = 'AI'): + assert self.notify.debugStateCall(self) + + # Save the district Id (needed for calculations in AIRepository code) + self.districtId = districtId + self.districtName = districtName + self.districtType = districtType + + AIRepository.__init__( + self, mdip, mdport, esip, esport, dcFileNames, + serverId, + minChannel, maxChannel, dcSuffix) + self.setClientDatagram(0) + assert minChannel > districtId + if hasattr(self, 'setVerbose'): + if self.config.GetBool('verbose-airepository'): + self.setVerbose(1) + + # Save the state server id + self.serverId = serverId + + # Record the reason each client leaves the shard, according to + # the client. + self._avatarDisconnectReasons = {} + + # A list of avIds to pretend to disconnect at the next poll + # cycle, for debugging purposes only. + self._debugDisconnectIds = [] + + # player avatars will increment and decrement this count + self._population = 0 + + # The AI State machine + self.fsm = ClassicFSM.ClassicFSM('AIDistrict', + [State.State('off', + self.enterOff, + self.exitOff, + ['connect']), + State.State('connect', + self.enterConnect, + self.exitConnect, + ['districtReset', 'noConnection', + # I added this because Skyler removed the transition to + # districtReset -- Joe + 'playGame', + ]), + State.State('districtReset', + self.enterDistrictReset, + self.exitDistrictReset, + ['playGame','noConnection']), + State.State('playGame', + self.enterPlayGame, + self.exitPlayGame, + ['noConnection']), + State.State('noConnection', + self.enterNoConnection, + self.exitNoConnection, + ['connect'])], + # initial state + 'off', + # final state + 'off', + ) + + self.fsm.enterInitialState() + + self.fsm.request("connect") + + def uniqueName(self, desc): + return desc+"-"+str(self.districtId) + + def getGameDoId(self): + self.notify.error('derived must override') + + def incrementPopulation(self): + self._population += 1 + def decrementPopulation(self): + if __dev__: + assert self._population > 0 + self._population = max(0, self._population - 1) + + def getPopulation(self): + if simbase.fakeDistrictPopulations: + if not hasattr(self, '_fakePopulation'): + import random + self._fakePopulation = random.randrange(1000) + return self._fakePopulation + return self._population + + def printPopulationToLog(self, task): + self.notify.info("district-name %s | district-id %s | population %s" % (self.districtName, self.districtId, self._population)) + return Task.again + + # check if this is a player avatar in a location where they should not be + def _isValidPlayerLocation(self, parentId, zoneId): + return True + + #### Init #### + + def writeServerEvent(self, eventType, who, description): + AIRepository.writeServerEvent(self, eventType, who, description, + serverId=self.districtId) + + #### DistrictReset #### + def enterDistrictReset(self): + self.handler = self.handleDistrictReset + self.deleteDistrict(self.districtId) + + def exitDistrictReset(self): + self.handler = None + + def handleDistrictReset(self, msgType, di): + if msgType == STATESERVER_OBJECT_DELETE_RAM: + doId = di.getUint32() + self.notify.info("Got request to delete doId: " +str(doId)) + if(doId == self.districtId): + self.fsm.request("playGame") + elif msgType == STATESERVER_OBJECT_NOTFOUND: + doId = di.getUint32() + self.notify.info("Got Not Found For doId: " +str(doId)) + if(doId == self.districtId): + self.fsm.request("playGame") + else: + self.handleMessageType(msgType, di) + + #### DistrictReset #### + def enterPlayGame(self): + self._zoneDataStore = AIZoneDataStore() + AIRepository.enterPlayGame(self) + if simbase.config.GetBool('game-server-tests', 0): + from otp.distributed import DistributedTestObjectAI + self.testObject = DistributedTestObjectAI.DistributedTestObjectAI(self) + self.testObject.generateOtpObject(self.getGameDoId(), 3) + + taskMgr.doMethodLater(300, self.printPopulationToLog, self.uniqueName("printPopulationTask")) + + + def getZoneDataStore(self): + """This will crash (as designed) if called outside of the PlayGame state.""" + return self._zoneDataStore + + def getRender(self, parentId, zoneId): + # distributed objects should call getRender on themselves rather than + # call this function. Only call this for zones that are actively being + # used, otherwise the zone data will be destroyed before this function + # returns + zd = AIZoneData(self, parentId, zoneId) + render = zd.getRender() + zd.destroy() + return render + + def getNonCollidableParent(self, parentId, zoneId): + # distributed objects should call getNonCollidableParent on themselves rather than + # call this function. Only call this for zones that are actively being + # used, otherwise the zone data will be destroyed before this function + # returns + zd = AIZoneData(self, parentId, zoneId) + ncParent = zd.getNonCollidableParent() + zd.destroy() + return ncParent + + def getCollTrav(self, parentId, zoneId, *args, **kArgs): + # see comment in getRender + zd = AIZoneData(self, parentId, zoneId) + collTrav = zd.getCollTrav(*args, **kArgs) + zd.destroy() + return collTrav + + def getParentMgr(self, parentId, zoneId): + # see comment in getRender + zd = AIZoneData(self, parentId, zoneId) + parentMgr = zd.getParentMgr() + zd.destroy() + return parentMgr + + def exitPlayGame(self): + if simbase.config.GetBool('game-server-tests', 0): + self.testObject.requestDelete() + del self.testObject + self._zoneDataStore.destroy() + del self._zoneDataStore + taskMgr.remove(self.uniqueName("printPopulationTask")) + AIRepository.exitPlayGame(self) + + #### connect ##### + def enterConnect(self): + self.handler = self.handleConnect + self.lastMessageTime = 0 + + self.connect([self.mdurl], + successCallback = self._connected, + failureCallback = self._failedToConnect) + + def _failedToConnect(self, statusCode, statusString): + self.fsm.request("noConnection") + + def _connected(self): + # Register our channel + self.setConnectionName(self.districtName) + AIRepository._connected(self) + self.registerShardDownMessage(self.serverId) + if self.districtType is not None: + self.fsm.request("districtReset") + + def _handleValidDistrictDown(self, msgType, di): + downDistrictId = di.getUint32() + if (downDistrictId != self.districtId): + self.notify.error("Tried to bring down " + + str(self.districtId) + + " but " + + str(downDistrictId) + + " came down instead!") + else: + # We don't really need to do anything here. + pass + + def _handleIgnorableObjectDelete(self, msgType, di): + doId = di.getUint32() + self.notify.debug("Ignoring request to delete doId: " + + str(doId)) + + def _handleValidDistrictUp(self, msgType, di): + if msgType == STATESERVER_DISTRICT_UP: + upDistrictId = di.getUint32() + if (upDistrictId != self.districtId): + self.notify.error("Tried to bring up " + + str(self.districtId) + + " but " + + str(downDistrictId) + + " came up instead!") + else: + self.notify.info("District %s %s is up. Creating objects..." % + (self.districtId, self.districtName)) + self.fsm.request("playGame") + + def exitConnect(self): + self.handler = None + # Clean up the create district tasks + taskMgr.remove(self.uniqueName("newDistrictWait")) + del self.lastMessageTime + + def readerPollUntilEmpty(self, task): + # This overrides AIRepository.readerPollUntilEmpty() + # to provide an additional debugging hook. + + while self._debugDisconnectIds: + avId = self._debugDisconnectIds.pop() + self._doDebugDisconnectAvatar(avId) + + try: + return AIRepository.readerPollUntilEmpty(self, task) + except Exception as e: + appendStr(e, '\nSENDER ID: %s' % self.getAvatarIdFromSender()) + raise + + def handleReaderOverflow(self): + # may as well delete the shard at this point + self.deleteDistrict(self.districtId) + raise StandardError("incoming-datagram buffer overflowed, " + "aborting AI process") + + ##### General Purpose functions ##### + + def getAvatarExitEvent(self, avId): + return ("districtExit-" + str(avId)) + + def debugDisconnectAvatar(self, avId): + # This function will pretend to disconnect the indicated + # avatar at the next poll cycle, as if the avatar suddenly + # disconnected. This is for the purposes of debugging only. + # It makes the AI totally forget who this avatar is, but the + # avatar is still connected to the server. + self._debugDisconnectIds.append(avId) + + def _doDebugDisconnectAvatar(self, avId): + obj = self.doId2do.get(avId) + if obj: + self.deleteDistObject(obj) + self._announceDistObjExit(avId) + + def _announceDistObjExit(self, avId): + # This announces the exiting of this particular avatar + messenger.send(self.getAvatarExitEvent(avId)) + + # This announces generally that an avatar has left. + #messenger.send("avatarExited") + + # Now we don't need to store the disconnect reason any more. + try: + del self._avatarDisconnectReasons[avId] + except: + pass + + def setAvatarDisconnectReason(self, avId, disconnectReason): + # This is told us by the client just before he disconnects. + self._avatarDisconnectReasons[avId] = disconnectReason + + def getAvatarDisconnectReason(self, avId): + # Returns the reason (as reported by the client) for an + # avatar's unexpected exit, or 0 if the reason is unknown. It + # is only valid to query this during the handler for the + # avatar's unexpected-exit event. + return self._avatarDisconnectReasons.get(avId, 0) + + def _handleUnexpectedDistrictDown(self, di): + # Get the district Id + downDistrict = di.getUint32() + if downDistrict == self.districtId: + self.notify.warning("Somebody brought my district(" + + str(self.districtId) + + ") down! I'm shutting down!") + sys.exit() + else: + self.notify.warning("Weird... My district is " + + str(self.districtId) + + " and I just got a message that district " + + str(downDistrict) + + " is going down. I'm ignoring it." + ) + + def _handleUnexpectedDistrictUp(self, di): + # Get the district Id + upDistrict = di.getUint32() + if upDistrict == self.districtId: + self.notify.warning("Somebody brought my district(" + + str(self.districtId) + + ") up! I'm shutting down!") + sys.exit() + else: + self.notify.warning("Weird... My district is " + + str(self.districtId) + + " and I just got a message that district " + + str(upDistrict) + + " is coming up. I'm ignoring it." + ) + + def _handleMakeFriendsReply(self, di): + result = di.getUint8() + context = di.getUint32() + messenger.send("makeFriendsReply", [result, context]) + + def _handleRequestSecretReply(self, di): + result = di.getUint8() + secret = di.getString() + requesterId = di.getUint32() + messenger.send("requestSecretReply", [result, secret, requesterId]) + + def _handleSubmitSecretReply(self, di): + result = di.getUint8() + secret = di.getString() + requesterId = di.getUint32() + avId = di.getUint32() + self.writeServerEvent('entered-secret', requesterId, '%s|%s|%s' % (result, secret, avId)) + messenger.send("submitSecretReply", [result, secret, requesterId, avId]) + + def registerShardDownMessage(self, stateserverid): + datagram = PyDatagram() + datagram.addServerHeader( + stateserverid, self.ourChannel, STATESERVER_SHARD_REST) + datagram.addChannel(self.ourChannel) + # schedule for execution on socket close + self.addPostSocketClose(datagram) + + def sendSetZone(self, distobj, zoneId): + datagram = PyDatagram() + datagram.addServerHeader( + distobj.doId, self.ourChannel, STATESERVER_OBJECT_SET_ZONE) + # Add the zone parent id + # HACK: + parentId = oldParentId = self.districtId + datagram.addUint32(parentId) + # Put in the zone id + datagram.addUint32(zoneId) + # Send it + self.send(datagram) + # The servers don't inform us of this zone change, because we're the + # one that requested it. Update immediately. + # TODO: pass in the old parent and old zone + distobj.setLocation(parentId, zoneId) #, oldParentId, distobj.zoneId) + + def deleteDistrict(self, districtId): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + self.serverId, self.ourChannel, STATESERVER_OBJECT_DELETE_RAM) + # The Id of the object in question + datagram.addUint32(districtId) + # Send the message + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def makeFriends(self, avatarAId, avatarBId, flags, context): + """ + Requests to make a friendship between avatarA and avatarB with + the indicated flags (or upgrade an existing friendship with + the indicated flags). The context is any arbitrary 32-bit + integer. When the friendship is made, or the operation fails, + the "makeFriendsReply" event is generated, with two + parameters: an integer result code, and the supplied context. + """ + datagram = PyDatagram() + datagram.addServerHeader( + DBSERVER_ID, self.ourChannel, DBSERVER_MAKE_FRIENDS) + + # Indicate the two avatars who are making friends + datagram.addUint32(avatarAId) + datagram.addUint32(avatarBId) + datagram.addUint8(flags) + datagram.addUint32(context) + self.send(datagram) + + def requestSecret(self, requesterId): + """ + Requests a "secret" from the database server. This is a + unique string that will be associated with the indicated + requesterId, for the purposes of authenticating true-life + friends. + + When the secret is ready, a "requestSecretReply" message will + be thrown with three parameters: the result code (0 or 1, + indicating failure or success), the generated secret, and the + requesterId again. + """ + datagram = PyDatagram() + datagram.addServerHeader( + DBSERVER_ID,self.ourChannel,DBSERVER_REQUEST_SECRET) + + # Indicate the number we want to associate with the new secret. + datagram.addUint32(requesterId) + # Send it off! + self.send(datagram) + + def submitSecret(self, requesterId, secret): + """ + Submits a "secret" back to the database server for validation. + This attempts to match the indicated string, entered by the + user, to a string returned by a previous call to + requestSecret(). + + When the response comes back from the server, a + "submitSecretReply" message will be thrown with four + parameters: the result code (0 or 1, indicating failure or + success), the secret again, the requesterId again, and the + number associated with the original secret (that is, the + original requesterId). + """ + datagram = PyDatagram() + datagram.addServerHeader( + DBSERVER_ID, self.ourChannel, DBSERVER_SUBMIT_SECRET) + # Pass in our identifying number, and the string. + datagram.addUint32(requesterId) + datagram.addString(secret) + self.send(datagram) + + def replaceMethod(self, oldMethod, newFunction): + return 0 diff --git a/otp/ai/AIRepository.py b/otp/ai/AIRepository.py new file mode 100644 index 0000000..edf1a00 --- /dev/null +++ b/otp/ai/AIRepository.py @@ -0,0 +1,1913 @@ +from pandac.PandaModules import * +from otp.otpbase import OTPGlobals +from .AIMsgTypes import * +from direct.showbase.PythonUtil import Functor +from direct.directnotify.DirectNotifyGlobal import directNotify +from direct.fsm import ClassicFSM +from direct.fsm import State +from direct.task import Task +from direct.distributed.AsyncRequest import cleanupAsyncRequests +from direct.distributed import ParentMgr +from direct.distributed.ConnectionRepository import ConnectionRepository +import sys +import os +import copy +import types +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator +from direct.distributed.NetMessenger import NetMessenger +from direct.showbase.ContainerLeakDetector import ContainerLeakDetector +from direct.showbase import MessengerLeakDetector +from direct.showbase import LeakDetectors +from direct.showbase.GarbageReportScheduler import GarbageReportScheduler +from otp.avatar.DistributedPlayerAI import DistributedPlayerAI +from otp.distributed import OtpDoGlobals +from otp.ai.GarbageLeakServerEventAggregatorAI import GarbageLeakServerEventAggregatorAI +import time +import gc + +class AIRepository(ConnectionRepository): + """ + The new AIRepository base class + + It does not have: + - district or shard code (see AIDistrict.py) + - friends or secret friends code + - a collision traverser + + of course, a derived class may add those. + + It does have: + + object creation code + + channel listening code + + context allocator/manager + """ + notify = directNotify.newCategory("AIRepository") + + InitialContext = 100000 + + def __init__( + self, mdip, mdport, esip, esport, dcFileNames, + serverId, + minChannel, maxChannel, dcSuffix = 'AI'): + assert self.notify.debugStateCall(self) + self._channels={} + self.AIRunningNetYield = simbase.config.GetBool('ai-running-net-yield', 0) + + self._msgBundleNames = [] + + # doId->requestDeleted object + self._requestDeletedDOs = {} + + ConnectionRepository.__init__( + self, ConnectionRepository.CM_NATIVE, simbase.config) + self.dcSuffix = dcSuffix + + simbase.setupCpuAffinities(minChannel) + + self.distributedObjectRequests=set() + + self.context=self.InitialContext + self.contextToClassName={} + self.setClientDatagram(0) + + self.readDCFile(dcFileNames = dcFileNames) + + # Save the state server id + self.serverId = serverId + + # Save the connect info + self.mdurl = URLSpec(mdip, 1) + self.esurl = URLSpec(esip, 1) + + if not self.mdurl.hasPort(): + self.mdurl.setPort(mdport) + + if not self.esurl.empty() and not self.esurl.hasPort(): + self.esurl.setPort(esport) + + self.notify.info("event server at %s." % (repr(self.esurl))) + + # UDP socket for sending events to the event server. + self.udpSock = None + + if not self.esurl.empty(): + udpEventServer = SocketAddress() + if not udpEventServer.setHost(self.esurl.getServer(), self.esurl.getPort()): + self.notify.warning("Invalid host for event server: %s" % (self.esurl)) + + self.udpSock = SocketUDPOutgoing() + self.udpSock.InitToAddress(udpEventServer) + + # Save the ranges of channels that the AI controls + self.minChannel = minChannel + self.maxChannel = maxChannel + self.notify.info("dynamic doIds in range [%s, %s], total %s" % ( + minChannel, maxChannel, maxChannel - minChannel + 1)) + assert maxChannel >= minChannel + + # initialize the channel allocation + self.channelAllocator = UniqueIdAllocator(minChannel, maxChannel) + + # Define the ranges of zones. + self.minZone = self.getMinDynamicZone() + self.maxZone = self.getMaxDynamicZone() + self.notify.info("dynamic zoneIds in range [%s, %s], total %s" % ( + self.minZone, self.maxZone, self.maxZone - self.minZone + 1)) + assert self.maxZone >= self.minZone + self.zoneAllocator = UniqueIdAllocator(self.minZone, self.maxZone) + + if config.GetBool('detect-leaks', 0) or config.GetBool('ai-detect-leaks', 0): + self.startLeakDetector() + + if config.GetBool('detect-messenger-leaks', 0) or config.GetBool('ai-detect-messenger-leaks', 0): + self.messengerLeakDetector = MessengerLeakDetector.MessengerLeakDetector( + 'AI messenger leak detector') + if config.GetBool('leak-messages', 0): + MessengerLeakDetector._leakMessengerObject() + + if config.GetBool('run-garbage-reports', 0) or config.GetBool('ai-run-garbage-reports', 0): + noneValue = -1. + reportWait = config.GetFloat('garbage-report-wait', noneValue) + reportWaitScale = config.GetFloat('garbage-report-wait-scale', noneValue) + if reportWait == noneValue: + reportWait = None + if reportWaitScale == noneValue: + reportWaitScale = None + self.garbageReportScheduler = GarbageReportScheduler(waitBetween=reportWait, + waitScale=reportWaitScale) + + self._proactiveLeakChecks = (config.GetBool('proactive-leak-checks', 1) and + config.GetBool('ai-proactive-leak-checks', 1)) + self._crashOnProactiveLeakDetect = config.GetBool('crash-on-proactive-leak-detect', 1) + + # Give ourselves the first channel in the range + self.ourChannel = self.allocateChannel() + + # These are used to query database objects directly; currently + # used only for offline utilities. + self.dbObjContext = 0 + self.dbObjMap = {} + + # The UtilityAIRepository sets this to 0 to indicate we should + # not do things like issue new catalogs to toons that we load + # in. However, in the normal AI repository, we should do + # these things. + self.doLiveUpdates = 1 + + #for generating unqiue names for non-dos, manly used for tasks + self.keyCounter = 0 + + self.MaxEpockSpeed = self.config.GetFloat('ai-net-yield-epoch', 1.0/30.0) + + if self.AIRunningNetYield : + taskMgr.doYield =self.taskManagerDoYieldNetwork + + # use this for time yields without sleeps + #taskMgr.doYield = self.taskManagerDoYield + + # Used for moderation of report-a-player feature + self.centralLogger = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_CENTRAL_LOGGER, + "CentralLogger") + + self.garbageLeakLogger = GarbageLeakServerEventAggregatorAI(self) + + taskMgr.add(self._checkBundledMsgs, 'checkBundledMsgs', priority=-100) + + # skip a bit so we miss the startup sequence (it has very long frames as things are set up) + taskMgr.doMethodLater(2 * 60., self._startPerformanceLogging, + 'startPerformanceLogging') + + self.connectionName = None + self.connectionURL = None + + def _startPerformanceLogging(self, task=None): + period = self.config.GetFloat( + 'ai-performance-log-period', + self.config.GetFloat('server-performance-log-period', + choice(__dev__, 60. * 10., 60.) + ) + ) + self._sampledMaxFrameDuration = 0. + self._sampleMaxFrameDuration() + self._numPyObjs = None + self._getNumObjCounterLimit = int(max(1, (60 * 60.) / period)) + self._getNumObjCounter = 0 + taskMgr.doMethodLater(period, self._logPerformanceData, 'logPerformanceData') + return Task.done + + def _sampleMaxFrameDuration(self, task=None): + self._sampledMaxFrameDuration = max(self._sampledMaxFrameDuration, + globalClock.getMaxFrameDuration()) + # call this at a higher frequency than the frequency at which the global + # clock completely replaces its frame duration samples. that should ensure that + # we don't miss a slow frame in the performance logging + taskMgr.doMethodLater(globalClock.getAverageFrameRateInterval() * .75, + self._sampleMaxFrameDuration, 'sampleMaxFrameDuration') + return Task.done + + def _logPerformanceData(self, task=None): + avgFrameDur = 1. / globalClock.getAverageFrameRate() + maxFrameDur = max(self._sampledMaxFrameDuration, + globalClock.getMaxFrameDuration()) + self._sampledMaxFrameDuration = 0. + # gc.get_objects can be slow. only sample object count every once in a while + self._getNumObjCounter -= 1 + if self._getNumObjCounter <= 0: + self._numPyObjs = len(gc.get_objects()) + self._getNumObjCounter = self._getNumObjCounterLimit + self.notify.info( + 'avg frame duration=%fs, max frame duration=%fs, num Python objects=%s' % ( + avgFrameDur, maxFrameDur, self._numPyObjs)) + return Task.again + + def startLeakDetector(self): + if hasattr(self, 'leakDetector'): + return False + firstCheckDelay = config.GetFloat('leak-detector-first-check-delay', -1) + if firstCheckDelay == -1: + firstCheckDelay = None + self.leakDetector = ContainerLeakDetector( + 'AI container leak detector', firstCheckDelay = firstCheckDelay) + self.accept(self.leakDetector.getLeakEvent(), self._handleLeak) + self.objectTypesLeakDetector = LeakDetectors.ObjectTypesLeakDetector() + self.garbageLeakDetector = LeakDetectors.GarbageLeakDetector() + self.cppMemoryUsageLeakDetector = LeakDetectors.CppMemoryUsage() + self.taskLeakDetector = LeakDetectors.TaskLeakDetector() + self.messageListenerTypesLeakDetector = LeakDetectors.MessageListenerTypesLeakDetector() + # this isn't necessary with the current messenger implementation + #self.messageTypesLeakDetector = LeakDetectors.MessageTypesLeakDetector() + return True + + def _getMsgName(self, msgId): + # we might get a list of message names, use the first one + return makeList(AIMsgId2Names.get(msgId, 'UNKNOWN MESSAGE: %s' % msgId))[0] + + def _handleLeak(self, container, containerName): + # TODO: send email with warning + self.notify.info('sending memory leak server event') + self.writeServerEvent('memoryLeak', self.ourChannel, '%s|%s|%s|%s' % ( + containerName, len(container), itype(container), + fastRepr(container, maxLen=1, strFactor=50))) + + def getPlayerAvatars(self): + return [i for i in self.doId2do.values() + if isinstance(i, DistributedPlayerAI)] + + def uniqueName(self, desc): + return desc+"-"+str(self.serverId) + + def trueUniqueName(self, desc): + self.keyCounter += 1 + return desc+"-"+str(self.serverId)+"-"+str(self.keyCounter) + + def allocateContext(self): + self.context+=1 + if self.context >= (1<<32): + self.context=self.InitialContext + return self.context + + #### Init #### + + def writeServerEvent(self, eventType, who, description, serverId=None): + """ + Sends the indicated event data to the event server via UDP for + recording and/or statistics gathering. All related events + should be given the same eventType (some arbitrary string). + The who field should be the numeric or alphanumeric + representation of who generated this activity; generally this + will be an avatarId or the doId of the AI or something. The + description is a one-line description of the event, possibly + with embedded vertical bars as separators. + """ + if not self.udpSock: + self.notify.debug("Unable to log server event: no udpSock.") + return + + # Make who be a string (it might be passed in as an integer) + who = str(who) + + # log the access string for avatar-related messages + doId = None + try: + doId = int(who) + except: + pass + if doId is not None and doId in self.doId2do: + av = self.getDo(doId) + if hasattr(av, 'getAccess'): + description = '%s|%s' % (description, av.getAccess()) + + # break it up into chunks that will fit in a UDP packet (max 1024 bytes) + # leave some room for the header/eventType/who + maxLen = 900 + breakCount = 0 + # always run this loop at least once + while True: + if breakCount > 0: + eventType = '%s-continued%s' % (eventType, breakCount) + + # Count up the number of bytes in the packet + length = 2 + 2 + 4 + 2 + len(eventType) + 2 + len(who) + 2 + len(description) + dg = PyDatagram() + dg.addUint16(length) + dg.addUint16(1) # message type 1: server event + dg.addUint16(6) # server type 6: AI server + dg.addUint32(self.ourChannel) + dg.addString(eventType) + dg.addString(who) + dg.addString(description[:maxLen]) + description = description[maxLen:] + + self.notify.debug('%s|AIevent:%s|%s|%s' % (eventType, self.serverId, who, description)) + + if not self.udpSock.Send(dg.getMessage()): + self.notify.warning("Unable to log server event: %s" % (self.udpSock.GetLastError())) + + if len(description) == 0: + break + breakCount += 1 + + + def writeServerStatus(self, who, avatar_count, object_count): + """ + Sends the Status Packet to the event server via UDP for + recording. Used to monito the health of a AI Server. + """ + if not self.udpSock: + self.notify.debug("Unable to log server Status: no udpSock.") + return + + # Make who be a string (it might be passed in as an integer) + #who = str(who) + who=""; + + # Count up the number of bytes in the packet + length = 2 + 2 + 4 + (len(who)+2) + 4 +4 + dg = PyDatagram() + dg.addUint16(length) + dg.addUint16(2) # message type 2: server event + dg.addUint16(6) # server type 6: AI server + dg.addUint32(self.ourChannel) + dg.addString(who) + dg.addUint32(avatar_count) + dg.addUint32(object_count) + + if not self.udpSock.Send(dg.getMessage()): + self.notify.warning("Unable to log server status: %s" % (self.udpSock.GetLastError())) + + def writeServerStatus2(self, who, avatar_count, object_count): + """ + Sends the Status Packet to the event server via UDP for + recording. Used to monito the health of a AI Server. + ServerStatus2 has an additional channel code added for a ping message + We can consolidate the two writeServerStatus messages when toontown can + adopt the new otp_server + """ + if not self.udpSock: + self.notify.debug("Unable to log server Status: no udpSock.") + return + + # Make who be a string (it might be passed in as an integer) + #who = str(who) + who=""; + + # Count up the number of bytes in the packet + length = 2 + 2 + 4 + (len(who)+2) + 8 + 4 + 4 + dg = PyDatagram() + dg.addUint16(length) + dg.addUint16(3) # message type 2: server event + dg.addUint16(6) # server type 6: AI server + dg.addUint32(self.ourChannel) + dg.addString(who) + dg.addUint64(self.ourChannel) + dg.addUint32(avatar_count) + dg.addUint32(object_count) + + if not self.udpSock.Send(dg.getMessage()): + self.notify.warning("Unable to log server status: %s" % (self.udpSock.GetLastError())) + + ##### Off ##### + def enterOff(self): + self.handler = None + + def exitOff(self): + self.handler = None + + #### connect ##### + def enterConnect(self): + self.handler = self.handleConnect + self.lastMessageTime = 0 + + self.connect([self.mdurl], + successCallback = self._connected, + failureCallback = self._failedToConnect) + + def _failedToConnect(self, statusCode, statusString): + self.fsm.request("noConnection") + + def _connected(self): + # Register our channel + self.registerForChannel(self.ourChannel) + self.netMessenger = NetMessenger(self, (OTP_NET_MSGR_CHANNEL_ID_ALL_AI,)) + ## self.netMessenger.accept("transferDo", self, self.handleTransferDo) + + def _handleIgnorableObjectDelete(self, msgType, di): + doId = di.getUint32() + self.notify.debug("Ignoring request to delete doId: " + + str(doId)) + + def exitConnect(self): + self.handler = None + # Clean up the create district tasks + taskMgr.remove(self.uniqueName("newDistrictWait")) + del self.lastMessageTime + + ##### PlayGame ##### + + def enterPlayGame(self): + self.handler = self.handlePlayGame + self.createObjects() + + def handleConnect(self, msgType, di): + self.lastMessageTime = globalClock.getRealTime() + + if msgType == STATESERVER_DISTRICT_DOWN: + self._handleValidDistrictDown(msgType, di) + elif msgType == STATESERVER_DISTRICT_UP: + self._handleValidDistrictUp(msgType, di) + elif msgType == STATESERVER_OBJECT_DELETE_RAM: + self._handleIgnorableObjectDelete(msgType, di) + else: + self.handleMessageType(msgType, di) + + def handlePlayGame(self, msgType, di): + # NOTE: Inheritors may override this to check their + # own message types before calling this handler + # See ToontownAIRepository.py for example. + if msgType == STATESERVER_OBJECT_UPDATE_FIELD: + self._handleUpdateField(di) + elif msgType == STATESERVER_OBJECT_CHANGE_ZONE: + self._handleObjectChangeZone(di) + elif msgType == STATESERVER_OBJECT_DELETE_RAM: + self._handleDeleteObject(di) + elif msgType == DBSERVER_MAKE_FRIENDS_RESP: + self._handleMakeFriendsReply(di) + elif msgType == DBSERVER_REQUEST_SECRET_RESP: + self._handleRequestSecretReply(di) + elif msgType == DBSERVER_SUBMIT_SECRET_RESP: + self._handleSubmitSecretReply(di) + else: + self.handleMessageType(msgType, di) + + def handleAvatarUsage(self, di): + """ + Should only be handled by the UD process containing the AvatarManagerUD + """ + pass + + def handleAccountUsage(self, di): + """ + Should only be handled by the UD process containing the AvatarManagerUD + """ + pass + + def handleObjectDeleteDisk(self, di): + pass + + def handleObjectQueryField(self, di): + assert self.notify.debugStateCall(self) + + doId = di.getUint32() + fieldId = di.getUint16() + context = di.getUint32() + success = di.getUint8() + if success and context: + className = self.contextToClassName.pop(context, None) + # This prevents a crash that occurs when an AI sends a query, + # crashes, comes back up, then receives the response + if className: + dclass = self.dclassesByName.get(className) + interface = dclass.getFieldByIndex(fieldId) + packer = DCPacker() + packer.setUnpackData(di.getRemainingBytes()) + packer.beginUnpack(interface) + value = packer.unpackObject() + messenger.send( + "doFieldResponse-%s"%(context,), [context, value]) + + def handleObjectQueryFields(self, di): + assert self.notify.debugStateCall(self) + + doId = di.getUint32() + context = di.getUint32() + success = di.getUint8() + if success and context: + className = self.contextToClassName.pop(context) + # This prevents a crash that occurs when an AI sends a query, + # crashes, comes back up, then receives the response + if className: + dclass = self.dclassesByName.get(className) + packer = DCPacker() + objData = {} + + while di.getRemainingSize() > 0: + fieldId = di.getUint16() + interface = dclass.getFieldByIndex(fieldId) + packer.setUnpackData(di.getRemainingBytes()) + packer.beginUnpack(interface) + value = packer.unpackObject() + packer.endUnpack() + objData[interface.getName()] = value + di.skipBytes(packer.getNumUnpackedBytes()) + + messenger.send("doFieldResponse-%s"%(context,),[context,objData]) + else: + self.notify.warning("STATESERVER_OBJECT_QUERY_FIELDS_RESP received with invalid context: %s" % context) + messenger.send("doFieldQueryFailed-%s"%(context),[context]) + else: + messenger.send("doFieldQueryFailed-%s"%(context),[context]) + + def handleServerPing(self, di): + assert self.notify.debugStateCall(self) + # Deconstruct ping message from stateserver + sec = di.getUint32() + usec = di.getUint32() + url = di.getString() + channel = di.getUint32() + + # Send ping back to state server + datagram = PyDatagram() + sender=self.getMsgSender() + datagram.addServerHeader( + sender, self.ourChannel, SERVER_PING) + # A context that can be used to index the response if needed + datagram.addUint32(sec) + datagram.addUint32(usec) + datagram.addString(url) + datagram.addUint32(channel) + self.send(datagram) + + + + def handleMessageType(self, msgType, di): + if msgType == CLIENT_GET_STATE_RESP: + # This one comes back after we sendSetAvatarIdMsg() + pass + elif msgType == DBSERVER_GET_STORED_VALUES_RESP: + self._handleDatabaseGetStoredValuesResp(di) + elif msgType == DBSERVER_CREATE_STORED_OBJECT_RESP: + self._handleDatabaseCreateStoredObjectResp(di) + elif msgType == STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT_RESP: + self._handleDatabaseGenerateResponse(di) + elif msgType == STATESERVER_OBJECT_SET_ZONE: + # import pdb;pdb.set_trace() + self.handleSetLocation(di) + elif msgType == STATESERVER_OBJECT_LEAVING_AI_INTEREST: + # import pdb;pdb.set_trace() + self.handleDistObjExit(di) + elif msgType == STATESERVER_QUERY_OBJECT_ALL_RESP: + self.handleDistObjRequestResponse(di) + elif msgType == STATESERVER_OBJECT_ENTER_AI_RECV: + self.handleDistObjEnter(di) + elif msgType == STATESERVER_OBJECT_ENTERZONE_WITH_REQUIRED_OTHER: + self.handleDistObjEnterZone(di) + elif msgType == STATESERVER_QUERY_OBJECT_CHILDREN_RESP: + # This acts much like a generate with required and other + self.handleDistObjEnter(di) + elif msgType == STATESERVER_QUERY_OBJECT_CHILDREN_LOCAL_DONE: + # This acts much like a generate with required and other + self.handleQueryObjectChildrenLocalDone(di) + elif msgType == STATESERVER_OBJECT_ENTER_OWNER_RECV: + # This message is sent at some stage during avatar creation. + self.handleDistObjEnterOwner(di) + elif msgType == STATESERVER_OBJECT_CHANGE_OWNER_RECV: + self.handleDistObjChangeOwner(di) + elif msgType == ACCOUNT_AVATAR_USAGE: + self.handleAvatarUsage(di) + elif msgType == ACCOUNT_ACCOUNT_USAGE: + self.handleAccountUsage(di) + elif msgType == STATESERVER_OBJECT_DELETE_DISK: + self.handleObjectDeleteDisk(di) + elif msgType == STATESERVER_OBJECT_QUERY_FIELD_RESP: + self.handleObjectQueryField(di) + elif msgType == STATESERVER_OBJECT_QUERY_FIELDS_RESP: + self.handleObjectQueryFields(di) + elif msgType == STATESERVER_OBJECT_QUERY_MANAGING_AI: + pass + elif msgType == SERVER_PING: + self.handleServerPing(di) + else: + AIRepository.notify.warning( + "Ignoring unexpected message type: %s in state: %s" % + (msgType, self.fsm.getCurrentState().getName())) + if __dev__: + import pdb + pdb.set_trace() + + def exitPlayGame(self): + self.handler = None + self.stopReaderPollTask() + + self.deleteDistributedObjects() + cleanupAsyncRequests() + + # Make sure there are no leftover tasks that shouldn't be here. + for task in taskMgr.getTasks(): + if (task.name in ("igLoop", + "aiSleep", + "doLaterProcessor", + "eventManager", + "tkLoop", + )): + # These tasks are ok + continue + else: + print(taskMgr) + self.notify.error("You can't leave otp until you clean up your tasks.") + + # Make sure there are no event hooks still hanging. + if not messenger.isEmpty(): + print(messenger) + self.notify.error("Messenger should not have any events in it.") + + ##### NoConnection ##### + + def enterNoConnection(self): + self.handler = self.handleMessageType + + AIRepository.notify.warning( + "Failed to connect to message director at %s." % (repr(self.mdurl))) + # Wait five seconds, then try to reconnect + taskMgr.doMethodLater(5, self.reconnect, self.uniqueName("waitToReconnect")) + + def reconnect(self, task): + self.fsm.request("connect") + return Task.done + + def exitNoConnection(self): + self.handler = None + taskMgr.remove(self.uniqueName("waitToReconnect")) + + ##### General Purpose functions ##### + + def createObjects(self): + # This is meant to be a pure virtual function that gets + # overridden by inheritors. This is where you should create + # DistributedObjectAI's (and generate them), create the objects + # that manage them, as well as spawn + # any tasks that will create DistributedObjectAI's in the future. + pass + + def getHandleClassNames(self): + # This is meant to be a pure virtual function that gets + # overridden by inheritors. + + # This function should return a tuple or list of string names + # that represent distributed object classes for which we want + # to make a special 'handle' class available. For instance, + # to make the DistributedToonHandleAI class available, this + # function should return ('DistributedToon',). + return () + + def handleDistObjRequestResponse(self, di): + assert self.notify.debugStateCall(self) + context = di.getUint32() + parentId = di.getUint32() + zoneId = di.getUint32() + classId = di.getUint16() + doId = di.getUint32() + # if there's no context, there's no point in doing anything else + if context: + # Look up the dclass + dclass = self.dclassesByNumber[classId] + # Create a new distributed object + distObj = self._generateFromDatagram( + parentId, zoneId, dclass, doId, di, addToTables=False) + #distObj.isQueryAllResponse = True + + self.writeServerEvent('doRequestResponse', doId, '%s'%(context,)) + messenger.send("doRequestResponse-%s"%(context,), [context, distObj]) + + def postGenerate(self, context, distObj): + parentId = distObj.parentId + zoneId = distObj.zoneId + doId = distObj.doId + self.distributedObjectRequests.discard(doId) + distObj.setLocation(parentId, zoneId) + self.writeServerEvent('distObjEnter', doId, '') + + def handleDistObjEnter(self, di): + assert self.notify.debugStateCall(self) + context = di.getUint32() + parentId = di.getUint32() + zoneId = di.getUint32() + classId = di.getUint16() + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + # Is it in our dictionary? + if self.doId2do.has_key(doId): + self.notify.warning("Object Entered " + str(doId) + + " re-entered without exiting") + # Create a new distributed object + distObj = self._generateFromDatagram( + parentId, zoneId, dclass, doId, di) + self.postGenerate(context, distObj) + + # Put it in the dictionary - Is it already in our dictionary? Not + # sure why or how this happens, but it does come up from time to + # time in production + # Asad Todo take out this security check for now don't publish to toontown!!! +## if self.doId2do.has_key(doId): +## self.writeServerEvent('suspicious', doId, 'Avatar re-entered without exiting') +## # Note: until we figure out what is causing this bug, it will be an error +## # This is listed as bug 50608 in the production remarks db +## self.notify.error("Avatar %s re-entered without exiting" % doId) + + + def handleDistObjEnterZone(self, di): + assert self.notify.debugStateCall(self) + parentId = di.getUint32() + zoneId = di.getUint32() + classId = di.getUint16() + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + # Is it in our dictionary? + if self.doId2do.has_key(doId): + self.notify.warning("Object Entered " + str(doId) + + " re-entered without exiting") + # Create a new distributed object + distObj = self._generateFromDatagram( + parentId, zoneId, dclass, doId, di) + # self.postGenerate(context, distObj) + + # Put it in the dictionary - Is it already in our dictionary? Not + # sure why or how this happens, but it does come up from time to + # time in production + # Asad Todo take out this security check for now don't publish to toontown!!! +## if self.doId2do.has_key(doId): +## self.writeServerEvent('suspicious', doId, 'Avatar re-entered without exiting') +## # Note: until we figure out what is causing this bug, it will be an error +## # This is listed as bug 50608 in the production remarks db +## self.notify.error("Avatar %s re-entered without exiting" % doId) + + + def handleDistObjEnterOwner(self, di): + # TEMP + return self.handleDistObjEnter(di) + """ + assert self.notify.debugStateCall(self) + # The context is bogus + context = di.getUint32() + # The zone the avatar is in + parentId = di.getUint32() + zoneId = di.getUint32() + # Get the class Id + classId = di.getUint16() + # Get the DO Id + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + self.notify.info( + 'ignoring owner create, context=%s, parentId=%s, ' + 'zoneId=%s, doId=%s, dclass=%s' % ( + context, parentId, zoneId, doId, dclass.getName())) + """ + + def handleDistObjChangeOwner(self, di): + assert self.notify.debugStateCall(self) + doId = di.getUint32() + newOwnerId = di.getUint32() + oldOwnerId = di.getUint32() + self.notify.info( + 'ignoring owner change, ' + 'doId=%s, newOwnerId=%s, oldOwnerID=%s' % ( + doId, newOwnerId, oldOwnerId)) + + def getDeleteDoIdEvent(self, doId): + # this event is sent after the object is deleted, + # and is sent even if the object was not in the tables. + return 'AIRDeleteDoId-%s' % doId + + def handleDistObjExit(self, di): + # Get the distributed object id + doId = di.getUint32() + self.notify.debug("handleDistObjExit %s" % doId) + + # If it is in the dictionary, remove it. + obj = self.doId2do.get(doId) + if obj: + self.deleteDistObject(obj) + else: + self.notify.warning("DistObj " + str(doId) + + " exited, but never entered.") + + # announce delete event for this doId, even if obj doesn't exist + # send it after we delete any existing object + messenger.send(self.getDeleteDoIdEvent(doId)) + + # TODO: remove this in favor of getDeleteDoIdEvent + # Throw an event telling everyone that the distObj is gone + self._announceDistObjExit(doId) + + def _announceDistObjExit(self, doId): + pass + + def _generateFromDatagram(self, parentId, zoneId, dclass, doId, di, addToTables=True): + if (self.doId2do.has_key(doId)): + # added to prevent objects already generated from being generated again (was + # happening with some traded inventory objects, quests specfically) + return self.doId2do[doId] + # We got a datagram telling us to create a new DO instance + classDef = dclass.getClassDef() + try: + distObj = classDef(self) + except TypeError as e: + self.notify.error('%s (class %s, parentId %d, zoneId %d, doId %d)' % \ + (e, dclass.getName(), parentId, zoneId, doId)) + distObj.dclass = dclass + # Assign it an Id + distObj.doId = doId + # Since the distObj has been created explicitly from the + # server, we do not own its doId, and hence we shouldn't try + # to deallocate it. + distObj.doNotDeallocateChannel = 1 + + # init the parentId and zoneId + if addToTables: + # add the new DO to the tables + # let addDoToTables set the parentId and zoneId, it ignores + # the location if it matches what's already on the object + distObj.parentId = None + distObj.zoneId = None + self.addDOToTables(distObj, location = (parentId,zoneId)) + else: + distObj.parentId = parentId + distObj.zoneId = zoneId + + # Generate this Object + distObj.generate() + + # Update the required fields + distObj.updateAllRequiredOtherFields(dclass, di) + + return distObj + + def _handleUpdateField(self, di): + # Get the Do Id + doId = di.getUint32() + # Find the do + do = self.doId2do[doId] + # Let the dclass finish the job + do.dclass.receiveUpdate(do, di) + + def _handleObjectChangeZone(self, di): + # Get the Do Id + doId = di.getUint32() + newParentId = di.getUint32() + newZoneId = di.getUint32() + oldParentId = di.getUint32() + oldZoneId = di.getUint32() + # Find the do + do = self.doId2do.get(doId) + if do is None: + self.notify.warning( + 'handleObjectChangeZone: (NOT PRESENT) %s:(%s, %s)->(%s, %s)' %( + doId, oldParentId, oldZoneId, newParentId, newZoneId)) + else: + self.notify.debug('handleObjectChangeZone: %s:(%s, %s)->(%s, %s)' %( + doId, oldParentId, oldZoneId, newParentId, newZoneId)) + # TODO: pass in the old parent and old zone + do.setLocation(newParentId, newZoneId) #, oldParentId, oldZoneId) + + def _handleDeleteObject(self, di): + # Get the DO Id + doId = di.getUint32() + obj = self.doId2do.get(doId) + if obj: + # If it is in the dictionary, remove it. + self.deleteDistObject(obj) + else: + # Otherwise ignore it + AIRepository.notify.warning( + "Asked to delete non-existent DistObjAI " + str(doId)) + + # announce delete event for this doId, even if obj doesn't exist + messenger.send(self.getDeleteDoIdEvent(doId)) + + def sendUpdate(self, do, fieldName, args): + #print("---------------sendUpdate--") + #print(do) + #print(do.doId) + #print(fieldName) + #print(args) + dg = do.dclass.aiFormatUpdate( + fieldName, do.doId, do.doId, self.ourChannel, args) + self.sendDatagram(dg) + + def sendUpdateToDoId(self, dclassName, fieldName, doId, args, + channelId=None): + """ + channelId can be used as a recipient if you want to bypass the normal + airecv, ownrecv, broadcast, etc. If you don't include a channelId + or if channelId == doId, then the normal broadcast options will + be used. + + See Also: def queryObjectField + """ + dclass=self.dclassesByName.get(dclassName+self.dcSuffix) + assert dclass is not None + if channelId is None: + channelId=doId + if dclass is not None: + dg = dclass.aiFormatUpdate( + fieldName, doId, channelId, self.ourChannel, args) + self.send(dg) + + def createDgUpdateToDoId(self, dclassName, fieldName, doId, args, + channelId=None): + """ + channelId can be used as a recipient if you want to bypass the normal + airecv, ownrecv, broadcast, etc. If you don't include a channelId + or if channelId == doId, then the normal broadcast options will + be used. + + This is just like sendUpdateToDoId, but just returns + the datagram instead of immediately sending it. + """ + result = None + dclass=self.dclassesByName.get(dclassName+self.dcSuffix) + assert dclass is not None + if channelId is None: + channelId=doId + if dclass is not None: + dg = dclass.aiFormatUpdate( + fieldName, doId, channelId, self.ourChannel, args) + result = dg + return result + + def sendUpdateToGlobalDoId(self, dclassName, fieldName, doId, args): + """ + Used for sending messages from an AI directly to an + uber object. + """ + dclass = self.dclassesByName.get(dclassName) + assert dclass, 'dclass %s not found in DC files' % dclassName + dg = dclass.aiFormatUpdate( + fieldName, doId, doId, self.ourChannel, args) + self.send(dg) + + def sendUpdateToChannel(self, do, channelId, fieldName, args): + dg = do.dclass.aiFormatUpdate( + fieldName, do.doId, channelId, self.ourChannel, args) + self.sendDatagram(dg) + + def sendUpdateToChannelFrom(self, do, channelId, fieldName, fromid, args): + dg = do.dclass.aiFormatUpdate(fieldName, do.doId, channelId, + fromid, args) + self.send(dg) + + def startMessageBundle(self, name): + # start bundling messages together. Use this for instance if you want to + # make sure that location and position of an object are processed atomically + # on the state server, to prevent clients from getting the new location and an + # old position (relative to old location) on client generate of the object + self._msgBundleNames.append(name) + ConnectionRepository.startMessageBundle(self) + def sendMessageBundle(self, senderChannel): + # stop bundling messages and send the bundle + # senderChannel is typically the doId of the object affected by the messages + ConnectionRepository.sendMessageBundle(self, self.districtId, senderChannel) + self._msgBundleNames.pop() + + def _checkBundledMsgs(self, task=None): + # message bundles should not last across frames + num = len(self._msgBundleNames) + while len(self._msgBundleNames): + self.notify.warning('abandoning message bundle: %s' % self._msgBundleNames.pop()) + if num > 0: + self.abandonMessageBundles() + self.notify.error('message bundling leak, see warnings above (most recent first)') + return task.cont + + def registerForChannel(self, channelNumber): + if self._channels.get(channelNumber): + # We are already registered for this channel. + return + self._channels[channelNumber]=1 + # Time to send a register for channel message to the msgDirector + datagram = PyDatagram() +# datagram.addServerControlHeader(CONTROL_SET_CHANNEL) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_SET_CHANNEL) + + datagram.addChannel(channelNumber) + self.send(datagram) + + def addPostSocketClose(self, themessage): + # Time to send a register for channel message to the msgDirector + datagram = PyDatagram() +# datagram.addServerControlHeader(CONTROL_ADD_POST_REMOVE) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_ADD_POST_REMOVE) + + datagram.addString(themessage.getMessage()) + self.send(datagram) + + def addPostSocketCloseUD(self, dclassName, fieldName, doId, args): + dclass = self.dclassesByName.get(dclassName) + assert dclass, 'dclass %s not found in DC files' % dclassName + dg = dclass.aiFormatUpdate( + fieldName, doId, doId, self.ourChannel, args) + self.addPostSocketClose(dg) + + def unregisterForChannel(self, channelNumber): + if self._channels.get(channelNumber) is None: + # We are already unregistered for this channel. + return + del self._channels[channelNumber] + # Time to send a unregister for channel message to the msgDirector + datagram = PyDatagram() +# datagram.addServerControlHeader(CONTROL_REMOVE_CHANNEL) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_REMOVE_CHANNEL) + + datagram.addChannel(channelNumber) + self.send(datagram) + + #---------------------------------- + + def addAvatarToChannels(self, avatarId, listOfChannels): + """ + avatarId is a 32 bit doId + """ + assert self.notify.debugCall() + self.addConnectionToChannels((1<<32)+avatarId, listOfChannels) + + def removeAvatarFromChannels(self, avatarId, listOfChannels): + """ + avatarId is a 32 bit doId + """ + assert self.notify.debugCall() + self.removeConnectionToChannels((1<<32)+avatarId, listOfChannels) + + def addConnectionToChannels(self, targetConnection, listOfChannels): + """ + avatarId is a 32 bit doId + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + targetConnection, self.serverId, CLIENT_AGENT_OPEN_CHANNEL) + for i in listOfChannels: + dg.addUint64(i) + self.send(dg) + + def removeConnectionToChannels(self, targetConnection, listOfChannels): + """ + avatarId is a 32 bit doId + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + targetConnection, self.serverId, CLIENT_AGENT_CLOSE_CHANNEL) + for i in listOfChannels: + dg.addUint64(i) + self.send(dg) + + def addInterestToConnection(self, targetConnection, interestId, + contextId, parentDoId, zoneIdList): + """ + Allows the AIRepository to initiate interest on the client. + See otp.ai.AIInterestHandles for a list of interestId's to use. + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + (1<<32)+targetConnection, self.serverId, + CLIENT_AGENT_SET_INTEREST) + + # Set the high bit to indicate that the interest is being governed by + # the AI and not the client + dg.addUint16((1<<15)+interestId) + dg.addUint32(contextId) + dg.addUint32(parentDoId) + dg.addUint32(contextId) + if isinstance(zoneIdList, types.ListType): + # sort and remove repeated entries + zIdSet = set(zoneIdList) + for zoneId in zIdSet: + dg.addUint32(zoneId) + else: + dg.addUint32(zoneIdList) + self.send(dg) + + def removeInterestFromConnection(self, targetConnection, interestId, + contextId=0): + """ + Allows the AIRepository to remove interest on the client. + See otp.ai.AIInterestHandles for a list of interestId's to use. + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + (1<<32)+targetConnection, self.serverId, + CLIENT_AGENT_REMOVE_INTEREST) + # set the high bit to indicate that the interest is being governed by + # the AI and not the client + dg.addUint16((1<<15)+interestId) + dg.addUint32(contextId) + self.send(dg) + + def setAllowClientSend(self, avatarId, + dObject, fieldNameList = []): + """ + Allow an AI to temporarily give a client 'clsend' privileges + on a particular fields on a particular object. This should + be used on fields that are 'ownsend' by default. When you want + to revoke these privileges, use clearAllowClientSend() to end + these privileges. + """ + assert self.notify.debugCall() + dg = PyDatagram() + dg.addServerHeader( + (1<<32)+avatarId, self.serverId, + CLIENT_SET_FIELD_SENDABLE) + + # Set the high bit to indicate that the interest is being governed by + # the AI and not the client + dg.addUint32(dObject.doId) + assert isinstance(fieldNameList, types.ListType) + + dclass = dObject.dclass + # sort and remove repeated entries as we discover the field + # ids for the specified names + fieldIdSet = set(dclass.getFieldByName(name).getNumber() \ + for name in fieldNameList) + + # insert the fieldIds into the datagram + for fieldId in sorted(fieldIdSet): + dg.addUint16(fieldId) + self.send(dg) + + def clearAllowClientSend(self, avatarId, dObject): + self.setAllowClientSend(avatarId, dObject) + + # ---------------------------------- + + def setConnectionName(self, name): + self.connectionName = name + # Time to send a register for channel message to the msgDirector + datagram = PyDatagram() + # datagram.addServerControlHeader(CONTROL_SET_CON_NAME) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_SET_CON_NAME) + + datagram.addString(name) + self.send(datagram) + + def setConnectionURL(self, url): + self.connectionURL = url + # Time to send a register for channel message to the msgDirector + datagram = PyDatagram() + # datagram.addServerControlHeader(CONTROL_SET_CON_NAME) + datagram.addInt8(1) + datagram.addChannel(CONTROL_MESSAGE) + datagram.addUint16(CONTROL_SET_CON_URL) + + datagram.addString(url) + self.send(datagram) + + def deleteObjects(self): + # This is meant to be a pure virtual function that gets + # overridden by inheritors. + # This function is where objects that manage DistributedObjectAI's + # should be cleaned up. Since this is only called during a district + # shutdown of some kind, it is not useful to delete the existing + # DistributedObjectAI's, but rather to just make sure that they + # are no longer referenced, and no new ones are created. + pass + + def allocateChannel(self): + channel=self.channelAllocator.allocate() + if channel==-1: + raise RuntimeError("channelAllocator.allocate() is out of channels") + if self.channelAllocator.fractionUsed()>0.75: + # There is some debate about how bad it is to run out of + # channels. Being ignorant about what exactly will happen + # if a channel is reused too quickly, we decided to bail + # out if we got low on channels. By the way, being low on + # channels is not the real problem. The problem is reusing + # a channel too soon after it's freed. There is an assumption + # here that if there are a lot of free channels and the + # channels are reused in the same order that they are freed, + # then there is a good chance that any allocated channel has + # "aged" properly. + # Schuyler has daydreamed about a system that will track the age + # of freed ids and sleep or flag an error as apropriate. See him + # for details (esp. if you want to write a cross-platform version + # of said feature). + raise RuntimeError("Dangerously low on channels.") + # Sanity check + assert (channel >= self.minChannel) and (channel <= self.maxChannel) + + if 0: ## used to debug the channel code + import traceback + if not hasattr(self, "debug_dictionary"): + self.debug_dictionary = {} + __builtins__["debug_dictionary"] = self.debug_dictionary + + for id in self.debug_dictionary.keys(): + if not self.doId2do.has_key(id): + print("--------------------- Not In DOID table") + print(id) + #traceback.print_list(self.debug_dictionary[id]) + print(self.debug_dictionary[id]) + del self.debug_dictionary[id] # never report it again .. + + self.debug_dictionary[channel] = traceback.extract_stack(None,7) + + return channel + + def deallocateChannel(self, channel): + if 0: ## used to debug the channel code .. see above + del self.debug_dictionary[channel] + + self.channelAllocator.free(channel) + + def getMinDynamicZone(self): + # Override this to return the minimum allowable value for a + # dynamically-allocated zone id. + return 0 + + def getMaxDynamicZone(self): + # Override this to return the maximum allowable value for a + # dynamically-allocated zone id. + return 0 + + def allocateZone(self): + zoneId=self.zoneAllocator.allocate() + if zoneId==-1: + raise RuntimeError("zoneAllocator.allocate() is out of zoneIds") + # Sanity check + assert (zoneId >= self.minZone) and (zoneId <= self.maxZone) + return zoneId + + def deallocateZone(self, zoneId): + self.zoneAllocator.free(zoneId) + + # NOTE: the public API for this is in DistributedObjectAI.b_setLocation() + # @report(types = ['args'], dConfigParam = 'avatarmgr') + def sendSetLocation(self, distobj, parentId, zoneId, owner=None): + datagram = PyDatagram() + datagram.addServerHeader( + distobj.doId, self.ourChannel, STATESERVER_OBJECT_SET_ZONE) + datagram.addUint32(parentId) + datagram.addUint32(zoneId) + self.send(datagram) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def sendSetLocationDoId(self, doId, parentId, zoneId, owner=None): + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_SET_ZONE) + datagram.addUint32(parentId) + datagram.addUint32(zoneId) + self.send(datagram) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def sendSetOwnerDoId(self, doId, ownerId): + # allowed channels are (from http://aspen.online.disney.com/mediawiki/index.php/P2P%2C_OWNERSHIP_STUFF): + # account<<32 + avatar + # 1<<32 + avatar + # 1<<32 + account + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_SET_OWNER_RECV) + datagram.addChannel((1<<32) + ownerId) + self.send(datagram) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def sendClearOwnerDoId(self, doId): + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_SET_OWNER_RECV) + datagram.addChannel(0) + self.send(datagram) + + def sendSetZone(self, distobj, zoneId): + self.notify.error("non-district types should not call sendSetZone") + + def startTrackRequestDeletedDO(self, obj): + if obj.doId in self._requestDeletedDOs: + self.notify.warning('duplicate requestDelete for %s %s' % (obj.__class__.__name__, obj.doId)) + # store object and time of requestDelete + self._requestDeletedDOs[obj.doId] = (obj, globalClock.getRealTime()) + + def stopTrackRequestDeletedDO(self, obj): + # sometimes objects are deleted without having requested a delete + #assert obj.doId in self._requestDeletedDOs + if (hasattr(obj,'doId')) and (obj.doId in self._requestDeletedDOs): + del self._requestDeletedDOs[obj.doId] + + def getRequestDeletedDOs(self): + # returns list of (obj, age of delete request), sorted by descending age + response = [] + now = globalClock.getRealTime() + for obj, requestTime in self._requestDeletedDOs.values(): + # calculate how long it has been since delete was requested + age = now - requestTime + index = 0 + while index < len(response): + if age > response[index][1]: + break + index += 1 + response.insert(index, (obj, age)) + return response + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def requestDelete(self, distobj): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + distobj.doId, self.ourChannel, STATESERVER_OBJECT_DELETE_RAM) + # The Id of the object in question + datagram.addUint32(distobj.doId) + self.send(datagram) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def requestDeleteDoId(self, doId): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_DELETE_RAM) + # The Id of the object in question + datagram.addUint32(doId) + self.send(datagram) + + def requestDeleteDoIdFromDisk(self, doId): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_DELETE_DISK) + # The Id of the object in question + datagram.addUint32(doId) + self.send(datagram) + + def getDatabaseGenerateResponseEvent(self, context): + # handler must accept (doId) + return 'DBGenResponse-%s' % context + + def _handleDatabaseGenerateResponse(self, di): + assert self.notify.debugCall(self) + context = di.getUint32() + doId = di.getUint32() + self.notify.debug( + '_handleDatabaseGenerateResponse, context=%s, doId=%s' % + (context, doId)) + messenger.send( + self.getDatabaseGenerateResponseEvent(context), [doId]) + + def getDatabaseIdForClassName(self, className): + assert 0 + # You probably want to override this to return something better. + return 0 + + def requestDatabaseGenerate( + self, classId, context, + parentId=0, zoneId=0, + ownerChannel=0, ownerAvId=None, + databaseId=None, values = None): + """ + context is any 32 bit integer to be used a reference + for this message (and its reply). + parentId may be 0 or a valid distributed object ID. + zoneId may be 0 or a valid zone ID (int32). + ownerChannel may be 0 or a valid owner channel (int64) + OR + ownerAvId may be None or a player doId + databaseId is a distributed object ID for the database + (maybe a constant). + values is a dictionary of other member values (or None) + + To get doId of new object, listen for this event: + air.getDatabaseGenerateResponseEvent(context) + If object location was provided, and the location is on this + district, the object will be generated shortly after the + above message is sent. + """ + AIRepository.notify.debugCall() + #self.notify.info('requestDatabaseGenerate, class=%s, context=%s' % + # (classId, context)) + if ownerChannel == 0 and ownerAvId is not None: + ownerChannel = (1<<32) + ownerAvId + if self.dclassesByNumber.has_key(classId): + dclass = self.dclassesByNumber[classId] + else: + if self.dclassesByName.has_key(classId): + dclass = self.dclassesByName[classId] + elif self.dclassesByName.has_key(classId+self.dcSuffix): + dclass = self.dclassesByName[classId+self.dcSuffix] + elif self.dclassesByName.has_key(classId+'AI'): + dclass = self.dclassesByName[classId+'AI'] + else: + self.notify.warning("dclass not found %s"%(classId,)) + if __dev__: + import pdb; pdb.set_trace() + if databaseId is None: + databaseId=self.getDatabaseIdForClassName(dclass.getName()) + if values is None: + if simbase.newDBRequestGen: + dg = dclass.aiDatabaseGenerateContext( + context, parentId, zoneId, ownerChannel, + databaseId, self.ourChannel) + else: + dg = dclass.aiDatabaseGenerateContextOld( + context, parentId, zoneId, + databaseId, self.ourChannel) + self.send(dg) + else: + packer = DCPacker() + packer.rawPackUint8(1) + packer.rawPackUint64(databaseId) + packer.rawPackUint64(self.ourChannel) + packer.rawPackUint16( + STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT) + ## packer.rawPackUint16( + ## STATESERVER_OBJECT_CREATE_WITH_REQUIR_OTHER_CONTEXT) + packer.rawPackUint32(parentId) + packer.rawPackUint32(zoneId) + if simbase.newDBRequestGen: + packer.rawPackUint64(ownerChannel) + packer.rawPackUint16(dclass.getNumber()) + packer.rawPackUint32(context) + + optionalFields = [] + + for i in range(dclass.getNumInheritedFields()): + field = dclass.getInheritedField(i) + if field.asMolecularField() == None: + if field.isRequired(): + # Packs Required Fields + value = values.get(field.getName(), None) + packer.beginPack(field) + + if value == None: + packer.packDefaultValue() + else: + if not field.packArgs(packer, value): + raise StandardError + packer.endPack() + else: + value = values.get(field.getName(), None) + + if value != None: + fieldDec = {} + fieldDec['field'] = field + fieldDec['value'] = value + optionalFields.append(fieldDec) + + packer.rawPackUint16(len(optionalFields)) + + # Packs Optional Fields + for i in optionalFields: + field = i['field'] + value = i['value'] + + packer.rawPackUint16(field.getNumber()) + packer.beginPack(field) + field.packArgs(packer, value) + packer.endPack() + + if packer.hadError(): + raise StandardError + + dg = Datagram(packer.getString()) + self.send(dg) + + def lostConnection(self): + ConnectionRepository.lostConnection(self) + sys.exit() + + def handleDatagram(self, di): + if self.notify.getDebug(): + print("AIRepository received datagram:") + di.getDatagram().dumpHex(ostream) + + channel=self.getMsgChannel() + if channel in self.netMessenger.channels: + self.netMessenger.handle(di.getString()) + else: + self.handler(self.getMsgType(), di) + + def deleteDistObject(self, do): + assert self.notify.debugCall() + + # if not hasattr(do, "isQueryAllResponse") or not do.isQueryAllResponse: + do.sendDeleteEvent() + # Remove it from the dictionary + self.removeDOFromTables(do) + # Delete the object itself + do.delete() + if self._proactiveLeakChecks: + # make sure we're not leaking + do.detectLeaks() + + def sendAnotherGenerate(self, distObj, toChannel): + assert self.notify.debugCall() + dg = distObj.dclass.aiFormatGenerate( + distObj, distObj.doId, distObj.parentId, distObj.zoneId, + toChannel, self.ourChannel, []) + self.send(dg) + + def generateWithRequired(self, distObj, parentId, zoneId, optionalFields=[]): + assert self.notify.debugStateCall(self) + # Assign it an id + distObj.doId = self.allocateChannel() + # Put the new DO in the dictionaries + self.addDOToTables(distObj, location = (parentId,zoneId)) + # Send a generate message + distObj.sendGenerateWithRequired(self, parentId, zoneId, optionalFields) + + + # this is a special generate used for estates, or anything else that + # needs to have a hard coded doId as assigned by the server + def generateWithRequiredAndId( + self, distObj, doId, parentId, zoneId, optionalFields=[]): + assert self.notify.debugStateCall(self) + # Assign it an id + distObj.doId = doId + # Since the distObj has been created explicitly from the + # server, we do not own its doId, and hence we shouldn't try + # to deallocate it. + distObj.doNotDeallocateChannel = 1 + # Put the new DO in the dictionaries + self.addDOToTables(distObj, location = (parentId,zoneId)) + # Send a generate message + distObj.sendGenerateWithRequired(self, parentId, zoneId, optionalFields) + + def queryObjectAll(self, doId, context=0): + """ + Get a one-time snapshot look at the object. + """ + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_QUERY_OBJECT_ALL) + # A context that can be used to index the response if needed + datagram.addUint32(context) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def queryObjectZoneIds(self, stateServerId, obj2ZoneDict): + # obj2ZoneDict should be a dict looking like this: + # { objId : [zoneId, zoneId, ...], + # objId : [zoneId, zoneId, ...], + # } + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + stateServerId, self.ourChannel, STATESERVER_QUERY_ZONE_OBJECT_ALL) + numObjs = len(obj2ZoneDict.keys()) + datagram.addUint16(numObjs) + for objId, zoneIds in obj2ZoneDict.values(): + datagram.addUint32(objId) + datagram.addUint16(len(zoneIds)) + for zoneId in zoneIds: + datagram.addUint32(zoneId) + # This is for a forwarding system that I did not hook up. + # Ask Roger for details. + datagram.addUint16(0) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def queryObjectChildrenLocal(self, parentId, context=0): + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + self.serverId, self.ourChannel, STATESERVER_QUERY_OBJECT_CHILDREN_LOCAL) + datagram.addUint32(parentId) + datagram.addUint32(context) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def handleQueryObjectChildrenLocalDone(self, di): + # We asked to get generates for all objects that are children of + # us. This is the callback that says the server is done sending all + # those generates. + assert self.notify.debugCall() + parentId = di.getUint16() + context = di.getUint32() + parent = self.doId2do.get(parentId) + if parent: + parent.handleQueryObjectChildrenLocalDone(context) + else: + self.notify.warning('handleQueryObjectChildrenLocalDone: parentId %s not found' % + parentId) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectFieldId(self, doId, fieldId, context=0): + """ + Get a one-time snapshot look at the object. + """ + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_QUERY_FIELD) + datagram.addUint32(doId) + datagram.addUint16(fieldId) + # A context that can be used to index the response if needed + datagram.addUint32(context) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectFieldIds(self, doId, fieldIds, context=0): + """ + Get a one-time snapshot look at the object. + Query multiple field IDs from the same object. + """ + assert self.notify.debugStateCall(self) + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + doId, self.ourChannel, STATESERVER_OBJECT_QUERY_FIELDS) + datagram.addUint32(doId) + datagram.addUint32(context) + for x in fieldIds: + datagram.addUint16(x) + self.send(datagram) + # Make sure the message gets there. + self.flush() + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectStringFieldIds(self, dbId, objString, fieldIds, context=0): + """ + Get a one-time snapshot look at the object. + Query multiple field IDs from the same object, by object string. + """ + assert self.notify.debugStateCall(self) + # Create a message + dg = PyDatagram() + dg.addServerHeader( + dbId, self.ourChannel, STATESERVER_OBJECT_QUERY_FIELDS_STRING) + dg.addString(objString) + dg.addUint32(context) + for x in fieldIds: + dg.addUint16(x) + self.send(dg) + # Make sure the message gets there. + self.flush() + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectStringFields( + self, dbId, dclassName, objString, fieldNames, context=0): + """ + Get a one-time snapshot look at the object. + Query multiple field names from the same object, by object string. + """ + assert self.notify.debugStateCall(self) + assert len(dclassName) > 0 + for fn in fieldNames: + assert len(fn) > 0 + dclass = self.dclassesByName.get(dclassName) + assert dclass is not None + if not dclass: + self.notify.error( + "queryObjectStringFields invalid dclassName %s"%(dclassName)) + return + if dclass is not None: + fieldIds = [] + for fn in fieldNames: + id = dclass.getFieldByName(fn).getNumber() + assert id + if not id: + self.notify.error( + "queryObjectStrongFields invalid field %s, %s"%(doId,fn)) + return + fieldIds.append(id) + self.queryObjectStringFieldIds(dbId,objString,fieldIds,context) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectField(self, dclassName, fieldName, doId, context=0): + """ + See Also: def sendUpdateToDoId + """ + assert self.notify.debugStateCall(self) + assert len(dclassName) > 0 + assert len(fieldName) > 0 + assert doId > 0 + dclass = self.dclassesByName.get(dclassName) + assert dclass is not None + if not dclass: + self.notify.error( + "queryObjectField invalid dclassName %s, %s"%(doId, fieldName)) + return + if dclass is not None: + fieldId = dclass.getFieldByName(fieldName).getNumber() + assert fieldId # is 0 a valid value? + if not fieldId: + self.notify.error( + "queryObjectField invalid field %s, %s"%(doId, fieldName)) + return + self.queryObjectFieldId(doId, fieldId, context) + + @report(types = ['args'], dConfigParam = 'avatarmgr') + def queryObjectFields(self, dclassName, fieldNames, doId, context=0): + """ + See Also: def sendUpdateToDoId + """ + assert self.notify.debugStateCall(self) + assert len(dclassName) > 0 + assert len(fieldNames) > 0 + for fieldName in fieldNames: + assert len(fieldName) > 0 + assert doId > 0 + dclass = self.dclassesByName.get(dclassName) + assert dclass is not None + if not dclass: + self.notify.error( + "queryObjectField invalid dclassName %s, %s"%(doId, fieldName)) + return + if dclass is not None: + fieldIds = [dclass.getFieldByName(fieldName).getNumber() \ + for fieldName in fieldNames] + # is 0 a valid value? + assert 0 not in fieldIds + if 0 not in fieldIds: + self.queryObjectFieldIds(doId, fieldIds, context) + else: + assert self.notify.error( + "queryObjectFields invalid field in %s, %s"%(doId, repr(fieldNames))) + + + def requestDistributedObject(self, doId): + """ + Ask for the object to be added to the private + distributed object cache. + + Normally a query object all does not actually enter the object + returned into the doId2do table. This function will change a query + request to a distributed object that is part of the normal set of + objects on this server. + """ + assert self.notify.debugCall() + distObj = self.doId2do.get(doId) + if distObj is not None: + # Already have it + return + if doId in self.distributedObjectRequests: + # Already requested it + return + #todo: add timeout for to remove request + self.distributedObjectRequests.add(doId) + context=self.allocateContext() + self.acceptOnce( + "doRequestResponse-%s"%(context,), + self.postGenerate, []) + self.registerForChannel(doId) + self.queryObjectAll(doId, context) + + # If you want to set the airecv you should set the location + def setAIReceiver(self, objectId, aiChannel=None): + # Create a message + datagram = PyDatagram() + datagram.addServerHeader( + self.ourChannel, self.ourChannel, STATESERVER_ADD_AI_RECV) + # The Id of the object in question + datagram.addUint32(objectId) + if aiChannel is None: + aiChannel = self.ourChannel + datagram.addUint32(aiChannel) + # Send the message + self.send(datagram) + # Make sure the message gets there. + self.flush() + + def replaceMethod(self, oldMethod, newFunction): + return 0 + + def sendUpdate(self, distObj, fieldName, args): + dg = distObj.dclass.aiFormatUpdate( + fieldName, distObj.doId, distObj.doId, self.ourChannel, args) + self.sendDatagram(dg) + + def _handleDatabaseGetStoredValuesResp(self, di): + context = di.getUint32() + dbObj = self.dbObjMap.get(context) + if dbObj: + del self.dbObjMap[context] + dbObj.getFieldsResponse(di) + else: + AIRepository.notify.warning( + "Ignoring unexpected context %d for DBSERVER_GET_STORED_VALUES" % + context) + + def _handleDatabaseCreateStoredObjectResp(self, di): + context = di.getUint32() + dbObj = self.dbObjMap.get(context) + if dbObj: + del self.dbObjMap[context] + dbObj.handleCreateObjectResponse(di) + else: + AIRepository.notify.warning( + "Ignoring unexpected context %d for DBSERVER_CREATE_STORED_OBJECT" % + context) + + # LEADERBOARD + def setLeaderboardValue(self, category, whoId, whoName, value, senderId=None): + self.writeServerEvent('setLeaderboardValue', whoId, + '%s|%s|%s' % (whoName, category, value)) + dcfile = self.getDcFile() + dclass = dcfile.getClassByName('LeaderBoard') + if senderId is None: + senderId = self.districtId + dg = dclass.aiFormatUpdate('setValue', + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + OtpDoGlobals.OTP_DO_ID_LEADERBOARD_MANAGER, + senderId, + [[category], whoId, whoName, value]) + self.send(dg) + + ################################################################## + # msgsend is set by the C code on the repository.. These Functions + # will let you parse the special encoded sender id if you want to + # know the AvatarID or The AccountID of the Sender.. + + def getAccountIdFromSender(self): + """ + only works on the dc updates from the client agent + """ + return self.getMsgSender() >> 32 + + def getAvatarIdFromSender(self): + """ + only works on the dc updates from the client agent + """ + return self.getMsgSender() & 0xffffffff + + def getSenderReturnChannel(self): + return self.getMsgSender() + + + ######################################## + # Network reading and time device.. for ai's + def taskManagerDoYieldNetwork(self , frameStartTime, nextScheuledTaksTime): + minFinTime = frameStartTime + self.MaxEpockSpeed + if nextScheuledTaksTime > 0 and nextScheuledTaksTime < minFinTime: + minFinTime = nextScheuledTaksTime + + self.networkBasedReaderAndYielder(self.handleDatagram,globalClock,minFinTime) + + if not self.isConnected(): + self.stopReaderPollTask() + self.lostConnection() + + ############################################################### + # Optimized version of old behavior.. + def readerPollUntilEmpty(self, task): + self.checkDatagramAi(self.handleDatagram) + if not self.isConnected(): + self.stopReaderPollTask() + self.lostConnection() + + return Task.cont + + + ############################################################### + # This can be used to do time based yielding instead of the sleep task. + def taskManagerDoYield(self , frameStartTime, nextScheuledTaksTime): + minFinTime = frameStartTime + self.MaxEpockSpeed + if nextScheuledTaksTime > 0 and nextScheuledTaksTime < minFinTime: + minFinTime = nextScheuledTaksTime + + delta = minFinTime - globalClock.getRealTime() + while(delta > 0.002): + time.sleep(delta) + delta = minFinTime - globalClock.getRealTime() + + + ############################################################### + # This can be used to do time based yielding instead of the sleep task. + def startReaderPollTask(self): + if not self.AIRunningNetYield: + ConnectionRepository.startReaderPollTask(self) + else: + print('########## startReaderPollTask New ') + self.stopReaderPollTask() + self.accept(CConnectionRepository.getOverflowEventName(),self.handleReaderOverflow) diff --git a/otp/ai/GarbageLeakServerEventAggregatorAI.py b/otp/ai/GarbageLeakServerEventAggregatorAI.py new file mode 100644 index 0000000..b00ad33 --- /dev/null +++ b/otp/ai/GarbageLeakServerEventAggregatorAI.py @@ -0,0 +1,89 @@ +from direct.showbase.DirectObject import DirectObject +from direct.showbase import GarbageReport + +class GarbageLeakServerEventAggregatorAI(DirectObject): + ClientLeakEvent = 'LeakAggregator-ClientGarbageLeakReceived' + def __init__(self, air): + self.air = air + self._eventFreq = config.GetFloat('garbage-leak-server-event-frequency', 60 * 60.) + self._doLaterName = None + self._sentLeakDesc2num = {} + self._curLeakDesc2num = {} + self.accept(GarbageReport.GarbageCycleCountAnnounceEvent, + self._handleCycleCounts) + self._clientStartFDC = None + self._doLaterNameClient = None + self._sentClientDesc2num = {} + self._curClientDesc2num = {} + self.accept(self.ClientLeakEvent, self._handleClientCycleCount) + + def destroy(self): + self.ignoreAll() + self._clientStartFDC.destroy() + self._stopSending() + self._stopSendingClientLeaks() + del self.air + + def _handleCycleCounts(self, desc2num): + self._curLeakDesc2num = desc2num + self._startSending() + + def _handleClientCycleCount(self, num, description): + self._curClientDesc2num.setdefault(description, 0) + self._curClientDesc2num[description] += num + if not self._clientStartFDC: + # do an FDC to allow other concurrent client events to make it in, for dev/testing + self._clientStartFDC = FrameDelayedCall( + uniqueName('%s-startClientSend' % self.__class__.__name__), + self._startSendingClientLeaks) + + def _startSending(self): + if not self._doLaterName: + self._sendLeaks() + self._doLaterName = uniqueName('%s-sendGarbageServerEvents' % self.__class__.__name__) + self.doMethodLater(self._eventFreq, self._sendLeaks, self._doLaterName) + + def _stopSending(self): + self.removeTask(self._doLaterName) + self._doLaterName = None + + def _sendLeaks(self, task=None): + # only send the number of occurences of each leak that + # we haven't already sent + for desc, curNum in self._curLeakDesc2num.iteritems(): + self._sentLeakDesc2num.setdefault(desc, 0) + num = curNum - self._sentLeakDesc2num[desc] + if num > 0: + if hasattr(self.air, 'districtId'): + who = self.air.districtId + eventName = 'ai-garbage' + else: + who = self.air.ourChannel + eventName = 'ud-garbage' + self.air.writeServerEvent(eventName, who, '%s|%s' % (num, desc)) + self._sentLeakDesc2num[desc] = curNum + if task: + return task.again + + def _startSendingClientLeaks(self): + if not self._doLaterNameClient: + self._sendClientLeaks() + self._doLaterNameClient = uniqueName( + '%s-sendClientGarbageServerEvents' % self.__class__.__name__) + self.doMethodLater(self._eventFreq, self._sendClientLeaks, self._doLaterNameClient) + + def _stopSendingClientLeaks(self): + self.removeTask(self._doLaterNameClient) + self._doLaterNameClient = None + + def _sendClientLeaks(self, task=None): + # only send the number of occurences of each leak that + # we haven't already sent + for desc, curNum in self._curClientDesc2num.iteritems(): + self._sentClientDesc2num.setdefault(desc, 0) + num = curNum - self._sentClientDesc2num[desc] + if num > 0: + self.air.writeServerEvent('client-garbage', self.air.districtId, '%s|%s' % (num, desc)) + self._sentClientDesc2num[desc] = curNum + if task: + return task.again diff --git a/toontown/ai/AIStart.py b/toontown/ai/AIStart.py index 8063b05..213bdfa 100644 --- a/toontown/ai/AIStart.py +++ b/toontown/ai/AIStart.py @@ -1,3 +1,10 @@ +from panda3d.core import loadPrcFile + +# TODO: use argparse for this? +configs = ('etc/Configrc.prc',) +for prc in configs: + loadPrcFile(prc) + import builtins class game: @@ -7,7 +14,6 @@ builtins.game = game() # NOTE: this file is not used in production. See AIServiceStart.py -import time import os import sys @@ -19,7 +25,7 @@ from direct.showbase import PythonUtil # Clear the default model extension for AI developers, so they'll know # when they screw up and omit it. -from pandac.PandaModules import loadPrcFileData +from panda3d.core import loadPrcFileData loadPrcFileData("AIStart.py", "default-model-extension") simbase.mdip = simbase.config.GetString("msg-director-ip", "localhost") diff --git a/toontown/ai/ToontownAIRepository.py b/toontown/ai/ToontownAIRepository.py index c6d95bb..47e9bf6 100644 --- a/toontown/ai/ToontownAIRepository.py +++ b/toontown/ai/ToontownAIRepository.py @@ -1,474 +1,1059 @@ +from toontown.toonbase.ToontownGlobals import * +from otp.otpbase import OTPGlobals +from otp.ai import BanManagerAI from direct.directnotify import DirectNotifyGlobal -from panda3d.core import * -from panda3d.toontown import * - -from otp.ai.AIZoneData import AIZoneDataStore -from otp.ai.TimeManagerAI import TimeManagerAI -from otp.distributed.OtpDoGlobals import * -from toontown.ai.HolidayManagerAI import HolidayManagerAI -from toontown.ai.NewsManagerAI import NewsManagerAI -from toontown.ai.WelcomeValleyManagerAI import WelcomeValleyManagerAI -from toontown.building.DistributedTrophyMgrAI import DistributedTrophyMgrAI -from toontown.catalog.CatalogManagerAI import CatalogManagerAI -from toontown.coghq.CogSuitManagerAI import CogSuitManagerAI -from toontown.coghq.CountryClubManagerAI import CountryClubManagerAI -from toontown.coghq.FactoryManagerAI import FactoryManagerAI -from toontown.coghq.LawOfficeManagerAI import LawOfficeManagerAI -from toontown.coghq.MintManagerAI import MintManagerAI -from toontown.coghq.PromotionManagerAI import PromotionManagerAI -from toontown.distributed.ToontownDistrictAI import ToontownDistrictAI -from toontown.distributed.ToontownDistrictStatsAI import ToontownDistrictStatsAI -from toontown.distributed.ToontownInternalRepository import ToontownInternalRepository +from otp.ai.AIDistrict import AIDistrict +from toontown.suit import DistributedSuitPlannerAI +from toontown.safezone import DistributedBoatAI +from toontown.safezone import DistributedMMPianoAI +from toontown.safezone import DistributedDGFlowerAI +from toontown.safezone import DistributedTrolleyAI +#from otp.friends import FriendManagerAI +from toontown.shtiker import DeleteManagerAI +from toontown.safezone import SafeZoneManagerAI +from . import ToontownMagicWordManagerAI +from toontown.tutorial import TutorialManagerAI +from toontown.catalog import CatalogManagerAI +from otp.ai import TimeManagerAI +import WelcomeValleyManagerAI +from toontown.building import DistributedBuildingMgrAI +from toontown.building import DistributedTrophyMgrAI +from toontown.estate import DistributedBankMgrAI +from toontown.hood import TTHoodDataAI +from toontown.hood import DDHoodDataAI +from toontown.hood import MMHoodDataAI +from toontown.hood import DGHoodDataAI +from toontown.hood import BRHoodDataAI +from toontown.hood import DLHoodDataAI +from toontown.hood import CSHoodDataAI +from toontown.hood import GSHoodDataAI +from toontown.hood import OZHoodDataAI +from toontown.hood import GZHoodDataAI +from toontown.hood import CashbotHQDataAI +from toontown.hood import LawbotHQDataAI +from toontown.hood import BossbotHQDataAI +from toontown.quest import QuestManagerAI +from toontown.fishing import FishManagerAI +from toontown.shtiker import CogPageManagerAI +from toontown.coghq import FactoryManagerAI +from toontown.coghq import MintManagerAI +from toontown.coghq import LawOfficeManagerAI +from toontown.coghq import CountryClubManagerAI +import NewsManagerAI from toontown.hood import ZoneUtil -from toontown.hood.BRHoodDataAI import BRHoodDataAI -from toontown.hood.BossbotHQDataAI import BossbotHQDataAI -from toontown.hood.CSHoodDataAI import CSHoodDataAI -from toontown.hood.CashbotHQDataAI import CashbotHQDataAI -from toontown.hood.DDHoodDataAI import DDHoodDataAI -from toontown.hood.DGHoodDataAI import DGHoodDataAI -from toontown.hood.DLHoodDataAI import DLHoodDataAI -from toontown.hood.GSHoodDataAI import GSHoodDataAI -from toontown.hood.GZHoodDataAI import GZHoodDataAI -from toontown.hood.LawbotHQDataAI import LawbotHQDataAI -from toontown.hood.MMHoodDataAI import MMHoodDataAI -from toontown.hood.OZHoodDataAI import OZHoodDataAI -from toontown.hood.TTHoodDataAI import TTHoodDataAI -from toontown.pets.PetManagerAI import PetManagerAI -from toontown.quest.QuestManagerAI import QuestManagerAI -from toontown.racing import RaceGlobals -from toontown.racing.DistributedLeaderBoardAI import DistributedLeaderBoardAI -from toontown.racing.DistributedRacePadAI import DistributedRacePadAI -from toontown.racing.DistributedStartingBlockAI import DistributedStartingBlockAI -from toontown.racing.DistributedStartingBlockAI import DistributedViewingBlockAI -from toontown.racing.DistributedViewPadAI import DistributedViewPadAI -from toontown.racing.RaceManagerAI import RaceManagerAI -from toontown.safezone.SafeZoneManagerAI import SafeZoneManagerAI -from toontown.shtiker.CogPageManagerAI import CogPageManagerAI -from toontown.spellbook.ToontownMagicWordManagerAI import ToontownMagicWordManagerAI -from toontown.suit.SuitInvasionManagerAI import SuitInvasionManagerAI +from toontown.fishing import DistributedFishingPondAI +from toontown.safezone import DistributedFishingSpotAI +from toontown.safezone import DistributedButterflyAI +from toontown.safezone import DistributedPartyGateAI +from toontown.toon import NPCDialogueManagerAI from toontown.toon import NPCToons -from toontown.toonbase import ToontownGlobals -from toontown.uberdog.DistributedInGameNewsMgrAI import DistributedInGameNewsMgrAI + +from toontown.safezone import ButterflyGlobals +from toontown.estate import EstateManagerAI +from toontown.suit import SuitInvasionManagerAI +import HolidayManagerAI +from toontown.effects import FireworkManagerAI +from toontown.coghq import CogSuitManagerAI +from toontown.coghq import PromotionManagerAI +from direct.task.Task import Task +if simbase.wantKarts: + from toontown.racing import RaceGlobals + from toontown.racing import RaceManagerAI + from toontown.racing.DistributedRacePadAI import DistributedRacePadAI + from toontown.racing.DistributedViewPadAI import DistributedViewPadAI + from toontown.racing.DistributedStartingBlockAI import DistributedStartingBlockAI + from toontown.racing.DistributedStartingBlockAI import DistributedViewingBlockAI + from toontown.racing.DistributedLeaderBoardAI import DistributedLeaderBoardAI +if simbase.wantBingo: + from toontown.fishing import BingoManagerAI +if simbase.wantPets: + from toontown.pets import PetManagerAI + +import string import os +import time +import DatabaseObject +from direct.distributed.PyDatagram import PyDatagram +from direct.distributed.PyDatagramIterator import PyDatagramIterator +from ToontownAIMsgTypes import * +from otp.otpbase.OTPGlobals import * +from toontown.distributed.ToontownDistrictAI import ToontownDistrictAI +#from otp.distributed.DistributedDirectoryAI import DistributedDirectoryAI +from toontown.toon import DistributedToonAI +from otp.distributed import OtpDoGlobals +from toontown.uberdog import DistributedPartyManagerAI +from toontown.uberdog import DistributedInGameNewsMgrAI +from toontown.uberdog import DistributedCpuInfoMgrAI +from toontown.parties import ToontownTimeManager +from toontown.coderedemption.TTCodeRedemptionMgrAI import TTCodeRedemptionMgrAI +from toontown.distributed.NonRepeatableRandomSourceAI import NonRepeatableRandomSourceAI -class ToontownAIRepository(ToontownInternalRepository): - notify = DirectNotifyGlobal.directNotify.newCategory('ToontownAIRepository') +import ToontownGroupManager + +if __debug__: + import pdb + +class ToontownAIRepository(AIDistrict): + notify = DirectNotifyGlobal.directNotify.newCategory( + "ToontownAIRepository") + + # The zone table determines which dnaStores are created and + # whether bulding manager or suit planner ai objects are created. + # The elements consist of: + # (int the_zone_ID, bool create_building_manager, bool create_suit_planner) + zoneTable = { + ToontownCentral: ([ToontownCentral, 1, 0], + [ToontownCentral + 100, 1, 1], + [ToontownCentral + 200, 1, 1], + [ToontownCentral + 300, 1, 1], + ), + + DonaldsDock: ([DonaldsDock, 1, 0], + [DonaldsDock + 100, 1, 1], + [DonaldsDock + 200, 1, 1], + [DonaldsDock + 300, 1, 1], + ), + + MinniesMelodyland: ([MinniesMelodyland, 1, 0], + [MinniesMelodyland + 100, 1, 1], + [MinniesMelodyland + 200, 1, 1], + [MinniesMelodyland + 300, 1, 1], + ), + + TheBrrrgh: ([TheBrrrgh, 1, 0], + [TheBrrrgh + 100, 1, 1], + [TheBrrrgh + 200, 1, 1], + [TheBrrrgh + 300, 1, 1], + ), + + DonaldsDreamland: ([DonaldsDreamland, 1, 0], + [DonaldsDreamland + 100, 1, 1], + [DonaldsDreamland + 200, 1, 1], + ), + + DaisyGardens: ([DaisyGardens, 1, 0], + [DaisyGardens + 100, 1, 1], + [DaisyGardens + 200, 1, 1], + [DaisyGardens + 300, 1, 1], + ), + + GoofySpeedway: ([GoofySpeedway, 1, 0], + ), + + OutdoorZone: ([OutdoorZone, 0, 0], + ), + + SellbotHQ: ([SellbotHQ, 0, 1], + [SellbotHQ + 200, 0, 1], + ), + + CashbotHQ: ([CashbotHQ, 0, 1], + ), + + LawbotHQ: ([LawbotHQ, 0, 1], + ), + + GolfZone: ([GolfZone, 0, 0], + ), + + BossbotHQ: ([BossbotHQ, 0, 0], + ), + + } + + def __init__(self, *args, **kw): + AIDistrict.__init__(self, *args, **kw) + self.setTimeWarning(simbase.config.GetFloat('aimsg-time-warning', 4)) + + self.dnaSearchPath = DSearchPath() + if os.getenv('TTMODELS'): + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_3.5/dna')) + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_4/dna')) + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_5/dna')) + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_5.5/dna')) + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_6/dna')) + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_8/dna')) + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_9/dna')) + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_10/dna')) + self.dnaSearchPath.appendDirectory(Filename.expandFrom('$TTMODELS/built/phase_11/dna')) + + # In the publish environment, TTMODELS won't be on the model + # path by default, so we always add it there. In the dev + # environment, it'll be on the model path already, but it + # doesn't hurt to add it again. + getModelPath().appendDirectory(Filename.expandFrom("$TTMODELS")) + else: + self.dnaSearchPath.appendDirectory(Filename('.')) + self.dnaSearchPath.appendDirectory(Filename('ttmodels/src/dna')) + + # Initialize our query context. + self.__queryEstateContext = 0 + self.__queryEstateFuncMap = {} + + # Set a hook so we can process queryToonMaxHp() requests. + self.accept('queryToonMaxHp', self.__queryToonMaxHpResponse) + + # For debugging + wantNewToonhall = simbase.config.GetBool('want-new-toonhall', 1) + if (not wantNewToonhall) or \ + (wantNewToonhall and not simbase.config.GetBool('show-scientists', 0)): + for i in range(3): + npcId = 2018+i + if npcId in NPCToons.NPCToonDict: + del NPCToons.NPCToonDict[npcId] + + NPCToons.generateZone2NpcDict() + + if not simbase.config.GetBool('want-suits-everywhere', 1): + # This is a special mode for development: we don't want + # suits walking around all over the world. Turn off all + # the SuitPlanner flags. + for zones in self.zoneTable.values(): + for zone in zones: + zone[2] = 0 + + if not simbase.config.GetBool('want-suits-nowhere', 1): + # However, we do want them in at least one street. + self.zoneTable[ToontownCentral][1][2] = 1 + + # minigame debug flags + self.wantMinigameDifficulty = simbase.config.GetBool( + 'want-minigame-difficulty', 0) + + self.minigameDifficulty = simbase.config.GetFloat( + 'minigame-difficulty', -1.) + if self.minigameDifficulty == -1.: + del self.minigameDifficulty + self.minigameSafezoneId = simbase.config.GetInt( + 'minigame-safezone-id', -1) + if self.minigameSafezoneId == -1: + del self.minigameSafezoneId + + # should we pick from the entire list of minigames regardless of + # the number of participating toons? (for debugging) + self.useAllMinigames = simbase.config.GetBool('use-all-minigames', 0) + + self.wantCogdominiums = simbase.config.GetBool('want-cogdominiums', 0) - def __init__(self, baseChannel, serverId, districtName): - ToontownInternalRepository.__init__(self, baseChannel, serverId, dcSuffix='AI') - self.districtName = districtName - self.doLiveUpdates = config.GetBool('want-live-updates', True) - self.wantCogdominiums = config.GetBool('want-cogdominiums', True) - self.useAllMinigames = config.GetBool('want-all-minigames', True) - self.dataFolder = config.GetString('server-data-folder', '') - if self.dataFolder: - self.dataFolder = self.dataFolder + '/' - self.districtId = None - self.district = None - self.districtStats = None - self.holidayManager = None - self.zoneDataStore = None - self.petMgr = None - self.suitInvasionManager = None - self.zoneAllocator = None - self.zoneId2owner = {} - self.questManager = None - self.promotionMgr = None - self.cogPageManager = None - self.raceMgr = None - self.countryClubMgr = None - self.factoryMgr = None - self.mintMgr = None - self.lawMgr = None - self.cogSuitMgr = None - self.timeManager = None - self.newsManager = None - self.welcomeValleyManager = None - self.inGameNewsMgr = None - self.catalogManager = None - self.trophyMgr = None - self.safeZoneManager = None - self.magicWordManager = None - self.zoneTable = {} - self.dnaStoreMap = {} - self.dnaDataMap = {} self.hoods = [] self.buildingManagers = {} self.suitPlanners = {} - def handleConnected(self): - ToontownInternalRepository.handleConnected(self) + # Guard for publish + if simbase.wantBingo: + self.bingoMgr = None - # Generate our district... - self.notify.info('Generating district...') - self.districtId = self.allocateChannel() - self.district = ToontownDistrictAI(self) - self.district.setName(self.districtName) - self.district.generateWithRequiredAndId(self.districtId, self.getGameDoId(), OTP_ZONE_ID_DISTRICTS) + self.toontownTimeManager = ToontownTimeManager.ToontownTimeManager() + self.toontownTimeManager.updateLoginTimes(time.time(), time.time(), globalClock.getRealTime()) - # Claim ownership of that district... - self.notify.info('Declaring ownership...') - self.district.setAI(self.ourChannel) + # turn on garbage-collection debugging to see if it's related + # to the chugs we're seeing + # eventually we will probably put in our own gc pump + if simbase.config.GetBool('gc-debug', 0): + import gc + gc.set_debug(gc.DEBUG_STATS) + + self.deliveryManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_DELIVERY_MANAGER, + "DistributedDeliveryManager") - # Setup necessary files and things. - self.setupFiles() + self.mailManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_MAIL_MANAGER, + "DistributedMailManager") - # Create our local objects. - self.notify.info('Creating local objects...') - self.createLocals() + #self.partyManager = self.generateGlobalObject( + # OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER, + # "DistributedPartyManager") - # Create our global objects. - self.notify.info('Creating global objects...') - self.createGlobals() + #self.codeRedemptionManager = self.generateGlobalObject( + # OtpDoGlobals.OTP_DO_ID_TOONTOWN_CODE_REDEMPTION_MANAGER, + # "TTCodeRedemptionMgr") - # Create our zones. - self.notify.info('Creating zones...') - self.createZones() + #self.randomSourceManager = self.generateGlobalObject( + # OtpDoGlobals.OTP_DO_ID_TOONTOWN_NON_REPEATABLE_RANDOM_SOURCE, + # "NonRepeatableRandomSource") - # Make our district available, and we're done. - self.district.b_setAvailable(True) - self.notify.info('Done.') + if simbase.config.GetBool('want-ddsm', 1): + self.dataStoreManager = self.generateGlobalObject( + OtpDoGlobals.OTP_DO_ID_TOONTOWN_TEMP_STORE_MANAGER, + "DistributedDataStoreManager") + + self.groupManager = ToontownGroupManager.ToontownGroupManager() + + def getGameDoId(self): + return OTP_DO_ID_TOONTOWN - def createLocals(self): + def getDatabaseIdForClassName(self, className): + return DatabaseIdFromClassName.get( + className, DefaultDatabaseChannelId) + + def _isValidPlayerLocation(self, parentId, zoneId): + # keep players out of parents that are smaller than N, where N is smaller than all district IDs + # and N is random enough to be confusing for hackers + if parentId < 900000: + return False + # keep players out of uberzones and zones up to 900 which are not used anyway, to confuse hackers + if (OTPGlobals.UberZone <= zoneId <= 900): + return False """ - Creates "local" (non-distributed) objects. + # this doesn't work in the current TT LIVE publish, when teleporting, old district gets + # setLocation on new district + if parentId != self.districtId: + return False + if zoneId == 2: + return False + """ + return True + + def saveBuildings(self): """ - - # Create our holiday manager... - self.holidayManager = HolidayManagerAI(self) - - # Create our zone data store... - self.zoneDataStore = AIZoneDataStore() - - # Create our pet manager... - self.petMgr = PetManagerAI(self) - - # Create our suit invasion manager... - self.suitInvasionManager = SuitInvasionManagerAI(self) - - # Create our zone allocator... - self.zoneAllocator = UniqueIdAllocator(ToontownGlobals.DynamicZonesBegin, ToontownGlobals.DynamicZonesEnd) - - # Create our quest manager... - self.questManager = QuestManagerAI(self) - - # Create our promotion manager... - self.promotionMgr = PromotionManagerAI(self) - - # Create our Cog page manager... - self.cogPageManager = CogPageManagerAI(self) - - # Create our race manager... - self.raceMgr = RaceManagerAI(self) - - # Create our country club manager... - self.countryClubMgr = CountryClubManagerAI(self) - - # Create our factory manager... - self.factoryMgr = FactoryManagerAI(self) - - # Create our mint manager... - self.mintMgr = MintManagerAI(self) - - # Create our law office manager... - self.lawMgr = LawOfficeManagerAI(self) - - # Create our Cog suit manager... - self.cogSuitMgr = CogSuitManagerAI(self) - - def createGlobals(self): + Saves the state of all the buildings on all the managed + streets, so it will be restored on AI restart. """ - Creates "global" (distributed) objects. - """ - - # Generate our district stats... - self.districtStats = ToontownDistrictStatsAI(self) - self.districtStats.settoontownDistrictId(self.districtId) - self.districtStats.generateWithRequiredAndId(self.allocateChannel(), self.district.getDoId(), - OTP_ZONE_ID_DISTRICTS_STATS) - - # Generate our time manager... - self.timeManager = TimeManagerAI(self) - self.timeManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) - - # Generate our news manager... - self.newsManager = NewsManagerAI(self) - self.newsManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) - - # Generate our Welcome Valley manager... - self.welcomeValleyManager = WelcomeValleyManagerAI(self) - self.welcomeValleyManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) - - # Generate our in-game news manager... - self.inGameNewsMgr = DistributedInGameNewsMgrAI(self) - self.inGameNewsMgr.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) - - # Generate our catalog manager... - self.catalogManager = CatalogManagerAI(self) - self.catalogManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) - - # Generate our trophy manager... - self.trophyMgr = DistributedTrophyMgrAI(self) - self.trophyMgr.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) - - # Generate our safezone manager... - self.safeZoneManager = SafeZoneManagerAI(self) - self.safeZoneManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) - - # Generate our magic word manager... - self.magicWordManager = ToontownMagicWordManagerAI(self) - self.magicWordManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) - - def generateHood(self, hoodConstructor, zoneId): - # Bossbot HQ doesn't use DNA, so we skip over that. - if zoneId != ToontownGlobals.BossbotHQ: - self.dnaStoreMap[zoneId] = DNAStorage() - self.dnaDataMap[zoneId] = loadDNAFileAI(self.dnaStoreMap[zoneId], self.genDNAFileName(zoneId)) - if zoneId in ToontownGlobals.HoodHierarchy: - for streetId in ToontownGlobals.HoodHierarchy[zoneId]: - self.dnaStoreMap[streetId] = DNAStorage() - self.dnaDataMap[streetId] = loadDNAFileAI(self.dnaStoreMap[streetId], self.genDNAFileName(streetId)) - - hood = hoodConstructor(self, zoneId) - hood.startup() - self.hoods.append(hood) - - def createZones(self): - # First, generate our zone2NpcDict... - NPCToons.generateZone2NpcDict() - - # Donald's Dock - self.zoneTable[ToontownGlobals.DonaldsDock] = ( - (ToontownGlobals.DonaldsDock, 1, 0), (ToontownGlobals.BarnacleBoulevard, 1, 1), - (ToontownGlobals.SeaweedStreet, 1, 1), (ToontownGlobals.LighthouseLane, 1, 1) - ) - self.generateHood(DDHoodDataAI, ToontownGlobals.DonaldsDock) - - # Toontown Central - self.zoneTable[ToontownGlobals.ToontownCentral] = ( - (ToontownGlobals.ToontownCentral, 1, 0), (ToontownGlobals.SillyStreet, 1, 1), - (ToontownGlobals.LoopyLane, 1, 1), (ToontownGlobals.PunchlinePlace, 1, 1) - ) - self.generateHood(TTHoodDataAI, ToontownGlobals.ToontownCentral) - - # The Brrrgh - self.zoneTable[ToontownGlobals.TheBrrrgh] = ( - (ToontownGlobals.TheBrrrgh, 1, 0), (ToontownGlobals.WalrusWay, 1, 1), - (ToontownGlobals.SleetStreet, 1, 1), (ToontownGlobals.PolarPlace, 1, 1) - ) - self.generateHood(BRHoodDataAI, ToontownGlobals.TheBrrrgh) - - # Minnie's Melodyland - self.zoneTable[ToontownGlobals.MinniesMelodyland] = ( - (ToontownGlobals.MinniesMelodyland, 1, 0), (ToontownGlobals.AltoAvenue, 1, 1), - (ToontownGlobals.BaritoneBoulevard, 1, 1), (ToontownGlobals.TenorTerrace, 1, 1) - ) - self.generateHood(MMHoodDataAI, ToontownGlobals.MinniesMelodyland) - - # Daisy Gardens - self.zoneTable[ToontownGlobals.DaisyGardens] = ( - (ToontownGlobals.DaisyGardens, 1, 0), (ToontownGlobals.ElmStreet, 1, 1), - (ToontownGlobals.MapleStreet, 1, 1), (ToontownGlobals.OakStreet, 1, 1) - ) - self.generateHood(DGHoodDataAI, ToontownGlobals.DaisyGardens) - - # Chip 'n Dale's Acorn Acres - self.zoneTable[ToontownGlobals.OutdoorZone] = ( - (ToontownGlobals.OutdoorZone, 1, 0), - ) - self.generateHood(OZHoodDataAI, ToontownGlobals.OutdoorZone) - - # Goofy Speedway - self.zoneTable[ToontownGlobals.GoofySpeedway] = ( - (ToontownGlobals.GoofySpeedway, 1, 0), - ) - self.generateHood(GSHoodDataAI, ToontownGlobals.GoofySpeedway) - - # Donald's Dreamland - self.zoneTable[ToontownGlobals.DonaldsDreamland] = ( - (ToontownGlobals.DonaldsDreamland, 1, 0), (ToontownGlobals.LullabyLane, 1, 1), - (ToontownGlobals.PajamaPlace, 1, 1) - ) - self.generateHood(DLHoodDataAI, ToontownGlobals.DonaldsDreamland) - - # Bossbot HQ - self.zoneTable[ToontownGlobals.BossbotHQ] = ( - (ToontownGlobals.BossbotHQ, 0, 0), - ) - self.generateHood(BossbotHQDataAI, ToontownGlobals.BossbotHQ) - - # Sellbot HQ - self.zoneTable[ToontownGlobals.SellbotHQ] = ( - (ToontownGlobals.SellbotHQ, 0, 1), (ToontownGlobals.SellbotFactoryExt, 0, 1) - ) - self.generateHood(CSHoodDataAI, ToontownGlobals.SellbotHQ) - - # Cashbot HQ - self.zoneTable[ToontownGlobals.CashbotHQ] = ( - (ToontownGlobals.CashbotHQ, 0, 1), - ) - self.generateHood(CashbotHQDataAI, ToontownGlobals.CashbotHQ) - - # Lawbot HQ - self.zoneTable[ToontownGlobals.LawbotHQ] = ( - (ToontownGlobals.LawbotHQ, 0, 1), - ) - self.generateHood(LawbotHQDataAI, ToontownGlobals.LawbotHQ) - - # Chip 'n Dale's MiniGolf - self.zoneTable[ToontownGlobals.GolfZone] = ( - (ToontownGlobals.GolfZone, 1, 0), - ) - self.generateHood(GZHoodDataAI, ToontownGlobals.GolfZone) - - # Welcome Valley zones - self.welcomeValleyManager.createWelcomeValleyZones() - - # Assign the initial suit buildings. - for suitPlanner in list(self.suitPlanners.values()): - suitPlanner.assignInitialSuitBuildings() + for mgr in self.buildingManagers.values(): + mgr.save() def genDNAFileName(self, zoneId): - canonicalZoneId = ZoneUtil.getCanonicalZoneId(zoneId) - canonicalHoodId = ZoneUtil.getCanonicalHoodId(canonicalZoneId) - hood = ToontownGlobals.dnaMap[canonicalHoodId] - if canonicalHoodId == canonicalZoneId: - canonicalZoneId = 'sz' - phase = ToontownGlobals.phaseMap[canonicalHoodId] + """ + determines the name of the DNA file that should + be loaded for the neighborhood. + """ + zoneId = ZoneUtil.getCanonicalZoneId(zoneId) + hoodId = ZoneUtil.getCanonicalHoodId(zoneId) + hood = dnaMap[hoodId] + if hoodId == zoneId: + zoneId = "sz" + + # Nowadays, we use a search path to find the DNA file, instead + # of looking for it in only one place. Not sure if this is a + # great idea; but it makes it easier to run a test AI on + # ttown. + + return self.lookupDNAFileName("%s_%s.dna" % (hood, zoneId)) + + def lookupDNAFileName(self, filename): + dnaFile = Filename(filename) + found = vfs.resolveFilename(dnaFile, self.dnaSearchPath) + + return dnaFile.cStr() + +## phase = streetPhaseMap[hoodId] +## if hoodId==zoneId: +## zoneId="sz" +## if hood=='toontown_central': +## phase-=1 + +## try: +## # There may be no simbase, if this is client code. +## if simbase.aiService: +## return "./" + hood + "_" + str(zoneId) + ".dna" +## else: +## return "phase_" + str(phase) + "/dna/" + hood + "_" + \ +## str(zoneId) + ".dna" +## except: +## return "phase_" + str(phase) + "/dna/" + hood + "_" + \ +## str(zoneId) + ".dna" + + def loadDNA(self): + """ + Return a dictionary of zoneId to DNAStorage objects + """ + self.dnaStoreMap = {} + self.dnaDataMap = {} + for zones in self.zoneTable.values(): + for zone in zones: + zoneId=zone[0] + dnaStore = DNAStorage() + dnaFileName = self.genDNAFileName(zoneId) + dnaData = self.loadDNAFileAI(dnaStore, dnaFileName) + self.dnaStoreMap[zoneId] = dnaStore + self.dnaDataMap[zoneId] = dnaData + + + def createObjects(self): + # First, load up all of our DNA files for the world. + self.loadDNA() + + # Create a new district (aka shard) for this AI: + self.district = ToontownDistrictAI(self, self.districtName) + self.district.generateOtpObject( + OTP_DO_ID_TOONTOWN, OTP_ZONE_ID_DISTRICTS, + doId=self.districtId) + + # The Time manager. This negotiates a timestamp exchange for + # the purposes of synchronizing clocks between client and + # server with a more accurate handshaking protocol than we + # would otherwise get. + # + # We must create this object first, so clients who happen to + # come into the world while the AI is still coming up + # (particularly likely if the AI crashed while players were + # in) will get a chance to synchronize. + self.timeManager = TimeManagerAI.TimeManagerAI(self) + self.timeManager.generateOtpObject( + self.district.getDoId(), OTPGlobals.UberZone) + + self.partyManager = DistributedPartyManagerAI.DistributedPartyManagerAI(self) + self.partyManager.generateOtpObject( + self.district.getDoId(), OTPGlobals.UberZone) + + self.inGameNewsMgr = DistributedInGameNewsMgrAI.DistributedInGameNewsMgrAI(self) + self.inGameNewsMgr.generateOtpObject( + self.district.getDoId(), OTPGlobals.UberZone) + + self.cpuInfoMgr = DistributedCpuInfoMgrAI.DistributedCpuInfoMgrAI(self) + self.cpuInfoMgr.generateOtpObject( + self.district.getDoId(), OTPGlobals.UberZone) + + if config.GetBool('want-code-redemption', 1): + self.codeRedemptionManager = TTCodeRedemptionMgrAI(self) + self.codeRedemptionManager.generateOtpObject( + self.district.getDoId(), OTPGlobals.UberZone) + + self.randomSourceManager = NonRepeatableRandomSourceAI(self) + # QuietZone so that the client doesn't get a generate + self.randomSourceManager.generateOtpObject( + self.district.getDoId(), OTPGlobals.QuietZone) + + self.welcomeValleyManager = WelcomeValleyManagerAI.WelcomeValleyManagerAI(self) + self.welcomeValleyManager.generateWithRequired(OTPGlobals.UberZone) + + # The trophy manager should be created before the building + # managers. + self.trophyMgr = DistributedTrophyMgrAI.DistributedTrophyMgrAI(self) + self.trophyMgr.generateWithRequired(OTPGlobals.UberZone) + + # The bank manager handles banking transactions + self.bankMgr = DistributedBankMgrAI.DistributedBankMgrAI(self) + self.bankMgr.generateWithRequired(OTPGlobals.UberZone) + + # The Friend Manager + #self.friendManager = FriendManagerAI.FriendManagerAI(self) + #self.friendManager.generateWithRequired(OTPGlobals.UberZone) + + # The Delete Manager + self.deleteManager = DeleteManagerAI.DeleteManagerAI(self) + self.deleteManager.generateWithRequired(OTPGlobals.UberZone) + + # The Safe Zone manager + self.safeZoneManager = SafeZoneManagerAI.SafeZoneManagerAI(self) + self.safeZoneManager.generateWithRequired(OTPGlobals.UberZone) + + # The Magic Word Manager + magicWordString = simbase.config.GetString('want-magic-words', '1') + if magicWordString not in ('', '0', '#f'): + self.magicWordManager = ToontownMagicWordManagerAI.ToontownMagicWordManagerAI(self) + self.magicWordManager.generateWithRequired(OTPGlobals.UberZone) + + # The Tutorial manager + self.tutorialManager = TutorialManagerAI.TutorialManagerAI(self) + self.tutorialManager.generateWithRequired(OTPGlobals.UberZone) + + # The Catalog Manager + self.catalogManager = CatalogManagerAI.CatalogManagerAI(self) + self.catalogManager.generateWithRequired(OTPGlobals.UberZone) + + # The Quest manager + self.questManager = QuestManagerAI.QuestManagerAI(self) + + # The Fish manager + self.fishManager = FishManagerAI.FishManagerAI(self) + + # The Cog Page manager + self.cogPageManager = CogPageManagerAI.CogPageManagerAI(self) + + # The Suit Invasion Manager + self.suitInvasionManager = SuitInvasionManagerAI.SuitInvasionManagerAI(self) + + # The Firework Manager: This object really only exists so we can + # fire off fireworks with magic words. Normally this is a holiday + # manager driven event and therefore the constructor needs a + # holidayId. Pass in fourth of july as default. To do: override + # holiday ID with a magic word + self.fireworkManager = FireworkManagerAI.FireworkManagerAI( + self, NEWYEARS_FIREWORKS) + + # Create an NPC Dialogue manager that manages conversations + # amongst a set of NPC's + self.dialogueManager = NPCDialogueManagerAI.NPCDialogueManagerAI() + + # The News manager + self.newsManager = NewsManagerAI.NewsManagerAI(self) + self.newsManager.generateWithRequired(OTPGlobals.UberZone) + + # The Factory Manager + self.factoryMgr = FactoryManagerAI.FactoryManagerAI(self) + + # The Mint Manager + self.mintMgr = MintManagerAI.MintManagerAI(self) + + #the Law Office Manager + self.lawMgr = LawOfficeManagerAI.LawOfficeManagerAI(self) + + # The Cog Country Club Manager + self.countryClubMgr = CountryClubManagerAI.CountryClubManagerAI(self) + + if simbase.wantKarts: + # The Race Manager + self.raceMgr = RaceManagerAI.RaceManagerAI(self) + + self.cogSuitMgr = CogSuitManagerAI.CogSuitManagerAI(self) + self.promotionMgr = PromotionManagerAI.PromotionManagerAI(self) + + # Housing + self.estateMgr = EstateManagerAI.EstateManagerAI(self) + self.estateMgr.generateWithRequired(OTPGlobals.UberZone) + + if simbase.wantPets: + # Pets -- must be created after estateMgr + self.petMgr = PetManagerAI.PetManagerAI(self) + + # Now create the neighborhood-specific objects. + self.startupHood(TTHoodDataAI.TTHoodDataAI(self)) + self.startupHood(DDHoodDataAI.DDHoodDataAI(self)) + self.startupHood(MMHoodDataAI.MMHoodDataAI(self)) + self.startupHood(DGHoodDataAI.DGHoodDataAI(self)) + self.startupHood(BRHoodDataAI.BRHoodDataAI(self)) + self.startupHood(DLHoodDataAI.DLHoodDataAI(self)) + self.startupHood(CSHoodDataAI.CSHoodDataAI(self)) + self.startupHood(GSHoodDataAI.GSHoodDataAI(self)) + self.startupHood(OZHoodDataAI.OZHoodDataAI(self)) + self.startupHood(GZHoodDataAI.GZHoodDataAI(self)) + self.startupHood(CashbotHQDataAI.CashbotHQDataAI(self)) + self.startupHood(LawbotHQDataAI.LawbotHQDataAI(self)) + self.startupHood(BossbotHQDataAI.BossbotHQDataAI(self)) + + # The Holiday Manager should be instantiated after the each + # of the hoods and estateMgrAI are generated because Bingo Night + # needs to reference the HoodDataAI and EstateMgrAI for pond + # information. (JJT - 7/22/04) + self.holidayManager = HolidayManagerAI.HolidayManagerAI(self) + + self.banManager = BanManagerAI.BanManagerAI() + + # Now that we've created all the suit planners, any one of + # them can be used to fill the world with requests for suit + # buildings. + if self.suitPlanners: + self.suitPlanners.values()[0].assignInitialSuitBuildings() + + # mark district as avaliable + self.district.b_setAvailable(1) + + # Now that everything's created, start checking the leader + # boards for correctness. We only need to check every 30 + # seconds or so. + self.__leaderboardFlush(None) + taskMgr.doMethodLater(30, self.__leaderboardFlush, + 'leaderboardFlush', appendTask = True) + + def __leaderboardFlush(self, task): + messenger.send('leaderboardFlush') + return Task.again + + def getWelcomeValleyCount(self): + # avatars in Welcom Vally + return self.welcomeValleyManager.getAvatarCount(); + + def getHandleClassNames(self): + # This function should return a tuple or list of string names + # that represent distributed object classes for which we want + # to make a special 'handle' class available. + return ('DistributedToon',) + + def deleteObjects(self): + # This function is where objects that manage DistributedObjectAI's + # should be cleaned up. Since this is only called during a district + # shutdown of some kind, it is not useful to delete the existing + # DistributedObjectAI's, but rather to just make sure that they + # are no longer referenced, and no new ones are created. + for hood in self.hoods: + hood.shutdown() + self.hoods = [] + + taskMgr.remove('leaderboardFlush') + + def queryToonMaxHp(self, toonId, callback, *args): + """ + Looks up the maxHp of the given toon, and calls the given + callback function, passing the maxHp as a parameter (or None + if the toonId is unknown) followed by the given extra args. + If the toon happens to be online and on the same shard, the + callback is made immediately; otherwise, it will be made at + some point in the future, after we have heard back from the + database. + """ + toon = self.doId2do.get(toonId, None) + if toon != None: + # The toon is online and on this shard. + callback(toon.getMaxHp(), *args) else: - phase = ToontownGlobals.streetPhaseMap[canonicalHoodId] + # The toon is offline or on another shard; we have to + # query the database. + db = DatabaseObject.DatabaseObject(self, toonId) + db.doneEvent = 'queryToonMaxHp' + db.userCallback = callback + db.userArgs = args + db.getFields(['setMaxHp']) - if 'outdoor_zone' in hood or 'golf_zone' in hood: - phase = '6' - - return 'phase_%s/dna/%s_%s.dna' % (phase, hood, canonicalZoneId) - - def lookupDNAFileName(self, dnaFileName): - searchPath = DSearchPath() - searchPath.appendDirectory(Filename('resources/phase_3.5/dna')) - searchPath.appendDirectory(Filename('resources/phase_4/dna')) - searchPath.appendDirectory(Filename('resources/phase_5/dna')) - searchPath.appendDirectory(Filename('resources/phase_5.5/dna')) - searchPath.appendDirectory(Filename('resources/phase_6/dna')) - searchPath.appendDirectory(Filename('resources/phase_8/dna')) - searchPath.appendDirectory(Filename('resources/phase_9/dna')) - searchPath.appendDirectory(Filename('resources/phase_10/dna')) - searchPath.appendDirectory(Filename('resources/phase_11/dna')) - searchPath.appendDirectory(Filename('resources/phase_12/dna')) - searchPath.appendDirectory(Filename('resources/phase_13/dna')) - filename = Filename(dnaFileName) - found = vfs.resolveFilename(filename, searchPath) - if not found: - self.notify.warning('lookupDNAFileName - %s not found on:' % dnaFileName) - print(searchPath) + def __queryToonMaxHpResponse(self, db, retCode): + maxHpDatagram = db.values.get('setMaxHp', None) + if retCode == 0 and maxHpDatagram != None: + di = PyDatagramIterator(maxHpDatagram) + maxHp = di.getInt16() else: - return filename.getFullpath() + # The toonId is unknown or not a toon. + maxHp = None - def loadDNAFileAI(self, dnaStore, dnaFileName): - return loadDNAFileAI(dnaStore, dnaFileName) + db.userCallback(maxHp, *db.userArgs) - def findFishingPonds(self, dnaData, zoneId, area): - return [], [] # TODO - def findPartyHats(self, dnaData, zoneId): - return [] # TODO + def getMinDynamicZone(self): + # Override this to return the minimum allowable value for a + # dynamically-allocated zone id. + return DynamicZonesBegin - def findRacingPads(self, dnaData, zoneId, area, type='racing_pad', overrideDNAZone=False): - kartPads, kartPadGroups = [], [] - if type in dnaData.getName(): - if type == 'racing_pad': - nameSplit = dnaData.getName().split('_') - racePad = DistributedRacePadAI(self) - racePad.setArea(area) - racePad.index = int(nameSplit[2]) - racePad.genre = nameSplit[3] - trackInfo = RaceGlobals.getNextRaceInfo(-1, racePad.genre, racePad.index) - racePad.setTrackInfo([trackInfo[0], trackInfo[1]]) - racePad.laps = trackInfo[2] - racePad.generateWithRequired(zoneId) - kartPads.append(racePad) - kartPadGroups.append(dnaData) - elif type == 'viewing_pad': - viewPad = DistributedViewPadAI(self) - viewPad.setArea(area) - viewPad.generateWithRequired(zoneId) - kartPads.append(viewPad) - kartPadGroups.append(dnaData) + def getMaxDynamicZone(self): + # Override this to return the maximum allowable value for a + # dynamically-allocated zone id. - for i in range(dnaData.getNumChildren()): - foundKartPads, foundKartPadGroups = self.findRacingPads(dnaData.at(i), zoneId, area, type, overrideDNAZone) - kartPads.extend(foundKartPads) - kartPadGroups.extend(foundKartPadGroups) + # Note that each zone requires the use of the channel derived + # by self.districtId + zoneId. Thus, we cannot have any zones + # greater than or equal to self.minChannel - self.districtId, + # which is our first allocated doId. + return min(self.minChannel - self.districtId, DynamicZonesEnd) - 1 - return kartPads, kartPadGroups + def findPartyHats(self, dnaGroup, zoneId, overrideDNAZone = 0): + """ + Recursively scans the given DNA tree for party hats. These + are defined as all the groups whose code includes the string + "party_gate". For each such group, creates a + DistributedPartyGateAI. Returns the list of distributed + objects. + """ + partyHats = [] - def findStartingBlocks(self, dnaData, kartPad): + if ((isinstance(dnaGroup, DNAGroup)) and + # If it is a DNAGroup, and the name has party_gate, count it + (string.find(dnaGroup.getName(), 'party_gate') >= 0)): + # Here's a party hat! + ph = DistributedPartyGateAI.DistributedPartyGateAI(self) + ph.generateWithRequired(zoneId) + partyHats.append(ph) + else: + # Now look in the children + # Party hats cannot have other party hats in them, + # so do not search the one we just found: + # If we come across a visgroup, note the zoneId and then recurse + if (isinstance(dnaGroup, DNAVisGroup) and not overrideDNAZone): + # Make sure we get the real zone id, in case we are in welcome valley + zoneId = ZoneUtil.getTrueZoneId( + int(dnaGroup.getName().split(':')[0]), zoneId) + for i in range(dnaGroup.getNumChildren()): + childPartyHats = self.findPartyHats(dnaGroup.at(i), zoneId, overrideDNAZone) + partyHats += childPartyHats + + return partyHats + + def findFishingPonds(self, dnaGroup, zoneId, area, overrideDNAZone = 0): + """ + Recursively scans the given DNA tree for fishing ponds. These + are defined as all the groups whose code includes the string + "fishing_pond". For each such group, creates a + DistributedFishingPondAI. Returns the list of distributed + objects and a list of the DNAGroups so we can search them for + spots and targets. + """ + fishingPonds = [] + fishingPondGroups = [] + + if ((isinstance(dnaGroup, DNAGroup)) and + # If it is a DNAGroup, and the name starts with fishing_pond, count it + (string.find(dnaGroup.getName(), 'fishing_pond') >= 0)): + # Here's a fishing pond! + fishingPondGroups.append(dnaGroup) + fp = DistributedFishingPondAI.DistributedFishingPondAI(self, area) + fp.generateWithRequired(zoneId) + fishingPonds.append(fp) + else: + # Now look in the children + # Fishing ponds cannot have other ponds in them, + # so do not search the one we just found: + # If we come across a visgroup, note the zoneId and then recurse + if (isinstance(dnaGroup, DNAVisGroup) and not overrideDNAZone): + # Make sure we get the real zone id, in case we are in welcome valley + zoneId = ZoneUtil.getTrueZoneId( + int(dnaGroup.getName().split(':')[0]), zoneId) + for i in range(dnaGroup.getNumChildren()): + childFishingPonds, childFishingPondGroups = self.findFishingPonds( + dnaGroup.at(i), zoneId, area, overrideDNAZone) + fishingPonds += childFishingPonds + fishingPondGroups += childFishingPondGroups + return fishingPonds, fishingPondGroups + + def findFishingSpots(self, dnaPondGroup, distPond): + """ + Scans the given DNAGroup pond for fishing spots. These + are defined as all the props whose code includes the string + "fishing_spot". Fishing spots should be the only thing under a pond + node. For each such prop, creates a DistributedFishingSpotAI. + Returns the list of distributed objects created. + """ + fishingSpots = [] + # Search the children of the pond + for i in range(dnaPondGroup.getNumChildren()): + dnaGroup = dnaPondGroup.at(i) + if ((isinstance(dnaGroup, DNAProp)) and + (string.find(dnaGroup.getCode(), 'fishing_spot') >= 0)): + # Here's a fishing spot! + pos = dnaGroup.getPos() + hpr = dnaGroup.getHpr() + fs = DistributedFishingSpotAI.DistributedFishingSpotAI( + self, distPond, pos[0], pos[1], pos[2], hpr[0], hpr[1], hpr[2]) + fs.generateWithRequired(distPond.zoneId) + fishingSpots.append(fs) + else: + self.notify.debug("Found dnaGroup that is not a fishing_spot under a pond group") + return fishingSpots + + def findRacingPads(self, dnaGroup, zoneId, area, overrideDNAZone = 0, type = 'racing_pad'): + racingPads = [] + racingPadGroups = [] + if ((isinstance(dnaGroup, DNAGroup)) and (string.find(dnaGroup.getName(), type) >= 0)): + racingPadGroups.append(dnaGroup) + if (type == 'racing_pad'): + nameInfo = dnaGroup.getName().split('_') + #pdb.set_trace() + #print("Name Info: ", nameInfo) + #print("Race Info: ", raceInfo) + racingPad = DistributedRacePadAI(self, area, nameInfo[3], int(nameInfo[2])) + else: + racingPad = DistributedViewPadAI(self, area) + racingPad.generateWithRequired(zoneId) + racingPads.append(racingPad) + else: + if (isinstance(dnaGroup, DNAVisGroup) and not overrideDNAZone): + zoneId = ZoneUtil.getTrueZoneId(int(dnaGroup.getName().split(':')[0]), zoneId) + for i in range(dnaGroup.getNumChildren()): + childRacingPads, childRacingPadGroups = self.findRacingPads(dnaGroup.at(i), zoneId, area, overrideDNAZone, type) + racingPads += childRacingPads + racingPadGroups += childRacingPadGroups + return racingPads, racingPadGroups + + def getRacingPadList(self): + list = [] + for do in self.doId2do.values(): + if (isinstance(do, DistributedRacePadAI)): + list.append(do.doId) + return list + + def getViewPadList(self): + list = [] + for do in self.doId2do.values(): + if (isinstance(do, DistributedViewPadAI)): + list.append(do.doId) + return list + + def getStartingBlockDict(self): + dict = {} + for do in self.doId2do.values(): + if (isinstance(do, DistributedStartingBlockAI)): + if (isinstance(do.kartPad, DistributedRacePadAI)): + # Add the do to the dict + if (dict.has_key(do.kartPad.doId)): + dict[do.kartPad.doId].append(do.doId) + else: + dict[do.kartPad.doId] = [do.doId] + return dict + + def getViewingBlockDict(self): + dict = {} + for do in self.doId2do.values(): + if (isinstance(do, DistributedStartingBlockAI)): + if (isinstance(do.kartPad, DistributedViewPadAI)): + # Add the do to the dict + if (dict.has_key(do.kartPad.doId)): + dict[do.kartPad.doId].append(do.doId) + else: + dict[do.kartPad.doId] = [do.doId] + return dict + + def findStartingBlocks(self, dnaRacingPadGroup, distRacePad): + """ + Comment goes here... + """ startingBlocks = [] - for i in range(dnaData.getNumChildren()): - groupName = dnaData.getName() - block = dnaData.at(i) - blockName = block.getName() - if 'starting_block' in blockName: - cls = DistributedStartingBlockAI if 'racing_pad' in groupName else DistributedViewingBlockAI - x, y, z = block.getPos() - h, p, r = block.getHpr() - padLocationId = int(blockName[-1]) - startingBlock = cls(self, kartPad, x, y, z, h, p, r, padLocationId) - startingBlock.generateWithRequired(kartPad.zoneId) - startingBlocks.append(startingBlock) + # Search the children of the racing pad + for i in range(dnaRacingPadGroup.getNumChildren()): + dnaGroup = dnaRacingPadGroup.at(i) + # TODO - check if DNAProp instance + if ((string.find(dnaGroup.getName(), 'starting_block') >= 0)): + padLocation = dnaGroup.getName().split('_')[2] + pos = dnaGroup.getPos() + hpr = dnaGroup.getHpr() + + if (isinstance(distRacePad, DistributedRacePadAI)): + sb = DistributedStartingBlockAI(self, distRacePad, pos[0], pos[1], pos[2], hpr[0], hpr[1], hpr[2], int(padLocation)) + else: + sb = DistributedViewingBlockAI(self, distRacePad, pos[0], pos[1], pos[2], hpr[0], hpr[1], hpr[2], int(padLocation)) + sb.generateWithRequired(distRacePad.zoneId) + startingBlocks.append(sb) + else: + self.notify.debug("Found dnaGroup that is not a starting_block under a race pad group") return startingBlocks - - def findLeaderBoards(self, dnaData, zoneId): + + def findLeaderBoards(self, dnaPool, zoneID): + ''' + Find and return leader boards + ''' leaderBoards = [] - if 'leaderBoard' in dnaData.getName(): - x, y, z = dnaData.getPos() - h, p, r = dnaData.getHpr() - leaderBoard = DistributedLeaderBoardAI(self, dnaData.getName(), x, y, z, h, p, r) - leaderBoard.generateWithRequired(zoneId) - leaderBoards.append(leaderBoard) - - for i in range(dnaData.getNumChildren()): - foundLeaderBoards = self.findLeaderBoards(dnaData.at(i), zoneId) - leaderBoards.extend(foundLeaderBoards) - + if (string.find(dnaPool.getName(), 'leaderBoard') >= 0): + #found a leader board + pos = dnaPool.getPos() + hpr = dnaPool.getHpr() + + lb = DistributedLeaderBoardAI(self, dnaPool.getName(), zoneID, [], pos, hpr) + lb.generateWithRequired(zoneID) + leaderBoards.append(lb) + else: + for i in range(dnaPool.getNumChildren()): + result = self.findLeaderBoards(dnaPool.at(i), zoneID) + if result: + leaderBoards += result + return leaderBoards + + def loadDNAFileAI(self, dnaStore, dnaFile): + return loadDNAFileAI(dnaStore, dnaFile, CSDefault) - def getTrackClsends(self): - return False + #AIGEOM + def loadDNAFile(self, dnaStore, dnaFile, cs=CSDefault): + """ + load everything, including geometry + """ + return loadDNAFile(dnaStore, dnaFile, cs) - def getAvatarExitEvent(self, avId): - return 'distObjDelete-%d' % avId + def startupHood(self, hoodDataAI): + hoodDataAI.startup() + self.hoods.append(hoodDataAI) - def getAvatarDisconnectReason(self, avId): - return self.timeManager.avId2disconnectcode.get(avId, ToontownGlobals.DisconnectUnknown) + def shutdownHood(self, hoodDataAI): + hoodDataAI.shutdown() + self.hoods.remove(hoodDataAI) - def getZoneDataStore(self): - return self.zoneDataStore - def incrementPopulation(self): - self.districtStats.b_setAvatarCount(self.districtStats.getAvatarCount() + 1) + def getEstate(self, avId, zone, callback): + """ + Asks the database to fill in details about this avatars + estate. - def decrementPopulation(self): - self.districtStats.b_setAvatarCount(self.districtStats.getAvatarCount() - 1) + We make a request to the server and wait for its response. + """ + context = self.__queryEstateContext + self.__queryEstateContext += 1 + self.__queryEstateFuncMap[context] = callback + self.__sendGetEstate(avId, context) + + def __sendGetEstate(self, avId, context): + """ + Sends the query-object message to the server. The return + message will be handled by __handleGetEstateResp(). + See getEstate(). + """ + datagram = PyDatagram() + datagram.addServerHeader( + DBSERVER_ID, self.ourChannel, DBSERVER_GET_ESTATE) + datagram.addUint32(context) + # The avId we are querying. + datagram.addUint32(avId) + self.send(datagram) - def allocateZone(self, owner=None): - zoneId = self.zoneAllocator.allocate() - if owner: - self.zoneId2owner[zoneId] = owner + def __handleGetEstateResp(self, di): + # Use the context to retrieve the callback parameter passed in + # to getEstate(). + context = di.getUint32() + callback = self.__queryEstateFuncMap.get(context) + if callback == None: + self.notify.warning("Got unexpected estate context: %s" % (context)) + return + del self.__queryEstateFuncMap[context] - return zoneId + # return code = 0 if estate was returned without problems + retCode = di.getUint8() - def deallocateZone(self, zone): - if self.zoneId2owner.get(zone): - del self.zoneId2owner[zone] + estateVal = {} + if (retCode == 0): + estateId = di.getUint32() + numFields = di.getUint16() + + for i in range(numFields): + key = di.getString() + #key = key[2:] + #right why to do this???? ask Roger and/or Dave + value = di.getString() + found = di.getUint8() + + #print(key); + #print(value); + #print(found); - self.zoneAllocator.free(zone) + if found: + # create another datagram for this value + #vdg = PyDatagram(estateVal[i]) + #vdgi = PyDatagramIterator(vdg) + # do something with this data + estateVal[key] = value + + + numHouses = di.getUint16() + self.notify.debug("numHouses = %s" % numHouses) + houseId = [None] * numHouses + for i in range(numHouses): + houseId[i] = di.getUint32() + self.notify.debug("houseId = %s" % houseId[i]) + + numHouseKeys = di.getUint16() + self.notify.debug("numHouseKeys = %s" % numHouseKeys) + houseKey = [None] * numHouseKeys + for i in range(numHouseKeys): + houseKey[i] = di.getString() - def trueUniqueName(self, idString): - return self.uniqueName(idString) + numHouseVal = di.getUint16() + assert (numHouseVal == numHouseKeys) + tempHouseVal = [None] * numHouseVal + for i in range(numHouseVal): + numHouses2 = di.getUint16() + assert(numHouses2 == numHouses) + tempHouseVal[i] = [None] * numHouses + for j in range(numHouses): + tempHouseVal[i][j] = di.getString() + # do we need a check for "value found" here? - def setupFiles(self): - if not os.path.exists(self.dataFolder): - os.mkdir(self.dataFolder) + #print(houseKey) + #print(tempHouseVal) + + numHouseFound = di.getUint16() + + + # keep track of which attributes are found + foundVal = [None] * numHouses + for i in range(numHouses): + foundVal[i] = [None] * numHouseVal + + # create empty dictionaries for each house + houseVal = [] + for i in range(numHouses): + houseVal.append({}) + + for i in range(numHouseVal): + hvLen = di.getUint16() + for j in range(numHouses): + found = di.getUint8() + if found: + houseVal[j][houseKey[i]] = tempHouseVal[i][j] + foundVal[j][i] = 1 + else: + foundVal[j][i] = 0 + + numPets = di.getUint16() + petIds = [] + for i in xrange(numPets): + petIds.append(di.getUint32()) + + # create estate with houses + # and call DistributedEstateAI's initEstateData func + + # call function originally passed to getEstate + callback(estateId, estateVal, numHouses, houseId, houseVal, + petIds, estateVal) + else: + print("ret code != 0, something went wrong with estate creation") + + def getFirstBattle(self): + # Return the first battle in the repository (for testing purposes) + from toontown.battle import DistributedBattleBaseAI + for dobj in self.doId2do.values(): + if isinstance( + dobj, DistributedBattleBaseAI.DistributedBattleBaseAI): + return dobj + + def handlePlayGame(self, msgType, di): + # Handle Toontown specific message types before + # calling the base class + if msgType == DBSERVER_GET_ESTATE_RESP: + self.__handleGetEstateResp(di) + elif msgType == PARTY_MANAGER_UD_TO_ALL_AI: + self.__handlePartyManagerUdToAllAi(di) + elif msgType == IN_GAME_NEWS_MANAGER_UD_TO_ALL_AI: + self.__handleInGameNewsManagerUdToAllAi(di) + else: + AIDistrict.handlePlayGame(self, msgType, di) + + def handleAvCatch(self, avId, zoneId, catch): + """ + avId - ID of avatar to update + zoneId - zoneId of the pond the catch was made in. + This is used by the BingoManagerAI to + determine which PBMgrAI needs to update + the catch. + catch - a fish tuple of (genus, species) + returns: None + + This method instructs the BingoManagerAI to + tell the appropriate PBMgrAI to update the + catch of an avatar at the particular pond. This + method is called in the FishManagerAI's + RecordCatch method. + """ + # Guard for publish + if simbase.wantBingo: + if self.bingoMgr: + self.bingoMgr.setAvCatchForPondMgr(avId, zoneId, catch) + + def createPondBingoMgrAI(self, estate): + """ + estate - the estate for which the PBMgrAI should + be created. + returns: None + + This method instructs the BingoManagerAI to + create a new PBMgrAI for a newly generated + estate. + """ + # Guard for publish + if simbase.wantBingo: + if self.bingoMgr: + self.notify.info('createPondBingoMgrAI: Creating a DPBMAI for Dynamic Estate') + self.bingoMgr.createPondBingoMgrAI(estate, 1) + + def __handlePartyManagerUdToAllAi(self,di): + """Send all msgs of this type to the party manager on our district.""" + # we know the format is STATE_SERVER_OBJECT_UPDATE_FIELD + # we just changed the msg type to PARTY_MANAGER_UD_TO_ALL_AI + # so that it gets handled here + # otherwise it just gets dropped on the floor + do = self.partyManager + if do: + globalId = di.getUint32() + if globalId != OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER: + self.notify.error('__handlePartyManagerUdToAllAi globalId=%d not equal to %d' % + (globalId, OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER)) + # Let the dclass finish the job + do.dclass.receiveUpdate(do, di) + + def __handleInGameNewsManagerUdToAllAi(self,di): + """Send all msgs of this type to the party manager on our district.""" + # we know the format is STATE_SERVER_OBJECT_UPDATE_FIELD + # we just changed the msg type to PARTY_MANAGER_UD_TO_ALL_AI + # so that it gets handled here + # otherwise it just gets dropped on the floor + do = self.inGameNewsMgr + if do: + globalId = di.getUint32() + if globalId != OtpDoGlobals.OTP_DO_ID_TOONTOWN_IN_GAME_NEWS_MANAGER: + self.notify.error('__handleInGameNewsManagerUdToAllAi globalId=%d not equal to %d' % + (globalId, OtpDoGlobals.OTP_DO_ID_TOONTOWN_PARTY_MANAGER)) + # Let the dclass finish the job + do.dclass.receiveUpdate(do, di) diff --git a/toontown/ai/ToontownMagicWordManagerAI.py b/toontown/ai/ToontownMagicWordManagerAI.py new file mode 100644 index 0000000..83af2a6 --- /dev/null +++ b/toontown/ai/ToontownMagicWordManagerAI.py @@ -0,0 +1,3617 @@ +# python imports +import string +import time +import random +import datetime + +# panda3d imports +from pandac.PandaModules import * +from direct.showbase import PythonUtil +from direct.task import Task + +# toontown imports +from otp.ai.AIBaseGlobal import * +from otp.ai.AIZoneData import AIZoneData +from direct.distributed import DistributedObjectAI +from direct.directnotify import DirectNotifyGlobal +from toontown.toon import InventoryBase +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +from toontown.suit import DistributedSuitPlannerAI +from toontown.battle import DistributedBattleBaseAI +from toontown.toon import DistributedToonAI +from . import WelcomeValleyManagerAI +from toontown.hood import ZoneUtil +from toontown.battle import SuitBattleGlobals +from toontown.quest import Quests +from toontown.minigame import MinigameCreatorAI +from toontown.estate import DistributedPhoneAI +from toontown.suit import DistributedBossCogAI +from toontown.suit import DistributedSellbotBossAI +from toontown.suit import DistributedCashbotBossAI +from toontown.suit import DistributedLawbotBossAI +from toontown.suit import DistributedBossbotBossAI +from toontown.catalog import CatalogItemList +from toontown.pets import PetTricks +from toontown.suit import SuitDNA +from toontown.toon import ToonDNA +from toontown.toonbase import TTLocalizer +from otp.ai import MagicWordManagerAI +from toontown.estate import GardenGlobals +from otp.otpbase import OTPGlobals +from toontown.golf import GolfManagerAI +from toontown.golf import GolfGlobals +from toontown.parties import PartyGlobals +from toontown.parties import PartyUtils +from toontown.uberdog.DataStoreAIClient import DataStoreAIClient +from toontown.uberdog import DataStoreGlobals + +if (simbase.wantKarts): + from toontown.racing.KartDNA import * + +class ToontownMagicWordManagerAI(MagicWordManagerAI.MagicWordManagerAI): + notify = DirectNotifyGlobal.directNotify.newCategory("ToontownMagicWordManagerAI") + + GameAvatarClass = DistributedToonAI.DistributedToonAI + + # is it a safezone? + Str2szId = { + 'ttc': ToontownGlobals.ToontownCentral, + 'tt': ToontownGlobals.ToontownCentral, + 'tc': ToontownGlobals.ToontownCentral, + 'dd': ToontownGlobals.DonaldsDock, + 'dg': ToontownGlobals.DaisyGardens, + 'mml': ToontownGlobals.MinniesMelodyland, + 'mm': ToontownGlobals.MinniesMelodyland, + 'br': ToontownGlobals.TheBrrrgh, + 'ddl': ToontownGlobals.DonaldsDreamland, + 'dl': ToontownGlobals.DonaldsDreamland, + } + + + def __init__(self, air): + MagicWordManagerAI.MagicWordManagerAI.__init__(self, air) + self.__bossBattleZoneId = [None, None, None, None] + self.__bossCog = [None, None, None, None] + + def doMagicWord(self, word, av, zoneId, senderId): + def wordIs(w, word=word): + return word[:(len(w)+1)] == ('%s ' % w) or word == w + + if (MagicWordManagerAI.MagicWordManagerAI.doMagicWord(self, word, av, + zoneId, senderId) == 1): + pass + elif word == "~allstuff": + av.inventory.maxOutInv() + av.d_setInventory(av.inventory.makeNetString()) + self.notify.debug("Maxing out inventory for " + av.name) + elif word == "~nostuff": + av.inventory.zeroInv(1) + av.d_setInventory(av.inventory.makeNetString()) + self.notify.debug("Zeroing inventory for " + av.name) + elif word == "~restock": + av.doRestock(1) + elif word == "~restockUber": + av.doRestock(0) + + elif word == "~rich": + av.b_setMoney(av.maxMoney) + av.b_setBankMoney(av.maxBankMoney) + self.notify.debug(av.name + " is now rich") + elif word == "~poor": + av.b_setMoney(0) + av.b_setBankMoney(0) + self.notify.debug(av.name + " is now poor") + elif wordIs("~jelly"): + args = word.split() + if len(args) > 1: + count = int(args[1]) + # this will just fill up the pocketbook, + # but wont add to the bank + av.b_setMoney(min(count, av.getMaxMoney())) + else: + av.b_setMoney(av.getMaxMoney()) + elif wordIs("~bank"): + args = word.split() + if len(args) > 1: + count = int(args[1]) + av.b_setBankMoney(count) + else: + av.b_setBankMoney(av.getMaxBankMoney()) + elif wordIs("~maxBankMoney"): + args = word.split() + if len(args) > 1: + count = int(args[1]) + av.b_setMaxBankMoney(count) + response = "Max bank money set to %s" % (av.getMaxBankMoney()) + self.down_setMagicWordResponse(senderId, response) + else: + response = "Max bank money is %s" % (av.getMaxBankMoney()) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs("~pie"): + # Give ourselves a pie. Or four. + count = 0 + type = None + args = word.split() + if len(args) == 1: + count = 1 + for arg in args[1:]: + from toontown.toonbase import ToontownBattleGlobals + if arg in ToontownBattleGlobals.pieNames: + type = ToontownBattleGlobals.pieNames.index(arg) + else: + try: + count = int(arg) + except: + response = "Invalid pie argument: %s" % (arg) + self.down_setMagicWordResponse(senderId, response) + return + + if type != None: + av.b_setPieType(type) + av.b_setNumPies(av.numPies + count) + + elif word == "~amateur": + av.b_setTrackAccess([0, 0, 0, 0, 1, 1, 0]) + av.b_setMaxCarry(20) + av.b_setQuestCarryLimit(1) + av.experience.zeroOutExp() + av.d_setExperience(av.experience.makeNetString()) + av.b_setMaxHp(15) + av.b_setHp(15) + newInv = InventoryBase.InventoryBase(av) + newInv.maxOutInv() + av.inventory.setToMin(newInv.inventory) + av.d_setInventory(av.inventory.makeNetString()) + self.notify.debug("Default exp for " + av.name) + elif word == "~amateur+": + av.experience.setAllExp(9) + av.d_setExperience(av.experience.makeNetString()) + av.b_setMaxHp(30) + av.b_setHp(30) + # make sure we're not over maxProps too + newInv = InventoryBase.InventoryBase(av) + newInv.maxOutInv() + av.inventory.setToMin(newInv.inventory) + av.d_setInventory(av.inventory.makeNetString()) + self.notify.debug("Setting exp to 9 for " + av.name) + elif word == "~professional": + av.b_setTrackAccess([1, 1, 1, 1, 1, 1, 1]) + av.b_setMaxCarry(ToontownGlobals.MaxCarryLimit) + av.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit) + av.experience.maxOutExp() + av.d_setExperience(av.experience.makeNetString()) + av.b_setMaxHp(ToontownGlobals.MaxHpLimit) + av.b_setHp(ToontownGlobals.MaxHpLimit) + self.notify.debug("Max exp for " + av.name) + elif word == "~professional--": + av.b_setTrackAccess([1, 1, 1, 1, 1, 1, 1]) + av.b_setMaxCarry(ToontownGlobals.MaxCarryLimit) + av.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit) + av.experience.makeExpHigh() + av.d_setExperience(av.experience.makeNetString()) + av.b_setMaxHp(ToontownGlobals.MaxHpLimit-15) + av.b_setHp(ToontownGlobals.MaxHpLimit-15) + self.notify.debug("High exp for " + av.name) + elif word == "~regularToon": + pickTrack = ([1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1]) + av.b_setTrackAccess(random.choice(pickTrack)) + av.b_setMaxCarry(ToontownGlobals.MaxCarryLimit) + av.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit) + av.experience.makeExpRegular() + av.d_setExperience(av.experience.makeNetString()) + laughminus = int(random.random() * 20.0) + 10.0 + av.b_setMaxHp(ToontownGlobals.MaxHpLimit-laughminus) + av.b_setHp(ToontownGlobals.MaxHpLimit-laughminus) + self.notify.debug("regular exp for " + av.name) + elif word == "~maxexp--": + av.b_setTrackAccess([1, 1, 1, 1, 1, 1, 1]) + av.b_setMaxCarry(ToontownGlobals.MaxCarryLimit) + av.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit) + av.experience.maxOutExpMinusOne() + av.d_setExperience(av.experience.makeNetString()) + av.b_setMaxHp(ToontownGlobals.MaxHpLimit) + av.b_setHp(ToontownGlobals.MaxHpLimit) + self.notify.debug("Max exp-- for " + av.name) + elif wordIs('~mintRaider'): + av.experience.maxOutExp() + av.d_setExperience(av.experience.makeNetString()) + av.b_setQuestHistory([]) + av.b_setRewardHistory(Quests.DL_TIER+2, []) + av.fixAvatar() + # 7LP for fishing, + 5LP for SellbotHQ, /2 + av.b_setMaxHp(av.getMaxHp()+6) + av.b_setHp(av.getMaxHp()) + trackAccess = [1, 1, 1, 1, 1, 1, 1] + trackAccess[random.choice((0, 1, 2, 3, 6))] = 0 + av.b_setTrackAccess(trackAccess) + + elif word[:4] == "~exp": + self.doExp(word, av, zoneId, senderId) + + elif word[:7] == "~trophy": + self.doTrophy(word, av, zoneId, senderId) + + elif word == "~trophies": + # Report the top 10 trophy holders. + scores = self.air.trophyMgr.getSortedScores() + response = '' + for i in range(min(len(scores), 10)): + score, avId = scores[i] + av = self.air.doId2do.get(avId, None) + if av: + avName = av.name + else: + avName = avId + response += '%s %s\n' % (score, avName) + + self.down_setMagicWordResponse(senderId, response) + + elif word[:8] == "~effect ": + # Apply a cheesy rendering effect. + self.doCheesyEffect(word, av, zoneId, senderId) + + elif word[:12] == "~cogTakeOver": + self.doCogTakeOver(word, av, zoneId, senderId) + + elif wordIs("~cogdoTakeOver"): + if simbase.air.wantCogdominiums: + self.doCogdoTakeOver(word, av, zoneId, senderId) + + elif word[:13] == "~toonTakeOver": + self.doToonTakeOver(word, av, zoneId, senderId) + + elif word[:8] == "~welcome": + self.doWelcome(word, av, zoneId, senderId) + + elif word == "~finishTutorial": + av.b_setTutorialAck(1) + av.b_setQuests([]) + av.b_setQuestHistory([]) + av.b_setRewardHistory(2, []) + av.fixAvatar() + self.down_setMagicWordResponse(senderId, "Finished tutorial.") + + elif word == "~finishQuests": + self.air.questManager.completeAllQuestsMagically(av) + self.down_setMagicWordResponse(senderId, "Finished quests.") + + elif word[:12] == "~finishQuest": + args = word.split() + index = int(args[1]) + result = self.air.questManager.completeQuestMagically(av, index) + if result: + self.down_setMagicWordResponse(senderId, ("Finished quest %s." % (index))) + else: + self.down_setMagicWordResponse(senderId, ("Quest %s not found." % (index))) + + elif word == "~clearQuests": + # Reset all quest fields as if this were a new toon + av.b_setQuests([]) + av.b_setQuestHistory([]) + currentTier = av.getRewardTier() + av.b_setRewardHistory(currentTier, []) + self.down_setMagicWordResponse(senderId, "Cleared quests.") + + elif word == "~getQuestTier": + # Report the current quest tier + response = "tier %d" % (av.getRewardTier()) + self.down_setMagicWordResponse(senderId, response) + + elif word[:13] == "~setQuestTier": + # Sets reward tier and optionally index + args = word.split() + tier = int(args[1]) + tier = min(tier, Quests.getNumTiers()) + av.b_setQuestHistory([]) + av.b_setRewardHistory(tier, []) + av.fixAvatar() + + elif word[:12] == "~assignQuest": + # Intelligently assigns a quest + args = word.split() + questId = int(args[1]) + + # Make sure this quest exists + questDesc = Quests.QuestDict.get(questId) + if questDesc is None: + self.down_setMagicWordResponse(senderId, "Quest %s not found" % (questId)) + return + + # Make sure the av is in that tier + avTier = av.getRewardTier() + tier = questDesc[Quests.QuestDictTierIndex] + if tier != avTier: + self.down_setMagicWordResponse(senderId, "Avatar not in that tier: %s. You can ~setQuestTier %s, if you want." % (tier, tier)) + return + + # Make sure the av has room for this quest + if not self.air.questManager.needsQuest(av): + self.down_setMagicWordResponse(senderId, "Quests are already full") + return + + # Make sure the av does not already have this quest + for questDesc in av.quests: + if questId == questDesc[0]: + self.down_setMagicWordResponse(senderId, "Already has quest: %s" % (questId)) + return + + # Should we check your reward history too? + + fromNpcId = Quests.ToonHQ # A reasonable default + + rewardId = Quests.getQuestReward(questId, av) + # Some quests do not have a reward specified. Instead they have + # the keyword which tells the quest system to try to + # match something up. In our case, we are not going through + # normal channels, so just pick some reward so things do not + # crash. How about some jellybeans? + if rewardId == Quests.Any: + # Just give 100 jellybeans (rewardId = 604 from Quests.py) + rewardId = 604 + + toNpcId = Quests.getQuestToNpcId(questId) + # Account for some trickery in the quest description + # If the toNpcId is marked or let's just use ToonHQ + if toNpcId == Quests.Any: + toNpcId = Quests.ToonHQ + elif toNpcId == Quests.Same: + toNpcId = Quests.ToonHQ + + startingQuest = Quests.isStartingQuest(questId) + self.air.questManager.assignQuest(av.doId, + fromNpcId, + questId, + rewardId, + toNpcId, + startingQuest, + ) + self.down_setMagicWordResponse(senderId, "Quest %s assigned" % (questId)) + + elif wordIs("~nextQuest"): + # forces NPCs to offer you a particular quest + args = word.split() + if len(args) == 1: + # clear any existing request + questId = self.air.questManager.cancelNextQuest(av.doId) + if questId: + self.down_setMagicWordResponse( + senderId, "Cancelled request for quest %s" % (questId)) + return + + questId = int(args[1]) + + # Make sure this quest exists + questDesc = Quests.QuestDict.get(questId) + if questDesc is None: + self.down_setMagicWordResponse( + senderId, "Quest %s not found" % (questId)) + return + + # Make sure the av is in that tier + avTier = av.getRewardTier() + tier = questDesc[Quests.QuestDictTierIndex] + if tier != avTier: + self.down_setMagicWordResponse(senderId, "Avatar not in that tier: %s. You can ~setQuestTier %s, if you want." % (tier, tier)) + return + + # Make sure the av does not already have this quest + for questDesc in av.quests: + if questId == questDesc[0]: + self.down_setMagicWordResponse( + senderId, "Already has quest: %s" % (questId)) + return + + self.air.questManager.setNextQuest(av.doId, questId) + self.down_setMagicWordResponse(senderId, + "Quest %s queued" % (questId)) + + elif word == "~visitHQ": + # Sets quests to return to HQ Officer instead of whomever. + # Saves walking all over the map to test quests. + for quest in av.quests: + quest[2] = Quests.ToonHQ + av.b_setQuests(av.quests) + + elif wordIs('~teleportAll'): + av.b_setHoodsVisited(ToontownGlobals.HoodsForTeleportAll) + av.b_setTeleportAccess(ToontownGlobals.HoodsForTeleportAll) + + elif word[:10] == "~buildings": + self.doBuildings(word, av, zoneId, senderId) + + elif word[:16] == "~buildingPercent": + self.doBuildingPercent(word, av, zoneId, senderId) + + elif wordIs('~call'): + self.doCall(word, av, zoneId, senderId) + + elif wordIs('~battle'): + self.doBattle(word, av, zoneId, senderId) + + elif word[:5] == "~cogs": + self.doCogs(word, av, zoneId, senderId) + + # Suit Invasions + elif word[:12] == "~getInvasion": + self.getCogInvasion(word, av, zoneId, senderId) + elif word[:14] == "~startInvasion": + self.doCogInvasion(word, av, zoneId, senderId) + elif word[:13] == "~stopInvasion": + self.stopCogInvasion(word, av, zoneId, senderId) + + # Fireworks + elif word[:18] == "~startAllFireworks": + self.startAllFireworks(word, av, zoneId, senderId) + elif word[:15] == "~startFireworks": + self.startFireworks(word, av, zoneId, senderId) + elif word[:14] == "~stopFireworks": + self.stopFireworks(word, av, zoneId, senderId) + elif word[:17] == "~stopAllFireworks": + self.stopAllFireworks(word, av, zoneId, senderId) + + elif word == "~save": + # Save the AI state. Presently, this is just the set of + # buildings. + self.air.saveBuildings() + + response = "Building state saved." + self.down_setMagicWordResponse(senderId, response) + + elif word[:9] == "~minigame": + self.doMinigame(word, av, zoneId, senderId) + + elif wordIs('~factory'): + # select which factory to enter + # AI-global for now + from toontown.coghq import FactoryManagerAI + args = word.split() + if len(args) == 1: + # no arguments given + if FactoryManagerAI.FactoryManagerAI.factoryId is None: + self.down_setMagicWordResponse( + senderId, "usage: ~factory [id]") + else: + fId = FactoryManagerAI.FactoryManagerAI.factoryId + FactoryManagerAI.FactoryManagerAI.factoryId = None + self.down_setMagicWordResponse( + senderId, "cancelled request for factory %s" % fId) + else: + factoryId = int(args[1]) + if not factoryId in ToontownGlobals.factoryId2factoryType: + self.down_setMagicWordResponse( + senderId, + "unknown factory '%s'" % factoryId) + else: + FactoryManagerAI.FactoryManagerAI.factoryId = factoryId + self.down_setMagicWordResponse( + senderId, + "selected factory %s" % factoryId) + + elif wordIs('~mintId'): + args = word.split() + postName = 'mintId-%s' % av.doId + if len(args) < 2: + if bboard.has(postName): + bboard.remove(postName) + response = 'cleared mint request' + else: + response = '~mintId id' + else: + try: + id = int(args[1]) + # make sure it's a valid id + foo = ToontownGlobals.MintNumRooms[id] + except: + response = 'bad mint id: %s' % args[1] + else: + bboard.post(postName, id) + response = 'selected mint %s' % id + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~mintFloor'): + args = word.split() + postName = 'mintFloor-%s' % av.doId + if len(args) < 2: + if bboard.has(postName): + bboard.remove(postName) + response = 'cleared mint floor request' + else: + response = '~mintFloor num' + else: + try: + floor = int(args[1]) + except: + response = 'bad floor index: %s' % args[1] + else: + bboard.post(postName, floor) + response = 'selected floor %s' % floor + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~mintRoom'): + args = word.split() + postName = 'mintRoom-%s' % av.doId + if len(args) < 2: + if bboard.has(postName): + bboard.remove(postName) + response = 'cleared mint room request' + else: + response = '~mintRoom ' + else: + from toontown.coghq import MintRoomSpecs + id = None + name = None + try: + id = int(args[1]) + name = MintRoomSpecs.CashbotMintRoomId2RoomName[id] + except: + if args[1] in MintRoomSpecs.CashbotMintRoomName2RoomId: + name = args[1] + id = MintRoomSpecs.CashbotMintRoomName2RoomId[name] + else: + response = 'invalid room: %s' % args[1] + bboard.post(postName, id) + response = 'selected mint room %s: %s' % (id, name) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~stageRoom'): + args = word.split() + postName = 'stageRoom-%s' % av.doId + if len(args) < 2: + if bboard.has(postName): + bboard.remove(postName) + response = 'cleared stage room request' + else: + response = '~stageRoom ' + else: + from toontown.coghq import StageRoomSpecs + id = None + name = None + try: + id = int(args[1]) + name = StageRoomSpecs.CashbotStageRoomId2RoomName[id] + except: + if args[1] in StageRoomSpecs.CashbotStageRoomName2RoomId: + name = args[1] + id = StageRoomSpecs.CashbotStageRoomName2RoomId[name] + else: + response = 'invalid room: %s' % args[1] + bboard.post(postName, id) + response = 'selected stage room %s: %s' % (id, name) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs("~autoRestock"): + args = word.split() + postName = 'autoRestock-%s' % av.doId + enable = not bboard.get(postName, 0) + # are they explicitly setting the state? + if len(args) > 1: + try: + enable = int(args[1]) + except: + self.down_setMagicWordResponse(senderId, + 'invalid state flag: %s' % + args[1]) + return + if enable: + state = 'ON' + bboard.post(postName, 1) + else: + state = 'OFF' + bboard.remove(postName) + self.down_setMagicWordResponse(senderId, 'autoRestock %s' % state) + + elif wordIs("~resistanceRestock"): + from toontown.chat import ResistanceChat + args = word.split() + if len(args) < 2: + charges = 10 + else: + charges = int(args[1]) + msgs = [] + for menuIndex in ResistanceChat.resistanceMenu: + for itemIndex in ResistanceChat.getItems(menuIndex): + textId = ResistanceChat.encodeId(menuIndex, itemIndex) + msgs.append([textId, charges]) + av.b_setResistanceMessages(msgs) + response = 'Resistance phrases restocked - %d charges' % charges + self.down_setMagicWordResponse(senderId, response) + + elif wordIs("~restockSummons"): + numSuits = len(SuitDNA.suitHeadTypes) + fullSetForSuit = 0x01 | 0x02 | 0x04 + allSummons = numSuits * [fullSetForSuit] + av.b_setCogSummonsEarned(allSummons) + self.down_setMagicWordResponse(senderId, "Summons restocked") + + elif wordIs("~clearSummons"): + numSuits = len(SuitDNA.suitHeadTypes) + allSummons = numSuits * [0] + av.b_setCogSummonsEarned(allSummons) + self.down_setMagicWordResponse(senderId, "Summons emptied") + + elif wordIs("~newSummons"): + args = word.split() + + level = None + if len(args) > 1: + level = int(args[1]) + + if level and (level < 0 or level >= SuitDNA.suitsPerDept): + self.down_setMagicWordResponse( + senderId, "usage: ~newSummons [level:0-11]") + else: + (suitIndex, type) = av.assignNewCogSummons(level) + suitName = SuitDNA.suitHeadTypes[suitIndex] + suitFullName = SuitBattleGlobals.SuitAttributes[suitName]['name'] + self.down_setMagicWordResponse( + senderId, "%s summons added for %s" % (type, suitFullName)) + + elif wordIs("~treasures"): + self.doTreasures(word, av, zoneId, senderId) + + elif wordIs("~emote"): + self.doEmotes(word, av, zoneId, senderId) + + elif wordIs("~catalog"): + self.doCatalog(word, av, zoneId, senderId) + + elif word == "~resetFurniture": + house = None + if av.houseId: + house = self.air.doId2do.get(av.houseId) + if house: + house.setInitialFurniture() + house.resetFurniture() + response = "Furniture reset." + else: + response = "Could not find house." + self.down_setMagicWordResponse(senderId, response) + + # Fishing + elif word == "~clearFishTank": + av.b_setFishTank([], [], []) + self.down_setMagicWordResponse(senderId, "Cleared fish tank.") + + elif word == "~sellFishTank": + # Add up the total value of all the fish + totalValue = 0 + totalValue = av.fishTank.getTotalValue() + # Credit the money + av.addMoney(totalValue) + # Clear the fish tank + av.b_setFishTank([], [], []) + # Feedback to sender + self.down_setMagicWordResponse(senderId, ("Sold fish in tank for %s jellbeans." % totalValue)) + + elif word == "~clearFishCollection": + av.b_setFishCollection([], [], []) + av.b_setFishingTrophies([]) + self.down_setMagicWordResponse(senderId, "Cleared fish collection.") + + elif word == "~completeFishCollection": + from toontown.fishing import FishGlobals + genusList = [] + speciesList = [] + weightList = [] + for genus in FishGlobals.getGenera(): + numSpecies = len(FishGlobals.getSpecies(genus)) + for species in range(numSpecies): + weight = FishGlobals.getRandomWeight(genus, species) + genusList.append(genus) + speciesList.append(species) + weightList.append(weight) + + av.b_setFishCollection(genusList, speciesList, weightList) + self.down_setMagicWordResponse(senderId, "Complete fish collection.") + + elif word == "~randomFishTank": + av.makeRandomFishTank() + self.down_setMagicWordResponse(senderId, "Created random fishtank") + + elif word == "~allFishingTrophies": + from toontown.fishing import FishGlobals + allTrophyList = FishGlobals.TrophyDict.keys() + av.b_setFishingTrophies(allTrophyList) + self.down_setMagicWordResponse(senderId, "All fishing trophies") + + elif word[:4] == "~rod": + from toontown.fishing import FishGlobals + # Sets reward tier and optionally index + args = word.split() + rodId = int(args[1]) + if ((rodId > FishGlobals.MaxRodId) or + (rodId < 0)): + self.down_setMagicWordResponse(senderId, ("Invalid rod: %s" % (rodId))) + else: + av.b_setFishingRod(rodId) + self.down_setMagicWordResponse(senderId, ("New fishing rod: %s" % (rodId))) + + + elif wordIs('~inGameEdit'): + # edit a level in the game engine + # Note - do not call this as a toon - call ~edit instead. That + # will automatically append your level doId so the AI knows + # which one you want to edit, along with your edit username + + # make sure this AI is set up for editing + if not __dev__: + self.down_setMagicWordResponse(senderId, + "AI not running in dev mode") + return + from otp.level import EditorGlobals + msg = EditorGlobals.checkNotReadyToEdit() + if msg is not None: + self.down_setMagicWordResponse(senderId, msg) + return + + args = word.split() + levelDoId = int(args[1]) + editUsername = args[2] + + level = self.air.doId2do.get(levelDoId) + if not level: + self.down_setMagicWordResponse( + senderId, ("Level %s not found" % levelDoId)) + return + + from toontown.coghq import DistributedInGameEditorAI + editor = DistributedInGameEditorAI.DistributedInGameEditorAI( + self.air, level, senderId, editUsername) + + self.down_setMagicWordResponse( + senderId, ("Editing level %s as %s" % ( + levelDoId, editUsername))) + + elif word[:13] == "~setNPCFriend": + args = word.split() + npcId = int(args[1]) + numCalls = int(args[2]) + if self.doNpcFriend(av, npcId, numCalls): + self.down_setMagicWordResponse(senderId, "added NPC friend") + else: + self.down_setMagicWordResponse(senderId, "invalid NPC name") + + elif wordIs("~pianos"): + if self.doNpcFriend(av, 1116, 100): + self.down_setMagicWordResponse(senderId, "got pianos") + else: + self.down_setMagicWordResponse(senderId, "error getting pianos") + + elif wordIs("~resetNPCFriendsDict"): + av.resetNPCFriendsDict() + self.down_setMagicWordResponse(senderId, "Reset NPC Friends Dict") + + elif wordIs('~uberDrop'): + from toontown.toon import NPCToons + if (av.attemptAddNPCFriend( + 1116, DistributedToonAI.DistributedToonAI.maxCallsPerNPC + ) == 1): + self.down_setMagicWordResponse(senderId, "added NPC friend") + else: + self.down_setMagicWordResponse(senderId, "failed") + + elif wordIs("~bossBattle"): + self.doBossBattle(word, av, zoneId, senderId) + + elif wordIs('~disguisePage'): + args = word.split() + flag = 1 + if len(args) >= 2: + flag = int(args[1]) + av.b_setDisguisePageFlag(flag) + self.down_setMagicWordResponse(senderId, "Disguise page = %s" % (flag)) + + elif wordIs("~allParts"): + from toontown.coghq import CogDisguiseGlobals + args = word.split() + for dept in self.getDepts(args): + parts = av.getCogParts() + parts[dept] = CogDisguiseGlobals.PartsPerSuitBitmasks[dept] + + av.b_setCogParts(parts) + self.down_setMagicWordResponse(senderId, "Set cog parts: %s" % (parts)) + + elif wordIs("~noParts"): + args = word.split() + for dept in self.getDepts(args): + parts = av.getCogParts() + parts[dept] = 0 + + av.b_setCogParts(parts) + self.down_setMagicWordResponse(senderId, "Set cog parts: %s" % (parts)) + + elif wordIs("~part"): + args = word.split() + depts = self.getDepts(args) + if len(args) > 1: + # trust that user typed the factory type correctly... + factoryType = args[1] + else: + factoryType = ToontownGlobals.FT_FullSuit + + for dept in depts: + av.giveGenericCogPart(factoryType, dept) + + self.down_setMagicWordResponse(senderId, "Set cog parts: %s" % (av.getCogParts())) + + elif wordIs("~merits"): + args = word.split() + depts = self.getDepts(args) + if len(args) > 1: + numMerits = int(args[1]) + if numMerits > 32767: + numMerits = 32767 + else: + self.down_setMagicWordResponse(senderId, "Specify number of merits to set.") + return + + merits = av.getCogMerits()[:] + for dept in depts: + merits[dept] = numMerits + av.b_setCogMerits(merits) + + self.down_setMagicWordResponse(senderId, "Set cog merits: %s" % (merits)) + + elif wordIs("~promote"): + args = word.split() + depts = self.getDepts(args) + + for dept in depts: + av.b_promote(dept) + + self.down_setMagicWordResponse(senderId, "Set cogTypes: %s and cogLevels: %s" % (av.getCogTypes(), av.getCogLevels())) + + elif wordIs("~cogSuit"): + args = word.split() + if len(args) > 1: + cogType = args[1] + else: + self.down_setMagicWordResponse(senderId, "Specify cog type, or 'clear'.") + return + + if cogType == 'clear': + av.b_setCogTypes([0, 0, 0, 0]) + av.b_setCogLevels([0, 0, 0, 0]) + + elif cogType == 'on': + if len(args) > 2 and args[2] in SuitDNA.suitDepts: + dept = SuitDNA.suitDepts.index(args[2]) + av.b_setCogIndex(dept) + else: + self.down_setMagicWordResponse(senderId, "Specify dept.") + return + + elif cogType == 'off': + av.b_setCogIndex(-1) + return + + else: + dept = SuitDNA.getSuitDept(cogType) + if dept == None: + self.down_setMagicWordResponse(senderId, "Unknown cog type: %s" % (cogType)) + return + + deptIndex = SuitDNA.suitDepts.index(dept) + type = SuitDNA.getSuitType(cogType) + minLevel = SuitBattleGlobals.SuitAttributes[cogType]['level'] + + # determine max level (usually minLevel + 4, but 50 for last cog) + if type >= (SuitDNA.suitsPerDept - 1): + maxLevel = ToontownGlobals.MaxCogSuitLevel + 1 + else: + maxLevel = minLevel + 4 + + if len(args) > 2: + level = int(args[2]) - 1 + if level < minLevel or level > maxLevel: + self.down_setMagicWordResponse(senderId, "Invalid level for %s (should be %s to %s)" % (cogType, minLevel + 1, minLevel + 5)) + return + else: + level = minLevel + + cogTypes = av.getCogTypes()[:] + cogLevels = av.getCogLevels()[:] + cogTypes[deptIndex] = type - 1 + cogLevels[deptIndex] = level + av.b_setCogTypes(cogTypes) + av.b_setCogLevels(cogLevels) + + self.down_setMagicWordResponse(senderId, "Set cogTypes: %s and cogLevels: %s" % (av.getCogTypes(), av.getCogLevels())) + + elif wordIs("~pinkSlips"): + args = word.split() + depts = self.getDepts(args) + if len(args) > 1: + numSlips = int(args[1]) + if numSlips > 255: + numSlips = 255 + else: + self.down_setMagicWordResponse(senderId, "Specify number of pinkSlips to set.") + return + + av.b_setPinkSlips(numSlips) + + self.down_setMagicWordResponse(senderId, "Set PinkSlips: %s" % (numSlips)) + + + elif wordIs("~setPaid"): + args = word.split() + depts = self.getDepts(args) + if len(args) > 1: + paid = int(args[1]) + if paid: + paid = 1 + av.b_setAccess(OTPGlobals.AccessFull) + else: + paid = 0 + av.b_setAccess(OTPGlobals.AccessVelvetRope) + else: + self.down_setMagicWordResponse(senderId, "0 for unpaid 1 for paid") + return + + self.down_setMagicWordResponse(senderId, "setPaid: %s" % (paid)) + + + + elif wordIs("~holiday"): + # Start or stop a holiday + holiday = 5 + fStart = 1 + args = word.split() + if len(args) == 1: + self.down_setMagicWordResponse( + senderId, "Usage:\n~holiday id\n~holiday id start\n~holiday id end\n~holiday list") + return + elif len(args) > 1: + if args[1] == 'list': + self.down_setMagicWordResponse( + senderId, + "1: July 4\n2: New Years\n3: Halloween\n4: Winter Decorations\n5: Skelecog Invades\n6: Mr. Holly Invades\n7: Fish Bingo\n8: Species Election\n9: Black Cat\n10: Resistance Event\n11: Reset Daily Recs\n12: Reset Weekly Recs\n13: Trick-or-Treat\n14: Grand Prix\n17: Trolley Metagame") + return + else: + holiday = int(args[1]) + doPhase = None + stopForever = False + if len(args) > 2: + if args[2] == 'start': + fStart = 1 + elif args[2] == 'end': + fStart = 0 + if len(args) > 3: + if args[3] == 'forever': + stopForever = True + elif args[2] == 'phase': + if len(args) >3 : + doPhase = args[3] + else: + self.down_setMagicWordResponce(senderId,"need a number after phase") + else: + self.down_setMagicWordResponse( + senderId, 'Arg 2 should be "start" or "end" or "end forever" or "phase"') + return + if doPhase: + result = self.air.holidayManager.forcePhase(holiday, doPhase) + self.down_setMagicWordResponse(senderId, "succeeded=%s forcing holiday %d to phase %s" %(result,holiday,doPhase)) + elif fStart: + self.down_setMagicWordResponse( + senderId, "Starting holiday %d" % holiday) + self.air.holidayManager.startHoliday(holiday) + else: + self.down_setMagicWordResponse( + senderId, "Ending holiday %d stopForever=%s" % (holiday, stopForever)) + self.air.holidayManager.endHoliday(holiday, stopForever) + + elif wordIs('~pet') and simbase.wantPets: + def summonPet(petId, callback=None, zoneId=zoneId): + def handleGetPet(success, pet, petId=petId, zoneId=zoneId): + if success: + pet.generateWithRequiredAndId(petId, self.air.districtId, zoneId) + if callback is not None: + callback(success, pet) + self.air.petMgr.getPetObject(petId, handleGetPet) + + petId = av.getPetId() + if petId == 0: + def handleCreate(success, petId, zoneId=zoneId): + if success: + self.air.petMgr.assignPetToToon(petId, av.doId) + def handlePetGenerated(success, pet, avId=av.doId, + zoneId=zoneId): + if success: + pet._initDBVals( + avId, + traitSeed=PythonUtil.randUint31()) + pet.sendSetZone(zoneId) + pet.delete() + # since this is the first time the pet is being + # created, and we're going to be setting properties + # on the pet, generate it in the Quiet zone first, + # then move it to the requested zone. + summonPet(petId, callback=handlePetGenerated, + zoneId=ToontownGlobals.QuietZone) + self.air.petMgr.createNewPetObject(handleCreate) + response = 'creating new pet...' + else: + if petId in self.air.doId2do: + response = 'pet %s already active' % petId + else: + summonPet(petId) + response = 'summoning pet %s...' % petId + + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~dismiss') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + pet.requestDelete() + response = "pet %s dismissed" % pet.doId + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~deletePet') and simbase.wantPets: + petId = av.getPetId() + if petId == 0: + response = "don't have a pet" + else: + # if the pet is active, dismiss it + pet = self.air.doId2do.get(petId) + if pet is not None: + pet.requestDelete() + self.air.petMgr.deleteToonsPet(av.doId) + response = "deleted pet %s" % petId + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~petName') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + args = word.split() + if len(args) < 2: + response = "~petName name" + else: + pet.b_setPetName(args[1]) + response = 'name changed' + self.down_setMagicWordResponse(senderId, response) + + + elif wordIs('~feed') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + pet.feed(av) + response = "fed pet" + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~attend') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + pet.scratch(av) + response = "attended pet" + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~callPet') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + pet.call(av) + response = "called pet" + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~stay') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + pet.handleStay(av) + response = "Stay, %s." % pet.getPetName() + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~shoo') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + pet.handleShoo(av) + response = "Shoo, %s." % pet.getPetName() + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~maxMood') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + pet.maxMood() + response = "pet mood maxed" + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~minMood') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + pet.minMood() + response = "pet mood minimized" + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~petMood') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + args = word.split() + if len(args) < 3: + response = '~petMood component value' + else: + from toontown.pets import PetMood + comp = args[1] + value = float(args[2]) + if comp not in PetMood.PetMood.Components: + response = "unknown mood '%s'" % comp + else: + pet.mood.setComponent(comp, value) + response = "mood set" + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~moodTimescale') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + args = word.split() + if len(args) < 2: + response = '~moodTimescale timescale' + else: + simbase.petMoodTimescale = float(args[1]) + # make the new timescale take effect immediately + pet.mood._driftMoodTask() + response = 'pet mood timescale = %s' % args[1] + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~typicalTraits') and simbase.wantPets: + szId = None + args = word.split() + if len(args) > 1: + szId = self.Str2szId.get(args[1]) + if szId == None: + szId = ToontownGlobals.ToontownCentral + + pet, response = self.getPet(av) + if pet: + pet._setTypicalTraits(szId) + # delete him to make sure we don't have a pet whose + # internal state is out-of-sync + pet.requestDelete() + response = 'set typical traits for %s' % szId + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~medianTraits') and simbase.wantPets: + szId = None + args = word.split() + if len(args) > 1: + szId = self.Str2szId.get(args[1]) + if szId == None: + szId = ToontownGlobals.ToontownCentral + + pet, response = self.getPet(av) + if pet: + pet._setMedianTraits(szId) + # delete him to make sure we don't have a pet whose + # internal state is out-of-sync + pet.requestDelete() + response = 'set median traits for %s' % szId + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~lowTraits') and simbase.wantPets: + szId = None + args = word.split() + if len(args) > 1: + szId = self.Str2szId.get(args[1]) + if szId == None: + szId = ToontownGlobals.ToontownCentral + + pet, response = self.getPet(av) + if pet: + pet._setLowTraits(szId) + # delete him to make sure we don't have a pet whose + # internal state is out-of-sync + pet.requestDelete() + response = 'set low traits for %s' % szId + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~highTraits') and simbase.wantPets: + szId = None + args = word.split() + if len(args) > 1: + szId = self.Str2szId.get(args[1]) + if szId == None: + szId = ToontownGlobals.ToontownCentral + + pet, response = self.getPet(av) + if pet: + pet._setHighTraits(szId) + # delete him to make sure we don't have a pet whose + # internal state is out-of-sync + pet.requestDelete() + response = 'set high traits for %s' % szId + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~leash') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + response = pet.toggleLeash(av.doId) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~lockPet') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + if pet.isLockedDown(): + response = 'pet already locked down' + else: + pet.lockPet() + response = 'pet locked down' + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~unlockPet') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + if not pet.isLockedDown(): + response = 'pet already not locked down' + else: + pet.unlockPet() + response = 'pet no longer locked down' + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~attendPet') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + response = 'attendPet' + pet.handleAvPetInteraction(4, av.getDoId()) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~feedPet') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + response = 'feedPet' + pet.handleAvPetInteraction(3, av.getDoId()) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~petStress') and simbase.wantPets: + from toontown.pets import DistributedPetAI + args = word.split() + numPets = 56 # 50 friends, six Toons/acct #100 * 3 # 500 toons, 100 at estate, 3 pets per + if len(args) > 1: + numPets = int(args[1]) + for i in xrange(numPets): + pet = DistributedPetAI.DistributedPetAI(self.air) + # make up a fake owner doId + pet._initFakePet(100+i, 'StressPet%s' % i) + pet.generateWithRequired(zoneId) + pet.setPos(randFloat(-30, 30), + randFloat(-30, 30), 0) + pet.b_setParent(ToontownGlobals.SPRender) + + elif wordIs('~petTricks') and simbase.wantPets: + from toontown.pets import PetConstants, PetTricks + args = word.split() + trickIds = [] + invalid = 0 + for num in args[1:]: + id = int(num) + if id not in PetTricks.Tricks: + invalid = 1 + response = 'invalid trick ID: %s' % id + break + trickIds.append(id) + if not invalid: + av.b_setPetTrickPhrases(trickIds) + response = 'set pet trick phrases: %s' % trickIds + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~trickAptitude') and simbase.wantPets: + pet, response = self.getPet(av) + if pet: + args = word.split() + if len(args) < 2: + response = '~trickAptitude aptitude' + else: + from toontown.pets import PetTricks + pet.b_setTrickAptitudes([float(args[1])] * + (len(PetTricks.Tricks)-1)) + response = 'trick aptitude = %s' % args[1] + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~spamTrick'): + # spam the nearby pets with N commands to do the 'jump' trick + from toontown.pets import PetObserve + for i in xrange(20): + PetObserve.send(av.zoneId, + PetObserve.getSCObserve(21200, av.doId)) + + elif wordIs('~blackCat'): + response = 'done' + error = av.makeBlackCat() + if error: + response = error + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~bingoCheat') and simbase.wantBingo: + av.bingoCheat = not av.bingoCheat + if av.bingoCheat: + response = "Bingo cheating turned on. You will always catch boots." + else: + response = "Bingo cheating turned off" + + self.down_setMagicWordResponse(senderId, response) + + elif __dev__ and wordIs('~airender'): + showZone = zoneId + args = word.split() + if len(args) > 1: + showZone = int(args[1]) + from otp.ai import ShowBaseAI + base = ShowBaseAI.ShowBaseAI('zone %s' % showZone) + base.zoneData = AIZoneData(self.air.districtId, showZone) + render = base.zoneData.getRender() + base.camNode.setScene(render) + base.camera.reparentTo(render) + render.showCS() + axis = loader.loadModel('models/misc/xyzAxis') + axis.reparentTo(render) + axis.setPosHpr(render, 0, 0, 0, 0, 0, 0) + self.down_setMagicWordResponse( + senderId, 'rendering AI zone %s' % showZone) + + elif __dev__ and wordIs("~aics"): + showZone = zoneId + args = word.split() + if len(args) > 1: + showZone = int(args[1]) + zoneData = AIZoneData(self.air.districtId, showZone) + render = zoneData.getRender(showZone) + + csVisible = 0 + if hasattr(render, 'csVisible'): + csVisible = render.csVisible + + if csVisible: + render.hideCS() + action = 'hidden' + else: + render.showCS() + action = 'shown' + + render.csVisible = not csVisible + self.down_setMagicWordResponse( + senderId, 'collisions %s for %s' % (action, showZone)) + + ### Karting ### + elif(word[:8] == '~BuyKart'): + if (simbase.wantKarts): + accList = av.getKartAccessoriesOwned() + #accList[-1] = getDefaultColor() + accList[-2] = getDefaultRim() + + response = "Av %s now owns accessories: %s" % (av.getDoId(), accList) + av.b_setKartAccessoriesOwned(accList) + self.down_setMagicWordResponse(senderId, response) + + elif(word[:13] == '~BuyAccessory'): + if (simbase.wantKarts): + response = "Av %s attempting to buy accessory %s" % (av.getDoId(), word[14:]) + av.addOwnedAccessory(int(word[14:])) + self.down_setMagicWordResponse(senderId, response) + + elif(word[:8] == '~Tickets'): + if (simbase.wantKarts): + response = "Av %s now has %s tickets" % (av.getDoId(), word[9:]) + av.b_setTickets(int(word[9:])) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~allowSoloRace'): + if (simbase.wantKarts): + av.setAllowSoloRace(not av.allowSoloRace) + if av.allowSoloRace: + response = "Av %s can now race solo" % (av.getDoId()) + else: + response = "Av %s can no longer Race solo" % (av.getDoId()) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~allowRaceTimeout'): + if (simbase.wantKarts): + av.setAllowRaceTimeout(not av.allowRaceTimeout) + if av.allowRaceTimeout: + response = "Av %s can timeout of races" % (av.getDoId()) + else: + response = "Av %s can not timeout of races" % (av.getDoId()) + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~drive'): + # Execute an arbitrary Python command on the AI. + av.takeOutKart() + self.down_setMagicWordResponse( + senderId, "I feel the need... the need for SPEED!") + + # Golf + elif wordIs("~golf"): + self.doGolf(word, av, zoneId, senderId) + + # Mail + elif wordIs("~mail"): + self.doMail(word, av, zoneId, senderId) + + # Parties + elif wordIs("~party"): + self.doParty(word, av, zoneId, senderId) + + #Gardening + elif wordIs("~garden"): + self.doGarden(word, av, zoneId, senderId) + + elif word == "~clearGardenSpecials": + newWord = '~garden clearCarriedSpecials' + self.doGarden(newWord, av, zoneId, senderId) + + elif word == "~clearFlowerCollection": + newWord = '~garden clearCollection' + self.doGarden(newWord, av, zoneId, senderId) + + elif word == "~completeFlowerCollection": + newWord = '~garden completeCollection' + self.doGarden(newWord, av, zoneId, senderId) + + elif wordIs('~startGarden'): + newWord = '~garden start' + self.doGarden(newWord, av, zoneId, senderId) + + elif wordIs('~clearGarden'): + newWord = '~garden clear' + self.doGarden(newWord, av, zoneId, senderId) + + elif wordIs('~cannons'): + sender = simbase.air.doId2do.get(senderId) + if sender: + zoneId = sender.zoneId + estateOwnerDoId = simbase.air.estateMgr.zone2owner.get(zoneId) + estate = simbase.air.estateMgr.estate.get(estateOwnerDoId) + + if hasattr(estate, "cannonFlag"): + if estate.cannonFlag: + estate.endCannons() + response = "Cannons Ended" + else: + estate.startCannons() + response = "Cannons Started" + + elif wordIs('~gameTable'): + sender = simbase.air.doId2do.get(senderId) + if sender: + zoneId = sender.zoneId + estateOwnerDoId = simbase.air.estateMgr.zone2owner.get(zoneId) + estate = simbase.air.estateMgr.estate.get(estateOwnerDoId) + + if hasattr(estate, 'gameTableFlag'): + if estate.gameTableFlag: + estate.endGameTable() + response = 'Game Table Ended' + else: + estate.startGameTable() + response = 'Game Table Started' + + elif wordIs('~plantGarden'): + newWord = '~garden plant' + args = word.split() + for i in range(1, len(args)): + newWord += ' ' + newWord += args[i] + self.doGarden(newWord, av, zoneId, senderId) + + elif wordIs('~trialerCount'): + total = 0 + trialer =0 + paid = 0 + unknown = 0 + allToons = simbase.air.doFindAll('DistributedToonAI') + for toon in allToons: + total += 1 + if toon.getGameAccess() == ToontownGlobals.AccessFull: + paid += 1 + elif toon.getGameAccess() == ToontownGlobals.AccessVelvetRope: + trialer += 1 + else: + unknown += 1 + response = "%d trialers, %d paid, %d total" % (trialer, paid, total) + if unknown: + response += "%d unknown" % unknown + self.down_setMagicWordResponse(senderId, response) + + elif wordIs('~deleteBackupStores'): + storeClient = DataStoreAIClient(self.air, DataStoreGlobals.GEN, None) + storeClient.deleteBackupStores() + storeClient.closeStore() + + elif wordIs("~autoRich"): + # Available only __dev__ and GMs. Guard against hacked clients sending this. If a non-GM + # needs to do this on LIVE, simply use ~rich instead. + if __dev__ or av.hasGMName(): + # Basically this is a signal that a GM has logged in. + if av.hasGMName(): + self.air.writeServerEvent('GM', av.doId, 'GM %s used auto-rich' % av.getName()) + assert self.notify.debug('GM %s %s used auto-rich' % (av.doId, av.getName())) + + args = word.split() + postName = 'autoRich-%s' % av.doId + enable = not bboard.get(postName, 0) + # are they explicitly setting the state? + if len(args) > 1: + try: + enable = int(args[1]) + except: + self.down_setMagicWordResponse(senderId, + 'invalid state flag: %s' % + args[1]) + return + if enable: + state = 'ON' + av.b_setMoney(av.maxMoney) + av.b_setBankMoney(av.maxBankMoney) + bboard.post(postName, 1) + else: + state = 'OFF' + bboard.remove(postName) + self.down_setMagicWordResponse(senderId, 'autoRich %s' % state) + else: + self.air.writeServerEvent('suspicious', av.doId, 'non-GM toon with name %s using ~autoRich' % av.getName()) + + else: + # The word is not an AI-side magic word. If the sender is + # different than the target avatar, then pass the magic + # word down to the target client-side MagicWordManager to + # execute a client-side magic word. + if (senderId != av.doId): + self.sendUpdateToAvatarId(av.doId, 'setMagicWord', [word, av.doId, zoneId]) + + def doNpcFriend(self, av, npcId, numCalls): + return av.attemptAddNPCFriend(npcId, numCalls) == 1 + + def getDepts(self, args): + # Returns a list of the dept indices specified by args[1], or + # all depts if nothing is specified. If a dept is specified, + # args[1] is removed from the list. + + depts = [] + if len(args) > 1: + if args[1] == 'all': + depts = [0, 1, 2, 3] + del args[1] + else: + allLettersGood = 1 + for letter in args[1]: + if letter in SuitDNA.suitDepts: + dept = SuitDNA.suitDepts.index(letter) + depts.append(dept) + else: + allLettersGood = 0 + break + + if allLettersGood: + del args[1] + else: + depts = [] + + if depts: + return depts + else: + return [0, 1, 2, 3] + + + def doExp(self, word, av, zoneId, senderId): + """Handle the ~exp magic word.""" + track = None + args=word.split() + trackIndex = -1 + increment = 0 + gotIncrement = 0 + + if len(args) > 1: + trackStr = args[1] + if trackStr != "all": + trackIndex = ToontownBattleGlobals.Tracks.index(trackStr) + + if len(args) > 2: + increment = int(args[2]) + gotIncrement = 1 + + if trackIndex == -1: + for trackIndex in range(ToontownBattleGlobals.MAX_TRACK_INDEX + 1): + if av.hasTrackAccess(trackIndex): + if not gotIncrement: + # No increment specified; the default is whatever + # it takes to get to the next track. + increment = av.experience.getNextExpValue(trackIndex) - av.experience.getExp(trackIndex) + + self.notify.debug("Adding %d to %s track for %s." % (increment, ToontownBattleGlobals.Tracks[trackIndex], av.name)) + av.experience.addExp(trackIndex, increment) + else: + if not gotIncrement: + # No increment specified; the default is whatever + # it takes to get to the next track. + increment = av.experience.getNextExpValue(trackIndex) - av.experience.getExp(trackIndex) + + self.notify.debug("Adding %d to %s track for %s." % (increment, ToontownBattleGlobals.Tracks[trackIndex], av.name)) + av.experience.addExp(trackIndex, increment) + + av.d_setExperience(av.experience.makeNetString()) + + + def doTrophy(self, word, av, zoneId, senderId): + """Handle the ~trophy magic word: artificially set (or + restore) the trophy score.""" + + args = word.split() + if len(args) > 1: + score = int(args[1]) + + response = "Set trophy score to %s." % (score) + self.down_setMagicWordResponse(senderId, response) + else: + # No score specified; restore the actual trophy score. + score = self.air.trophyMgr.getTrophyScore(av.doId) + + response = "Trophy score is %s." % (score) + self.down_setMagicWordResponse(senderId, response) + + av.d_setTrophyScore(score) + + + def doCheesyEffect(self, word, av, zoneId, senderId): + effect = None + if zoneId >= ToontownGlobals.DynamicZonesBegin: + hoodId = 1 + else: + hoodId = ZoneUtil.getCanonicalHoodId(zoneId) + timeLimit = 10 + + args = word.split() + try: + effect = eval("ToontownGlobals.CE" + args[1]) + except: + try: + effect = eval(args[1]) + except: + effect = None + + if effect == None: + self.down_setMagicWordResponse(senderId, "Unknown effect %s." % (args[1])) + return + + if len(args) > 2: + timeLimit = int(args[2]) + + # Let it expire in timeLimit minutes. + expireTime = (int)(time.time() / 60 + 0.5) + timeLimit + av.b_setCheesyEffect(effect, hoodId, expireTime) + + def doCogTakeOver(self, word, av, zoneId, senderId): + """Handle the ~cogTakeOver magic word.""" + track = None + level = None + streetId = ZoneUtil.getBranchZone(zoneId) + args = word.split() + + now = args.count("now") + if now: + args.remove("now") + slow = args.count("slow") + if slow: + args.remove("slow") + + if len(args)>1: + track=args[1] + if track == 'x': + track = None + + if len(args)>2: + if args[2] != 'x': + level=int(args[2]) + level = max(min(level, 9), 1) + + if len(args)>4: + if args[4] == 'all': + streetId = 'all' + else: + streetId=int(args[4]) + + if not self.air.suitPlanners.has_key(streetId): + response = "Street %d is not known." % (streetId) + self.down_setMagicWordResponse(senderId, response) + return + + if streetId == 'all': + # All buildings, everywhere. + blockMap = {} + for sp in self.air.suitPlanners.values(): + if sp.buildingMgr: + blockMap[sp.buildingMgr] = sp.buildingMgr.getToonBlocks() + + else: + # Just the buildings on this street. + sp = self.air.suitPlanners[streetId] + bm = sp.buildingMgr + + blocks = None + if len(args)>3: + if (args[3] == "all"): + blocks = bm.getToonBlocks() + elif (args[3] == "this"): + blocks = None + else: + blocks=[int(args[3])] + + if blocks == None: + # Try to figure out what doors we're standing in front + # of. + blocks = [] + for i in bm.getToonBlocks(): + building = bm.getBuilding(i) + if hasattr(building, "door"): + if building.door.zoneId == zoneId: + blocks.append(i) + + blockMap = { bm: blocks } + + points = sp.streetPointList[:] + failureCount = 0 + minPathLen = 2 + maxPathLen = 10 + if slow: + minPathLen = None + maxPathLen = None + + total = 0 + for bm, blocks in blockMap.items(): + total += len(blocks) + for i in blocks: + if now: + # Take over the building immediately. + level, type, track = \ + sp.pickLevelTypeAndTrack(level, None, track) + building = bm.getBuilding(i) + building.suitTakeOver(track, level - 1, None) + + else: + # Dispatch a suit to take over the building. + if not slow: + # Let the suit take its time. + points = sp.getStreetPointsForBuilding(i) + + suit = None + retryCount = 0 + while suit == None and len(points) > 0 and retryCount < 20: + suit = sp.createNewSuit([], points, + suitTrack = track, + suitLevel = level, + toonBlockTakeover = i, + minPathLen = minPathLen, + maxPathLen = maxPathLen) + retryCount += 1 + if suit == None: + failureCount += 1 + + self.notify.debug("cogTakeOver %s %s %d %d" % + (track, level, i, zoneId)) + + response = "%d buildings." % (total) + if (failureCount > 0): + response += " %d failed." % (failureCount) + + self.down_setMagicWordResponse(senderId, response) + + def doCogdoTakeOver(self, word, av, zoneId, senderId): + """Handle the ~cogdoTakeOver magic word.""" + level = None + streetId = ZoneUtil.getBranchZone(zoneId) + args = word.split() + + now = args.count("now") + if now: + args.remove("now") + slow = args.count("slow") + if slow: + args.remove("slow") + + if len(args)>1: + if args[1] != 'x': + level=int(args[2]) + level = max(min(level, 9), 1) + + if len(args)>4: + if args[4] == 'all': + streetId = 'all' + else: + streetId=int(args[4]) + + if not self.air.suitPlanners.has_key(streetId): + response = "Street %d is not known." % (streetId) + self.down_setMagicWordResponse(senderId, response) + return + + if streetId == 'all': + # All buildings, everywhere. + blockMap = {} + for sp in self.air.suitPlanners.values(): + if sp.buildingMgr: + blockMap[sp.buildingMgr] = sp.buildingMgr.getToonBlocks() + + else: + # Just the buildings on this street. + sp = self.air.suitPlanners[streetId] + bm = sp.buildingMgr + + blocks = None + if len(args)>3: + if (args[3] == "all"): + blocks = bm.getToonBlocks() + elif (args[3] == "this"): + blocks = None + else: + blocks=[int(args[3])] + + if blocks == None: + # Try to figure out what doors we're standing in front + # of. + blocks = [] + for i in bm.getToonBlocks(): + building = bm.getBuilding(i) + if hasattr(building, "door"): + if building.door.zoneId == zoneId: + blocks.append(i) + + blockMap = { bm: blocks } + + points = sp.streetPointList[:] + failureCount = 0 + minPathLen = 2 + maxPathLen = 10 + if slow: + minPathLen = None + maxPathLen = None + + total = 0 + for bm, blocks in blockMap.items(): + total += len(blocks) + for i in blocks: + if now: + # Take over the building immediately. + level, type, track = \ + sp.pickLevelTypeAndTrack(level, None, track) + building = bm.getBuilding(i) + building.cogdoTakeOver(level - 1, None) + + else: + # Dispatch a suit to take over the building. + if not slow: + # Let the suit take its time. + points = sp.getStreetPointsForBuilding(i) + + suit = None + retryCount = 0 + while suit == None and len(points) > 0 and retryCount < 20: + track = random.choice(SuitDNA.suitDepts) + suit = sp.createNewSuit([], points, + suitTrack = track, + suitLevel = level, + toonBlockTakeover = i, + cogdoTakeover = True, + minPathLen = minPathLen, + maxPathLen = maxPathLen) + retryCount += 1 + if suit == None: + failureCount += 1 + + self.notify.debug("cogdoTakeOver %s %s %d %d" % + (track, level, i, zoneId)) + + response = "%d cogdos." % (total) + if (failureCount > 0): + response += " %d failed." % (failureCount) + + self.down_setMagicWordResponse(senderId, response) + + def doToonTakeOver(self, word, av, zoneId, senderId): + """Handle the ~toonTakeOver magic word.""" + streetId = ZoneUtil.getBranchZone(zoneId) + args=word.split() + if len(args)>2: + if args[2] == 'all': + streetId = 'all' + else: + streetId=int(args[2]) + + if not self.air.suitPlanners.has_key(streetId): + response = "Street %d is not known." % (streetId) + self.down_setMagicWordResponse(senderId, response) + return + + if streetId == 'all': + # All buildings, everywhere. + blockMap = {} + for sp in self.air.suitPlanners.values(): + if sp.buildingMgr: + blockMap[sp.buildingMgr] = sp.buildingMgr.getSuitBlocks() + + else: + # Just the buildings on this street. + bm=self.air.buildingManagers[streetId] + blocks = None + if len(args)>1: + if (args[1] == "all"): + blocks = bm.getSuitBlocks() + elif (args[1] == "this"): + blocks = None + else: + blocks=[int(args[1])] + + if blocks == None: + # Try to figure out what doors we're standing in front + # of. + blocks = [] + for i in bm.getSuitBlocks(): + building = bm.getBuilding(i) + if hasattr(building, "elevator"): + if building.elevator.zoneId == zoneId and \ + building.elevator.fsm.getCurrentState().getName() == 'waitEmpty': + blocks.append(i) + blockMap = { bm: blocks } + + total = 0 + for bm, blocks in blockMap.items(): + total += len(blocks) + for i in blocks: + building = bm.getBuilding(i) + building.b_setVictorList([0, 0, 0, 0]) + building.updateSavedBy([(av.doId, av.name, av.dna.asTuple())]) + building.toonTakeOver() + self.notify.debug("Toon take over %s %s" % (i, streetId)) + + response = "%d buildings." % (total) + self.down_setMagicWordResponse(senderId, response) + + def formatWelcomeValley(self, hood): + mgr = self.air.welcomeValleyManager + + if hood == mgr.newHood: + return "%s N" % (hood[0].zoneId) + elif hood in mgr.removingHoods: + return "%s R" % (hood[0].zoneId) + else: + return "%s" % (hood[0].zoneId) + + def doWelcome(self, word, av, zoneId, senderId): + """Handle the ~welcome magic word, for managing Welcome Valley.""" + args = word.split() + + hoodId = ZoneUtil.getHoodId(zoneId) + mgr = self.air.welcomeValleyManager + + # if this is a GS hoodId, just grab the TT hood + if (hoodId % 2000) < 1000: + hood = mgr.welcomeValleys.get(hoodId) + else: + hood = mgr.welcomeValleys.get(hoodId - 1000) + + if len(args) == 1: + # No parameter: report the current zone and population. + if hood == None: + response = "Not in Welcome Valley." + else: + response = "%s, pg = %s, hood = %s" % ( + self.formatWelcomeValley(hood), + hood[0].getPgPopulation(), + hood[0].getHoodPopulation()) + + elif args[1] == "pg": + # ~welcome pg: list the playground population of all + # Welcome Valleys. + + response = "" + hoodIds = mgr.welcomeValleys.keys() + hoodIds.sort() + for hoodId in hoodIds: + hood = mgr.welcomeValleys[hoodId] + response += "\n%s %s" % ( + self.formatWelcomeValley(hood), + hood[0].getPgPopulation()) + + if response == "": + response = "No Welcome Valleys." + else: + response = response[1:] + + elif args[1] == "hood": + # ~welcome hood: list the hood population of all + # Welcome Valleys. + + response = "" + hoodIds = mgr.welcomeValleys.keys() + hoodIds.sort() + for hoodId in hoodIds: + hood = mgr.welcomeValleys[hoodId] + response += "\n%s %s" % ( + self.formatWelcomeValley(hood), + hood[0].getHoodPopulation()) + + if response == "": + response = "No Welcome Valleys." + else: + response = response[1:] + + elif args[1] == "login": + # ~welcome login avId: simulate a new player logging in. + # avId is an arbitrary number which should probably not + # match any real avatars. + avId = int(args[2]) + + zoneId = mgr.avatarRequestZone(avId, ToontownGlobals.WelcomeValleyToken) + response = "%s assigned to %s." % (avId, zoneId) + + elif args[1] == "logout": + # ~welcome logout avId: simulate a player logging out. + # avId is an arbitrary number which should probably not + # match any real avatars. + avId = int(args[2]) + + avZoneId = mgr.avatarZones.get(avId) + if avZoneId: + mgr.avatarLogout(avId) + response = "%s logged out from %s." % (avId, avZoneId) + else: + response = "%s is unknown." % (avId) + + elif args[1] == "zone": + # ~welcome zone avId [zoneId]: simulate a player switching + # zones. avId is an arbitrary number which should + # probably not match any real avatars. + avId = int(args[2]) + if len(args) > 3: + newZoneId = int(args[3]) + else: + newZoneId = zoneId + + zoneId = mgr.avatarRequestZone(avId, newZoneId) + response = "%s redirected to %s." % (avId, zoneId) + + elif args[1] == "new": + # ~welcome new [hoodId]: immediately move the indicated + # (or current) hood to the New pool. + if len(args) > 2: + hoodId = int(args[2]) + else: + hoodId = zoneId + response = mgr.makeNew(hoodId) + + elif args[1] == "stable": + # ~welcome stable [hoodId]: immediately move the indicated + # (or current) hood to the Stable pool. + if len(args) > 2: + hoodId = int(args[2]) + else: + hoodId = zoneId + response = mgr.makeStable(hoodId) + + elif args[1] == "remove": + # ~welcome remove [hoodId]: immediately move the indicated + # (or current) hood to the Removing pool. + if len(args) > 2: + hoodId = int(args[2]) + else: + hoodId = zoneId + response = mgr.makeRemoving(hoodId) + + elif args[1] == "check": + # ~welcome check: check that our record logged-in avatars + # are actually real avatars; logs out any others. + removed = mgr.checkAvatars() + if removed: + response = "Logged out %s phony avatars." % (len(removed)) + else: + response = "All avatars real." + + elif args[1] == "parms": + # ~welcome parms [min stable max]: report or adjust the + # balancing parameters. + if len(args) == 5: + PGminimum = int(args[2]) + PGstable = int(args[3]) + PGmaximum = int(args[4]) + WelcomeValleyManagerAI.PGminimum = PGminimum + WelcomeValleyManagerAI.PGstable = PGstable + WelcomeValleyManagerAI.PGmaximum = PGmaximum + + response = "min = %s, stable = %s, max = %s" % ( + WelcomeValleyManagerAI.PGminimum, + WelcomeValleyManagerAI.PGstable, + WelcomeValleyManagerAI.PGmaximum) + + else: + response = "Invalid welcome command: %s" % (args[1]) + + self.down_setMagicWordResponse(senderId, response) + + def __sortBuildingDist(self, a, b): + return a[0] - b[0] + + def doBuildings(self, word, av, zoneId, senderId): + """Handle the ~buildings magic word.""" + streetId = ZoneUtil.getBranchZone(zoneId) + + if word == "~buildings where": + # "~buildings where": report the distribution of buildings. + dist = {} + for sp in self.air.suitPlanners.values(): + if sp.buildingMgr: + numActual = len(sp.buildingMgr.getSuitBlocks()) + if not dist.has_key(numActual): + dist[numActual] = [] + dist[numActual].append(sp.zoneId) + + # Sort the distribution by number of buildings. + sorted = [] + for tuple in dist.items(): + sorted.append(tuple) + sorted.sort(self.__sortBuildingDist) + + # Now format the distribution into a text response. + response = "" + for numActual, zones in sorted: + if numActual != 0: + response += "\n%s: %d" % (zones, numActual) + + if response == "": + response = "No cog buildings." + else: + response = response[1:] + + else: + # "~buildings zoneId" or "~buildings all" + + args=word.split() + if len(args) > 1: + if args[1] == "all": + streetId = "all" + else: + streetId = int(args[1]) + + if streetId == "all": + numTarget = 0 + numActual = 0 + numTotalBuildings = 0 + numAttempting = 0 + numPerTrack = {} + numPerHeight = {} + for sp in self.air.suitPlanners.values(): + numTarget += sp.targetNumSuitBuildings + if sp.buildingMgr: + numActual += len(sp.buildingMgr.getSuitBlocks()) + numTotalBuildings += len(sp.frontdoorPointList) + numAttempting += sp.numAttemptingTakeover + sp.countNumBuildingsPerTrack(numPerTrack) + sp.countNumBuildingsPerHeight(numPerHeight) + + response = "Overall, %d cog buildings (%s, %s) out of %d; target is %d. %d cogs are attempting takeover." % ( + numActual, sp.formatNumSuitsPerTrack(numPerTrack), + sp.formatNumSuitsPerTrack(numPerHeight), + numTotalBuildings, numTarget, numAttempting) + + elif not self.air.suitPlanners.has_key(streetId): + response = "Street %d is not known." % (streetId) + + else: + sp = self.air.suitPlanners[streetId] + + numTarget = sp.targetNumSuitBuildings + if sp.buildingMgr: + numActual = len(sp.buildingMgr.getSuitBlocks()) + else: + numActual = 0 + numTotalBuildings = len(sp.frontdoorPointList) + numAttempting = sp.numAttemptingTakeover + numPerTrack = {} + numPerHeight = {} + sp.countNumBuildingsPerTrack(numPerTrack) + sp.countNumBuildingsPerHeight(numPerHeight) + + response = "Street %d has %d cog buildings (%s, %s) out of %d; target is %d. %d cogs are attempting takeover." % ( + streetId, numActual, + sp.formatNumSuitsPerTrack(numPerTrack), + sp.formatNumSuitsPerTrack(numPerHeight), + numTotalBuildings, numTarget, numAttempting) + + self.down_setMagicWordResponse(senderId, response) + + def doBuildingPercent(self, word, av, zoneId, senderId): + """Handle the ~buildingPercent magic word.""" + percent = None + + args=word.split() + if len(args) > 1: + percent = int(args[1]) + + if percent == None: + response = "Suit building target percentage is %d" % (DistributedSuitPlannerAI.DistributedSuitPlannerAI.TOTAL_SUIT_BUILDING_PCT) + else: + DistributedSuitPlannerAI.DistributedSuitPlannerAI.TOTAL_SUIT_BUILDING_PCT = percent + sp = self.air.suitPlanners.values()[0] + sp.assignInitialSuitBuildings() + response = "Reset target percentage to %d" % (percent) + + self.down_setMagicWordResponse(senderId, response) + + + def doCall(self, word, av, zoneId, senderId): + """Handle the ~call magic word.""" + streetId = ZoneUtil.getBranchZone(zoneId) + + args=word.split() + name = None + level = None + skelecog = None + revives = None + + if len(args) > 1: + name = args[1] + if name == 'x': + name = None + + if len(args) > 2: + level = int(args[2]) + + if len(args) > 3: + skelecog = int(args[3]) + + if len(args) > 4: + revives = int(args[4]) + + if not self.air.suitPlanners.has_key(streetId): + response = "Street %d is not known." % (streetId) + + else: + sp = self.air.suitPlanners[streetId] + map = sp.getZoneIdToPointMap() + canonicalZoneId = ZoneUtil.getCanonicalZoneId(zoneId) + if not map.has_key(canonicalZoneId): + response = "Zone %d isn't near a suit point." % (canonicalZoneId) + else: + points = map[canonicalZoneId][:] + suit = sp.createNewSuit([], points, + suitName = name, + suitLevel = level, + skelecog = skelecog, + revives = revives) + if suit: + response = "Here comes %s." % (SuitBattleGlobals.SuitAttributes[suit.dna.name]['name']) + else: + response = "Could not create suit." + + self.down_setMagicWordResponse(senderId, response) + + + def doBattle(self, word, av, zoneId, senderId): + """Handle the ~battle magic word.""" + + # There's no easy way to find the battle that the caller is + # near or involved in. We'll have to walk through all the + # battles currently in the doId2do map. + battle = self.__findInvolvedBattle(av.doId) + if battle == None: + battle = self.__findBattleInZone(zoneId) + + if battle == None: + response = "No battle in zone %s." % (zoneId) + else: + args = word.split() + if len(args) < 2: + response = "Battle %s in state %s" % (battle.doId, battle.fsm.getCurrentState().getName()) + + elif args[1] == 'abort': + battle.abortBattle() + response = "Battle %s aborted." % (battle.doId) + + else: + response = "Uknown battle command %s" % (args[1]) + + self.down_setMagicWordResponse(senderId, response) + + def __findInvolvedBattle(self, avId): + """Returns the battle, if any, in which the indicated avatar + is a participant, or None if the avatar is not involved in any + battles.""" + + for dobj in self.air.doId2do.values(): + if (isinstance(dobj, DistributedBattleBaseAI.DistributedBattleBaseAI)): + if avId in dobj.toons: + return dobj + + def __findBattleInZone(self, zoneId): + """Returns the battle, if any, in the indicated zoneId, or + None if no battle is occurring in the indicated zone.""" + + for dobj in self.air.doId2do.values(): + if (isinstance(dobj, DistributedBattleBaseAI.DistributedBattleBaseAI)): + if dobj.zoneId == zoneId: + return dobj + + + + + def doCogInvasion(self, word, av, zoneId, senderId): + """Handle the ~invasion magic word.""" + + invMgr = self.air.suitInvasionManager + + if invMgr.getInvading(): + cogType = invMgr.getCogType() + numRemaining = invMgr.getNumCogsRemaining() + cogName = SuitBattleGlobals.SuitAttributes[cogType[0]]['name'] + response = ("Invasion already in progress: %s, %s" % (cogName, numRemaining)) + else: + args=word.split() + if len(args) < 3 or len(args) > 4: + response = "Error: Must specify cogType and numCogs" + else: + cogType = args[1] + numCogs = int(args[2]) + if len(args) == 4: + skeleton = int(args[3]) + else: + skeleton = 0 + cogNameDict = SuitBattleGlobals.SuitAttributes.get(cogType) + if cogNameDict: + cogName = cogNameDict['name'] + if skeleton: + cogName = TTLocalizer.Skeleton + " " + cogName + if invMgr.startInvasion(cogType, numCogs, skeleton): + response = ("Invasion started: %s, %s" % (cogName, numCogs)) + else: + response = ("Invasion failed: %s, %s" % (cogName, numCogs)) + else: + response = ("Unknown cogType: %s" % (cogType)) + + self.down_setMagicWordResponse(senderId, response) + + def stopCogInvasion(self, word, av, zoneId, senderId): + invMgr = self.air.suitInvasionManager + + if invMgr.getInvading(): + self.air.suitInvasionManager.stopInvasion() + response = ("Invasion stopped.") + else: + response = ("No invasion found.") + + self.down_setMagicWordResponse(senderId, response) + + def getCogInvasion(self, word, av, zoneId, senderId): + invMgr = self.air.suitInvasionManager + + if invMgr.getInvading(): + cogType, skeleton = invMgr.getCogType() + numRemaining = invMgr.getNumCogsRemaining() + cogName = SuitBattleGlobals.SuitAttributes[cogType]['name'] + if skeleton: + cogName = TTLocalizer.Skeleton + " " + cogName + response = ("Invasion is in progress: %s, %s remaining" % (cogName, numRemaining)) + else: + response = ("No invasion found.") + + self.down_setMagicWordResponse(senderId, response) + + def startAllFireworks(self, word, av, zoneId, senderId): + fMgr = self.air.fireworkManager + fMgr.stopAllShows() + fMgr.startAllShows(None) + response = "Shows started in all hoods." + self.down_setMagicWordResponse(senderId, response) + + def startFireworks(self, word, av, zoneId, senderId): + fMgr = self.air.fireworkManager + if fMgr.isShowRunning(zoneId): + response = ("Show already running in zone: %s" % (zoneId)) + else: + args=word.split() + if len(args) == 2: + showType = int(args[1]) + if fMgr.startShow(zoneId, showType, 1): + response = ("Show started, showType: %s" % showType) + else: + response = ("Show failed, showType: %s" % showType) + else: + # Default to showType 0 + response = (TTLocalizer.startFireworksResponse \ + %( ToontownGlobals.NEWYEARS_FIREWORKS, \ + PartyGlobals.FireworkShows.Summer, \ + ToontownGlobals.JULY4_FIREWORKS)) + self.down_setMagicWordResponse(senderId, response) + + def stopFireworks(self, word, av, zoneId, senderId): + if self.air.fireworkManager.stopShow(zoneId): + response = ("Show stopped, zoneId: %s" % zoneId) + else: + response = ("Show stop failed, zoneId: %s" % zoneId) + self.down_setMagicWordResponse(senderId, response) + + def stopAllFireworks(self, word, av, zoneId, senderId): + numStopped = self.air.fireworkManager.stopAllShows() + response = ("Stopped %s firework show(s)" % (numStopped)) + self.down_setMagicWordResponse(senderId, response) + + def doCogs(self, word, av, zoneId, senderId): + """Handle the ~cogs magic word.""" + streetId = ZoneUtil.getBranchZone(zoneId) + + args=word.split() + firstKeyword = 1 + + sync = 0 + fly = 0 + count = -1 + + if len(args) > 1: + if args[1] == "all": + streetId = "all" + firstKeyword = 2 + else: + try: + streetId = int(args[1]) + firstKeyword = 2 + except: + pass + + # Check for extra keywords. + for k in range(firstKeyword, len(args)): + word = args[k] + if word == "sync": + sync = 1 + elif word == "fly": + fly = 1 + elif word == "count=x": + count = None + elif word[:6] == "count=": + count = int(word[6:]) + else: + self.down_setMagicWordResponse(senderId, "invalid keyword %s" % (word)) + return + + if streetId == "all": + numTarget = 0 + numActual = 0 + numPerTrack = {} + sp = None + for sp in self.air.suitPlanners.values(): + numTarget += sp.calcDesiredNumFlyInSuits() + sp.calcDesiredNumBuildingSuits() + numActual += sp.numFlyInSuits + sp.numBuildingSuits + sp.countNumSuitsPerTrack(numPerTrack) + if sync: + sp.resyncSuits() + if fly: + sp.flySuits() + if count != -1: + sp.currDesired = count + + if sp == None: + response = "No cogs active." + else: + response = "Overall, %d cogs (%s); target is %d." % ( + numActual, sp.formatNumSuitsPerTrack(numPerTrack), numTarget) + + elif not self.air.suitPlanners.has_key(streetId): + response = "Street %d is not known." % (streetId) + + else: + sp = self.air.suitPlanners[streetId] + + numTarget = sp.calcDesiredNumFlyInSuits() + sp.calcDesiredNumBuildingSuits() + numActual = sp.numFlyInSuits + sp.numBuildingSuits + numPerTrack = {} + sp.countNumSuitsPerTrack(numPerTrack) + + if sync: + sp.resyncSuits() + if fly: + sp.flySuits() + if count != -1: + sp.currDesired = count + response = "Street %d has %d cogs (%s); target is %d." % ( + streetId, numActual, sp.formatNumSuitsPerTrack(numPerTrack), + numTarget) + + self.down_setMagicWordResponse(senderId, response) + + + def doMinigame(self, word, av, zoneId, senderId): + """Handle the ~minigame magic word: request a particular minigame.""" + args = word.split() + if len(args) == 1: + # No minigame parameter specified: clear the request. + mgRequest = MinigameCreatorAI.RequestMinigame.get(av.doId) + if mgRequest != None: + mgId = mgRequest[0] + del MinigameCreatorAI.RequestMinigame[av.doId] + response = "Request for minigame %d cleared." % (mgId) + else: + response = "Usage: ~minigame [ [difficulty] [safezone]]" + else: + # Try to determine the minigame id, keep flag, and the difficulty + # and safezone overrides, if any + name = args[1] + mgId = None + mgKeep = 0 + mgDiff = None + mgSzId = None + + try: + mgId = int(name) + numMgs = len(ToontownGlobals.MinigameIDs) + if mgId < 1 or mgId > numMgs or mgId not in ToontownGlobals.MinigameIDs: + response = "minigame ID '%s' is out of range" % mgId + mgId = None + except: + name = string.lower(name) + if name[-4:] == "game": + name = name[:-4] + if name[:11] == "distributed": + name = name[11:] + mgId = ToontownGlobals.MinigameNames.get(name) + if mgId == None: + response = "Unknown minigame '%s'." % (name) + + argIndex = 2 + while argIndex < len(args): + arg = args[argIndex] + arg = string.lower(arg) + argIndex += 1 + + # it's either a difficulty (float), 'keep', + # or a safezone (string) + + # is it 'keep'? + if arg == 'keep': + mgKeep = 1 + continue + + # is it a difficulty? + try: + mgDiff = float(arg) + continue + except: + pass + + mgSzId = self.Str2szId.get(arg) + if mgSzId is not None: + continue + + # it's a string, but it's not a safezone. + response = ("unknown safezone '%s'; use " + "tt, dd, dg, mm, br, dl" % arg) + mgId = None + break + + if mgId != None: + # mdId must be the first element + MinigameCreatorAI.RequestMinigame[av.doId] = ( + mgId, mgKeep, mgDiff, mgSzId) + response = "Selected minigame %d" % mgId + if mgDiff is not None: + response += ", difficulty %s" % mgDiff + if mgSzId is not None: + response += ", safezone %s" % mgSzId + if mgKeep: + response += ", keep=true" + + self.down_setMagicWordResponse(senderId, response) + + def doTreasures(self, word, av, zoneId, senderId): + """Handle the ~treasures magic word: fill up the safezone with + treasures.""" + args = word.split() + + hood = None + for h in self.air.hoods: + if h.zoneId == zoneId: + hood = h + break + + if hood == None or hood.treasurePlanner == None: + self.down_setMagicWordResponse(senderId, "Not in a safezone.") + return + + tp = hood.treasurePlanner + + if len(args) == 1: + # No parameter: report the current treasure count. + response = "%s treasures." % (tp.numTreasures()) + + elif args[1] == "all": + # ~treasures all: fill up all available treasures. + tp.placeAllTreasures() + response = "now %s treasures." % (tp.numTreasures()) + + else: + response = "Invalid treasures command: %s" % (args[1]) + + self.down_setMagicWordResponse(senderId, response) + + + def doEmotes(self, word, av, zoneId, senderId): + """Handle the ~emote magic word: turns on/off the specified emotion""" + args = word.split() + if len(args) == 1: + # No parameter specified: clear the request. + response = "No emote specified." + elif len(args) == 2: + # No parameter specified: clear the request. + response = "Need to specify 0 or 1." + else: + emoteId = int(args[1]) + on = int(args[2]) + if emoteId > len(av.emoteAccess) or emoteId < 0: + response = "Not a valid emote" + elif on not in [0, 1]: + response = "Not a valid bit" + else: + av.setEmoteAccessId(emoteId, on) + if on: + response = "Emote %d enabled" % emoteId + else: + response = "Emote %d disabled" % emoteId + self.down_setMagicWordResponse(senderId, response) + + + def doCatalog(self, word, av, zoneId, senderId): + """Handle the ~catalog magic word: manage catalogs""" + now = time.time() + args = word.split() + + # There may be an optional "after" parameter on many of these + # commands, which specifies the number of minutes to delay + # before doing the action. + afterMinutes = 0 + if "after" in args: + a = args.index("after") + afterMinutes = int(args[a + 1]) + del args[a + 1] + del args[a] + + if len(args) == 1: + # No parameter: report the current catalog. + duration = (av.catalogScheduleNextTime * 60) - time.time() + response = "Week %d, next catalog in %s." % \ + (av.catalogScheduleCurrentWeek, + PythonUtil.formatElapsedSeconds(duration)) + + elif args[1] == "next": + # ~catalog next: advance to the next catalog. + week = av.catalogScheduleCurrentWeek + 1 + self.air.catalogManager.forceCatalog(av, week, afterMinutes) + response = "Issued catalog for week %s." % (week) + + elif args[1] == "week": + # ~catalog week n: force to the catalog of the nth week. + # Note: need to have catalog-skip-seeks set to true to jump + # more than one week + week = int(args[2]) + if week > 0: + self.air.catalogManager.forceCatalog(av, week, afterMinutes) + response = "Forced to catalog week %s." % (week) + else: + response = "Invalid catalog week %s." % (week) + + elif args[1] == "season": + # ~catalog season mm/dd: regenerate the monthly catalog + # items as if it were the indicated month and day. + if len(args) == 3: + mmdd = args[2].split('/') + mm = int(mmdd[0]) + dd = int(mmdd[1]) + else: + mm = int(args[2]) + dd = int(args[3]) + + self.air.catalogManager.forceMonthlyCatalog(av, mm, dd) + response = "%s items for %d/%0d." % (len(av.monthlyCatalog), mm, dd) + + elif (args[1] == "clear") or (args[1] == "reset"): + # ~catalog clear: reset the catalog (and the back catalog) + # to its initial state. + av.b_setCatalog(CatalogItemList.CatalogItemList(), + CatalogItemList.CatalogItemList(), + CatalogItemList.CatalogItemList()) + av.catalogScheduleCurrentWeek = 0 + av.catalogScheduleNextTime = 0 + self.air.catalogManager.deliverCatalogFor(av) + response = "Catalog reset." + + elif args[1] == "deliver": + # ~catalog deliver: force the immediate delivery of all + # of the on-order item(s). + + now = (int)(time.time() / 60 + 0.5) + deliveryTime = now + afterMinutes + + for item in av.onOrder: + item.deliveryDate = deliveryTime + av.onOrder.markDirty() + av.b_setDeliverySchedule(av.onOrder) + + response = "Delivered %s item(s)." % (len(av.onOrder)) + + elif args[1] in ["reload", "dump"]: + # These commands are handled by the client; we ignore them. + return + + else: + response = "Invalid catalog command: %s" % (args[1]) + + self.down_setMagicWordResponse(senderId, response) + + def doDna(self, word, av, zoneId, senderId): + """Handle the ~dna magic word: change your dna""" + + # Strip of the "~dna" part; everything else is parameters to + # AvatarDNA.updateToonProperties. + parms = string.strip(word[4:]) + + # Get a copy of the avatar's current DNA. + dna = ToonDNA.ToonDNA(av.dna.makeNetString()) + + # Modify it according to the user's parameter selection. + eval("dna.updateToonProperties(%s)" % (parms)) + + av.b_setDNAString(dna.makeNetString()) + response = "%s" % (dna.asTuple(),) + + self.down_setMagicWordResponse(senderId, response) + + def getPet(self, av): + response = None + pet = None + + petId = av.getPetId() + if petId == 0: + response = "don't have a pet" + else: + pet = self.air.doId2do.get(petId) + if pet is None: + response = "pet not active, use ~pet" + return pet, response + + def doBossBattle(self, word, av, zoneId, senderId): + """Handle the ~bossBattle magic word: manage a final boss + battle.""" + + args = word.split() + + # Find the particular Boss Cog that's in the same zone with + # the avatar. + bossCog = None + for distObj in self.air.doId2do.values(): + if isinstance(distObj, DistributedBossCogAI.DistributedBossCogAI): + if distObj.zoneId == zoneId: + bossCog = distObj + break + + if bossCog == None: + # The caller isn't in a zone with a Boss Cog; use the + # default Boss zone. + + # In this case, we must accept a dept indicator. + if len(args) < 2 or args[1] not in SuitDNA.suitDepts: + response = "Error: Must specify boss dept: s, m, l, c" + self.down_setMagicWordResponse(senderId, response) + return + + dept = args[1] + del args[1] + deptIndex = SuitDNA.suitDepts.index(dept) + + if self.__bossBattleZoneId[deptIndex] == None: + # Make up a new zone for the battle. + zoneId = self.air.allocateZone() + self.__bossBattleZoneId[deptIndex] = zoneId + + bossCog = self.makeBossCog(dept, zoneId) + bossCog.b_setState('Frolic') + self.__bossCog[deptIndex] = bossCog + + else: + bossCog = self.__bossCog[deptIndex] + + response = None + if len(args) == 1: + # No parameter: send the avatar to the battle zone. + self.sendUpdateToAvatarId(av.doId, 'requestTeleport', + ["cogHQLoader", "cogHQBossBattle", + bossCog.getHoodId(), + bossCog.zoneId, 0]) + + elif args[1] == "list": + # ~bossBattle [smlc] list: list the current boss battles + # underway for the indicated type of suit. + response = "" + for bc in DistributedBossCogAI.AllBossCogs: + if bc.dept == bossCog.dept and bc != bossCog: + response += ", %s %s %s" % (bc.zoneId, bc.state, len(bc.involvedToons)) + if response: + response = response[2:] + else: + response = "No boss battles." + + elif args[1] == "spy": + # ~bossBattle spy [smlc] : go visit the indicated + # boss battle in ghost mode. + zoneId = int(args[2]) + bc = bossCog + for bc in DistributedBossCogAI.AllBossCogs: + if bc.zoneId == zoneId: + break + if bc.zoneId == zoneId: + self.sendUpdateToAvatarId(av.doId, 'requestTeleport', + ["cogHQLoader", "cogHQBossBattle", + bc.getHoodId(), + bc.zoneId, 0]) + av.b_setGhostMode(2) + else: + response = "No boss battle in zone %s" % (zoneId) + + + elif args[1] == "start": + # ~bossBattle start: restart the boss battle. + if not bossCog.hasToons(): + response = "No toons." + else: + response = "Elevator started." + bossCog.acceptNewToons() + bossCog.b_setState('WaitForToons') + + elif args[1] == "one": + # ~bossBattle one: straight to battle one. + if not bossCog.hasToons(): + response = "No toons." + else: + response = "Battle one started." + bossCog.acceptNewToons() + bossCog.makeBattleOneBattles() + bossCog.b_setState('BattleOne') + + elif args[1] == "preTwo": + # ~bossBattle preTwo: preview to battle two. + if not bossCog.hasToons(): + response = "No toons." + elif not hasattr(bossCog, 'enterRollToBattleTwo'): + response = "Battle two preview." + bossCog.acceptNewToons() + bossCog.b_setState('PrepareBattleTwo') + else: + response = "Battle two preview." + bossCog.acceptNewToons() + bossCog.b_setState('RollToBattleTwo') + + elif args[1] == "two": + # ~bossBattle two: straight to battle two. + if not bossCog.hasToons(): + response = "No toons." + else: + response = "Battle two started." + bossCog.acceptNewToons() + if hasattr(bossCog, 'makeBattleTwoBattles'): + bossCog.makeBattleTwoBattles() + bossCog.b_setState('BattleTwo') + + elif args[1] == "preThree": + # ~bossBattle preThree: preview to battle three. + if not bossCog.hasToons(): + response = "No toons." + else: + response = "Battle three preview." + bossCog.acceptNewToons() + bossCog.b_setState('PrepareBattleThree') + + elif args[1] == "three": + # ~bossBattle three: straight to battle three. + if not bossCog.hasToons(): + response = "No toons." + else: + response = "Battle three started." + bossCog.acceptNewToons() + bossCog.b_setState('BattleThree') + + elif args[1] == "preFour": + # ~bossBattle preFour: preview to battle four. + if not bossCog.hasToons(): + response = "No toons." + else: + response = "Battle four preview." + bossCog.acceptNewToons() + bossCog.b_setState('PrepareBattleFour') + + elif args[1] == "four": + # ~bossBattle four: straight to battle four. + if not bossCog.hasToons(): + response = "No toons." + else: + response = "Battle four started." + bossCog.acceptNewToons() + bossCog.b_setState('BattleFour') + + elif args[1] == "victory": + # ~bossBattle victory: straight to the victory sequence. + if not bossCog.hasToons(): + response = "No toons." + else: + response = "Victory sequence started." + bossCog.acceptNewToons() + bossCog.b_setState('Victory') + + elif args[1] == "fsm": + # ~bossBattle fsm state: directly to named state. + if len(args) <= 2: + response = "No state specified." + else: + response = "Requested state %s." % (args[2]) + bossCog.b_setState(args[2]) + + elif args[1] == "stop": + # ~bossBattle start: stop the boss battle. + response = "Battle stopped." + bossCog.acceptNewToons() + bossCog.b_setState('Frolic') + + elif args[1] == "hit": + # ~bossBattle hit [damage]: hit the boss cog during the + # pie scene. This will make him dizzy first if he is not + # already dizzy (as if we successfully lobbed a pie into + # the undercarriage) and then applies the indicated damage + # (as if we hit his upper body the indicated number of + # times). + + if len(args) <= 2: + damage = 1 + else: + damage = int(args[2]) + + ## + ## this is very suspect .. it is reasigning a internal var(msgSender).. + ## it is trying to make the message look like it came from a difrent avatarID + ## to other areas of the code .. ??? + self.air.msgSender = av.doId + bossCog.magicWordHit(damage, av.doId) + + elif args[1] == "safe": + # ~bossBattle safe: Ignore hits to the toon during the pie + # scene. This magic word is handled by the client. + pass + + elif args[1] == "avatarEnter": + # ~bossBattle avatarEnter: Force a call to d_avatarEnter + #This magic word is handled by the client. + pass + + elif args[1] == "stun": + # ~bossBattle stun: stun all of the goons in the CFO + # battle. This magic word is handled by the client. + pass + + elif args[1] == "destroy": + # ~bossBattle destroy: destroy all of the goons in the CFO + # battle. This magic word is handled by the client. + pass + + elif args[1] == "reset": + # ~bossBattle reset: reset all of the goons, cranes, and + # safes in the CFO sequence. + bossCog.magicWordReset() + + elif args[1] == "goons": + # ~bossBattle goons [num]: reset all of the goons and set + # the number to the indicated value. + if len(args) > 2: + bossCog.maxGoons = int(args[2]) + + bossCog.magicWordResetGoons() + elif args[1] == "toggleMove": + # make the ceo toggle doing move attacks + if hasattr(bossCog, 'toggleMove'): + doingMoveAttack = bossCog.toggleMove() + response = "doing move attack = %s" % doingMoveAttack + else: + response = "toggleMove is only for CEO" + else: + response = "Invalid bossBattle command: %s" % (args[1]) + + if response: + self.down_setMagicWordResponse(senderId, response) + + def makeBossCog(self, dept, zoneId): + bossCog = None + + if dept == 's': + bossCog = DistributedSellbotBossAI.DistributedSellbotBossAI(self.air) + bossCog.generateWithRequired(zoneId) + bossCog.b_setState('Frolic') + + elif dept == 'm': + bossCog = DistributedCashbotBossAI.DistributedCashbotBossAI(self.air) + bossCog.generateWithRequired(zoneId) + + elif dept == 'l': + bossCog = DistributedLawbotBossAI.DistributedLawbotBossAI(self.air) + bossCog.generateWithRequired(zoneId) + bossCog.b_setState('Frolic') + elif dept == 'c': + bossCog = DistributedBossbotBossAI.DistributedBossbotBossAI(self.air) + bossCog.generateWithRequired(zoneId) + bossCog.b_setState('Frolic') + + else: + raise StandardError('Not implemented: boss cog %s' % (dept)) + + return bossCog + + + def doGarden(self, word, av, zoneId, senderId): + """Handle the ~garden magic worde.""" + + args = word.split() + + response = None + action = None + if len(args) == 1: + # No parameter: change it to usage + self.down_setMagicWordResponse( + senderId, + "Usage:\n~garden action \n'~garden help' for more info ") + return + + action = args[1] + if action == 'help': + response = 'start\nclear\nwilt \nunwilt \nplant \nclearCarriedSpecials\nclearCollection\ncompleteCollection\nwater \nshovel <0-3> \nrandomBasket\nnuke\nepoch \nwateringCan <0-3> ' + elif action == 'start': + messenger.send("gardenTest", [senderId]) + response = "Test Garden Planted" + elif action == 'clear': + messenger.send("gardenClear", [senderId]) + response = "Garden Cleared" + elif action == 'nuke': + #clear the garden and remove the planters + #clear the garden started flag + messenger.send("gardenNuke", [senderId]) + av.b_setGardenStarted(False) + response = "Garden Nuked" + elif action == 'specials': + #messenger.send("gardenSpecials", [senderId]) + receiver = self.air.doId2do.get(senderId) + if receiver: + receiver.giveMeSpecials() + response = "Garden Specials Added" + elif action == 'wilt': + if len(args) >= 3: + messenger.send("wiltGarden", [senderId, int(args[2])] ) + else: + messenger.send("wiltGarden", [senderId]) + response = "Garden wilted" + elif action == 'unwilt': + if len(args) >= 3: + messenger.send("unwiltGarden", [senderId, int(args[2])] ) + else: + messenger.send("unwiltGarden", [senderId]) + response = "Garden unwilted" + elif action == 'water': + waterLevel = 1 + if len(args) > 2: + waterLevel = int(args[2]) + specificHardPoint = -1 + if len(args) > 3: + specificHardPoint = int(args[3]) + + messenger.send("waterGarden", [senderId, waterLevel, specificHardPoint] ) + + response = "water level changed to %d" % waterLevel + elif action == 'growth': + waterLevel = 1 + if len(args) > 2: + waterLevel = int(args[2]) + specificHardPoint = -1 + if len(args) > 3: + specificHardPoint = int(args[3]) + + messenger.send("growthGarden", [senderId, waterLevel, specificHardPoint] ) + + response = "growth level changed to %d" % waterLevel + elif action == 'plant': + type = 0 + plot = 0 + water = 0 + growth = 1 + variety = 0 + if len(args) > 2: + type = int(args[2]) + if len(args) > 3: + plot = int(args[3]) + if len(args) > 4: + water = int(args[4]) + if len(args) > 5: + growth = int(args[5]) + if len(args) > 6: + variety = int(args[6]) + + response = "Planting type=%d plot=%d water=%d growth=%d" % (type,plot,water,growth) + messenger.send("gardenPlant", [senderId, type, plot, water, growth, variety]) + elif action == 'clearCarriedSpecials': + av.b_setGardenSpecials( []) + response = "Cleared garden specials carried by toon" + elif action == 'clearCollection': + av.b_setFlowerCollection( [], []) + response = "Cleared flower collection." + elif action == 'completeCollection': + #from toontown.estate import GardenGlobals + varietyList = [] + speciesList = [] + flowerSpecies = GardenGlobals.getFlowerSpecies() + for species in flowerSpecies: + numVarieties = len(GardenGlobals.getFlowerVarieties(species)) + for variety in range(numVarieties): + speciesList.append(species) + varietyList.append(variety) + + av.b_setFlowerCollection( speciesList, varietyList) + av.b_setGardenTrophies([]) + response = "Complete flower collection." + elif action == 'epoch': + numEpoch = 1 + if len(args) > 2: + numEpoch = int(args[2]) + messenger.send("epochGarden", [senderId, numEpoch]) + response = "%d garden epoch has been run" % numEpoch + elif action == 'shovel': + if len(args) < 3: + response = "specify a shovel (0-3)" + else: + shovel = 0 + passedShovel = int(args[2]) + if passedShovel >= 0 and \ + passedShovel < GardenGlobals.MAX_SHOVELS: + shovel = passedShovel + skill =0 + if len(args) >= 4: + passedSkill = int(args[3]) + skill = min (passedSkill, GardenGlobals.ShovelAttributes[shovel]['skillPts'] - 1) + skill = max (skill, 0) + + av.b_setShovel(shovel) + av.b_setShovelSkill(skill) + response = "Set shovel=%d shovelSkill=%d" % (shovel,skill) + elif action == 'wateringCan': + if len(args) < 3: + response = "specify a watering can (0-3)" + else: + wateringCan = 0 + passedWateringCan = int(args[2]) + if passedWateringCan >= 0 and \ + passedWateringCan < GardenGlobals.MAX_WATERING_CANS: + wateringCan = passedWateringCan + skill =0 + if len(args) >= 4: + passedSkill = int(args[3]) + skill = min (passedSkill, GardenGlobals.WateringCanAttributes[wateringCan]['skillPts'] - 1) + skill = max (skill, 0) + + av.b_setWateringCan(wateringCan) + av.b_setWateringCanSkill(skill) + response = "Set wateringCan=%d wateringCanSkill=%d" % (wateringCan,skill) + elif action == 'randomBasket': + av.makeRandomFlowerBasket() + self.down_setMagicWordResponse(senderId, "Created random flower basket") + elif action == "allTrophies": + allTrophyList = GardenGlobals.TrophyDict.keys() + av.b_setGardenTrophies(allTrophyList) + self.down_setMagicWordResponse(senderId, "All garden trophies") + else: + response = 'Invalid garden command.' + if response: + self.down_setMagicWordResponse(senderId, response) + + def doGolf(self, word, av, zoneId, senderId): + """Handle the ~golf magic words.""" + args = word.split() + response = None + action = None + + if len(args) == 1: + # No parameter: change it to usage + self.down_setMagicWordResponse( + senderId, + "Usage:\n~golf action \n'~golf help' for more info ") + return + + action = args[1] + if action == 'help': + response = 'endHole\nendcourse\ntest\nclearHistory\nMaxHistory' + elif action == 'drive': + course = GolfManagerAI.GolfManagerAI().findGolfCourse(senderId) + response = "drive failed." + if course: + result = course.toggleDrivePermission(senderId) + if result: + response = "Press up, down, left&right simultaneously to drive." + else: + response = "Toon is no longer driving." + elif action == 'endhole' or action == 'endHole': + course = GolfManagerAI.GolfManagerAI().findGolfCourse(senderId) + if course: + holeId, holeDoId = course.abortCurrentHole() + response = "Aborting holeId %d, doId=%d" % (holeId, holeDoId) + else: + response = "Couldn't find golf course" + elif action == 'endcourse' or action == 'endCourse': + course = GolfManagerAI.GolfManagerAI().findGolfCourse(senderId) + if course: + course.setCourseAbort() + response = "Aborting course %d" % course.doId + else: + response = "Couldn't find golf course" + elif action == 'test': + #messenger.send("gardenTest", [senderId]) + response = "golf test" + avList = [senderId]; + args = word.split() + import string + for i in range(2, len(args)): + avList.append(string.atoi(args[i])) + manager = GolfManagerAI.GolfManagerAI() + #simbase.golfGoer.generateWithRequired(OTPGlobals.UberZone) + courseId = 0 + zoneId = manager.readyGolfCourse(avList, courseId) + for avId in avList: + golfer = simbase.air.doId2do.get(avId) + if golfer: + golfer.sendUpdate("sendToGolfCourse", [zoneId]) + response = 'sending to golf course %d courseId=%d' % \ + (zoneId, courseId) + elif action == 'clearBest': + response = 'clearBest failed' + av = simbase.air.doId2do.get(senderId) + if av: + emptyHoleBest = [0] * 18 + av.b_setGolfHoleBest(emptyHoleBest) + emptyCourseBest = [0] * 3 + av.b_setGolfCourseBest(emptyCourseBest) + response = 'golf best cleared' + elif action == 'maxBest': + response = 'maxBest failed' + av = simbase.air.doId2do.get(senderId) + if av: + emptyHoleBest = [1] * 18 + av.b_setGolfHoleBest(emptyHoleBest) + emptyCourseBest = [1] * 3 + av.b_setGolfCourseBest(emptyCourseBest) + response = 'golf best maxed' + elif action == 'clearHistory': + response = 'clearHistory failed' + emptyHistory = [0] * 18 + av = simbase.air.doId2do.get(senderId) + if av: + av.b_setGolfHistory(emptyHistory) + response = 'golf history cleared' + elif action == 'maxHistory': + response = 'maxHistory failed' + maxHistory = [600] * 18 + av = simbase.air.doId2do.get(senderId) + if av: + av.b_setGolfHistory(maxHistory) + response = 'golf history maxeded' + elif action == 'midHistory': + # set it up so that we just need one more course complete to get a cup + response = 'midHistory failed' + midHistory = [0] * 18 + midHistory[GolfGlobals.CoursesCompleted] = GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1] - 1 + #midHistory = [GolfGlobals.CoursesUnderPar] = GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0] + midHistory[GolfGlobals.HoleInOneShots] = GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0] + midHistory[GolfGlobals.EagleOrBetterShots] = GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0] + midHistory[GolfGlobals.BirdieOrBetterShots] = GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0] + midHistory[GolfGlobals.ParOrBetterShots] = GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0] + midHistory[GolfGlobals.MultiPlayerCoursesCompleted] = GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0] + midHistory[GolfGlobals.CourseZeroWins] = GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0] + midHistory[GolfGlobals.CourseOneWins] = GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0] + midHistory[GolfGlobals.CourseTwoWins] = GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0] + + av = simbase.air.doId2do.get(senderId) + if av: + av.b_setGolfHistory(midHistory) + response = 'golf history midded' + elif action == 'unlimitedSwing' or action == "us": + av.b_setUnlimitedSwing(not av.getUnlimitedSwing()) + if av.getUnlimitedSwing(): + response = "Av %s has an unlimited swing" % (av.getDoId()) + else: + response = "Av %s does NOT have unlimited swings" % (av.getDoId()) + elif action == 'hole': + import string + manager = GolfManagerAI.GolfManagerAI() + if len(args) <= 2: + # No minigame parameter specified: clear the request. + holeRequest = GolfManagerAI.RequestHole.get(av.doId) + if holeRequest != None: + holeId = holeRequest[0] + del GolfManagerAI.RequestHole[av.doId] + response = "Request for hole %d cleared." % (holeId) + else: + response = "Usage: ~golf hole []" + else: + holeId = None + holeKeep = 0 + name = args[2] + try: + holeId = int(name) + if holeId not in GolfGlobals.HoleInfo: + response = "hole ID '%s' is out of range" % holeId + holeId = None + except: + #name = string.lower(name) + #for testHoleId in GolfGlobals.HoleInfo: + # holeName = string.lower(GolfGlobals.getHoleName(testHoleId)) + # if name == holeName: + # holeId = testHoleId + # break; + if holeId == None: + response = "Unknown hole '%s'." % (name) + + argIndex = 2 + while argIndex < len(args): + arg = args[argIndex] + arg = string.lower(arg) + argIndex += 1 + + # it's either a difficulty (float), 'keep', + # or a safezone (string) + + # is it 'keep'? + if arg == 'keep': + holeKeep = 1 + continue + + if holeId != None: + # hoId must be the first element + GolfManagerAI.RequestHole[av.doId] = ( + holeId, holeKeep) + response = "Selected hole %d as 1st hole" % holeId + if holeKeep: + response += ", keep=true" + + + if response: + self.down_setMagicWordResponse(senderId, response) + + + def doMail(self,word, av, zoneId, senderId): + """Handle mail magic words.""" + args = word.split() + response = None + action = None + + if len(args) == 1: + # No parameter: change it to usage + self.down_setMagicWordResponse( + senderId, + "Usage:\n~mail action \n'~mail help' for more info ") + return + + action = args[1] + + if action == 'simple': + if len(args) < 4: + response = "~mail simple 'text'" + else: + recipient = int(args[2]) + text = args[3] + for i in xrange(4, len(args)): + text += ' ' + args[i] + self.air.mailManager.sendSimpleMail( + senderId, recipient, text) + + if response: + self.down_setMagicWordResponse(senderId, response) + + + def doParty(self,word, av, zoneId, senderId): + """Handle mail magic words.""" + args = word.split() + response = None + action = None + + if len(args) == 1: + # No parameter: change it to usage + self.down_setMagicWordResponse( + senderId, + "Available commands: plan, new, update, checkStart, end, debugGrid") + return + + action = args[1] + + if action == 'new': + if len(args) < 2: + response = "~party new ... " + else: + invitees = [] + for i in xrange(2, len(args)): + invitees.append( int(args[i])) + # start the party 1 minute from now + startTime = datetime.datetime.now(self.air.toontownTimeManager.serverTimeZone) + datetime.timedelta(minutes=-1) + endTime = startTime + datetime.timedelta(hours=PartyGlobals.DefaultPartyDuration) + + from toontown.parties.PartyEditorGrid import PartyEditorGrid + + # Make the avatar rich. + av.b_setMaxBankMoney(5000) + av.b_setMoney(av.maxMoney) + av.b_setBankMoney(av.maxBankMoney) + + gridEditor = PartyEditorGrid(None) + + # Flip on the Y so it matches the grid in-game. + gridEditor.grid.reverse() + + # Given a center coord (x or y) and a size (w or h), returns a list of indices in + # in the grid on that axis. (WARNING: Can return invalid indices.) + def gridComputeRange(centerGrid, size): + result = [] + if size == 1: + result = [centerGrid] + else: + # Need to round with negative values otherwise for center=0, size=3, the + # result will be [1, 0] when we expect [1, 0, -1]. + # The range without rounding: range(int(1.5), int(-1.5), -1) + # The range with rounding: range(int(1.5), int(-2), -1) + # Not a problem with center>=2 in this example: + # The range without rounding: range(int(3.5), int(0.5), -1) + # The range with rounding: range(int(3.5), int(0), -1) + result = range(int(centerGrid + size/2.0), + int(centerGrid - round(size/2.0)), -1) + + # The result list should be the same size as given. + assert len(result) == size, "Bad result range: c=%s s=%s result=%s" % (centerGrid, size, result) + + return result + + # Returns true if the given space is available centered at x,y with dims w,h. + def gridIsAvailable(x, y, w, h): + for j in gridComputeRange(y, h): + if 0 > j or j >= len(gridEditor.grid): + return False + for i in gridComputeRange(x, w): + if 0 > i or i >= len(gridEditor.grid[0]): + return False + if gridEditor.grid[j][i] == None: + return False + + #print("grid available: xy(%s %s) wh(%s %s)" % (x, y, w, h)) + return True + + # Returns the first x,y (centered) that has space for w,h. + def gridGetAvailable(w, h): + for y in range(len(gridEditor.grid)): + for x in range(len(gridEditor.grid[0])): + if gridIsAvailable(x, y, w, h): + return x, y + return None, None + + # Returns True and an x,y (centered) coord for the given space. Marks that space used. + def gridTryPlace(w, h): + x, y = gridGetAvailable(w, h) + if not x == None: + for j in gridComputeRange(y, h): + for i in gridComputeRange(x, w): + gridEditor.grid[j][i] = None + return True, x, y + else: + return False, None, None + + actualActIdsToAdd = [ + #PartyGlobals.ActivityIds.PartyJukebox, # mut.ex: PartyJukebox40 + PartyGlobals.ActivityIds.PartyCannon, + #PartyGlobals.ActivityIds.PartyTrampoline, + PartyGlobals.ActivityIds.PartyCatch, + #PartyGlobals.ActivityIds.PartyDance, # mut.ex: PartyDance20 + PartyGlobals.ActivityIds.PartyTugOfWar, + PartyGlobals.ActivityIds.PartyFireworks, + PartyGlobals.ActivityIds.PartyClock, + PartyGlobals.ActivityIds.PartyJukebox40, + PartyGlobals.ActivityIds.PartyDance20, + PartyGlobals.ActivityIds.PartyCog, + PartyGlobals.ActivityIds.PartyVictoryTrampoline, # victory party + ] + + actualDecorIdsToAdd = [ + PartyGlobals.DecorationIds.BalloonAnvil, + PartyGlobals.DecorationIds.BalloonStage, + PartyGlobals.DecorationIds.Bow, + PartyGlobals.DecorationIds.Cake, + PartyGlobals.DecorationIds.Castle, + PartyGlobals.DecorationIds.GiftPile, + PartyGlobals.DecorationIds.Horn, + PartyGlobals.DecorationIds.MardiGras, + PartyGlobals.DecorationIds.NoiseMakers, + PartyGlobals.DecorationIds.Pinwheel, + PartyGlobals.DecorationIds.GagGlobe, + #PartyGlobals.DecorationIds.BannerJellyBean, + PartyGlobals.DecorationIds.CakeTower, + #PartyGlobals.DecorationIds.HeartTarget, # valentoons + #PartyGlobals.DecorationIds.HeartBanner, # valentoons + #PartyGlobals.DecorationIds.FlyingHeart, # valentoons + PartyGlobals.DecorationIds.Hydra, # 16: victory party + PartyGlobals.DecorationIds.BannerVictory, # 17: victory party + PartyGlobals.DecorationIds.CannonVictory, # 18: victory party + PartyGlobals.DecorationIds.CogStatueVictory, # 19: victory party + PartyGlobals.DecorationIds.TubeCogVictory, # 20: victory party + PartyGlobals.DecorationIds.cogIceCreamVictory, # 21: victory party + ] + + activities = [] + + for itemId in actualActIdsToAdd: + item = PartyGlobals.ActivityInformationDict[itemId] + success, x, y = gridTryPlace(*item['gridsize']) + if success: + print("~party new ADDED: Activity %s %s at %s, %s" % (itemId, str(item['gridsize']), x, y)) + # item index, grid x, grid y, heading + partyItem = (itemId, x, y, 0) + activities.append(partyItem) + else: + print("~party new SKIPPED: No room for activity %s" % itemId) + + decorations = [] + + for itemId in actualDecorIdsToAdd: + item = PartyGlobals.DecorationInformationDict[itemId] + success, x, y = gridTryPlace(*item['gridsize']) + if success: + print("~party new ADDED: Decoration %s %s at %s, %s" % (itemId, str(item['gridsize']), x, y)) + # item index, grid x, grid y, heading + partyItem = (itemId, x, y, 0) + decorations.append(partyItem) + else: + print("~party new SKIPPED: No room for decoration %s" % itemId) + + isPrivate = False + inviteTheme = PartyGlobals.InviteTheme.Birthday + self.air.partyManager.addPartyRequest( + senderId, + startTime.strftime("%Y-%m-%d %H:%M:%S"), + endTime.strftime("%Y-%m-%d %H:%M:%S"), + isPrivate, + inviteTheme, + activities, + decorations, + invitees, + ) + # force an immediate check of which parties can start + self.air.partyManager.forceCheckStart() + + elif action == 'update': + # simulate this avatarLogging in, which forces invites + # and party updates from the dbs + self.air.partyManager.partyUpdate(senderId) + + elif action == 'checkStart': + # force an immediate check of which parties can start + self.air.partyManager.forceCheckStart() + + elif action == 'unreleasedServer': + newVal = self.air.partyManager.toggleAllowUnreleasedServer() + response = "Allow Unreleased Server= %s" % newVal + + elif action == 'canBuy': + newVal = self.air.partyManager.toggleCanBuyParties() + response = "can buy parties= %s" % newVal + + elif action == 'end': + response = self.air.partyManager.magicWordEnd(senderId) + + elif action == 'plan': + response = "Going to party grounds to plan" + + # hoodId determines the loading + hoodId = ToontownGlobals.PartyHood + + self.sendUpdateToAvatarId(av.doId, 'requestTeleport', + ["safeZoneLoader", "party", + hoodId, 0, 0]) + + if response: + self.down_setMagicWordResponse(senderId, response)