From 84e68b619b1ddd6a20021a53fbbed469b5aee84f Mon Sep 17 00:00:00 2001 From: Toontown Super <108632596+ToontownSuperForeverMVP@users.noreply.github.com> Date: Sun, 2 Aug 2026 03:03:17 -0400 Subject: [PATCH 1/2] MagicWords & UI: Implement full Magic Word system and add Spellbook WordPage to ShtikerBook --- otp/ai/MagicWordManager.py | 883 ++++ otp/ai/MagicWordManagerAI.py | 15 + toontown/ai/ToontownMagicWordManager.py | 840 ++++ toontown/ai/ToontownMagicWordManagerAI.py | 9 + toontown/shtiker/WordPage.py | 1851 +++++++ toontown/spellbook/MagicWordClientCommands.py | 0 toontown/spellbook/MagicWordConfig.py | 54 +- toontown/spellbook/MagicWordIndex.py | 4425 ++++++++++++++--- toontown/spellbook/MagicWordServerCommands.py | 0 toontown/spellbook/TTOffMagicWordManager.py | 153 + toontown/spellbook/TTOffMagicWordManagerAI.py | 204 + toontown/toon/LocalToon.py | 4 + toontown/toonbase/TTLocalizerEnglish.py | 29 + toontown/toonbase/ToontownGlobals.py | 1 + toontown/util/HotReloadWatcher.py | 77 + toontown/util/__init__.py | 0 toontown/util/astron/AstronDict.py | 18 + toontown/util/astron/AstronStruct.py | 19 + toontown/util/astron/__init__.py | 0 toontown/util/dev/Injector.py | 36 + toontown/util/dev/__init__.py | 0 toontown/util/dev/inject.py | 7 + toontown/util/ui.py | 13 + 23 files changed, 7932 insertions(+), 706 deletions(-) create mode 100644 otp/ai/MagicWordManager.py create mode 100644 otp/ai/MagicWordManagerAI.py create mode 100644 toontown/ai/ToontownMagicWordManager.py create mode 100644 toontown/ai/ToontownMagicWordManagerAI.py create mode 100644 toontown/shtiker/WordPage.py create mode 100644 toontown/spellbook/MagicWordClientCommands.py create mode 100644 toontown/spellbook/MagicWordServerCommands.py create mode 100644 toontown/spellbook/TTOffMagicWordManager.py create mode 100644 toontown/spellbook/TTOffMagicWordManagerAI.py create mode 100644 toontown/util/HotReloadWatcher.py create mode 100644 toontown/util/__init__.py create mode 100644 toontown/util/astron/AstronDict.py create mode 100644 toontown/util/astron/AstronStruct.py create mode 100644 toontown/util/astron/__init__.py create mode 100644 toontown/util/dev/Injector.py create mode 100644 toontown/util/dev/__init__.py create mode 100644 toontown/util/dev/inject.py create mode 100644 toontown/util/ui.py diff --git a/otp/ai/MagicWordManager.py b/otp/ai/MagicWordManager.py new file mode 100644 index 0000000..71a54de --- /dev/null +++ b/otp/ai/MagicWordManager.py @@ -0,0 +1,883 @@ +from panda3d.core import * +from direct.showbase import GarbageReport, ContainerReport, MessengerLeakDetector +from direct.distributed import DistributedObject +from direct.directnotify import DirectNotifyGlobal +from direct.showbase.InputStateGlobal import inputState +#from direct.showbase.ObjectCount import ObjectCount +from direct.task import Task +from direct.task.TaskProfiler import TaskProfiler +from otp.avatar import Avatar +import string +from direct.showbase import PythonUtil +from direct.showbase.PythonUtil import Functor, DelayedCall, ScratchPad +from otp.otpbase import OTPGlobals +from direct.distributed.ClockDelta import * +from direct.showutil.TexViewer import TexViewer + +class MagicWordManager(DistributedObject.DistributedObject): + notify = DirectNotifyGlobal.directNotify.newCategory('MagicWordManager') + neverDisable = 1 + GameAvatarClass = None + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + self.shownFontNode = None + self.csShown = 0 + self.guiPopupShown = 0 + self.texViewer = None + return + + def generate(self): + DistributedObject.DistributedObject.generate(self) + self.accept('magicWord', self.b_setMagicWord) + self.autoMagicWordEvent = localAvatar.getArrivedOnDistrictEvent() + if localAvatar.isGeneratedOnDistrict(): + self.doLoginMagicWords() + else: + self.accept(self.autoMagicWordEvent, self.doLoginMagicWords) + + def doLoginMagicWords(self): + pass + + def disable(self): + self.ignore(self.autoMagicWordEvent) + del self.autoMagicWordEvent + self.ignore('magicWord') + self.hidefont() + DistributedObject.DistributedObject.disable(self) + + def setMagicWord(self, word, avId, zoneId): + try: + self.doMagicWord(word, avId, zoneId) + except: + response = PythonUtil.describeException(backTrace=1) + self.notify.warning('Ignoring error in magic word:\n%s' % response) + self.setMagicWordResponse(response) + + def wordIs(self, word, w): + return word == w or word[:len(w) + 1] == '%s ' % w + + def getWordIs(self, word): + return Functor(self.wordIs, word) + + def doMagicWord(self, word, avId, zoneId): + wordIs = self.getWordIs(word) + print(word) + if wordIs('~oobe'): + base.oobe() + elif wordIs('~oobeCull'): + base.oobeCull() + elif wordIs('~tex'): + self.doTex(word) + elif wordIs('~texmem'): + base.toggleTexMem() + elif wordIs('~verts'): + base.toggleShowVertices() + elif wordIs('~wire'): + base.toggleWireframe() + elif wordIs('~stereo'): + base.toggleStereo() + elif wordIs('~showfont'): + self.showfont(word[9:]) + elif wordIs('~hidefont'): + self.hidefont() + elif wordIs('~guiPopup'): + self.toggleGuiPopup() + elif wordIs('~showCS') or wordIs('~showcs'): + bitmask = self.getCSBitmask(word[7:]) + render.showCS(bitmask) + self.csShown = 1 + elif wordIs('~hideCS') or wordIs('~hidecs'): + bitmask = self.getCSBitmask(word[7:]) + render.hideCS(bitmask) + self.csShown = 0 + elif wordIs('~cs'): + bitmask = self.getCSBitmask(word[3:]) + if self.csShown: + render.hideCS(bitmask) + self.csShown = 0 + else: + render.showCS(bitmask) + self.csShown = 1 + elif wordIs('~showShadowCollisions'): + self.showShadowCollisions() + elif wordIs('~hideShadowCollisions'): + self.hideShadowCollisions() + elif wordIs('~showCollisions'): + self.showCollisions() + elif wordIs('~hideCollisions'): + self.hideCollisions() + elif wordIs('~showCameraCollisions'): + self.showCameraCollisions() + elif wordIs('~hideCameraCollisions'): + self.hideCameraCollisions() + elif wordIs('~collidespam'): + n = Notify.ptr().getCategory(':collide') + if hasattr(self, '_collideSpamSeverity'): + n.setSeverity(self._collideSpamSeverity) + del self._collideSpamSeverity + else: + self._collideSpamSeverity = n.getSeverity() + n.setSeverity(NSSpam) + elif wordIs('~notify'): + args = word.split() + n = Notify.ptr().getCategory(args[1]) + n.setSeverity({'error': NSError, + 'warning': NSWarning, + 'info': NSInfo, + 'debug': NSDebug, + 'spam': NSSpam}[args[2]]) + elif wordIs('~stress'): + factor = word[7:] + if factor: + factor = float(factor) + LOD.setStressFactor(factor) + response = 'Set LOD stress factor to %s' % factor + else: + factor = LOD.getStressFactor() + response = 'LOD stress factor is %s' % factor + self.setMagicWordResponse(response) + elif wordIs('~for'): + self.forAnother(word, avId, zoneId) + elif wordIs('~badname'): + word = '~for %s ~badname' % word[9:] + print('word is %s' % word) + self.forAnother(word, avId, zoneId) + elif wordIs('~avId'): + self.setMagicWordResponse(str(localAvatar.doId)) + elif wordIs('~doId'): + name = string.strip(word[6:]) + objs = self.identifyDistributedObjects(name) + if len(objs) == 0: + response = '%s is unknown.' % name + else: + response = '' + for name, obj in objs: + response += '\n%s %d' % (name, obj.doId) + + response = response[1:] + self.setMagicWordResponse(response) + elif wordIs('~exec'): + from otp.chat import ChatManager + ChatManager.ChatManager.execChat = 1 + elif wordIs('~run'): + self.toggleRun() + elif wordIs('~runFaster'): + if config.GetBool('want-running', 1): + args = word.split() + if len(args) > 1: + base.debugRunningMultiplier = float(args[1]) + else: + base.debugRunningMultiplier = 10 + inputState.set('debugRunning', True) + elif wordIs('~who'): + avIds = [] + for av in Avatar.Avatar.ActiveAvatars: + if hasattr(av, 'getFriendsList'): + avIds.append(av.doId) + + self.d_setWho(avIds) + elif wordIs('~sync'): + tm = self.cr.timeManager + if tm == None: + response = 'No TimeManager.' + self.setMagicWordResponse(response) + else: + tm.extraSkew = 0.0 + skew = string.strip(word[5:]) + if skew != '': + tm.extraSkew = float(skew) + globalClockDelta.clear() + tm.handleHotkey() + elif wordIs('~period'): + timeout = string.strip(word[7:]) + if timeout != '': + seconds = int(timeout) + self.cr.stopPeriodTimer() + self.cr.resetPeriodTimer(seconds) + self.cr.startPeriodTimer() + if self.cr.periodTimerExpired: + response = 'Period timer has expired.' + elif self.cr.periodTimerStarted: + elapsed = globalClock.getFrameTime() - self.cr.periodTimerStarted + secondsRemaining = self.cr.periodTimerSecondsRemaining - elapsed + response = 'Period timer expires in %s seconds.' % int(secondsRemaining) + else: + response = 'Period timer not set.' + self.setMagicWordResponse(response) + elif wordIs('~DIRECT'): + args = word.split() + fEnableLight = 0 + if len(args) > 1: + if direct and args[1] == 'CAM': + direct.enable() + taskMgr.removeTasksMatching('updateSmartCamera*') + camera.wrtReparentTo(render) + direct.cameraControl.enableMouseFly() + self.setMagicWordResponse('Enabled DIRECT camera') + return + elif args[1] == 'LIGHT': + fEnableLight = 1 + base.startTk() + from direct.directtools import DirectSession + if fEnableLight: + direct.enableLight() + else: + direct.enable() + self.setMagicWordResponse('Enabled DIRECT') + elif wordIs('~TT'): + if not direct: + return + args = word.split() + if len(args) > 1: + if args[1] == 'CAM': + direct.cameraControl.disableMouseFly() + camera.wrtReparentTo(base.localAvatar) + base.localAvatar.startUpdateSmartCamera() + self.setMagicWordResponse('Disabled DIRECT camera') + return + direct.disable() + camera.wrtReparentTo(base.localAvatar) + base.localAvatar.startUpdateSmartCamera() + self.setMagicWordResponse('Disabled DIRECT') + elif wordIs('~net'): + if self.cr.networkPlugPulled(): + self.cr.restoreNetworkPlug() + self.cr.startHeartbeat() + response = 'Network restored.' + else: + self.cr.pullNetworkPlug() + self.cr.stopHeartbeat() + response = 'Network disconnected.' + self.setMagicWordResponse(response) + elif wordIs('~disconnect'): + base.cr.distributedDistrict.sendUpdate('broadcastMessage') + elif wordIs('~model'): + args = word.split() + path = args[1] + model = loader.loadModel(path) + model.reparentTo(localAvatar) + model.wrtReparentTo(render) + self.setMagicWordResponse('loaded %s' % path) + elif wordIs('~axis'): + axis = loader.loadModel('models/misc/xyzAxis.bam') + axis.reparentTo(render) + axis.setPos(base.localAvatar, 0, 0, 0) + axis.setHpr(render, 0, 0, 0) + axis10 = loader.loadModel('models/misc/xyzAxis.bam') + axis10.reparentTo(render) + axis10.setPos(base.localAvatar, 0, 0, 0) + axis10.setScale(10) + axis10.setHpr(render, 0, 0, 0) + axis10.setColorScale(1, 1, 1, 0.4) + axis10.setTransparency(1) + elif wordIs('~clearAxes') or wordIs('~clearAxis'): + render.findAllMatches('**/xyzAxis.egg').detach() + elif wordIs('~myAxis'): + if hasattr(self, 'myAxis'): + self.myAxis.detachNode() + del self.myAxis + else: + self.myAxis = loader.loadModel('models/misc/xyzAxis.bam') + self.myAxis.reparentTo(localAvatar) + elif wordIs('~osd'): + onScreenDebug.enabled = not onScreenDebug.enabled + elif wordIs('~osdScale'): + args = word.split() + defScale = 0.05 + if len(args) > 1: + scale = float(args[1]) + else: + scale = 1.0 + onScreenDebug.onScreenText.setScale(defScale * scale) + elif wordIs('~osdTaskMgr'): + if taskMgr.osdEnabled(): + taskMgr.stopOsd() + else: + if not onScreenDebug.enabled: + onScreenDebug.enabled = True + taskMgr.startOsd() + elif wordIs('~fps'): + self.doFps(word, avId, zoneId) + elif wordIs('~sleep'): + args = word.split() + if len(args) > 1: + s = float(args[1]) + base.setSleep(s) + response = 'sleeping %s' % s + else: + base.setSleep(0.0) + response = 'not sleeping' + self.setMagicWordResponse(response) + elif wordIs('~objects'): + args = word.split() + from direct.showbase import ObjectReport + report = ObjectReport.ObjectReport('client ~objects') + if 'all' in args: + self.notify.info('printing full object set...') + report.getObjectPool().printObjsByType(printReferrers='ref' in args) + if hasattr(self, 'baselineObjReport'): + self.notify.info('calculating diff from baseline ObjectReport...') + self.lastDiff = self.baselineObjReport.diff(report) + self.lastDiff.printOut(full='diff' in args or 'dif' in args) + if 'baseline' in args or not hasattr(self, 'baselineObjReport'): + self.notify.info('recording baseline ObjectReport...') + if hasattr(self, 'baselineObjReport'): + self.baselineObjReport.destroy() + self.baselineObjReport = report + self.setMagicWordResponse('objects logged') + elif wordIs('~objectcount'): + + #def handleObjectCountDone(objectCount): + # self.setMagicWordResponse('object count logged') + # + #oc = ObjectCount('~objectcount', doneCallback=handleObjectCountDone) + self.setMagicWordResponse('not supported') + elif wordIs('~objecthg'): + import gc + objs = gc.get_objects() + type2count = {} + for obj in objs: + tn = safeTypeName(obj) + type2count.setdefault(tn, 0) + type2count[tn] += 1 + + count2type = invertDictLossless(type2count) + counts = count2type.keys() + counts.sort() + counts.reverse() + for count in counts: + print('%s: %s' % (count, count2type[count])) + + self.setMagicWordResponse('~aiobjecthg complete') + elif wordIs('~containers'): + args = word.split() + limit = 30 + if 'full' in args: + limit = None + ContainerReport.ContainerReport('~containers', log=True, limit=limit, threaded=True) + elif wordIs('~garbage'): + args = word.split() + full = 'full' in args + safeMode = 'safe' in args + delOnly = 'delonly' in args + GarbageReport.GarbageLogger('~garbage', fullReport=full, threaded=True, safeMode=safeMode, delOnly=delOnly, doneCallback=self.garbageReportDone) + elif wordIs('~guicreates'): + base.printGuiCreates = True + self.setMagicWordResponse('printing gui creation stacks') + elif wordIs('~creategarbage'): + GarbageReport._createGarbage() + elif wordIs('~leakTask'): + + def leakTask(task): + return task.cont + + taskMgr.add(leakTask, uniqueName('leakedTask')) + leakTask = None + elif wordIs('~leakmessage'): + MessengerLeakDetector._leakMessengerObject() + self.down_setMagicWordResponse(senderId, 'messenger leak object created') + elif wordIs('~pstats'): + args = word.split() + hostname = None + port = None + if len(args) > 1: + hostname = args[1] + if len(args) > 2: + port = int(args[2]) + base.wantStats = 1 + Task.TaskManager.pStatsTasks = 1 + result = base.createStats(hostname, port) + connectionName = '%s' % hostname + if port is not None: + connectionName += ':%s' % port + if result: + response = 'connected client pstats to %s' % connectionName + else: + response = 'could not connect pstats to %s' % connectionName + self.setMagicWordResponse(response) + elif wordIs('~profile'): + args = word.split() + if len(args) > 1: + num = int(args[1]) + else: + num = 5 + session = taskMgr.getProfileSession('~profile') + session.setLogAfterProfile(True) + taskMgr.profileFrames(num, session) + self.setMagicWordResponse('profiling %s client frames...' % num) + elif wordIs('~frameprofile'): + args = word.split() + wasOn = bool(taskMgr.getProfileFrames()) + if len(args) > 1: + setting = bool(int(args[1])) + else: + setting = not wasOn + taskMgr.setProfileFrames(setting) + self.setMagicWordResponse('frame profiling %s%s' % (choice(setting, 'ON', 'OFF'), choice(wasOn == setting, ' already', ''))) + elif wordIs('~taskprofile'): + args = word.split() + wasOn = bool(taskMgr.getProfileTasks()) + if len(args) > 1: + setting = bool(int(args[1])) + else: + setting = not wasOn + taskMgr.setProfileTasks(setting) + self.setMagicWordResponse('task profiling %s%s' % (choice(setting, 'ON', 'OFF'), choice(wasOn == setting, ' already', ''))) + elif wordIs('~taskspikethreshold'): + args = word.split() + if len(args) > 1: + threshold = float(args[1]) + response = 'task spike threshold set to %ss' % threshold + else: + threshold = TaskProfiler.GetDefaultSpikeThreshold() + response = 'task spike threshold reset to %ss' % threshold + TaskProfiler.SetSpikeThreshold(threshold) + self.setMagicWordResponse(response) + elif wordIs('~logtaskprofiles'): + args = word.split() + if len(args) > 1: + name = args[1] + else: + name = None + taskMgr.logTaskProfiles(name) + response = 'logged task profiles%s' % choice(name, ' for %s' % name, '') + self.setMagicWordResponse(response) + elif wordIs('~taskprofileflush'): + args = word.split() + if len(args) > 1: + name = args[1] + else: + name = None + taskMgr.flushTaskProfiles(name) + response = 'flushed AI task profiles%s' % choice(name, ' for %s' % name, '') + self.setMagicWordResponse(response) + elif wordIs('~dobjectcount'): + base.cr.printObjectCount() + self.setMagicWordResponse('logging client distributed object count...') + elif wordIs('~taskmgr'): + print(taskMgr) + self.setMagicWordResponse('logging client taskMgr...') + elif wordIs('~jobmgr'): + print(jobMgr) + self.setMagicWordResponse('logging client jobMgr...') + elif wordIs('~jobtime'): + args = word.split() + if len(args) > 1: + time = float(args[1]) + else: + time = None + response = '' + if time is None: + time = jobMgr.getDefaultTimeslice() + response = 'reset client jobMgr timeslice to %s ms' % time + else: + response = 'set client jobMgr timeslice to %s ms' % time + time = time / 1000.0 + jobMgr.setTimeslice(time) + self.setMagicWordResponse(response) + elif wordIs('~detectleaks'): + started = self.cr.startLeakDetector() + self.setMagicWordResponse(choice(started, 'leak detector started', 'leak detector already started')) + elif wordIs('~taskthreshold'): + args = word.split() + if len(args) > 1.0: + threshold = float(args[1]) + else: + threshold = None + response = '' + if threshold is None: + threshold = taskMgr.DefTaskDurationWarningThreshold + response = 'reset task duration warning threshold to %s' % threshold + else: + response = 'set task duration warning threshold to %s' % threshold + taskMgr.setTaskDurationWarningThreshold(threshold) + self.setMagicWordResponse(response) + elif wordIs('~messenger'): + print(messenger) + self.setMagicWordResponse('logging client messenger...') + elif wordIs('~clientcrash'): + DelayedCall(Functor(self.notify.error, '~clientcrash: simulating a client crash')) + elif wordIs('~badDelete'): + doId = 0 + while doId in base.cr.doId2do: + doId += 1 + + DelayedCall(Functor(base.cr.deleteObjectLocation, ScratchPad(doId=doId), 1, 1)) + self.setMagicWordResponse('doing bad delete') + elif wordIs('~idTags'): + messenger.send('nameTagShowAvId', []) + base.idTags = 1 + elif wordIs('~nameTags'): + messenger.send('nameTagShowName', []) + base.idTags = 0 + elif wordIs('~hideNames'): + if NametagGlobals.getMasterNametagsVisible(): + NametagGlobals.setMasterNametagsVisible(0) + else: + NametagGlobals.setMasterNametagsVisible(1) + elif wordIs('~hideGui'): + if aspect2d.isHidden(): + aspect2d.show() + else: + aspect2d.hide() + elif wordIs('~flush'): + base.cr.doDataCache.flush() + base.cr.cache.flush() + self.setMagicWordResponse('client object and data caches flushed') + elif wordIs('~prof'): + import time + name = 'default' + p = Point3() + ts = time.time() + for i in range(1000000): + p.set(1, 2, 3) + + tf = time.time() + dt = tf - ts + response = 'prof(%s): %s secs' % (name, dt) + print(response) + self.setMagicWordResponse(response) + elif wordIs('~gptc'): + args = word.split() + if len(args) > 1.0 and hasattr(self.cr, 'leakDetector'): + gptcJob = self.cr.leakDetector.getPathsToContainers('~gptc', args[1], Functor(self._handleGPTCfinished, args[1])) + else: + self.setMagicWordResponse('error') + elif wordIs('~gptcn'): + args = word.split() + if len(args) > 1.0 and hasattr(self.cr, 'leakDetector'): + gptcnJob = self.cr.leakDetector.getPathsToContainersNamed('~gptcn', args[1], Functor(self._handleGPTCNfinished, args[1])) + else: + self.setMagicWordResponse('error') + else: + return 0 + return 1 + + def toggleRun(self): + if config.GetBool('want-running', 1): + inputState.set('debugRunning', inputState.isSet('debugRunning') != True) + + def d_setMagicWord(self, magicWord, avId, zoneId): + self.sendUpdate('setMagicWord', [magicWord, + avId, + zoneId, + base.cr.userSignature]) + + def b_setMagicWord(self, magicWord, avId=None, zoneId=None): + if self.cr.wantMagicWords: + if avId == None: + avId = base.localAvatar.doId + + if zoneId == None: + try: + zoneId = self.cr.playGame.getPlace().getZoneId() + except: + pass + + if zoneId == None: + zoneId = 0 + + self.d_setMagicWord(magicWord, avId, zoneId) + if magicWord.count("~crash"): + args = magicWord.split() + errorCode = 12 + if len(args) > 1: + errorCode = int(args[1]) + self.notify.info("Simulating client crash: exit error = %s" % errorCode) + base.exitShow(errorCode) + + if magicWord.count("~exception"): + self.notify.error("~exception: simulating a client exception...") + s = "" + while 1: + s += "INVALIDNAME" + eval(s) + + self.setMagicWord(magicWord, avId, zoneId) + + def setMagicWordResponse(self, response): + self.notify.info(response) + # Also surface the response as an on-screen notification popup (whisper-style), + # since chat bubbles can be missed or hidden. + try: + from libotp.nametag.WhisperGlobals import WhisperType + base.localAvatar.setSystemMessage(0, response, WhisperType.WTMagicWord) + except Exception: + pass + base.localAvatar.setChatAbsolute(response, CFSpeech | CFTimeout) + base.talkAssistant.receiveDeveloperMessage(response) + + def d_setWho(self, avIds): + self.sendUpdate('setWho', [avIds]) + + def _handleGPTCfinished(self, ct, gptcJob): + self.setMagicWordResponse('gptc(%s) finished' % ct) + + def _handleGPTCNfinished(self, cn, gptcnJob): + self.setMagicWordResponse('gptcn(%s) finished' % cn) + + def forAnother(self, word, avId, zoneId): + b = 5 + while word[b:b + 2] != ' ~': + b += 1 + if b >= len(word): + self.setMagicWordResponse('No next magic word!') + return + + nextWord = word[b + 1:] + name = string.strip(word[5:b]) + id = self.identifyAvatar(name) + if id == None: + self.setMagicWordResponse("Don't know who %s is." % name) + return + self.d_setMagicWord(nextWord, id, zoneId) + return + + def identifyAvatar(self, name): + self.notify.error('Pure virtual - please override me.') + + def identifyDistributedObjects(self, name): + result = [] + lowerName = string.lower(name) + for obj in self.cr.doId2do.values(): + className = obj.__class__.__name__ + try: + name = obj.getName() + except: + name = className + + if string.lower(name) == lowerName or string.lower(className) == lowerName or string.lower(className) == 'distributed' + lowerName: + result.append((name, obj)) + + return result + + def doTex(self, word): + args = word.split() + if len(args) <= 1: + if self.texViewer: + self.texViewer.cleanup() + self.texViewer = None + return + base.toggleTexture() + return + if self.texViewer: + self.texViewer.cleanup() + self.texViewer = None + tex = TexturePool.findTexture(args[1]) + if not tex: + tex = TexturePool.findTexture('*%s*' % args[1]) + if not tex: + self.setMagicWordResponse('Unknown texture: %s' % args[1]) + return + self.texViewer = TexViewer(tex) + return + + def getCSBitmask(self, str): + words = string.lower(str).split() + if len(words) == 0: + return None + invalid = '' + bitmask = BitMask32.allOff() + for w in words: + if w == 'wall': + bitmask |= OTPGlobals.WallBitmask + elif w == 'floor': + bitmask |= OTPGlobals.FloorBitmask + elif w == 'cam': + bitmask |= OTPGlobals.CameraBitmask + elif w == 'catch': + bitmask |= OTPGlobals.CatchBitmask + elif w == 'ghost': + bitmask |= OTPGlobals.GhostBitmask + elif w == 'pet': + bitmask |= OTPGlobals.PetBitmask + elif w == 'furniture': + bitmask |= OTPGlobals.FurnitureSideBitmask | OTPGlobals.FurnitureTopBitmask | OTPGlobals.FurnitureDragBitmask + elif w == 'furnitureside': + bitmask |= OTPGlobals.FurnitureSideBitmask + elif w == 'furnituretop': + bitmask |= OTPGlobals.FurnitureTopBitmask + elif w == 'furnituredrag': + bitmask |= OTPGlobals.FurnitureDragBitmask + elif w == 'pie': + bitmask |= OTPGlobals.PieBitmask + else: + try: + bitmask |= BitMask32.bit(int(w)) + print(bitmask) + except ValueError: + invalid += ' ' + w + + if invalid: + self.setMagicWordResponse('Unknown CS keyword(s): %s' % invalid) + return bitmask + + def getFontByName(self, fontname): + if fontname == 'default': + return TextNode.getDefaultFont() + elif fontname == 'interface': + return OTPGlobals.getInterfaceFont() + elif fontname == 'sign': + return OTPGlobals.getSignFont() + else: + return None + return None + + def showfont(self, fontname): + fontname = string.strip(string.lower(fontname)) + font = self.getFontByName(fontname) + if font == None: + self.setMagicWordResponse('Unknown font: %s' % fontname) + return + if not isinstance(font, DynamicTextFont): + self.setMagicWordResponse('Font %s is not dynamic.' % fontname) + return + self.hidefont() + self.shownFontNode = aspect2d.attachNewNode('shownFont') + tn = TextNode('square') + tn.setCardActual(0.0, 1.0, -1.0, 0.0) + tn.setFrameActual(0.0, 1.0, -1.0, 0.0) + tn.setCardColor(1, 1, 1, 0.5) + tn.setFrameColor(1, 1, 1, 1) + tn.setFont(font) + tn.setText(' ') + numXPages = 2 + numYPages = 2 + pageScale = 0.8 + pageMargin = 0.1 + numPages = font.getNumPages() + x = 0 + y = 0 + for pi in range(numPages): + page = font.getPage(pi) + tn.setCardTexture(page) + np = self.shownFontNode.attachNewNode(tn.generate()) + np.setScale(pageScale) + (np.setPos(float(x) / numXPages * 2 - 1 + pageMargin, 0, 1 - float(y) / numYPages * 2 - pageMargin),) + x += 1 + if x >= numXPages: + y += 1 + x = 0 + + return + + def hidefont(self): + if self.shownFontNode != None: + self.shownFontNode.removeNode() + self.shownFontNode = None + return + + def showShadowCollisions(self): + try: + base.shadowTrav.showCollisions(render) + except: + self.setMagicWordResponse('CollisionVisualizer is not compiled in.') + + def hideShadowCollisions(self): + base.shadowTrav.hideCollisions() + + def showCollisions(self): + try: + base.cTrav.showCollisions(render) + except: + self.setMagicWordResponse('CollisionVisualizer is not compiled in.') + + def hideCollisions(self): + base.cTrav.hideCollisions() + + def showCameraCollisions(self): + try: + localAvatar.ccTrav.showCollisions(render) + except: + self.setMagicWordResponse('CollisionVisualizer is not compiled in.') + + def hideCameraCollisions(self): + localAvatar.ccTrav.hideCollisions() + + def doFps(self, word, avId, zoneId): + args = word.split() + response = None + if len(args) == 1 or args[1] == 'normal': + if globalClock.getMode() != ClockObject.MNormal: + globalClock.setMode(ClockObject.MNormal) + response = 'Normal frame rate set.' + else: + base.setFrameRateMeter(not base.frameRateMeter) + elif args[1] == 'forced': + fps = float(args[2]) + globalClock.setMode(ClockObject.MForced) + globalClock.setDt(1.0 / fps) + response = 'Frame rate forced to %s fps.' % fps + base.setFrameRateMeter(1) + elif args[1] == 'degrade': + factor = float(args[2]) + globalClock.setMode(ClockObject.MDegrade) + globalClock.setDegradeFactor(factor) + response = 'Frame rate degraded by factor of %s.' % factor + base.setFrameRateMeter(1) + elif args[1][-1] == '%': + percent = float(args[1][:-1]) + if percent == 100: + globalClock.setMode(ClockObject.MNormal) + response = 'Normal frame rate set.' + else: + globalClock.setMode(ClockObject.MDegrade) + globalClock.setDegradeFactor(100.0 / percent) + response = 'Frame rate degraded to %s percent.' % percent + base.setFrameRateMeter(1) + else: + try: + fps = float(args[1]) + except: + fps = None + + if fps != None: + globalClock.setMode(ClockObject.MForced) + globalClock.setDt(1.0 / fps) + response = 'Frame rate forced to %s fps.' % fps + base.setFrameRateMeter(1) + else: + response = 'Unknown fps command: ~s' % args[1] + if base.frameRateMeter: + globalClock.setAverageFrameRateInterval(ConfigVariableDouble('average-frame-rate-interval').getValue()) + if response != None: + self.setMagicWordResponse(response) + return + + def identifyAvatar(self, name): + for av in Avatar.Avatar.ActiveAvatars: + if isinstance(av, self.GameAvatarClass) and av.getName() == name: + return av.doId + + lowerName = string.lower(name) + for av in Avatar.Avatar.ActiveAvatars: + if isinstance(av, self.GameAvatarClass) and string.strip(string.lower(av.getName())) == lowerName: + return av.doId + + try: + avId = int(name) + return avId + except: + pass + + return None + + def toggleGuiPopup(self): + if self.guiPopupShown: + base.mouseWatcherNode.hideRegions() + self.guiPopupShown = 0 + else: + base.mouseWatcherNode.showRegions(render2d, 'gui-popup', 0) + self.guiPopupShown = 1 + + def garbageReportDone(self, garbageReport): + self.setMagicWordResponse('%s garbage cycles' % garbageReport.getNumCycles()) + + +def magicWord(mw): + messenger.send('magicWord', [mw]) + + +import builtins +builtins.magicWord = magicWord diff --git a/otp/ai/MagicWordManagerAI.py b/otp/ai/MagicWordManagerAI.py new file mode 100644 index 0000000..ec53f66 --- /dev/null +++ b/otp/ai/MagicWordManagerAI.py @@ -0,0 +1,15 @@ +from direct.directnotify import DirectNotifyGlobal +from direct.distributed.DistributedObjectAI import DistributedObjectAI + +class MagicWordManagerAI(DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory("MagicWordManagerAI") + + def setMagicWord(self, todo0, todo1, todo2, todo3): + pass + + def setMagicWordResponse(self, todo0): + pass + + def setWho(self, todo0): + pass + diff --git a/toontown/ai/ToontownMagicWordManager.py b/toontown/ai/ToontownMagicWordManager.py new file mode 100644 index 0000000..6a2e2dc --- /dev/null +++ b/toontown/ai/ToontownMagicWordManager.py @@ -0,0 +1,840 @@ +from direct.interval.IntervalGlobal import * +from direct.distributed import PyDatagram +from direct.distributed.MsgTypes import MsgName2Id +from panda3d.core import * +from direct.distributed import DistributedObject +from toontown.toon import DistributedToon +from direct.directnotify import DirectNotifyGlobal +from toontown.town import TownBattleAttackPanel +from toontown.suit import RoguesGallery +from otp.avatar import Avatar +from otp.chat import ChatManager +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import ToontownBattleGlobals +import string +from toontown.toon import Toon +from direct.showbase import PythonUtil +from toontown.suit import DistributedSuitPlanner +from toontown.suit import DistributedBossCog +from otp.otpbase import OTPGlobals +from direct.distributed.ClockDelta import * +from otp.ai import MagicWordManager +from toontown.hood import ZoneUtil +from toontown.battle import Fanfare +from toontown.golf import GolfGlobals +from toontown.distributed import ToontownDistrictStats +from toontown.coderedemption import TTCodeRedemptionConsts +from toontown.rpc import AwardManagerConsts +if base.wantKarts: + from toontown.racing.KartDNA import * + from toontown.racing.KartShopGui import * +if (__debug__): + import pdb + +class ToontownMagicWordManager(MagicWordManager.MagicWordManager): + notify = DirectNotifyGlobal.directNotify.newCategory('ToontownMagicWordManager') + neverDisable = 1 + GameAvatarClass = DistributedToon.DistributedToon + + def __init__(self, cr): + MagicWordManager.MagicWordManager.__init__(self, cr) + self.rogues = None + self.ruler = None + self.dbg_running_fast = 0 + return + + def generate(self): + MagicWordManager.MagicWordManager.generate(self) + self.accept('magicWord', self.b_setMagicWord) + + def doLoginMagicWords(self): + MagicWordManager.MagicWordManager.doLoginMagicWords(self) + if base.config.GetBool('want-chat', 0): + self.d_setMagicWord('~chat', base.localAvatar.doId, 0) + if base.config.GetBool('want-run', 0) or base.config.GetBool('want-toontown-run', 0): + self.toggleRun() + if base.config.GetBool('immortal-mode', 0): + self.d_setMagicWord('~immortal', base.localAvatar.doId, 0) + mintFloor = base.config.GetInt('mint-floor', -1) + if mintFloor != -1: + self.d_setMagicWord('~mintFloor %s' % mintFloor, base.localAvatar.doId, 0) + mintId = base.config.GetInt('mint-id', -1) + if mintId != -1: + self.d_setMagicWord('~mint %s' % mintId, base.localAvatar.doId, 0) + autoRestock = base.config.GetInt('auto-restock', -1) + if autoRestock != -1: + self.d_setMagicWord('~autoRestock %s' % autoRestock, base.localAvatar.doId, 0) + autoRich = base.config.GetInt('auto-rich', -1) + if autoRich != -1: + self.d_setMagicWord('~autoRich %s' % autoRich, base.localAvatar.doId, 0) + autoResistanceRestock = base.config.GetInt('auto-resistance-restock', -1) + if autoResistanceRestock != -1: + self.d_setMagicWord('~autoResistanceRestock %s' % autoResistanceRestock, base.localAvatar.doId, 0) + autoRestockSOS = base.config.GetInt('auto-restock-sos', -1) + if autoRestockSOS != -1: + self.d_setMagicWord('~autoRestockSOS %s' % autoRestockSOS, base.localAvatar.doId, 0) + autoRestockPinkSlips = base.config.GetInt('auto-restock-pink-slips', -1) + if autoRestockPinkSlips != -1: + self.d_setMagicWord('~autoRestockPinkSlips %s' % autoRestockPinkSlips, base.localAvatar.doId, 0) + autoRestockSummons = base.config.GetInt('auto-restock-summons', -1) + if autoRestockSummons != -1: + self.d_setMagicWord('~autoRestockSummons %s' % autoRestockSummons, base.localAvatar.doId, 0) + paidStatus = base.config.GetString('force-paid-status', 'none') + if paidStatus != 'none': + self.d_setMagicWord('~setPaid %s' % choice(paidStatus == 'paid', 1, 0), localAvatar.doId, 0) + self.doConfigMagicWords() + + def doConfigMagicWords(self): + autoMagicWords = base.config.GetString('auto-magic-words', '').split('|') + for command in autoMagicWords: + if command: + self.d_setMagicWord(command.strip(), base.localAvatar.doId, 0) + + def disable(self): + self.ignore('magicWord') + if self.dbg_running_fast: + self.toggleRun() + MagicWordManager.MagicWordManager.disable(self) + + def doMagicWord(self, word, avId, zoneId): + wordIs = Functor(self.wordIs, word) + if MagicWordManager.MagicWordManager.doMagicWord(self, word, avId, zoneId) == 1: + pass + elif wordIs('~fanfare'): + go = Fanfare.makeFanfareWithMessageImage(0, base.localAvatar, 1, "You just did a ~fanfare. Here's a rake.", Vec2(0, 0.2), 0.08, base.localAvatar.inventory.buttonLookup(1, 1), Vec3(0, 0, 0), 4) + Sequence(go[0], Func(go[1].show), LerpColorScaleInterval(go[1], duration=0.5, startColorScale=Vec4(1, 1, 1, 0), colorScale=Vec4(1, 1, 1, 1)), Wait(2), LerpColorScaleInterval(go[1], duration=0.5, startColorScale=Vec4(1, 1, 1, 1), colorScale=Vec4(1, 1, 1, 0)), Func(go[1].remove)).start() + elif wordIs('~endgame'): + print('Requesting minigame abort...') + messenger.send('minigameAbort') + elif wordIs('~wingame'): + print('Requesting minigame victory...') + messenger.send('minigameVictory') + elif wordIs('~walk'): + try: + fsm = base.cr.playGame.getPlace().fsm + fsm.forceTransition('walk') + except: + pass + + elif wordIs('~movie'): + try: + fsm = base.cr.playGame.getPlace().fsm + fsm.forceTransition('movie') + except: + pass + + elif wordIs('~sit'): + try: + base.cr.playGame.getPlace().fsm.request('sit') + except: + pass + + elif wordIs('~rogues'): + suitname = None + if len(word) > 7: + suitname = word[7:].split(' ')[1] + self.rogues = RoguesGallery.RoguesGallery(suitname) + self.rogues.enter() + if suitname != None: + self.rogues.animate() + self.acceptOnce('mouse1', self.exit_rogues) + elif wordIs('~showPaths'): + for obj in self.cr.doId2do.values(): + if isinstance(obj, DistributedSuitPlanner.DistributedSuitPlanner): + obj.showPaths() + + place = base.cr.playGame.getPlace() + if hasattr(place, 'showPaths'): + place.showPaths() + elif wordIs('~hidePaths'): + for obj in self.cr.doId2do.values(): + if isinstance(obj, DistributedSuitPlanner.DistributedSuitPlanner): + obj.hidePaths() + + place = base.cr.playGame.getPlace() + if hasattr(place, 'hidePaths'): + place.hidePaths() + elif wordIs('~raceForever'): + base.raceForever = True + elif wordIs('~listen'): + base.localAvatar.garbleChat = 0 + elif wordIs('~nochat') or wordIs('~chat') or wordIs('~superchat'): + base.localAvatar.garbleChat = 1 + elif wordIs('~exec'): + ChatManager.ChatManager.execChat = 1 + elif wordIs('~photoshoot'): + base.cr.playGame.hood.sky.hide() + base.cr.playGame.getPlace().loader.geom.hide() + base.win.setClearColor(VBase4(1, 1, 1, 1)) + base.localAvatar.stopLookAroundNow() + base.localAvatar.stopBlink() + base.localAvatar.setNameVisible(0) + elif wordIs('~hideAttack'): + TownBattleAttackPanel.hideAttackPanel(1) + elif wordIs('~showAttack'): + TownBattleAttackPanel.hideAttackPanel(0) + elif wordIs('~collisions_on'): + base.localAvatar.collisionsOn() + elif wordIs('~collisions_off'): + base.localAvatar.collisionsOff() + elif wordIs('~battle_detect_off'): + from toontown.suit import DistributedSuit + DistributedSuit.ALLOW_BATTLE_DETECT = 0 + elif wordIs('~battle_detect_on'): + from toontown.suit import DistributedSuit + DistributedSuit.ALLOW_BATTLE_DETECT = 1 + elif wordIs('~battles'): + base.localAvatar.setWantBattles(not base.localAvatar.wantBattles) + if base.localAvatar.wantBattles: + response = 'battles ON' + else: + response = 'battles OFF' + self.setMagicWordResponse(response) + elif wordIs('~skipBattleMovie') or wordIs('~sbm'): + ToontownBattleGlobals.SkipMovie = not ToontownBattleGlobals.SkipMovie + if ToontownBattleGlobals.SkipMovie: + response = 'battle movies will be skipped' + else: + response = 'battle movies will be played' + self.setMagicWordResponse(response) + elif wordIs('~addCameraPosition'): + base.localAvatar.addCameraPosition() + elif wordIs('~removeCameraPosition'): + base.localAvatar.removeCameraPosition() + elif wordIs('~printCameraPosition'): + base.localAvatar.printCameraPosition(base.localAvatar.cameraIndex) + elif wordIs('~printCameraPositions'): + base.localAvatar.printCameraPositions() + elif wordIs('~worldCam') or wordIs('~wc'): + myCam = render.find('**/camera') + if not myCam.isEmpty(): + camParent = myCam.getParent() + myCam.wrtReparentTo(render) + pos = myCam.getPos() + hpr = myCam.getHpr() + response = '(%.2f, %.2f, %.2f,) (%.2f, %.2f, %.2f)' % (pos[0], + pos[1], + pos[2], + hpr[0], + hpr[1], + hpr[2]) + if not camParent.isEmpty(): + myCam.wrtReparentTo(camParent) + self.setMagicWordResponse(response) + print(response) + elif wordIs('~sync'): + tm = base.cr.timeManager + if tm == None: + response = 'No TimeManager.' + self.setMagicWordResponse(response) + else: + tm.extraSkew = 0.0 + skew = string.strip(word[5:]) + if skew != '': + tm.extraSkew = float(skew) + globalClockDelta.clear() + tm.handleHotkey() + elif wordIs('~period'): + timeout = string.strip(word[7:]) + if timeout != '': + seconds = int(timeout) + base.cr.stopPeriodTimer() + base.cr.resetPeriodTimer(seconds) + base.cr.startPeriodTimer() + if base.cr.periodTimerExpired: + response = 'Period timer has expired.' + elif base.cr.periodTimerStarted: + elapsed = globalClock.getFrameTime() - base.cr.periodTimerStarted + secondsRemaining = base.cr.periodTimerSecondsRemaining - elapsed + response = 'Period timer expires in %s seconds.' % int(secondsRemaining) + else: + response = 'Period timer not set.' + self.setMagicWordResponse(response) + elif wordIs('~net'): + if base.cr.networkPlugPulled(): + base.cr.restoreNetworkPlug() + base.cr.startHeartbeat() + response = 'Network restored.' + else: + base.cr.pullNetworkPlug() + base.cr.stopHeartbeat() + response = 'Network disconnected.' + self.setMagicWordResponse(response) + elif wordIs('~lag'): + if not hasattr(base.cr, 'magicLag'): + base.cr.startDelay(0.1, 0.35) + base.cr.magicLag = None + response = 'Simulated Lag On' + else: + base.cr.stopDelay() + del base.cr.magicLag + response = 'Simulated Lag Off' + self.setMagicWordResponse(response) + elif wordIs('~endlessquietzone'): + base.endlessQuietZone = not base.endlessQuietZone + response = 'endless quiet zone %s' % choice(base.endlessQuietZone, 'ON', 'OFF') + self.setMagicWordResponse(response) + elif wordIs('~cogPageFull'): + base.localAvatar.suitPage.updateAllCogs(3) + elif wordIs('~mintId'): + args = word.split() + postName = 'mintIdOverride' + if len(args) < 2: + if bboard.has(postName): + bboard.remove(postName) + else: + try: + id = int(args[1]) + foo = ToontownGlobals.MintNumRooms[id] + except: + pass + else: + bboard.post(postName, id) + + elif wordIs('~mintRoom'): + args = word.split() + postName = 'mintRoom' + if len(args) < 2: + if bboard.has(postName): + bboard.remove(postName) + else: + try: + id = int(args[1]) + except: + pass + else: + bboard.post(postName, id) + + elif wordIs('~mintWarp'): + args = word.split() + if len(args) < 2: + self.setMagicWordResponse('Usage: ~mintWarp roomId') + return + try: + roomNum = int(args[1]) + except: + self.setMagicWordResponse('roomId not found: %s' % args[1]) + return + + if not bboard.has('mint'): + self.setMagicWordResponse('not in a mint') + return + mint = bboard.get('mint') + if not mint.warpToRoom(roomNum): + self.setMagicWordResponse('invalid roomId or roomId not in this mint: %s' % args[1]) + return + elif wordIs('~mintLayouts'): + from toontown.coghq import MintLayout + MintLayout.printAllCashbotInfo() + self.setMagicWordResponse('logged mint layouts') + elif wordIs('~edit'): + if not __dev__: + self.setMagicWordResponse('client not running in dev mode') + return + from otp.level import EditorGlobals + level = bboard.get(EditorGlobals.EditTargetPostName) + if level == None: + self.setMagicWordResponse('no level available for editing') + return + from toontown.coghq import DistributedInGameEditor + EditorGlobals.assertReadyToEdit() + editUsername = EditorGlobals.getEditUsername() + editors = base.cr.doFindAll('DistributedInGameEditor') + for e in editors: + if isinstance(e, DistributedInGameEditor.DistributedInGameEditor): + if e.getLevelDoId() == level.doId: + if e.editorIsLocalToon() or e.getEditUsername() == editUsername: + self.setMagicWordResponse("you ('%s') are already editing this level" % editUsername) + return + + cmd = '~inGameEdit %s %s' % (level.doId, editUsername) + self.b_setMagicWord(cmd) + elif wordIs('~fshow'): + from toontown.coghq import DistributedFactory + factories = base.cr.doFindAll('DistributedFactory') + factory = None + for f in factories: + if isinstance(f, DistributedFactory.DistributedFactory): + factory = f + break + + if factory is None: + self.setMagicWordResponse('factory not found') + return + factory.setColorZones(not factory.fColorZones) + elif wordIs('~fzone'): + args = word.split() + if len(args) < 2: + self.setMagicWordResponse('Usage: ~fzone ') + return + zoneId = int(args[1]) + from toontown.coghq import DistributedFactory + factories = base.cr.doFindAll('DistributedFactory') + factory = None + for f in factories: + if isinstance(f, DistributedFactory.DistributedFactory): + factory = f + break + + if factory is None: + self.setMagicWordResponse('factory not found') + return + factory.warpToZone(zoneId) + elif wordIs('~undead'): + try: + goons = base.cr.doFindAll('Goon') + for goon in goons: + goon.undead() + + except: + self.notify.warning('Error in undead') + + elif wordIs('~resyncGoons'): + try: + goons = base.cr.doFindAll('Goon') + for goon in goons: + goon.resync() + + except: + self.notify.warning('Error in resyncing') + + elif wordIs('~catalog'): + self.doCatalog(word) + elif wordIs('~petCam') and base.wantPets: + if hasattr(base, 'petCamPrevParent'): + base.cam.reparentTo(base.petCamPrevParent) + del base.petCamPrevParent + else: + petId = base.localAvatar.getPetId() + pet = self.cr.doId2do.get(petId) + if pet: + if not hasattr(pet, 'camNode'): + pet.camNode = pet.attachNewNode('camNode') + pet.camNode.setPos(0, 0, 2.5) + base.petCamPrevParent = base.cam.getParent() + base.cam.reparentTo(pet.camNode) + elif wordIs('~lockPet') and base.wantPets: + petId = base.localAvatar.getPetId() + pet = self.cr.doId2do.get(petId) + if pet: + if not pet.isLockedDown(): + pet.lockPet() + elif wordIs('~unlockPet') and base.wantPets: + petId = base.localAvatar.getPetId() + pet = self.cr.doId2do.get(petId) + if pet: + if pet.isLockedDown(): + pet.unlockPet() + elif wordIs('~resetPetTutorial') and base.wantPets: + base.localAvatar.setPetTutorialDone(False) + response = 'Pet Tutorial flag reset' + self.setMagicWordResponse(response) + elif wordIs('~bossBattle'): + self.doBossBattle(word) + elif wordIs('~RaceChat'): + base.localAvatar.chatMgr.chatInputSpeedChat.addKartRacingMenu() + elif wordIs('~BuyKart'): + if base.wantKarts: + + def doShtikerLater(task): + base.localAvatar.addKartPage() + return 0 + + if base.localAvatar.hasKart(): + response = 'Returning Kart %s' % base.localAvatar.getKartBodyType() + base.localAvatar.requestKartDNAFieldUpdate(KartDNA.bodyType, InvalidEntry) + self.setMagicWordResponse(response) + else: + base.localAvatar.requestKartDNAFieldUpdate(KartDNA.rimsType, getDefaultRim()) + taskMgr.doMethodLater(1.0, doShtikerLater, 'doShtikerLater') + response = 'Kart %s has been purchased with body and accessory color %s.' % (word[9], getDefaultColor()) + base.localAvatar.requestKartDNAFieldUpdate(KartDNA.bodyType, int(word[9])) + self.setMagicWordResponse(response) + else: + self.setMagicWordResponse('Enable wantKarts in Config.prc') + elif wordIs('~leaveRace'): + messenger.send('leaveRace') + elif wordIs('~kartParticles'): + b = ConfigVariableBool('want-kart-particles', 0) + b.setValue(not b) + elif wordIs('~gardenGame'): + messenger.send('gardenGame') + response = 'You must be on your estate' + self.setMagicWordResponse(response) + elif wordIs('~verboseState'): + base.localAvatar.verboseState() + elif wordIs('~golf'): + self.doGolf(word) + elif wordIs('~whiteList'): + base.localAvatar.chatMgr.chatInputSpeedChat.addWhiteList() + elif wordIs('~updateWhiteList'): + self.notify.info('Updating WhiteList') + base.whiteList.redownloadWhitelist() + elif wordIs('~noWhiteList'): + base.localAvatar.chatMgr.chatInputSpeedChat.removeWhiteList() + elif wordIs('~setPaid'): + args = word.split() + if len(args) > 1: + paid = int(args[1]) + statusString = base.config.GetString('force-paid-status', 'none') + if paid: + paid = 1 + if statusString != 'none': + if statusString == 'VELVET': + ConfigVariableString('force-paid-status').setValue('FULL') + elif statusString == 'unpaid': + ConfigVariableString('force-paid-status').setValue('paid') + base.cr.setIsPaid(1) + else: + paid = 0 + if statusString != 'none': + if statusString == 'FULL': + ConfigVariableString('force-paid-status').setValue('VELVET') + elif statusString == 'paid': + ConfigVariableString('force-paid-status').setValue('unpaid') + base.cr.setIsPaid(0) + else: + return + elif wordIs('~party'): + self.doParty(word, avId, zoneId) + elif wordIs('~news'): + self.doNews(word, avId, zoneId) + elif wordIs('~bgui'): + if not hasattr(self, 'groupPanel'): + from toontown.toon import GroupPanel + self.groupPanel = GroupPanel.GroupPanel(base.localAvatar.boardingParty) + self.groupPanel.frame.show() + else: + self.groupPanel.frame.hide() + self.groupPanel.cleanup() + del self.groupPanel + elif wordIs('~generateOrder'): + args = word.split() + if len(args) > 1: + newOrder = int(args[1]) + if newOrder >= 0 and newOrder <= 2: + datagram = PyDatagram.PyDatagram() + datagram.addUint16(MsgName2Id['CLIENT_CHANGE_GENERATE_ORDER']) + datagram.addUint32(newOrder) + base.cr.send(datagram) + response = 'changing generate order to %s' % newOrder + self.setMagicWordResponse(response) + else: + response = 'args: 0 default, 1 reversed, 2 shuffled' + self.setMagicWordResponse(response) + elif wordIs('~ruler'): + response = 'Each unit is equal to one foot' + self.setMagicWordResponse(response) + if self.ruler: + self.ruler.detachNode() + del self.ruler + self.ruler = loader.loadModel('phase_3/models/props/xyzAxis') + self.ruler.reparentTo(render) + self.ruler.setPos(base.localAvatar.getPos()) + self.ruler.place() + elif wordIs('~toonIdTags'): + otherToons = base.cr.doFindAllOfType('DistributedToon') + if otherToons: + for toon in otherToons[0]: + toon.setNametagStyle(0) + + messenger.send('nameTagShowAvId', []) + base.idTags = 1 + elif wordIs('~code'): + args = word.split() + if len(args) > 1: + code = word[len(args[0]) + 1:] + base.codeRedemptionMgr.redeemCode(code, self._handleCodeRedemptionResponse) + response = 'sending code %s to server...' % code + else: + response = '~code ' + self.setMagicWordResponse(response) + return + + def _handleCodeRedemptionResponse(self, result, awardMgrResult): + if not result: + msg = 'code redeemed' + else: + if result != TTCodeRedemptionConsts.RedeemErrors.AwardCouldntBeGiven: + errMsg = TTCodeRedemptionConsts.RedeemErrorStrings[result] + else: + errMsg = AwardManagerConsts.GiveAwardErrorStrings[awardMgrResult] + msg = 'code NOT redeemed (%s)' % (errMsg,) + base.localAvatar.setChatAbsolute(msg, CFSpeech | CFTimeout) + + def doParty(self, word, av, zoneId): + args = word.split() + response = None + action = None + if len(args) == 1: + return + action = args[1] + if action == 'plan': + base.localAvatar.aboutToPlanParty = True + base.localAvatar.creatingNewPartyWithMagicWord = False + elif action == 'new': + base.localAvatar.aboutToPlanParty = False + base.localAvatar.creatingNewPartyWithMagicWord = True + elif action == 'start': + base.localAvatar.creatingNewPartyWithMagicWord = False + base.localAvatar.aboutToPlanParty = False + hoodId = ToontownGlobals.PartyHood + ToontownDistrictStats.refresh('shardInfoUpdated') + curShardTuples = base.cr.listActiveShards() + lowestPop = 100000000000000000 + shardId = None + for shardInfo in curShardTuples: + pop = shardInfo[2] + if pop < lowestPop: + lowestPop = pop + shardId = shardInfo[0] + + if shardId == base.localAvatar.defaultShard: + shardId = None + base.cr.playGame.getPlace().requestLeave({'loader': 'safeZoneLoader', + 'where': 'party', + 'how': 'teleportIn', + 'hoodId': hoodId, + 'zoneId': -1, + 'shardId': shardId, + 'avId': -1}) + elif action == 'unreleasedClient': + newVal = base.cr.partyManager.toggleAllowUnreleasedClient() + response = 'Allow Unreleased Client = %s' % newVal + elif action == 'showdoid': + newVal = base.cr.partyManager.toggleShowDoid() + response = 'show doid = %s' % newVal + elif action == 'debugGrid': + newVal = not ConfigVariableBool('show-debug-party-grid') + ConfigVariableBool('show-debug-party-grid').setValue(newVal) + response = 'Grid: %s; re-enter party to see changes.' % newVal + if response is not None: + self.setMagicWordResponse(response) + return + + def doCatalog(self, word): + args = word.split() + if len(args) == 1: + return + elif args[1] == 'reload': + phone = base.cr.doFind('phone') + if phone and phone.phoneGui: + phone.phoneGui.reload() + response = 'Reloaded catalog screen' + else: + response = 'Phone is not active.' + elif args[1] == 'dump': + if len(args) <= 2: + response = 'Specify output filename.' + else: + from toontown.catalog import CatalogGenerator + cg = CatalogGenerator.CatalogGenerator() + cg.outputSchedule(args[2]) + response = 'Catalog schedule written to file %s.' % args[2] + else: + return + self.setMagicWordResponse(response) + + def toggleRun(self): + if self.dbg_running_fast: + self.dbg_running_fast = 0 + OTPGlobals.ToonForwardSpeed = self.save_fwdspeed + OTPGlobals.ToonReverseSpeed = self.save_revspeed + OTPGlobals.ToonRotateSpeed = self.save_rotspeed + base.localAvatar.setWalkSpeedNormal() + else: + self.dbg_running_fast = 1 + self.save_fwdspeed = OTPGlobals.ToonForwardSpeed + self.save_revspeed = OTPGlobals.ToonReverseSpeed + self.save_rotspeed = OTPGlobals.ToonRotateSpeed + OTPGlobals.ToonForwardSpeed = 60 + OTPGlobals.ToonReverseSpeed = 30 + OTPGlobals.ToonRotateSpeed = 100 + base.localAvatar.setWalkSpeedNormal() + + def requestTeleport(self, loaderId, whereId, hoodId, zoneId, avId): + place = base.cr.playGame.getPlace() + if loaderId == '': + loaderId = ZoneUtil.getBranchLoaderName(zoneId) + if whereId == '': + whereId = ZoneUtil.getToonWhereName(zoneId) + if hoodId == 0: + hoodId = place.loader.hood.id + if avId == 0: + avId = -1 + place.fsm.forceTransition('teleportOut', [{'loader': loaderId, + 'where': whereId, + 'how': 'teleportIn', + 'hoodId': hoodId, + 'zoneId': zoneId, + 'shardId': None, + 'avId': avId}]) + return + + def exit_rogues(self): + self.rogues.exit() + del self.rogues + self.rogues = None + return + + def identifyDistributedObjects(self, name): + result = [] + lowerName = string.lower(name) + for obj in base.cr.doId2do.values(): + className = obj.__class__.__name__ + try: + name = obj.getName() + except: + name = className + + if string.lower(name) == lowerName or string.lower(className) == lowerName or string.lower(className) == 'distributed' + lowerName: + result.append((name, obj)) + + return result + + def getCSBitmask(self, str): + words = string.lower(str).split() + if len(words) == 0: + return None + invalid = '' + bitmask = BitMask32.allOff() + for w in words: + if w == 'wall': + bitmask |= ToontownGlobals.WallBitmask + elif w == 'floor': + bitmask |= ToontownGlobals.FloorBitmask + elif w == 'cam': + bitmask |= ToontownGlobals.CameraBitmask + elif w == 'catch': + bitmask |= ToontownGlobals.CatchBitmask + elif w == 'ghost': + bitmask |= ToontownGlobals.GhostBitmask + elif w == 'furniture': + bitmask |= ToontownGlobals.FurnitureSideBitmask | ToontownGlobals.FurnitureTopBitmask | ToontownGlobals.FurnitureDragBitmask + elif w == 'furnitureside': + bitmask |= ToontownGlobals.FurnitureSideBitmask + elif w == 'furnituretop': + bitmask |= ToontownGlobals.FurnitureTopBitmask + elif w == 'furnituredrag': + bitmask |= ToontownGlobals.FurnitureDragBitmask + elif w == 'pie': + bitmask |= ToontownGlobals.PieBitmask + else: + invalid += ' ' + w + + if invalid: + self.setMagicWordResponse('Unknown CS keyword(s): %s' % invalid) + return bitmask + + def getFontByName(self, fontname): + if fontname == 'toon': + return ToontownGlobals.getToonFont() + elif fontname == 'building': + return ToontownGlobals.getBuildingNametagFont() + elif fontname == 'minnie': + return ToontownGlobals.getMinnieFont() + elif fontname == 'suit': + return ToontownGlobals.getSuitFont() + else: + return MagicWordManager.MagicWordManager.getFontByName(self, fontname) + + def doBossBattle(self, word): + args = word.split() + bossCog = None + for distObj in self.cr.doId2do.values(): + if isinstance(distObj, DistributedBossCog.DistributedBossCog): + bossCog = distObj + break + + response = None + if len(args) == 1: + pass + elif args[1] == 'safe': + if len(args) <= 2: + flag = not bossCog.localToonIsSafe + else: + flag = int(args[2]) + bossCog.localToonIsSafe = flag + if flag: + response = 'LocalToon is now safe from boss attacks' + else: + response = 'LocalToon is now vulnerable to boss attacks' + elif args[1] == 'stun': + bossCog.stunAllGoons() + elif args[1] == 'destroy': + bossCog.destroyAllGoons() + elif args[1] == 'avatarEnter': + bossCog.d_avatarEnter() + response = 'called d_avatarEnter' + if response: + self.setMagicWordResponse(response) + return + + def doGolf(self, word): + args = word.split() + response = None + if len(args) == 1: + pass + elif args[1] == 'debugBarrier': + golfHole = base.cr.doFind('DistributedGolfHole') + if golfHole: + if hasattr(golfHole, 'golfBarrier') and not golfHole.golfBarrier.isEmpty(): + if golfHole.golfBarrier.isHidden(): + golfHole.golfBarrier.show() + response = 'showing golf barrier' + else: + golfHole.golfBarrier.hide() + response = 'hiding golf barrier' + else: + response = 'no golf hole' + elif args[1] == 'contact': + messenger.send('ode toggle contacts') + elif args[1] == 'power': + if len(args) > 2: + base.golfPower = args[2] + response = 'setting power to %s' % args[2] + else: + base.golfPower = None + response = 'unsetting power' + elif args[1] == 'heading': + if len(args) > 2: + golfHole = base.cr.doFind('DistributedGolfHole') + if golfHole: + golfHole.doMagicWordHeading(args[2]) + response = 'setting heading to %s' % args[2] + else: + response = 'need heading parameter' + elif args[1] == 'list': + response = '' + for holeId in GolfGlobals.HoleInfo: + if holeId < 18: + response += '%d: %s\n' % (holeId, GolfGlobals.getHoleName(holeId)) + + elif args[1] == 'list2': + response = '' + for holeId in GolfGlobals.HoleInfo: + if holeId >= 18: + response += '%d: %s\n' % (holeId, GolfGlobals.getHoleName(holeId)) + + if response: + self.setMagicWordResponse(response) + return + + def doNews(self, word, av, zoneId): + args = word.split() + response = None + action = None + if len(args) == 1: + return + action = args[1] + if action == 'frame': + NametagGlobals.setMasterArrowsOn(0) + from toontown.shtiker import InGameNewsFrame + base.newsFrame = InGameNewsFrame.InGameNewsFrame() + base.newsFrame.activate() + response = 'putting in game news direct frame up' + elif action == 'snapshot': + response = localAvatar.newsPage.doSnapshot() + if response is not None: + self.setMagicWordResponse(response) + return diff --git a/toontown/ai/ToontownMagicWordManagerAI.py b/toontown/ai/ToontownMagicWordManagerAI.py new file mode 100644 index 0000000..d3ca368 --- /dev/null +++ b/toontown/ai/ToontownMagicWordManagerAI.py @@ -0,0 +1,9 @@ +from direct.directnotify import DirectNotifyGlobal +from otp.ai.MagicWordManagerAI import MagicWordManagerAI + +class ToontownMagicWordManagerAI(MagicWordManagerAI): + notify = DirectNotifyGlobal.directNotify.newCategory("ToontownMagicWordManagerAI") + + def requestTeleport(self, todo0, todo1, todo2, todo3, todo4): + pass + diff --git a/toontown/shtiker/WordPage.py b/toontown/shtiker/WordPage.py new file mode 100644 index 0000000..47bfe9e --- /dev/null +++ b/toontown/shtiker/WordPage.py @@ -0,0 +1,1851 @@ +from enum import IntEnum +from direct.gui import DirectGuiGlobals +from panda3d.core import * +from direct.directnotify import DirectNotifyGlobal +from direct.interval.IntervalGlobal import * +from direct.distributed import DistributedObject +from direct.gui.DirectGui import * +from otp.otpbase import PythonUtil +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.toon import Toon +from toontown.toon import ToonDNA +from toontown.spellbook.MagicWordIndex import * +from . import ShtikerPage +from ..util.ui import make_dsl_scrollable + +PageMode = IntEnum('PageMode', ('Words', 'IDs', 'Acc1', 'Acc2')) + +class WordPage(ShtikerPage.ShtikerPage): + notify = DirectNotifyGlobal.directNotify.newCategory('WordPage') + + def __init__(self): + ShtikerPage.ShtikerPage.__init__(self) + + def load(self): + ShtikerPage.ShtikerPage.load(self) + self.wordsTabPage = WordsTabPage(self) + self.wordsTabPage.hide() + self.clothingTabPage = ClothingTabPage(self) + self.clothingTabPage.hide() + self.acc1Page = AccTabPage1(self) + self.acc1Page.hide() + self.acc2Page = AccTabPage2(self) + self.acc2Page.hide() + titleHeight = 0.61 + self.title = DirectLabel(parent=self, relief=None, text=TTLocalizer.SpellbookPageTitle, text_scale=0.12, pos=(0, 0, titleHeight)) + normalColor = (1, 1, 1, 1) + clickColor = (0.8, 0.8, 0, 1) + rolloverColor = (0.15, 0.82, 1.0, 1) + diabledColor = (1.0, 0.98, 0.15, 1) + gui = loader.loadModel('phase_3.5/models/gui/fishingBook') + self.wordsTab = DirectButton(parent=self, relief=None, text=TTLocalizer.WordPageTabTitle, text_scale=0.063, + text_align=TextNode.ALeft, text_pos=(0.0, 0.0, 0.0), + image=gui.find('**/tabs/polySurface1'), image_pos=(0.55, 1, -0.91), + image_hpr=(0, 0, -90), image_scale=(0.033, 0.033, 0.035), image_color=normalColor, + image1_color=clickColor, image2_color=rolloverColor, image3_color=diabledColor, + text_fg=Vec4(0.2, 0.1, 0, 1), command=self.setMode, extraArgs=[PageMode.Words], + pos=(-0.83, 0, 0.77)) + self.idsTab = DirectButton(parent=self, relief=None, text=TTLocalizer.ClothingPageTitle, text_scale=0.065, + text_align=TextNode.ALeft, text_pos=(0.0, 0.0, 0.0), + image=gui.find('**/tabs/polySurface2'), image_pos=(0.12, 1, -0.91), + image_hpr=(0, 0, -90), image_scale=(0.033, 0.033, 0.035), image_color=normalColor, + image1_color=clickColor, image2_color=rolloverColor, image3_color=diabledColor, + text_fg=Vec4(0.2, 0.1, 0, 1), command=self.setMode, extraArgs=[PageMode.IDs], + pos=(-0.36, 0, 0.77)) + self.acc1Tab = DirectButton(parent=self, relief=None, text=TTLocalizer.AccessoriesPageHeadTab, text_scale=0.045, + text_align=TextNode.ACenter, text_pos=(0.125, 0.035, 0.0), + image=gui.find('**/tabs/polySurface2'), image_pos=(0.12, 1, -0.91), + image_hpr=(0, 0, -90), image_scale=(0.033, 0.033, 0.035), image_color=normalColor, + image1_color=clickColor, image2_color=rolloverColor, image3_color=diabledColor, + text_fg=Vec4(0.2, 0.1, 0, 1), command=self.setMode, extraArgs=[PageMode.Acc1], + pos=(0.11, 0, 0.77)) + self.acc2Tab = DirectButton(parent=self, relief=None, text=TTLocalizer.AccessoriesPageBodyTab, text_scale=0.045, + text_align=TextNode.ACenter, text_pos=(0.125, 0.035, 0.0), + image=gui.find('**/tabs/polySurface2'), image_pos=(0.12, 1, -0.91), + image_hpr=(0, 0, -90), image_scale=(0.033, 0.033, 0.035), image_color=normalColor, + image1_color=clickColor, image2_color=rolloverColor, image3_color=diabledColor, + text_fg=Vec4(0.2, 0.1, 0, 1), command=self.setMode, extraArgs=[PageMode.Acc2], + pos=(0.58, 0, 0.77)) + return + + def enter(self): + self.setMode(PageMode.Words, updateAnyways=1) + ShtikerPage.ShtikerPage.enter(self) + + def exit(self): + self.clothingTabPage.exit() + self.wordsTabPage.exit() + self.acc1Page.exit() + self.acc2Page.exit() + ShtikerPage.ShtikerPage.exit(self) + + def unload(self): + self.wordsTabPage.unload() + self.clothingTabPage.unload() + self.acc1Page.unload() + self.acc2Page.unload() + del self.title + ShtikerPage.ShtikerPage.unload(self) + + def setMode(self, mode, updateAnyways = 0): + messenger.send('wakeup') + if not updateAnyways: + if self.mode == mode: + return + else: + self.mode = mode + if mode == PageMode.Words: + self.mode = PageMode.Words + self.title['text'] = TTLocalizer.WordPageTabTitle + self.wordsTab['state'] = DGG.DISABLED + self.wordsTabPage.enter() + self.idsTab['state'] = DGG.NORMAL + self.clothingTabPage.exit() + self.acc1Tab['state'] = DGG.NORMAL + self.acc1Page.exit() + self.acc2Tab['state'] = DGG.NORMAL + self.acc2Page.exit() + elif mode == PageMode.IDs: + self.mode = PageMode.IDs + self.title['text'] = TTLocalizer.ClothingPageTitle + self.wordsTab['state'] = DGG.NORMAL + self.wordsTabPage.exit() + self.idsTab['state'] = DGG.DISABLED + self.clothingTabPage.enter() + self.acc1Tab['state'] = DGG.NORMAL + self.acc1Page.exit() + self.acc2Tab['state'] = DGG.NORMAL + self.acc2Page.exit() + elif mode == PageMode.Acc1: + self.mode = PageMode.Acc1 + self.title['text'] = TTLocalizer.AccessoriesPageHead + self.wordsTab['state'] = DGG.NORMAL + self.wordsTabPage.exit() + self.idsTab['state'] = DGG.NORMAL + self.clothingTabPage.exit() + self.acc1Tab['state'] = DGG.DISABLED + self.acc1Page.enter() + self.acc2Tab['state'] = DGG.NORMAL + self.acc2Page.exit() + elif mode == PageMode.Acc2: + self.mode = PageMode.Acc2 + self.title['text'] = TTLocalizer.AccessoriesPageBody + self.wordsTab['state'] = DGG.NORMAL + self.wordsTabPage.exit() + self.idsTab['state'] = DGG.NORMAL + self.clothingTabPage.exit() + self.acc1Tab['state'] = DGG.NORMAL + self.acc1Page.exit() + self.acc2Tab['state'] = DGG.DISABLED + self.acc2Page.enter() + else: + raise Exception('WordPage::setMode - Invalid Mode %s' % mode) + + +class WordsTabPage(DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory('WordsTabPage') + + def __init__(self, parent = aspect2d): + self._parent = parent + self.currentSizeIndex = None + DirectFrame.__init__(self, parent=self._parent, relief=None, pos=(0.0, 0.0, 0.0), scale=(1.0, 1.0, 1.0)) + self.load() + + def load(self): + self.priorityMagicWords = [] + self.magicWords = [] + self.hiddenMagicWords = [] + self.shownMagicWords = [] + self.setupWords() + + gui = loader.loadModel('phase_3.5/models/gui/friendslist_gui') + coolbutton = loader.loadModel('phase_3/models/gui/pick_a_toon_gui') + guiButton = loader.loadModel('phase_3/models/gui/quit_button') + cdrGui = loader.loadModel('phase_3.5/models/gui/tt_m_gui_sbk_codeRedemptionGui') + guiClose = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui') + self.helpLabel = DirectLabel(parent=self, relief=None, text=TTLocalizer.WordPageHelp, text_scale=0.06, + text_wordwrap=12, text_align=TextNode.ALeft, textMayChange=1, + pos=(0.058, 0, 0.403)) + self.searchLabel = DirectLabel(parent=self, relief=None, text=TTLocalizer.WordPageSearch, text_scale=0.06, + text_wordwrap=12, text_align=TextNode.ACenter, textMayChange=1, + pos=(0.439, 0, -0.2)) + self.searchBarFrame = DirectFrame(parent=self, relief=None, image=cdrGui.find('**/tt_t_gui_sbk_cdrCodeBox'), + pos=(0.439, 0.0, -0.3225), scale=0.7) + self.searchBarEntry = DirectEntry(parent=self, relief=None, text_scale=0.06, width=7.75, textMayChange=1, + pos=(0.209, 0, -0.3325), text_align=TextNode.ALeft, backgroundFocus=0, + focusInCommand=self.toggleEntryFocus) + self.searchBarEntry.bind(DGG.TYPE, self.updateWordSearch) + self.searchBarEntry.bind(DGG.ERASE, self.updateWordSearch) + self.activatorLabel = DirectLabel(parent=self, relief=None, + text=TTLocalizer.WordPageActivator + TTLocalizer.WordPageNA, + text_scale=0.06, text_wordwrap=12, text_align=TextNode.ALeft, textMayChange=1, + pos=(0.058, 0, 0.15)) + self.totalLabel = DirectLabel(parent=self, relief=None, + text=TTLocalizer.WordPageTotal + str(len(self.shownMagicWords)), text_scale=0.06, + text_wordwrap=12, text_align=TextNode.ALeft, textMayChange=1, + pos=(0.058, 0, 0.05)) + self.currentLabel = DirectLabel(parent=self, relief=None, + text=TTLocalizer.WordPageCurrent + TTLocalizer.WordPageNA, text_scale=0.06, + text_wordwrap=12, text_align=TextNode.ALeft, textMayChange=1, + pos=(0.058, 0, -0.05)) + self.copyToChatButton = DirectButton(parent=self, relief=None, image=(guiButton.find('**/QuitBtn_UP'), + guiButton.find('**/QuitBtn_DN'), + guiButton.find('**/QuitBtn_RLVR')), + image_scale=(0.7, 1, 1), text=TTLocalizer.WordPageCopyToChat, + text_scale=0.04, text_pos=(0, -0.01), pos=(0.629, 0.0, -0.5325), + command=self.copyToChat) + self.infoButton = DirectButton(parent=self, relief=None, image=(guiButton.find('**/QuitBtn_UP'), + guiButton.find('**/QuitBtn_DN'), + guiButton.find('**/QuitBtn_RLVR')), + image_scale=(0.7, 1, 1), text=TTLocalizer.WordPageMoreInfo, + text_scale=0.04, text_pos=(0, -0.01), pos=(0.249, 0.0, -0.5325), + command=self.showInfoPanel) + self.scrollList = DirectScrolledList(parent=self, forceHeight=0.07, pos=(-0.5, 0, 0), + incButton_image=(gui.find('**/FndsLst_ScrollUp'), + gui.find('**/FndsLst_ScrollDN'), + gui.find('**/FndsLst_ScrollUp_Rllvr'), + gui.find('**/FndsLst_ScrollUp')), + incButton_relief=None, incButton_scale=(1.3, 1.3, -1.3), + incButton_pos=(0.08, 0, -0.60), incButton_image3_color=Vec4(1, 1, 1, 0.2), + decButton_image=(gui.find('**/FndsLst_ScrollUp'), + gui.find('**/FndsLst_ScrollDN'), + gui.find('**/FndsLst_ScrollUp_Rllvr'), + gui.find('**/FndsLst_ScrollUp')), + decButton_relief=None, decButton_scale=(1.3, 1.3, 1.3), + decButton_pos=(0.08, 0, 0.52), decButton_image3_color=Vec4(1, 1, 1, 0.2), + itemFrame_pos=(-0.237, 0, 0.41), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, + itemFrame_frameSize=(-0.05, 0.66, -0.98, 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), + numItemsVisible=14, items=self.shownMagicWords) + make_dsl_scrollable(self.scrollList) + self.slider = DirectSlider(parent=self, range=(len(self.shownMagicWords), 0), + scale=(0.7, 0.7, 0.515), pos=(-0.1, 0, -0.045), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + self.magicWordInfo = DirectFrame(parent=self, relief=None, pos=(0.0, 0.0, -0.05), scale=0.9, + geom=DGG.getDefaultDialogGeom(), geom_scale=(1.4, 1, 1), + geom_color=ToontownGlobals.GlobalDialogColor) + self.magicWordTitle = DirectLabel(parent=self.magicWordInfo, relief=None, text=TTLocalizer.WordPageNA, + text_scale=0.12, textMayChange=1, pos=(0, 0, 0.3500)) + self.magicWordInfoLabel = DirectLabel(parent=self.magicWordInfo, relief=None, text=TTLocalizer.WordPageNA, + text_scale=0.06, text_wordwrap=20, text_align=TextNode.ALeft, textMayChange=1, + pos=(-0.6, 0, 0.2)) + self.magicWordQuitButton = DirectButton(parent=self.magicWordInfo, + image=(guiClose.find('**/CloseBtn_UP'), + guiClose.find('**/CloseBtn_DN'), + guiClose.find('**/CloseBtn_Rllvr'), + guiClose.find('**/CloseBtn_UP')), + relief=None, scale=1.5, pos=(0.6, 0, -0.4), + command=self.hideInfoPanel) + self.magicWordInfo.hide() + gui.removeNode() + coolbutton.removeNode() + guiButton.removeNode() + cdrGui.removeNode() + guiClose.removeNode() + + def unload(self): + for word in self.magicWords + self.hiddenMagicWords + self.shownMagicWords + self.priorityMagicWords: + word.destroy() + del word + + gui = [self.helpLabel, self.searchLabel, self.searchBarFrame, self.searchBarEntry, self.activatorLabel, + self.totalLabel, self.currentLabel, self.copyToChatButton, self.infoButton, self.scrollList, + self.slider, self.magicWordTitle, self.magicWordInfoLabel, self.magicWordQuitButton, self.magicWordInfo] + + for dgui in gui: + dgui.destroy() + del dgui + + def exit(self): + for word in self.magicWords: + if word['state'] != DGG.NORMAL: + word['state'] = DGG.NORMAL + + self.activatorLabel['text'] = TTLocalizer.WordPageActivator + base.cr.magicWordManager.chatPrefix + ' (' + str(MagicWordConfig.PREFIX_ALLOWED.index(base.cr.magicWordManager.chatPrefix)) + ')' + self.currentLabel['text'] = TTLocalizer.WordPageCurrent + TTLocalizer.WordPageNA + self.ignore('mouse1') + self.toggleEntryFocus(True) + self.searchBarEntry.set('') + self.updateWordSearch() + self.hideInfoPanel() + self.hide() + + def enter(self): + for word in self.magicWords: + if word['state'] != DGG.NORMAL: + word['state'] = DGG.NORMAL + + self.activatorLabel['text'] = TTLocalizer.WordPageActivator + base.cr.magicWordManager.chatPrefix + ' (' + str(MagicWordConfig.PREFIX_ALLOWED.index(base.cr.magicWordManager.chatPrefix)) + ')' + self.currentLabel['text'] = TTLocalizer.WordPageCurrent + TTLocalizer.WordPageNA + self.accept('mouse1', self.toggleEntryFocus, extraArgs=[True]) + self.searchBarEntry.set('') + self.updateWordSearch() + self.hideInfoPanel() + self.show() + + def scrollListTo(self): + self.scrollList.scrollTo(int(self.slider['value'])) + + def toggleEntryFocus(self, lose=False): + if lose: + self.searchBarEntry['focus'] = 0 + base.localAvatar.chatMgr.fsm.request('mainMenu') + messenger.send("enable-hotkeys") + else: + base.localAvatar.chatMgr.fsm.request('otherDialog') + messenger.send("disable-hotkeys") + + def setupWords(self, returnWords=False): + words = [] + priorityWords = [] + nonCheatWords = [] + + for wordName in magicWordIndex: + word = magicWordIndex[wordName] + if word['classname'] not in words: + if not word['hidden']: + if word['priority']: + priorityWords.append(word['classname']) + if word['access'] == "NO_ACCESS": + nonCheatWords.append(word['classname']) + words.append(word['classname']) + + for word in priorityWords: + if word in words: + words.remove(word) + if word in nonCheatWords: + nonCheatWords.remove(word) + + for word in nonCheatWords: + if word in words: + words.remove(word) + + sortedWords = sorted(priorityWords) + sorted(nonCheatWords) + sorted(words) + + numWords = len(sortedWords) + if returnWords: + return numWords + + currentWordIndex = 0 + while currentWordIndex < numWords: + newWordButton = DirectButton(parent=self, relief=None, text=sortedWords[currentWordIndex], + text_align=TextNode.ALeft, text_scale=0.05, text1_bg=Vec4(0.5, 0.9, 1, 1), + text2_bg=Vec4(1, 1, 0, 1), text3_fg=Vec4(0.4, 0.8, 0.4, 1), textMayChange=0, + command=self.showWordInfo, extraArgs=[currentWordIndex]) + self.magicWords.append(newWordButton) + self.shownMagicWords.append(newWordButton) + currentWordIndex += 1 + + def showWordInfo(self, wordNum): + for word in self.magicWords: + if word['state'] != DGG.NORMAL: + word['state'] = DGG.NORMAL + + wordName = self.magicWords[wordNum] + wordName['state'] = DGG.DISABLED + self.currentLabel['text'] = TTLocalizer.WordPageCurrent + wordName['text'] + + def updateWordSearch(self, extraArgs=None): + self.hiddenMagicWords = [] + searchTerm = self.searchBarEntry.get().lower() + for word in self.magicWords + self.hiddenMagicWords: + wordLabel = word['text'].lower() + if searchTerm not in wordLabel and word in self.magicWords: + self.hiddenMagicWords.append(word) + if word['state'] != DGG.NORMAL: + self.currentLabel['text'] = TTLocalizer.WordPageCurrent + TTLocalizer.WordPageNA + word['state'] = DGG.NORMAL + word.hide() + elif searchTerm in wordLabel and word in self.hiddenMagicWords: + self.hiddenMagicWords.remove(word) + word.show() + + self.shownMagicWords = [] + for word in self.magicWords: + if word not in self.hiddenMagicWords: + self.shownMagicWords.append(word) + + magicWordCount = len(self.shownMagicWords) + if magicWordCount == 0: + magicWordCount = 1 + + self.totalLabel['text'] = TTLocalizer.WordPageTotal + str(len(self.shownMagicWords)) + self.scrollList['items'] = self.shownMagicWords + self.slider['range'] = (magicWordCount, 0) + + def copyToChat(self): + wordName = None + for word in self.magicWords: + if word['state'] != DGG.NORMAL: + wordName = word['text'] + break + + if not wordName: + return + + phrase = base.cr.magicWordManager.chatPrefix + wordName + ' ' + localAvatar.book.closeBook() + localAvatar.chatMgr.fsm.request('mainMenu') + localAvatar.chatMgr.chatInputNormal.typeCallback(None) + localAvatar.chatMgr.chatInputNormal.chatEntry.enterText(phrase) + + def showInfoPanel(self): + wordName = None + for word in self.magicWords: + if word['state'] != DGG.NORMAL: + wordName = word['text'] + break + + if not wordName: + return + + wordInfo = magicWordIndex[wordName.lower()] + wordText = TTLocalizer.WordPageDescription + wordInfo['desc'] + prefix = base.cr.magicWordManager.chatPrefix + if not wordInfo['example']: + exampleText = TTLocalizer.WordPageExample + prefix + wordName.lower() + else: + exampleText = TTLocalizer.WordPageExample + prefix + wordName.lower() + ' ' + wordInfo['example'] + aliasText = TTLocalizer.WordPageAliases + for alias in wordInfo['aliases']: + aliasText += alias + if wordInfo['aliases'].index(alias) != len(wordInfo['aliases']) - 1: + aliasText += ', ' + accessLevelText = TTLocalizer.WordPageAccessLevel + wordInfo['access'] + ' (' + str(OTPGlobals.AccessLevelName2Int.get(wordInfo['access'])) + ')' + + lineBreak = '\n\n' + infoText = wordText + lineBreak + exampleText + lineBreak + aliasText + lineBreak + accessLevelText + + self.magicWordTitle['text'] = wordName + self.magicWordInfoLabel['text'] = infoText + + self.magicWordInfo.show() + + def hideInfoPanel(self): + self.magicWordTitle['text'] = TTLocalizer.WordPageNA + self.magicWordInfoLabel['text'] = TTLocalizer.WordPageNA + + self.magicWordInfo.hide() + + +class ClothingTabPage(DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory('ClothingTabPage') + + # TODO Clean Up if possible + + def __init__(self, parent = aspect2d): + self.textRolloverColor = Vec4(1, 1, 0, 1) + self.textDownColor = Vec4(0.5, 0.9, 1, 1) + self.textDisabledColor = Vec4(0.4, 0.8, 0.4, 1) + self._parent = parent + self.currentSizeIndex = None + DirectFrame.__init__(self, parent=self._parent, relief=None, pos=(0.0, 0.0, 0.0), scale=(1.0, 1.0, 1.0)) + self.load() + + def load(self): + self.model = None + self.turnSeq = Sequence() + self.freakSeq = None + self.word1Desc = DirectLabel(parent=hidden, relief=None, text='', text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(0.058, 0, 0)) + self.shirtList = [] + self.shirtIDs = [] + self.sleeveList = [] + self.sleeveIDs = [] + self.boyPantList = [] + self.boyPantIDs = [] + self.girlPantList = [] + self.girlPantIDs = [] + self.girlPantTypes = [] + self.colorList = [] + self.colorIDs = [] + av = base.cr.doId2do.get(base.localAvatar.doId) + self.shirt = av.style.topTex + self.shirtColor = av.style.topTexColor + self.sleeve = av.style.sleeveTex + self.sleeveColor = av.style.sleeveTexColor + self.pant = av.style.botTex + self.pantColor = av.style.botTexColor + genderList = ['m', 'f'] + self.gender = genderList.index(av.style.gender) + self.color = 0 + self.torsoSize = av.style.torso[0] + self.torsoType = 's' + if len(av.style.torso) > 1: + self.torsoType = av.style.torso[1] + self.l = locals() + self.makeList() + self.shirtWords = list(self.shirtList) + self.sleeveWords = list(self.sleeveList) + self.boyPantWords = list(self.boyPantList) + self.girlPantWords = list(self.girlPantList) + self.colorWords = list(self.colorIDs) + self.rainbowEffect = Sequence() + + self.makeScrollLists() + self.createModel() + self.makeButtons() + + + def scrollListTo(self, scrollList, slider): + if slider == 'shirt': + slider = self.shirtslider + elif slider == 'sleeve': + slider = self.sleeveslider + elif slider == 'boypants': + slider = self.pantslider1 + elif slider == 'girlpants': + slider = self.pantslider2 + elif slider == 'color': + slider = self.colorslider + scrollList.scrollTo(int(slider['value'])) + + def makeList(self): + for x in range(len(ToonDNA.Shirts)): + self.l['self.shirts' + str(x)] = DirectButton(parent=self, relief=None, text=str(x) + " - " + ToonDNA.Shirts[x].split('/', 1)[-1].split('/', 1)[-1].replace(".jpg", ""), text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'shirt']) + #--disposing of long labels-- + + if ToonDNA.Shirts[x].split('/', 1)[-1].split('/', 1)[-1].startswith('apparel/') or ToonDNA.Shirts[x].split('/', 1)[-1].split('/', 1)[-1].startswith('tiers/'): + self.l['self.shirts' + str(x)]["text"] = str(x) + " - " + ToonDNA.Shirts[x].split('/', 1)[-1].split('/', 1)[-1].split('/', 1)[-1].replace(".jpg", "") + elif ToonDNA.Shirts[x].split('/', 1)[-1].split('/', 1)[-1].startswith('tt_t_chr_'): + self.l['self.shirts' + str(x)]["text"] = str(x) + " - " + ToonDNA.Shirts[x].split('/', 1)[-1].split('/', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].replace(".jpg", "") + + self.shirtList.append(self.l['self.shirts' + str(x)]) + self.shirtIDs.append( x ) + for x in range(len(ToonDNA.Sleeves)): + self.l['self.sleeves' + str(x)] = DirectButton(parent=self, relief=None, text=str(x) + " - " + ToonDNA.Sleeves[x].split('/', 1)[-1].split('/', 1)[-1].replace(".jpg", ""), text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'sleeve']) + + if ToonDNA.Sleeves[x].split('/', 1)[-1].split('/', 1)[-1].startswith('apparel/') or ToonDNA.Sleeves[x].split('/', 1)[-1].split('/', 1)[-1].startswith('tiers/'): + self.l['self.sleeves' + str(x)]["text"] = str(x) + " - " + ToonDNA.Sleeves[x].split('/', 1)[-1].split('/', 1)[-1].split('/', 1)[-1].replace(".jpg", "") + elif ToonDNA.Sleeves[x].split('/', 1)[-1].split('/', 1)[-1].startswith('tt_t_chr_'): + self.l['self.sleeves' + str(x)]["text"] = str(x) + " - " + ToonDNA.Sleeves[x].split('/', 1)[-1].split('/', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].replace(".jpg", "") + + self.sleeveList.append(self.l['self.sleeves' + str(x)]) + self.sleeveIDs.append( x ) + for x in range(len(ToonDNA.BoyShorts)): + self.l['self.boypants' + str(x)] = DirectButton(parent=self, relief=None, text=str(x) + " - " + ToonDNA.BoyShorts[x].split('/', 1)[-1].split('/', 1)[-1].replace(".jpg", ""), text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'pants']) + + if ToonDNA.BoyShorts[x].split('/', 1)[-1].split('/', 1)[-1].startswith('apparel/') or ToonDNA.BoyShorts[x].split('/', 1)[-1].split('/', 1)[-1].startswith('tiers/'): + self.l['self.boypants' + str(x)]["text"] = str(x) + " - " + ToonDNA.BoyShorts[x].split('/', 1)[-1].split('/', 1)[-1].split('/', 1)[-1].replace(".jpg", "") + elif ToonDNA.BoyShorts[x].split('/', 1)[-1].split('/', 1)[-1].startswith('tt_t_chr_'): + self.l['self.boypants' + str(x)]["text"] = str(x) + " - " + ToonDNA.BoyShorts[x].split('/', 1)[-1].split('/', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].replace(".jpg", "") + + self.boyPantList.append(self.l['self.boypants' + str(x)]) + self.boyPantIDs.append( x ) + for x in range(len(ToonDNA.GirlBottoms)): + pantsTypes = ['s', 'd'] + self.l['self.girlpants' + str(x)] = DirectButton(parent=self, relief=None, text=str(x) + " - " + ToonDNA.GirlBottoms[x][0].split('/', 1)[-1].split('/', 1)[-1].replace(".jpg", ""), text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'pants']) + + if ToonDNA.GirlBottoms[x][0].split('/', 1)[-1].split('/', 1)[-1].startswith('apparel/') or ToonDNA.GirlBottoms[x][0].split('/', 1)[-1].split('/', 1)[-1].startswith('tiers/'): + self.l['self.girlpants' + str(x)]["text"] = str(x) + " - " + ToonDNA.GirlBottoms[x][0].split('/', 1)[-1].split('/', 1)[-1].split('/', 1)[-1].replace(".jpg", "") + elif ToonDNA.GirlBottoms[x][0].split('/', 1)[-1].split('/', 1)[-1].startswith('tt_t_chr_'): + self.l['self.girlpants' + str(x)]["text"] = str(x) + " - " + ToonDNA.GirlBottoms[x][0].split('/', 1)[-1].split('/', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].replace(".jpg", "") + + #--end of eye-bleeding code-- + self.girlPantList.append(self.l['self.girlpants' + str(x)]) + self.girlPantIDs.append( x ) + self.girlPantTypes.append(pantsTypes[ToonDNA.GirlBottoms[x][1]]) + for x in range(len(ToonDNA.allColorsList)): + self.l['self.colors' + str(x)] = DirectButton(parent=self, relief=None, text=str(x), text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=0, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'color']) + self.colorList.append(self.l['self.colors' + str(x)]) + self.colorIDs.append(x) + + self.shirtLabel = DirectLabel(parent=self, relief=None, + text="Shirt ID: " + str(self.shirt) + ' / ' + str(self.shirtColor), + text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(-0.75, 0, 0.5)) + self.sleeveLabel = DirectLabel(parent=self, relief=None, + text="Sleeve ID: " + str(self.sleeve) + ' / ' + str(self.sleeveColor), + text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(-0.2, 0, 0.5)) + self.pantLabel = DirectLabel(parent=self, relief=None, + text="Pants ID: " + str(self.pant) + ' / ' + str(self.pantColor), + text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(0.35, 0, 0.5)) + self.colorSelectedLabel = DirectLabel(parent=self, relief=None, + text="Color Selected: " + str(self.color), + text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(0.35, 0, -0.65)) + pickAToonGui = loader.loadModel('phase_3.5/models/gui/matching_game_gui') + + self.colorTest = DirectButton(parent=self, relief=None, image=pickAToonGui.find('**/minnieCircle'), + image_scale=0.375, image2_scale=0.4375, pos=(0.3, 0, -0.635), text_scale=0.04, + text_align=TextNode.ACenter, text_pos=(-0.00625, 0.00625), text="Color\nAll", text_fg=Vec4(0, 0, 0, 0), + text1_fg=Vec4(0, 0, 0, 0), text2_fg=Vec4(0, 0, 0, 1), text3_fg=Vec4(0, 0, 0, 0), command=self.showWordInfo, + extraArgs=[8008135, 'huh', 'all', True]) + + pickAToonGui.removeNode() + self.colorTest.setColor(ToonDNA.allColorsList[self.color]) + self.colorTest['text2_fg'] = Vec4(0, 0, 0, 1) + Vec4(ToonDNA.allColorsList[self.color][0], ToonDNA.allColorsList[self.color][1], + ToonDNA.allColorsList[self.color][2], 1)/3 + self.nakedLabel = DirectLabel(relief=None, + text="Naked Toons\nDon't Wear Clothing!", text_font=ToontownGlobals.getSignFont(), pos=(0, 0, 0), + text_fg=(1, 0, 0, 1), text_align=TextNode.ACenter, text_scale=0.2) + self.freakSeq = Sequence(Func(self.nakedLabel.show), SoundInterval(loader.loadSfx('phase_9/audio/sfx/CHQ_GOON_tractor_beam_alarmed.ogg')), Func(self.nakedLabel.hide), Func(self.enableClothButton, 1)) + self.nakedLabel.hide() + + def unload(self): + for x in range(len(ToonDNA.Shirts)): + del self.l['self.shirts' + str(x)] + for x in range(len(ToonDNA.Sleeves)): + del self.l['self.sleeves' + str(x)] + for x in range(len(ToonDNA.BoyShorts)): + del self.l['self.boypants' + str(x)] + for x in range(len(ToonDNA.GirlBottoms)): + del self.l['self.girlpants' + str(x)] + for x in range(len(ToonDNA.allColorsList)): + del self.l['self.colors' + str(x)] + del self.shirtLabel + del self.sleeveLabel + del self.pantLabel + del self.colorSelectedLabel + del self.colorTest + if self.freakSeq.isPlaying(): + self.freakSeq.finish() + del self.nakedLabel + + self.shirtScrollList.destroy() + del self.shirtScrollList + self.sleeveScrollList.destroy() + del self.sleeveScrollList + self.pantScrollList1.destroy() + del self.pantScrollList1 + self.pantScrollList2.destroy() + del self.pantScrollList2 + self.colorScrollList.destroy() + del self.colorScrollList + + self.shirtslider.destroy() + del self.shirtslider + self.sleeveslider.destroy() + del self.sleeveslider + self.pantslider1.destroy() + del self.pantslider1 + self.pantslider2.destroy() + del self.pantslider2 + self.colorslider.destroy() + del self.colorslider + + self.toggleGender.destroy() + del self.toggleGender + self.toggleBody.destroy() + del self.toggleBody + self.toggleNude.destroy() + del self.toggleNude + self.colorShirt.destroy() + del self.colorShirt + self.colorSleeve.destroy() + del self.colorSleeve + self.colorPant.destroy() + del self.colorPant + self.setClothes.destroy() + del self.setClothes + + if self.turnSeq.isPlaying(): + self.turnSeq.finish() + del self.turnSeq + if self.rainbowEffect.isPlaying(): + self.rainbowEffect.finish() + del self.rainbowEffect + self.model.removeNode() + del self.model + + def exit(self): + self.ignore('confirmDone') + self.hide() + + def enter(self): + self.ignore('confirmDone') + self.show() + + def createModel(self): + if self.turnSeq.isPlaying(): + self.turnSeq.finish() + if self.rainbowEffect.isPlaying(): + self.rainbowEffect.finish() + self.rainbowEffect = Sequence() + magicParallel = Parallel() + if self.model: + self.model.removeNode() + del self.model + toon = Toon.Toon() + dna = ToonDNA.ToonDNA() + if self.gender == 1: + gender = 'f' + else: + gender = 'm' + dna.newToon(('cls', self.torsoSize + self.torsoType, 'm', gender)) + if self.torsoType != '': + dna.topTex = self.shirt + dna.topTexColor = self.shirtColor + dna.botTex = self.pant + dna.botTexColor = self.pantColor + dna.sleeveTex = self.sleeve + dna.sleeveTexColor = self.sleeveColor + dna.armColor = 27 + toon.setDNA(dna) + self.model = NodePath('clothing') + if self.torsoType != '': + pieceNames = ('**/1000/**/torso-top', '**/1000/**/sleeves', '**/1000/**/torso-bot',) + for name in pieceNames: + for piece in toon.findAllMatches(name): + piece.wrtReparentTo(self.model) + else: + for piece in toon.findAllMatches('**/1000/**/torso'): + piece.wrtReparentTo(self.model) + magicParallel.append(Sequence( + LerpColorInterval(piece, duration=1, color=VBase4(0.996094, 0.898438, 0.320312, 1.0)), + LerpColorInterval(piece, duration=1, color=VBase4(0.304688, 0.96875, 0.402344, 1.0)), + LerpColorInterval(piece, duration=1, color=VBase4(0.191406, 0.5625, 0.773438, 1.0)), + LerpColorInterval(piece, duration=0.5, color=VBase4(0.546875, 0.28125, 0.75, 1.0)), + LerpColorInterval(piece, duration=0.5, color=VBase4(0.933594, 0.265625, 0.28125, 1.0)) + )) + self.rainbowEffect.append(magicParallel) + self.rainbowEffect.loop() + + toon.delete() + self.model.setH(180) + self.model.reparentTo(self) + self.model.setScale(0.125) + self.model.setPos(0.2, 0, -0.425) + self.model.setBin('unsorted', 0, 1) + self.model.setDepthTest(True) + self.model.setDepthWrite(True) + self.turnSeq = LerpHprInterval(self.model, duration=5, hpr=(540, 0, 0), startHpr=(180, 0, 0)) + self.turnSeq.loop() + + def makeButtons(self): + if self.torsoSize == 'l': + b = "Long" + elif self.torsoSize == 'm': + b = "Med" + else: + b = "Short" + if self.gender == 0: + a = "Male" + else: + a = "Female" + gui = loader.loadModel('phase_3/models/gui/pick_a_toon_gui') + self.toggleGender = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), gui.find('**/QuitBtn_RLVR'), gui.find('**/QuitBtn_RLVR')), relief=None, + text='Toggle Gender (%s)' % a, text_scale=0.04, text_pos=(0, -0.0125), scale=0.8, pos=(0.2, 0, -0.4), command=self.changeBody, extraArgs=['gender']) + self.toggleBody = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), gui.find('**/QuitBtn_RLVR')), relief=None, + text='Toggle Body (%s)' % b, text_scale=0.04, text_pos=(0, -0.0125), scale=0.8, pos=(0.2, 0, -0.5), command=self.changeBody, extraArgs=['body']) + self.toggleNude = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR')), relief=None, + text='Streak', text_scale=0.065, text_pos=(0, -0.0125), scale=0.8, + pos=(0.6, 0, -0.525), command=self.changeBody, extraArgs=['nude']) + self.colorShirt = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR'), gui.find('**/QuitBtn_RLVR')), relief=None, + text='Color Shirt', text_scale=0.065, text_pos=(0, -0.0125), scale=0.6, + pos=(-0.7, 0, -0.55), command=self.showWordInfo, extraArgs=[self.color, 'huh', 'shirt', True]) + self.colorSleeve = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR'), gui.find('**/QuitBtn_RLVR')), relief=None, + text='Color Sleeve', text_scale=0.065, text_pos=(0, -0.0125), scale=0.6, + pos=(-0.45, 0, -0.55), command=self.showWordInfo, + extraArgs=[self.color, 'huh', 'sleeve', True]) + self.colorPant = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR'), gui.find('**/QuitBtn_RLVR')), relief=None, + text='Color Pants', text_scale=0.065, text_pos=(0, -0.0125), scale=0.6, + pos=(-0.2, 0, -0.55), command=self.showWordInfo, + extraArgs=[8008135, 'huh', 'pants', True]) + self.setClothes = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR'), gui.find('**/QuitBtn_DN')), relief=None, + text='Set As Outfit', text_scale=0.05, text_pos=(0, -0.0125), scale=0.8, + pos=(0, 0, -0.65), command=self.setAsClothing) + av = base.cr.doId2do.get(base.localAvatar.doId) + self.toggleNude.hide() + if len(av.style.torso) == 1: + self.changeBody("nude") + + def setAsClothing(self): + av = base.cr.doId2do.get(base.localAvatar.doId) + genderList = ['m', 'f'] + if av.style.gender != genderList[self.gender] and self.torsoType != '': + self.changeBody('gender') + newDNA = ToonDNA.ToonDNA() + newDNA.makeFromNetString(av.style.makeNetString()) + newDNA.torso = av.style.torso[0] + self.torsoType + newDNA.topTex = self.shirt + newDNA.topTexColor = self.shirtColor + newDNA.botTex = self.pant + newDNA.botTexColor = self.pantColor + newDNA.sleeveTex = self.sleeve + newDNA.sleeveTexColor = self.sleeveColor + av.sendUpdate('setMagicDNA', [newDNA.makeNetString()]) + + def nakedDetect(self): + self.enableClothButton(0) + self.freakSeq.start() + + def enableClothButton(self, active): + if active == 0: + self.setClothes['state'] = DGG.DISABLED + else: + self.setClothes['state'] = DGG.NORMAL + + def changeBody(self, type): + if type == 'gender': + if self.gender == 0: + self.gender = 1 + try: + self.torsoType = self.girlPantTypes[self.pant] + except: + self.torso = self.girlPantIDs[-1] + self.torsoType = self.girlPantTypes[-1] + self.pantScrollList1.hide() + self.pantslider1.hide() + self.pantScrollList2.show() + self.pantslider2.show() + self.toggleGender['text'] = 'Toggle Gender (Female)' + else: + self.gender = 0 + self.torsoType = 's' + self.pantScrollList2.hide() + self.pantslider2.hide() + self.pantScrollList1.show() + self.pantslider1.show() + self.toggleGender['text'] = 'Toggle Gender (Male)' + elif type == 'body': + if self.torsoSize == 'm': + self.torsoSize = 'l' + self.toggleBody['text'] = 'Toggle Body (Long)' + elif self.torsoSize == 'l': + self.torsoSize = 's' + self.toggleBody['text'] = 'Toggle Body (Short)' + else: + self.torsoSize = 'm' + self.toggleBody['text'] = 'Toggle Body (Med)' + elif type == 'nude': + if self.torsoType == '': + self.torsoType = 's' + if self.gender == 1: + try: + self.torsoType = self.girlPantTypes[self.pant] + except: + self.torso = self.girlPantIDs[-1] + self.torsoType = self.girlPantTypes[-1] + self.toggleGender['state'] = DGG.NORMAL + self.colorShirt['state'] = DGG.NORMAL + self.colorSleeve['state'] = DGG.NORMAL + self.colorPant['state'] = DGG.NORMAL + self.colorTest['state'] = DGG.NORMAL + self.toggleNude['text'] = "Streak" + else: + self.torsoType = '' + self.toggleGender['state'] = DGG.DISABLED + self.colorShirt['state'] = DGG.DISABLED + self.colorSleeve['state'] = DGG.DISABLED + self.colorPant['state'] = DGG.DISABLED + self.colorTest['state'] = DGG.DISABLED + self.toggleNude['text'] = "Clothe" + self.toggleAllWords() + self.createModel() + + def toggleAllWords(self): + i = 0 + j = 0 + currentEnabled = (self.shirt, self.sleeve, self.pant, self.pant, self.color) + for list in (self.shirtList, self.sleeveList, self.boyPantList, self.girlPantList, self.colorList): + for word in list: + if self.torsoType == '': + word['state'] = DGG.DISABLED + else: + if currentEnabled[i] == j: + word['state'] = DGG.DISABLED + else: + word['state'] = DGG.NORMAL + j += 1 + i += 1 + j = 0 + + def showWordInfo(self, wordNum, desc, clothType, isColorChange=False): + if isColorChange: + wordNum = self.color + if clothType == 'sleeve': + clothList = self.sleeveList + elif clothType == 'pants' and self.gender == 0: + clothList = self.boyPantList + elif clothType == 'pants' and self.gender == 1: + clothList = self.girlPantList + elif clothType == 'color': + clothList = self.colorList + elif clothType == 'all': + pass + else: + clothList = self.shirtList + if not isColorChange: + for word in clothList: + if word['state'] != DGG.NORMAL: + word['state'] = DGG.NORMAL + + wordName = clothList[wordNum] + wordName['state'] = DGG.DISABLED + + if clothType == 'all': + if isColorChange: + self.sleeveColor = wordNum + self.model.find('**/sleeves').setColor(ToonDNA.allColorsList[wordNum], 1) + self.pantColor = wordNum + self.model.find('**/torso-bot').setColor(ToonDNA.allColorsList[wordNum], 1) + self.shirtColor = wordNum + self.model.find('**/torso-top').setColor(ToonDNA.allColorsList[wordNum], 1) + self.shirtLabel['text'] = "Shirt ID: " + str(self.shirt) + ' / ' + str(self.shirtColor) + self.sleeveLabel['text'] = "Sleeve ID: " + str(self.sleeve) + ' / ' + str(self.sleeveColor) + self.pantLabel['text'] = "Pants ID: " + str(self.pant) + ' / ' + str(self.pantColor) + + if clothType == 'sleeve': + if isColorChange: + self.sleeveColor = wordNum + self.model.find('**/sleeves').setColor(ToonDNA.allColorsList[wordNum], 1) + else: + self.sleeve = wordNum + try: + self.model.find('**/sleeves').setTexture(loader.loadTexture(ToonDNA.Sleeves[wordNum]), 1) + except IOError: + wordNum = 0 + self.sleeve = wordNum + self.model.find('**/sleeves').setTexture(loader.loadTexture(ToonDNA.Sleeves[wordNum]), 1) + elif clothType == 'pants': + if isColorChange: + self.pantColor = wordNum + else: + self.pant = wordNum + if self.gender == 1 and self.girlPantTypes[wordNum] != self.torsoType: + self.torsoType = self.girlPantTypes[wordNum] + self.createModel() + elif self.gender == 0 and self.torsoType != 's': + self.torsoType = 's' + self.createModel() + else: + if isColorChange: + self.model.find('**/torso-bot').setColor(ToonDNA.allColorsList[wordNum], 1) + else: + try: + if self.gender == 1: + self.model.find('**/torso-bot').setTexture(loader.loadTexture(ToonDNA.GirlBottoms[wordNum][0]), 1) + else: + self.model.find('**/torso-bot').setTexture(loader.loadTexture(ToonDNA.BoyShorts[wordNum]), 1) + except IOError: + wordNum = 0 + self.pant = wordNum + if self.gender == 1: + self.model.find('**/torso-bot').setTexture(loader.loadTexture(ToonDNA.GirlBottoms[wordNum][0]), 1) + else: + self.model.find('**/torso-bot').setTexture(loader.loadTexture(ToonDNA.BoyShorts[wordNum]), 1) + elif clothType == 'color': + self.color = wordNum + else: + if isColorChange: + self.shirtColor = wordNum + self.model.find('**/torso-top').setColor(ToonDNA.allColorsList[wordNum], 1) + else: + self.shirt = wordNum + try: + self.model.find('**/torso-top').setTexture(loader.loadTexture(ToonDNA.Shirts[wordNum]), 1) + except IOError: + wordNum = 0 + self.shirt = wordNum + self.model.find('**/torso-top').setTexture(loader.loadTexture(ToonDNA.Shirts[wordNum]), 1) + + if clothType == 'shirt': + self.shirtLabel['text'] = "Shirt ID: " + str(self.shirt) + ' / ' + str(self.shirtColor) + elif clothType == 'sleeve': + self.sleeveLabel['text'] = "Sleeve ID: " + str(self.sleeve) + ' / ' + str(self.sleeveColor) + elif clothType == 'pants': + self.pantLabel['text'] = "Pants ID: " + str(self.pant) + ' / ' + str(self.pantColor) + elif clothType == 'color': + self.colorSelectedLabel['text'] = "Color Selected: " + str(self.color) + self.colorTest.setColor(ToonDNA.allColorsList[self.color]) + self.colorTest['text2_fg'] = Vec4(0, 0, 0, 1) + Vec4(ToonDNA.allColorsList[self.color][0], + ToonDNA.allColorsList[self.color][1], + ToonDNA.allColorsList[self.color][2], 1)/3 + + def makeScrollLists(self): + gui = loader.loadModel('phase_3.5/models/gui/friendslist_gui') + coolbutton = loader.loadModel('phase_3/models/gui/pick_a_toon_gui') + self.shirtScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(-0.5, 0, 0), + itemFrame_pos=(-0.237, 0, 0.41), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.shirtList)) + make_dsl_scrollable(self.shirtScrollList) + self.shirtScrollList.incButton.reparentTo(hidden) + self.shirtScrollList.decButton.reparentTo(hidden) + self.shirtslider = DirectSlider(parent=self, range=(len(self.shirtIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(-0.1, 0, 0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, extraArgs=[self.shirtScrollList, 'shirt'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.35, 1, 0.5)) + self.sleeveScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(0.35, 0, 0), + itemFrame_pos=(-0.237, 0, 0.41), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.sleeveList)) + make_dsl_scrollable(self.sleeveScrollList) + self.sleeveScrollList.incButton.reparentTo(hidden) + self.sleeveScrollList.decButton.reparentTo(hidden) + self.sleeveslider = DirectSlider(parent=self, range=(len(self.sleeveIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(0.75, 0, 0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, extraArgs=[self.sleeveScrollList, 'sleeve'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.35, 1, 0.5)) + self.pantScrollList1 = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(-0.5, 0, 0), + itemFrame_pos=(-0.237, 0, -0.08), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.boyPantList)) + make_dsl_scrollable(self.pantScrollList1) + self.pantScrollList1.incButton.reparentTo(hidden) + self.pantScrollList1.decButton.reparentTo(hidden) + self.pantslider1 = DirectSlider(parent=self, range=(len(self.boyPantIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(-0.1, 0, -0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, + extraArgs=[self.pantScrollList1, 'boypants'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + if self.gender == 1: + self.pantScrollList1.hide() + self.pantslider1.hide() + self.pantScrollList2 = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(-0.5, 0, 0), + itemFrame_pos=(-0.237, 0, -0.08), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.girlPantList)) + make_dsl_scrollable(self.pantScrollList2) + self.pantScrollList2.incButton.reparentTo(hidden) + self.pantScrollList2.decButton.reparentTo(hidden) + self.pantslider2 = DirectSlider(parent=self, range=(len(self.girlPantIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(-0.1, 0, -0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, + extraArgs=[self.pantScrollList2, 'girlpants'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + if self.gender == 0: + self.pantScrollList2.hide() + self.pantslider2.hide() + self.colorScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(0.68, 0, 0), + itemFrame_pos=(-0.237, 0, -0.08), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.33, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.colorList)) + make_dsl_scrollable(self.colorScrollList) + self.colorScrollList.incButton.reparentTo(hidden) + self.colorScrollList.decButton.reparentTo(hidden) + self.colorslider = DirectSlider(parent=self, range=(len(self.colorIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(0.75, 0, -0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, + extraArgs=[self.colorScrollList, 'color'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.75, 1, 0.5)) + gui.removeNode() + coolbutton.removeNode() + + +class AccTabPage1(DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory('AccTabPage1') + + # TODO Clean Up if possible + + def __init__(self, parent = aspect2d): + self.textRolloverColor = Vec4(1, 1, 0, 1) + self.textDownColor = Vec4(0.5, 0.9, 1, 1) + self.textDisabledColor = Vec4(0.4, 0.8, 0.4, 1) + self._parent = parent + self.currentSizeIndex = None + DirectFrame.__init__(self, parent=self._parent, relief=None, pos=(0.0, 0.0, 0.0), scale=(1.0, 1.0, 1.0)) + self.load() + + def load(self): + self.model = None + self.turnSeq = Sequence() + self.word1Desc = DirectLabel(parent=hidden, relief=None, text='', text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(0.058, 0, 0)) + self.hatList = [] + self.hatIDs = [] + self.hatTexList = [] + self.hatTexIDs = [] + self.glassesList = [] + self.glassesIDs = [] + self.glassesTexList = [] + self.glassesTexIDs = [] + self.hat = 0 + self.hatTex = 0 + self.glasses = 0 + self.glassesTex = 0 + av = base.cr.doId2do.get(base.localAvatar.doId) + self.headType = ToonDNA.toonHeadTypes.index(av.style.head) + self.headName = ToonDNA.toonHeadTypes[self.headType] + self.gender = av.style.gender + self.l = locals() + self.makeList() + self.hatWords = list(self.hatList) + self.hatTexWords = list(self.hatTexList) + self.glassesWords = list(self.glassesList) + self.glassesTexWords = list(self.glassesTexList) + + self.makeScrollLists() + self.createModel() + self.makeButtons() + + + def scrollListTo(self, scrollList, slider): + if slider == 'hat': + slider = self.hatslider + elif slider == 'hatTex': + slider = self.hattexslider + elif slider == 'glasses': + slider = self.glassesslider + elif slider == 'glassesTex': + slider = self.glassestexslider + scrollList.scrollTo(int(slider['value'])) + + def makeSplit(self, string): + abble = string.split('/', 1)[-1].split('/', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_',1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1] + return abble + + def makeList(self): + for x in range(len(ToonDNA.HatModels)): + text = str(x) + " - " + if ToonDNA.HatModels[x] == None: + text += "No Hat" + else: + text += self.makeSplit(ToonDNA.HatModels[x]) + + self.l['self.hats' + str(x)] = DirectButton(parent=self, relief=None, text=text, text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'hat']) + + self.l['self.hats' + str(x)]["text"] = text + + self.hatList.append(self.l['self.hats' + str(x)]) + self.hatIDs.append(x) + for x in range(len(ToonDNA.HatTextures)): + text = str(x) + " - " + if ToonDNA.HatTextures[x] == None: + text += "Normal Texture" + else: + text += self.makeSplit(ToonDNA.HatTextures[x].replace(".jpg", "")) + + self.l['self.hatTexs' + str(x)] = DirectButton(parent=self, relief=None, text=text, text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'hatTex']) + + self.l['self.hatTexs' + str(x)]["text"] = text + + self.hatTexList.append(self.l['self.hatTexs' + str(x)]) + self.hatTexIDs.append(x) + for x in range(len(ToonDNA.GlassesModels)): + text = str(x) + " - " + if ToonDNA.GlassesModels[x] == None: + text += "No Glasses" + else: + text += self.makeSplit(ToonDNA.GlassesModels[x]) + + self.l['self.glass' + str(x)] = DirectButton(parent=self, relief=None, text=text, text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'glasses']) + + self.l['self.glass' + str(x)]["text"] = text + + self.glassesList.append(self.l['self.glass' + str(x)]) + self.glassesIDs.append(x) + for x in range(len(ToonDNA.GlassesTextures)): + text = str(x) + " - " + if ToonDNA.GlassesTextures[x] == None: + text += "Normal Texture" + else: + text += self.makeSplit(ToonDNA.GlassesTextures[x].replace(".jpg", "")) + + self.l['self.glassTexs' + str(x)] = DirectButton(parent=self, relief=None, text=text, text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'glassesTex']) + + + self.l['self.glassTexs' + str(x)]["text"] = text + + self.glassesTexList.append(self.l['self.glassTexs' + str(x)]) + self.glassesTexIDs.append(x) + + self.hatLabel = DirectLabel(parent=self, relief=None, + text="Hat ID: (" + str(self.hat) + ', ' + str(self.hatTex) + ", 0)", + text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(-0.65, 0, 0.5)) + self.glassesLabel = DirectLabel(parent=self, relief=None, + text="Glasses ID: (" + str(self.glasses) + ', ' + str(self.glassesTex) + ", 0)", + text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(0.15, 0, 0.5)) + + + + def unload(self): + for x in range(len(ToonDNA.HatModels)): + del self.l['self.hats' + str(x)] + for x in range(len(ToonDNA.HatTextures)): + del self.l['self.hatTexs' + str(x)] + for x in range(len(ToonDNA.GlassesModels)): + del self.l['self.glass' + str(x)] + for x in range(len(ToonDNA.GlassesTextures)): + del self.l['self.glassTexs' + str(x)] + del self.hatLabel + del self.glassesLabel + + self.hatScrollList.destroy() + del self.hatScrollList + self.glassesScrollList.destroy() + del self.glassesScrollList + self.hatTexScrollList.destroy() + del self.hatTexScrollList + self.glassesTexScrollList.destroy() + del self.glassesTexScrollList + + self.hatslider.destroy() + del self.hatslider + self.glassesslider.destroy() + del self.glassesslider + self.hattexslider.destroy() + del self.hattexslider + self.glassestexslider.destroy() + del self.glassestexslider + + self.toggleGender.destroy() + del self.toggleGender + self.toggleBody.destroy() + del self.toggleBody + self.setClothes.destroy() + del self.setClothes + + if self.turnSeq.isPlaying(): + self.turnSeq.finish() + del self.turnSeq + self.model.removeNode() + del self.model + + def exit(self): + self.ignore('confirmDone') + self.hide() + + def enter(self): + self.ignore('confirmDone') + self.show() + + def createModel(self): + if self.turnSeq.isPlaying(): + self.turnSeq.finish() + if self.model: + self.model.removeNode() + del self.model + + frameGUI = loader.loadModel('phase_3.5/models/gui/fishingBook') + toonFrame = DirectFrame(parent=self, relief=None, image=frameGUI.find('**/fram3'), pos=(0.7, 0, -0.45), + scale=0.0225) + + toon = Toon.Toon() + dna = ToonDNA.ToonDNA() + dna.newToon((self.headName, 'ms', 'm', self.gender)) + dna.headColor = 27 + dna.armColor = 27 + dna.legColor = 27 + toon.setDNA(dna) + toon.setHat(self.hat, self.hatTex, 0) + toon.setGlasses(self.glasses, self.glassesTex, 0) + self.model = NodePath('clothing') + toon.getPart('head', '1000').reparentTo(self.model) + toon.getGeomNode().hide() + self.model.setH(180) + self.model.reparentTo(self) + self.model.setScale(0.125) + self.model.setPos(0.4, 0, -0.525) + self.model.setBin('unsorted', 0, 1) + self.model.setDepthTest(True) + self.model.setDepthWrite(True) + self.turnSeq = LerpHprInterval(self.model, duration=5, hpr=(540, 0, 0), startHpr=(180, 0, 0)) + self.turnSeq.loop() + + def makeButtons(self): + if self.gender == 'm': + a = "Male" + else: + a = "Female" + gui = loader.loadModel('phase_3/models/gui/pick_a_toon_gui') + self.toggleGender = DirectButton(parent=self, image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR')), relief=None, + text='Toggle Gender (%s)' % a, text_scale=0.04, text_pos=(0, -0.0125), + scale=0.8, pos=(-0.45, 0, -0.4), command=self.changeBody, extraArgs=['gender']) + self.toggleBody = DirectButton(parent=self, image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR')), relief=None, + text='Toggle Head (%s)' % self.headName, text_scale=0.04, text_pos=(0, -0.0125), + scale=0.8, pos=(-0.45, 0, -0.5), command=self.changeBody, extraArgs=['species']) + self.setClothes = DirectButton(parent=self, image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR')), relief=None, + text='Set Accessories', text_scale=0.05, text_pos=(0, -0.0125), scale=0.8, + pos=(-0.45, 0, -0.6), command=self.setAsClothing) + + def setAsClothing(self): + av = base.cr.doId2do.get(base.localAvatar.doId) + av.sendUpdate('setMagicHeadAccessories', [self.hat, self.hatTex, self.glasses, self.glassesTex]) + + def changeBody(self, type): + if type == 'gender': + if self.gender == 'm': + self.gender = 'f' + self.toggleGender['text'] = 'Toggle Gender (Female)' + else: + self.gender = 'm' + self.toggleGender['text'] = 'Toggle Gender (Male)' + elif type == 'species': + self.headType = self.headType + 1 + if self.headType > 33: + self.headType = 0 + self.headName = ToonDNA.toonHeadTypes[self.headType] + self.toggleBody['text'] = 'Toggle Head (%s)' % self.headName + self.createModel() + + def showWordInfo(self, wordNum, desc, clothType): + listNum = wordNum + if clothType == 'hat': + clothList = self.hatList + elif clothType == 'hatTex': + clothList = self.hatTexList + elif clothType == 'glasses': + clothList = self.glassesList + elif clothType == 'glassesTex': + clothList = self.glassesTexList + + for word in clothList: + if word['state'] != DGG.NORMAL: + word['state'] = DGG.NORMAL + + wordName = clothList[listNum] + wordName['state'] = DGG.DISABLED + + if clothType == 'hat': + self.hat = wordNum + elif clothType == 'hatTex': + self.hatTex = wordNum + elif clothType == 'glasses': + self.glasses = wordNum + elif clothType == 'glassesTex': + self.glassesTex = wordNum + + self.hatLabel['text'] = "Hat ID: (" + str(self.hat) + ', ' + str(self.hatTex) + ", 0)" + self.glassesLabel['text'] = "Glasses ID: (" + str(self.glasses) + ', ' + str(self.glassesTex) + ", 0)" + self.createModel() + + def makeScrollLists(self): + gui = loader.loadModel('phase_3.5/models/gui/friendslist_gui') + coolbutton = loader.loadModel('phase_3/models/gui/pick_a_toon_gui') + self.hatScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(-0.5, 0, 0), + itemFrame_pos=(-0.237, 0, 0.41), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.hatList)) + make_dsl_scrollable(self.hatScrollList) + self.hatScrollList.incButton.reparentTo(hidden) + self.hatScrollList.decButton.reparentTo(hidden) + self.hatslider = DirectSlider(parent=self, range=(len(self.hatIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(-0.1, 0, 0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, extraArgs=[self.hatScrollList, 'hat'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(-0.5, 1, 0.5)) + self.hatTexScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(-0.5, 0, 0), + itemFrame_pos=(-0.237, 0, -0.08), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.25, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=4, + items=list(self.hatTexList)) + make_dsl_scrollable(self.hatTexScrollList) + self.hatTexScrollList.incButton.reparentTo(hidden) + self.hatTexScrollList.decButton.reparentTo(hidden) + self.hattexslider = DirectSlider(parent=self, range=(len(self.hatTexIDs), 0), + scale=(0.7, 0.7, 0.15), pos=(-0.1, 0, -0.1685), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, extraArgs=[self.hatTexScrollList, 'hatTex'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + self.glassesScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(0.35, 0, 0), + itemFrame_pos=(-0.237, 0, 0.41), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.glassesList)) + make_dsl_scrollable(self.glassesScrollList) + self.glassesScrollList.incButton.reparentTo(hidden) + self.glassesScrollList.decButton.reparentTo(hidden) + self.glassesslider = DirectSlider(parent=self, range=(len(self.glassesIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(0.75, 0, 0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, + extraArgs=[self.glassesScrollList, 'glasses'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + self.glassesTexScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(0.35, 0, 0), + itemFrame_pos=(-0.237, 0, -0.08), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.25, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=4, + items=list(self.glassesTexList)) + make_dsl_scrollable(self.glassesTexScrollList) + self.glassesTexScrollList.incButton.reparentTo(hidden) + self.glassesTexScrollList.decButton.reparentTo(hidden) + self.glassestexslider = DirectSlider(parent=self, range=(len(self.glassesTexIDs), 0), + scale=(0.7, 0.7, 0.15), pos=(0.75, 0, -0.1685), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, + extraArgs=[self.glassesTexScrollList, 'glassesTex'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + gui.removeNode() + coolbutton.removeNode() + + +class AccTabPage2(DirectFrame): + notify = DirectNotifyGlobal.directNotify.newCategory('AccTabPage2') + + # TODO Clean Up if possible + + def __init__(self, parent = aspect2d): + self.textRolloverColor = Vec4(1, 1, 0, 1) + self.textDownColor = Vec4(0.5, 0.9, 1, 1) + self.textDisabledColor = Vec4(0.4, 0.8, 0.4, 1) + self._parent = parent + self.currentSizeIndex = None + DirectFrame.__init__(self, parent=self._parent, relief=None, pos=(0.0, 0.0, 0.0), scale=(1.0, 1.0, 1.0)) + self.load() + + def load(self): + self.model = None + self.turnSeq = Sequence() + self.word1Desc = DirectLabel(parent=hidden, relief=None, text='', text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(0.058, 0, 0)) + self.backpackList = [] + self.backpackIDs = [] + self.backpackTexList = [] + self.backpackTexIDs = [] + self.shoesList = [] + self.shoesIDs = [] + self.shoesTexList = [] + self.shoesTexIDs = [] + self.backpack = 0 + self.backpackTex = 0 + self.shoes = 0 + self.shoesTex = 0 + self.headType = 0 + av = base.cr.doId2do.get(base.localAvatar.doId) + self.torsoType = av.style.torso[0] + self.nudity = False + if len(av.style.torso) == 1: + self.nudity = True + self.gender = 'f' + else: + if av.style.torso[1] == 'd': + self.gender = 'f' + else: + self.gender = 'm' + self.l = locals() + self.makeList() + self.backpackWords = list(self.backpackList) + self.backpackTexWords = list(self.backpackTexList) + self.shoesWords = list(self.shoesList) + self.shoesTexWords = list(self.shoesTexList) + + self.makeScrollLists() + self.createModel() + self.makeButtons() + + + def scrollListTo(self, scrollList, slider): + if slider == 'backpack': + slider = self.backpackslider + elif slider == 'backpackTex': + slider = self.backpacktexslider + elif slider == 'shoes': + slider = self.shoesslider + elif slider == 'shoesTex': + slider = self.shoestexslider + scrollList.scrollTo(int(slider['value'])) + + def makeSplit(self, string): + abble = string.split('/', 1)[-1].split('/', 1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_',1)[-1].split('_', 1)[-1].split('_', 1)[-1].split('_', 1)[-1] + return abble + + def makeList(self): + for x in range(len(ToonDNA.BackpackModels)): + text = str(x) + " - " + if ToonDNA.BackpackModels[x] == None: + text += "No Backpack" + else: + text += self.makeSplit(ToonDNA.BackpackModels[x]) + + self.l['self.backpacks' + str(x)] = DirectButton(parent=self, relief=None, text=text, text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'backpack']) + + self.l['self.backpacks' + str(x)]["text"] = text + + self.backpackList.append(self.l['self.backpacks' + str(x)]) + self.backpackIDs.append(x) + for x in range(len(ToonDNA.BackpackTextures)): + text = str(x) + " - " + if ToonDNA.BackpackTextures[x] == None: + text += "Normal Texture" + else: + text += self.makeSplit(ToonDNA.BackpackTextures[x].replace(".jpg", "")) + + self.l['self.backpackTexs' + str(x)] = DirectButton(parent=self, relief=None, text=text, text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'backpackTex']) + + self.l['self.backpackTexs' + str(x)]["text"] = text + + self.backpackTexList.append(self.l['self.backpackTexs' + str(x)]) + self.backpackTexIDs.append(x) + for x in range(len(ToonDNA.ShoesModels)): + text = str(x) + " - " + if ToonDNA.ShoesModels[x] == None: + text += "FuckerA" + else: + text += ToonDNA.ShoesModels[x] + + self.l['self.shoe' + str(x)] = DirectButton(parent=self, relief=None, text=text, text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'shoes']) + + self.l['self.shoe' + str(x)]["text"] = text + + self.shoesList.append(self.l['self.shoe' + str(x)]) + self.shoesIDs.append(x) + for x in range(len(ToonDNA.ShoesTextures)): + text = str(x) + " - " + if ToonDNA.ShoesTextures[x] == None: + text += "FuckerB" + else: + text += self.makeSplit(ToonDNA.ShoesTextures[x].replace(".jpg", "")) + + self.l['self.shoeTexs' + str(x)] = DirectButton(parent=self, relief=None, text=text, text_align=TextNode.ALeft, text_scale=0.05, text1_bg=self.textDownColor, text2_bg=self.textRolloverColor, text3_fg=self.textDisabledColor, textMayChange=1, command=self.showWordInfo, extraArgs=[x, self.word1Desc, 'shoesTex']) + + + self.l['self.shoeTexs' + str(x)]["text"] = text + + self.shoesTexList.append(self.l['self.shoeTexs' + str(x)]) + self.shoesTexIDs.append(x) + + self.backpackLabel = DirectLabel(parent=self, relief=None, + text="Backpack ID: (" + str(self.backpack) + ', ' + str(self.backpackTex) + ", 0)", + text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(-0.75, 0, 0.5)) + self.shoesLabel = DirectLabel(parent=self, relief=None, + text="Shoes ID: (" + str(self.shoes) + ', ' + str(self.shoesTex) + ", 0)", + text_align=TextNode.ALeft, text_scale=0.06, text_wordwrap=12, pos=(0.15, 0, 0.5)) + + + + def unload(self): + for x in range(len(ToonDNA.BackpackModels)): + del self.l['self.backpacks' + str(x)] + for x in range(len(ToonDNA.BackpackTextures)): + del self.l['self.backpackTexs' + str(x)] + for x in range(len(ToonDNA.ShoesModels)): + del self.l['self.shoe' + str(x)] + for x in range(len(ToonDNA.ShoesTextures)): + del self.l['self.shoeTexs' + str(x)] + del self.backpackLabel + del self.shoesLabel + + self.backpackScrollList.destroy() + del self.backpackScrollList + self.shoesScrollList.destroy() + del self.shoesScrollList + self.backpackTexScrollList.destroy() + del self.backpackTexScrollList + self.shoesTexScrollList.destroy() + del self.shoesTexScrollList + + self.backpackslider.destroy() + del self.backpackslider + self.shoesslider.destroy() + del self.shoesslider + self.backpacktexslider.destroy() + del self.backpacktexslider + self.shoestexslider.destroy() + del self.shoestexslider + + self.toggleGender.destroy() + del self.toggleGender + self.toggleBody.destroy() + del self.toggleBody + self.setClothes.destroy() + del self.setClothes + + if self.turnSeq.isPlaying(): + self.turnSeq.finish() + del self.turnSeq + self.model.removeNode() + del self.model + + def exit(self): + self.ignore('confirmDone') + self.hide() + + def enter(self): + self.ignore('confirmDone') + self.show() + + def createModel(self): + if self.turnSeq.isPlaying(): + self.turnSeq.finish() + if self.model: + self.model.removeNode() + del self.model + toon = Toon.Toon() + dna = ToonDNA.ToonDNA() + if self.headType > 33: + self.headType = 0 + bodyEnd = 's' + if self.nudity: + bodyEnd = '' + elif self.gender == 'f': + bodyEnd = 'd' + dna.newToon(('cls', self.torsoType + bodyEnd, self.torsoType, self.gender)) + dna.sleeveTexColor = 27 + dna.topTexColor = 27 + dna.botTexColor = 27 + dna.headColor = 27 + dna.armColor = 27 + dna.legColor = 27 + toon.setDNA(dna) + toon.setBackpack(self.backpack, self.backpackTex, 0) + toon.setShoes(self.shoes, self.shoesTex, 0) + self.model = NodePath('clothing') + toon.getPart('head', '1000').removeNode() + toon.find('**/1000/**/neck').removeNode() + toon.reparentTo(self.model) + toon.loop('neutral') + self.model.setH(180) + self.model.reparentTo(self) + self.model.setScale(0.1) + self.model.setPos(0, 0, -0.525) + self.model.setBin('unsorted', 0, 1) + self.model.setDepthTest(True) + self.model.setDepthWrite(True) + self.turnSeq = LerpHprInterval(self.model, duration=5, hpr=(540, 0, 0), startHpr=(180, 0, 0)) + self.turnSeq.loop() + + def makeButtons(self): + if self.torsoType == 'l': + b = "Long" + elif self.torsoType == 'm': + b = "Med" + else: + b = "Short" + if self.nudity: + a = "None" + elif self.gender == 'f': + a = "Skirt" + else: + a = "Shorts" + gui = loader.loadModel('phase_3/models/gui/pick_a_toon_gui') + self.toggleGender = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), gui.find('**/QuitBtn_RLVR')), relief=None, + text='Toggle Clothes (%s)' % a, text_scale=0.04, text_pos=(0, -0.0125), scale=0.8, pos=(-0.4, 0, -0.5), command=self.changeBody, extraArgs=['gender']) + self.toggleBody = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), gui.find('**/QuitBtn_RLVR')), relief=None, + text='Toggle Body (%s)' % b, text_scale=0.04, text_pos=(0, -0.0125), scale=0.8, pos=(0.4, 0, -0.5), command=self.changeBody, extraArgs=['body']) + self.setClothes = DirectButton(parent=self, + image=(gui.find('**/QuitBtn_UP'), gui.find('**/QuitBtn_DN'), + gui.find('**/QuitBtn_RLVR')), relief=None, + text='Set Accessories', text_scale=0.05, text_pos=(0, -0.0125), scale=0.8, + pos=(0, 0, -0.65), command=self.setAsClothing) + + def setAsClothing(self): + av = base.cr.doId2do.get(base.localAvatar.doId) + av.sendUpdate('setMagicBodyAccessories', [self.backpack, self.backpackTex, self.shoes, self.shoesTex]) + + def changeBody(self, type): + if type == 'gender': + if self.nudity: + self.nudity = False + self.gender = 'm' + self.toggleGender['text'] = 'Toggle Clothes (Shorts)' + elif self.gender == 'f': + self.nudity = True + self.toggleGender['text'] = 'Toggle Clothes (None)' + else: + self.gender = 'f' + self.toggleGender['text'] = 'Toggle Clothes (Skirt)' + elif type == 'body': + if self.torsoType == 'm': + self.torsoType = 'l' + self.toggleBody['text'] = 'Toggle Body (Long)' + elif self.torsoType == 'l': + self.torsoType = 's' + self.toggleBody['text'] = 'Toggle Body (Short)' + else: + self.torsoType = 'm' + self.toggleBody['text'] = 'Toggle Body (Med)' + self.createModel() + + def showWordInfo(self, wordNum, desc, clothType): + if clothType == 'backpack': + clothList = self.backpackList + elif clothType == 'backpackTex': + clothList = self.backpackTexList + elif clothType == 'shoes': + clothList = self.shoesList + elif clothType == 'shoesTex': + clothList = self.shoesTexList + + for word in clothList: + if word['state'] != DGG.NORMAL: + word['state'] = DGG.NORMAL + + wordName = clothList[wordNum] + wordName['state'] = DGG.DISABLED + + if clothType == 'backpack': + self.backpack = wordNum + elif clothType == 'backpackTex': + self.backpackTex = wordNum + elif clothType == 'shoes': + self.shoes = wordNum + elif clothType == 'shoesTex': + self.shoesTex = wordNum + + self.backpackLabel['text'] = "Backpack ID: (" + str(self.backpack) + ', ' + str(self.backpackTex) + ", 0)" + self.shoesLabel['text'] = "shoes ID: (" + str(self.shoes) + ', ' + str(self.shoesTex) + ", 0)" + self.createModel() + + def makeScrollLists(self): + gui = loader.loadModel('phase_3.5/models/gui/friendslist_gui') + coolbutton = loader.loadModel('phase_3/models/gui/pick_a_toon_gui') + self.backpackScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(-0.5, 0, 0), + itemFrame_pos=(-0.237, 0, 0.41), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.backpackList)) + make_dsl_scrollable(self.backpackScrollList) + self.backpackScrollList.incButton.reparentTo(hidden) + self.backpackScrollList.decButton.reparentTo(hidden) + self.backpackslider = DirectSlider(parent=self, range=(len(self.backpackIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(-0.1, 0, 0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, extraArgs=[self.backpackScrollList, 'backpack'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(-0.5, 1, 0.5)) + self.backpackTexScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(-0.5, 0, 0), + itemFrame_pos=(-0.237, 0, -0.08), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.25, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=4, + items=list(self.backpackTexList)) + make_dsl_scrollable(self.backpackTexScrollList) + self.backpackTexScrollList.incButton.reparentTo(hidden) + self.backpackTexScrollList.decButton.reparentTo(hidden) + self.backpacktexslider = DirectSlider(parent=self, range=(len(self.backpackTexIDs), 0), + scale=(0.7, 0.7, 0.15), pos=(-0.1, 0, -0.1685), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, extraArgs=[self.backpackTexScrollList, 'backpackTex'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + self.shoesScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(0.35, 0, 0), + itemFrame_pos=(-0.237, 0, 0.41), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.4, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=6, + items=list(self.shoesList)) + make_dsl_scrollable(self.shoesScrollList) + self.shoesScrollList.incButton.reparentTo(hidden) + self.shoesScrollList.decButton.reparentTo(hidden) + self.shoesslider = DirectSlider(parent=self, range=(len(self.shoesIDs), 0), + scale=(0.7, 0.7, 0.225), pos=(0.75, 0, 0.245), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, + extraArgs=[self.shoesScrollList, 'shoes'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + self.shoesTexScrollList = DirectScrolledList(parent=self, relief=None, forceHeight=0.07, pos=(0.35, 0, 0), + itemFrame_pos=(-0.237, 0, -0.08), itemFrame_scale=1.0, + itemFrame_relief=DGG.SUNKEN, itemFrame_frameSize=(-0.05, + 0.66, + -0.25, + 0.07), + itemFrame_frameColor=(0.85, 0.95, 1, 1), + itemFrame_borderWidth=(0.01, 0.01), numItemsVisible=4, + items=list(self.shoesTexList)) + make_dsl_scrollable(self.shoesTexScrollList) + self.shoesTexScrollList.incButton.reparentTo(hidden) + self.shoesTexScrollList.decButton.reparentTo(hidden) + self.shoestexslider = DirectSlider(parent=self, range=(len(self.shoesTexIDs), 0), + scale=(0.7, 0.7, 0.15), pos=(0.75, 0, -0.1685), + pageSize=1, orientation=DGG.VERTICAL, command=self.scrollListTo, + extraArgs=[self.shoesTexScrollList, 'shoesTex'], + thumb_geom=(coolbutton.find('**/QuitBtn_UP'), + coolbutton.find('**/QuitBtn_DN'), + coolbutton.find('**/QuitBtn_RLVR'), + coolbutton.find('**/QuitBtn_UP')), + thumb_relief=None, thumb_geom_hpr=(0, 0, -90), thumb_geom_scale=(0.5, 1, 0.5)) + gui.removeNode() + coolbutton.removeNode() diff --git a/toontown/spellbook/MagicWordClientCommands.py b/toontown/spellbook/MagicWordClientCommands.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/spellbook/MagicWordConfig.py b/toontown/spellbook/MagicWordConfig.py index 99796ce..03de98d 100644 --- a/toontown/spellbook/MagicWordConfig.py +++ b/toontown/spellbook/MagicWordConfig.py @@ -1,26 +1,10 @@ -################################################## -# The Toontown Offline Magic Word Manager -################################################## -# Author: Benjamin Frisby -# Copyright: Copyright 2020, Toontown Offline -# Credits: Benjamin Frisby, John Cote, Ruby Lord, Frank, Nick, Little Cat, Ooowoo -# License: MIT -# Version: 1.0.0 -# Email: belloqzafarian@gmail.com -################################################## - -OUTGOING_CHAT_MESSAGE_NAME = 'magicWord' -CLICKED_NAMETAG_MESSAGE_NAME = 'clickedNametag' -FOCUS_OUT_MESSAGE_NAME = 'focusOut' - +INCOMING_CHAT_MESSAGE_NAME = 'magicWord' PREFIX_DEFAULT = '~' PREFIX_ALLOWED = ['~', '?', '/', '<', ':', ';'] if config.GetBool('exec-chat', False): PREFIX_ALLOWED.append('>') - -WIZARD_DEFAULT = 'Spellbook' - -MAGIC_WORD_SUCCESS_PHRASES = ['Alakazam!', 'Voila!', 'Ta-da!', 'Presto!', 'Abracadabra!'] +WIZARD_DEFAULT = 'Magic Minnie' +MAGIC_WORD_SUCCESS_PHRASES = ['Magic Word executed successfully!'] MAGIC_WORD_RESPONSES = { "SuccessNoResp": 'response will be randomly selected from MAGIC_WORD_SUCCESS_PHRASES', "Success": 'response will be provided by magic word', @@ -30,32 +14,27 @@ MAGIC_WORD_RESPONSES = { "CloseWord": 'This Magic Word doesn\'t exist! Did you mean {}?', "NoEffect": "This word doesn't affect anybody!", "BadTarget": 'Invalid target specified!', - "NoAccessToTarget": "You don't have a high enough Access Level to target them!", - "NoAccessSingleTarget": "You don't have a high enough Access Level to target {}! Their Access Level: {}. Your Access Level: {}.", + "NoAccessToTarget": "You don't have a high enough access level to target them!", "NoTarget": 'Unable to find a target!', "NoAccess": 'Your Toon does not have enough power to use this Magic Word!', + "NoCheatAccess": 'Your Toon does not have cheats enabled!', "NotEnoughArgs": 'This command takes at least {}!', "TooManyArgs": 'This command takes at most {}!', "BadArgs": 'These arguments are of the wrong type!', "CannotTarget": 'You cannot target other players with this Magic Word!', "Locked": 'You are temporarily locked down and cannot use Magic Words.', "RestrictionOther": 'You may only target one other player with this Magic Word!', - 'NonCheaty': 'You cannot use cheaty Magic Words at this time!', - 'Tutorial': 'You cannot use Magic Words in the Toontorial!' + 'LegitServer': 'You are playing on a Legit Mini-Server, so you can only use administrative commands!' } -MAGIC_WORD_NO_RESPONSE = "...I don't know how to respond!" HAS_EXTRA_MESSAGE_DATA = ["NotEnoughArgs", "TooManyArgs", "CloseWord"] -MAGIC_WORD_DEFAULT_DESC = 'A simple Magic Word.' -MAGIC_WORD_DEFAULT_ADV_DESC = 'This Magic Word does a lot of things, because reasons.' - AFFECT_TYPES = ['singular', 'zone', 'server', 'rank'] -AFFECT_TYPES_NAMES = ['Everyone in this zone', 'The entire server', 'Everyone with an Access Level of'] +AFFECT_TYPES_NAMES = ['Everyone in this zone', 'The entire server', 'Everyone with an access level of'] AFFECT_NONE = -1 -AFFECT_SELF = 0 +AFFECT_SINGLE = 0 AFFECT_OTHER = 1 AFFECT_BOTH = 2 -AFFECT_NORMAL = 0 +AFFECT_SINGULAR = 0 AFFECT_ZONE = 1 AFFECT_SERVER = 2 AFFECT_RANK = 3 @@ -69,18 +48,3 @@ ARGUMENT_NAME = 0 ARGUMENT_TYPE = 1 ARGUMENT_REQUIRED = 2 ARGUMENT_DEFAULT = 3 - -CUSTOM_SPELLBOOK_DEFAULT = '''{ - "words": - [ - { - "name": "SetPos", - "access": "MODERATOR" - }, - { - "name": "GetPos", - "access": "MODERATOR" - } - ] -} -''' diff --git a/toontown/spellbook/MagicWordIndex.py b/toontown/spellbook/MagicWordIndex.py index 543e02b..083fe3c 100644 --- a/toontown/spellbook/MagicWordIndex.py +++ b/toontown/spellbook/MagicWordIndex.py @@ -1,117 +1,135 @@ -################################################## -# The Toontown Offline Magic Word Manager -################################################## -# Author: Benjamin Frisby -# Copyright: Copyright 2020, Toontown Offline -# Credits: Benjamin Frisby, John Cote, Ruby Lord, Frank, Nick, Little Cat, Ooowoo -# License: MIT -# Version: 1.0.0 -# Email: belloqzafarian@gmail.com -################################################## +import traceback +import types +import collections +from typing import List, Dict -import collections, types - -from direct.distributed.ClockDelta import * from direct.interval.IntervalGlobal import * -from direct.showbase.DirectObject import DirectObject -from direct.showbase.PythonUtil import * -from panda3d.otp import NametagGroup, WhisperPopup +from libotp import NametagGroup from otp.otpbase import OTPLocalizer from otp.otpbase import OTPGlobals -from otp.otpbase.PythonUtil import * + +from toontown.battle import SuitBattleGlobals +from toontown.coghq import CogDisguiseGlobals +try: + from toontown.coghq import CraneLeagueGlobals + from toontown.coghq.ActivityLog import ActivityLog + from toontown.coghq.CraneLeagueHeatDisplay import CraneLeagueHeatDisplay +except Exception: + CraneLeagueGlobals = None + ActivityLog = None + CraneLeagueHeatDisplay = None +from toontown.estate import GardenGlobals +from toontown.fishing import FishGlobals +from toontown.golf import GolfGlobals +from toontown.quest import Quests +from toontown.racing.KartDNA import * +from toontown.racing import RaceGlobals +from toontown.shtiker import CogPageGlobals +from toontown.toon import NPCToons +from toontown.suit import SuitDNA +from toontown.toon import Experience +from toontown.toon import ToonDNA +from toontown.toonbase import ToontownBattleGlobals +from toontown.toonbase import ToontownGlobals +from toontown.toonbase import TTLocalizer +from toontown.hood import ZoneUtil from . import MagicWordConfig -import time, random, re, json +import time +import random +import json + +DEBUG_SCOREBOARD = None +DEBUG_HEAT = None +DEBUG_LOG = None magicWordIndex = collections.OrderedDict() -class MagicWord(DirectObject): - notify = DirectNotifyGlobal.directNotify.newCategory('MagicWord') +def getMagicWord(name): + magicWord = globals().get(name) + if not magicWord: + magicWord = MagicWord() + return magicWord - # Whether this Magic word should be considered "hidden" - # If your Toontown source has a page for Magic Words in the Sthickerbook, this will be useful for that + +class MagicWord: + # Whether this Magic word should be considered "hidden". This impacts if it is shown on the Magic Word page. hidden = False - # Whether this Magic Word is an administrative command or not - # Good for config settings where you want to disable cheaty Magic Words, but still want moderation ones + # Whether this Magic Word is an administrative command or not. + # Administrative commands will be allowed on "Legit" servers. + # Only these commands can be used on caged Toons. administrative = False - # List of names that will also invoke this word - a setHP magic word might have "hp", for example - # A Magic Word will always be callable with its class name, so you don't have to put that in the aliases + # List of names that will also invoke this word - a setHP magic word might have "hp", for example. + # A Magic Word will always be callable with its class name, so you don't have to put that in the aliases. aliases = None - # Description of the Magic Word - # If your Toontown source has a page for Magic Words in the Sthickerbook, this will be useful for that - desc = MagicWordConfig.MAGIC_WORD_DEFAULT_DESC + # Description of the Magic Word for use in the Shtikerbook. + desc = "A simple Magic Word." - # Advanced description that gives the user a lot more information than normal - # If your Toontown source has a page for Magic Words in the Sthickerbook, this will be useful for that - advancedDesc = MagicWordConfig.MAGIC_WORD_DEFAULT_ADV_DESC - - # Default example with for commands with no arguments set - # If your Toontown source has a page for Magic Words in the Sthickerbook, this will be useful for that + # Default example with for commands with no arguments set. example = "" - # The minimum access level required to use this Magic Word - accessLevel = 'MODERATOR' + # The minimum access level required to use this Magic Word. By default, USER. + accessLevel = 'USER' - # A restriction on the Magic Word which sets what kind or set of Distributed Objects it can be used on - # By default, a Magic Word can affect everyone - affectRange = [MagicWordConfig.AFFECT_SELF, MagicWordConfig.AFFECT_OTHER, MagicWordConfig.AFFECT_BOTH] + # This word has priority to be shown at the top of the spellbook page + priority = False - # Where the magic word will be executed -- EXEC_LOC_CLIENT or EXEC_LOC_SERVER + # A restriction on the Magic Word which sets what kind or set of Distributed Objects it can be used on. By default, AFFECT_EVERYONE. + affectRange = [MagicWordConfig.AFFECT_SINGLE, MagicWordConfig.AFFECT_OTHER, MagicWordConfig.AFFECT_BOTH] + + # Where the magic word will be executed -- EXEC_LOC_CLIENT or EXEC_LOC_SERVER. execLocation = MagicWordConfig.EXEC_LOC_INVALID # List of all arguments for this word, with the format [(type, isRequired), (type, isRequired)...] - # If the parameter is not required, you must provide a default argument: (type, False, default) + # If the parameter is not required, you must provide a default argument: (type, False, default). arguments = None - def __init__(self): - if self.__class__.__name__ != "MagicWord": - self.aliases = self.aliases if self.aliases is not None else [] - self.aliases.insert(0, self.__class__.__name__) - self.aliases = [x.lower() for x in self.aliases] + def __init__(self, air=None, cr=None, invokerId=None, targets=None, args=None): + self.air = air + self.cr = cr + self.invokerId = invokerId + self.targets = targets + self.args = args + self.posHprString = None + if self.__class__.__name__ != "MagicWord": # If not the base class, + self.aliases = self.aliases if self.aliases is not None else [] # if we use [] by default, it might get overwritten + self.aliases.insert(0, self.__class__.__name__) # add the class name to the alias list, + self.aliases = [x.lower() for x in self.aliases] # make all the aliases lowercase, self.arguments = self.arguments if self.arguments is not None else [] if len(self.arguments) > 0: for arg in self.arguments: argInfo = "" if not arg[MagicWordConfig.ARGUMENT_REQUIRED]: - argInfo += "(default: {0})".format(arg[MagicWordConfig.ARGUMENT_DEFAULT]) + argInfo += "(DF:{0})".format(arg[MagicWordConfig.ARGUMENT_DEFAULT]) self.example += "[{0}{1}] ".format(arg[MagicWordConfig.ARGUMENT_NAME], argInfo) - self.__register() + self.__register() # and register the magic word. def __register(self): for wordName in self.aliases: - if wordName in magicWordIndex: - self.notify.error('Duplicate Magic Word name or alias detected! Invalid name: {}'. format(wordName)) - magicWordIndex[wordName] = {'class': self, - 'classname': self.__class__.__name__, - 'hidden': self.hidden, - 'administrative': self.administrative, - 'aliases': self.aliases, - 'desc': self.desc, - 'advancedDesc': self.advancedDesc, - 'example': self.example, - 'execLocation': self.execLocation, - 'access': self.accessLevel, - 'affectRange': self.affectRange, - 'args': self.arguments} - - def loadWord(self, air=None, cr=None, invokerId=None, targets=None, args=None): - self.air = air - self.cr = cr - self.invokerId = invokerId - self.targets = targets - self.args = args + magicWordIndex[wordName] = { + 'classname': self.__class__.__name__, # This class + 'hidden': self.hidden, + 'administrative': self.administrative, + 'aliases': self.aliases, + 'desc': self.desc, + 'example': self.example, + 'execLocation': self.execLocation, + 'access': self.accessLevel, + 'priority': self.priority, + 'affectRange': self.affectRange, + 'args': self.arguments + } def executeWord(self): executedWord = None - validTargets = len(self.targets) for avId in self.targets: invoker = None toon = None @@ -121,48 +139,25 @@ class MagicWord(DirectObject): elif self.cr: invoker = self.cr.doId2do.get(self.invokerId) toon = self.cr.doId2do.get(avId) - if hasattr(toon, "getName"): - name = toon.getName() - else: - name = avId - if not self.validateTarget(toon): - if len(self.targets) > 1: - validTargets -= 1 - continue - return "{} is not a valid target!".format(name) - - # TODO: Should we implement locking? - # if toon.getLocked() and not self.administrative: - # if len(self.targets) > 1: - # validTargets -= 1 - # continue - # return "{} is currently locked. You can only use administrative commands on them.".format(name) - - if invoker.getAccessLevel() <= toon.getAccessLevel() and toon != invoker: - if len(self.targets) > 1: - validTargets -= 1 - continue - targetAccess = OTPGlobals.AccessLevelDebug2Name.get(OTPGlobals.AccessLevelInt2Name.get(toon.getAccessLevel())) - invokerAccess = OTPGlobals.AccessLevelDebug2Name.get(OTPGlobals.AccessLevelInt2Name.get(invoker.getAccessLevel())) - return "You don't have a high enough Access Level to target {0}! Their Access Level: {1}. Your Access Level: {2}.".format(name, targetAccess, invokerAccess) + if hasattr(toon, "getName"): + return "{} is not a valid target!".format(toon.getName()) + else: + return "{} is not a valid target!".format(avId) if self.execLocation == MagicWordConfig.EXEC_LOC_CLIENT: self.args = json.loads(self.args) - executedWord = self.handleWord(invoker, avId, toon, *self.args) - # If you're only using the Magic Word on one person and there is a response, return that response - if executedWord and len(self.targets) == 1: + # Attempt to run the command, if we fail then tell the user why. + try: + executedWord = self.handleWord(invoker, avId, toon, *self.args) + except Exception as e: + traceback.print_exc() + toCheck = 'AI logs' if self.execLocation == MagicWordConfig.EXEC_LOC_SERVER else 'logs' + return f"An exception was caught when trying to invoke {self.__class__.__name__}: {e}\n\nCheck {toCheck} for a traceback." + + if executedWord: return executedWord - # If the amount of targets is higher than one... - elif validTargets > 0: - # And it's only 1, and that's yourself, return None - if validTargets == 1 and self.invokerId in self.targets: - return None - # Otherwise, state how many targets you executed it on - return "Magic Word successfully executed on %s target(s)." % validTargets - else: - return "Magic Word unable to execute on any targets." def validateTarget(self, target): if self.air: @@ -174,17 +169,25 @@ class MagicWord(DirectObject): return False def handleWord(self, invoker, avId, toon, *args): - raise NotImplementedError + raise NotImplemented("Please implement the handleWord method!") + +# When creating new Magic Words, please try to use a consistent naming etiquette. Here are some rules we should follow: + +# Restock - If a word gives you an item that you can consistently obtain and use (for example, a cog summon), but you cannot specify the amount you recieve, we give this command the prefix of "Restock" +# Unlock - If a word gives you an item that you can only ever obtain once (for example, pet trick phrases or emotes), we give this command the prefix of "Unlock" +# Set - If a word gives you an item that you can consistently obtain and use, or if it is a value on your toon (for example, a pinkslip, unite, or laff point value), and you can specify the amount you recieve, we give this command the prefix of "Set" +# Transform - If a word transforms you into something, we give this command the prefix of "Transform" +# Toggle - If a word toggles something on or off, we give this command the prefix of "Toggle" + + +# Start Magic Words that were present in the source when forked from TTOff class SetHP(MagicWord): aliases = ["hp", "setlaff", "laff"] desc = "Sets the target's current laff." - advancedDesc = "This Magic Word will change the current amount of laff points the target has to whichever " \ - "value you specify. You are only allowed to specify a value between -1 and the target's maximum " \ - "laff points. If you specify a value less than 1, the target will instantly go sad unless they " \ - "are in Immortal Mode." execLocation = MagicWordConfig.EXEC_LOC_SERVER arguments = [("hp", int, True)] + accessLevel = 'USER' def handleWord(self, invoker, avId, toon, *args): hp = args[0] @@ -193,641 +196,3741 @@ class SetHP(MagicWord): return "Can't set {0}'s laff to {1}! Specify a value between -1 and {0}'s max laff ({2}).".format( toon.getName(), hp, toon.getMaxHp()) - if hp <= 0 and toon.immortalMode: - return "Can't set {0}'s laff to {1} because they are in Immortal Mode!".format(toon.getName(), hp) + if toon.getHp() > 0 >= hp: + toon.setDeathReason(DeathReason.SPELLBOOK) toon.b_setHp(hp) return "{}'s laff has been set to {}.".format(toon.getName(), hp) + class SetMaxHP(MagicWord): aliases = ["maxhp", "setmaxlaff", "maxlaff"] desc = "Sets the target's max laff." - advancedDesc = "This Magic Word will change the maximum amount of laff points the target has to whichever value " \ - "you specify. You are only allowed to specify a value between 15 and 137 laff points." execLocation = MagicWordConfig.EXEC_LOC_SERVER arguments = [("maxhp", int, True)] + accessLevel = 'USER' def handleWord(self, invoker, avId, toon, *args): maxhp = args[0] - if not 15 <= maxhp <= 137: - return "Can't set {}'s max laff to {}! Specify a value between 15 and 137.".format(toon.getName(), maxhp) + if not 1 <= maxhp <= ToontownGlobals.MaxHpLimit: + return "Can't set {}'s max laff to {}! Specify a value between 1 and {}.".format(toon.getName(), maxhp, + ToontownGlobals.MaxHpLimit) toon.b_setMaxHp(maxhp) toon.toonUp(maxhp) return "{}'s max laff has been set to {}.".format(toon.getName(), maxhp) + +class StartHoliday(MagicWord): + aliases = ["startH"] + desc = "Starts a specified holiday ID." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, True)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + id = args[0] + try: + self.air.holidayManager.startHoliday(id) + return "Started holiday %d" % id + except: + return "Invalid holiday ID: %d" % id + + +class Shout(MagicWord): + aliases = ["shout", "yell", "s", "scream"] + desc = "Sends a direct message to every toon on the server." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("shout", str, True)] + accessLevel = 'NO_ACCESS' + priority = True + + def handleWord(self, invoker, avId, toon, *args): + shout = args[0] + shoutString = f"{toon.getName()} shouts: {shout}" + try: + self.air.newsManager.d_setToonShout(shoutString) + return "Sent your shout!" + except: + return "Invalid shout!" + + +class EndHoliday(MagicWord): + aliases = ["endH"] + desc = "Ends a specified holiday ID." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, True)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + id = args[0] + try: + self.air.holidayManager.endHoliday(id) + return "Ended holiday %d" % id + except: + return "Invalid holiday ID: %d" % id + + class ToggleOobe(MagicWord): aliases = ["oobe"] desc = "Toggles the out of body experience mode, which lets you move the camera freely." - advancedDesc = "This Magic Word will toggle what is known as 'Out Of Body Experience' Mode, hence the name " \ - "'Oobe'. When this mode is active, you are able to move the camera around with your mouse- " \ - "though your camera will still follow your Toon." execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'NO_ACCESS' def handleWord(self, invoker, avId, toon, *args): base.oobe() return "Oobe mode has been toggled." + class ToggleRun(MagicWord): aliases = ["run"] desc = "Toggles run mode, which gives you a faster running speed." - advancedDesc = "This Magic Word will toggle Run Mode. When this mode is active, the target can run around at a " \ - "very fast speed." - execLocation = MagicWordConfig.EXEC_LOC_CLIENT + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'NO_ACCESS' + priority = True def handleWord(self, invoker, avId, toon, *args): - from direct.showbase.InputStateGlobal import inputState - inputState.set('debugRunning', not inputState.isSet('debugRunning')) + toon.d_setRun() return "Run mode has been toggled." -class MaxToon(MagicWord): - aliases = ["max", "idkfa"] - desc = "Maxes your target toon." - execLocation = MagicWordConfig.EXEC_LOC_SERVER - accessLevel = 'ADMIN' + +class SetSpeed(MagicWord): + aliases = ["speed"] + desc = "Sets your running speed." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + arguments = [("speed", float, False, OTPGlobals.ToonForwardSpeed)] + accessLevel = 'USER' def handleWord(self, invoker, avId, toon, *args): - from toontown.toonbase import ToontownGlobals - from toontown.quest import Quests - from toontown.suit import SuitDNA - from toontown.coghq import CogDisguiseGlobals + speed = args[0] - # TODO: Handle this better, like giving out all awards, set the quest tier, stuff like that. - # This is mainly copied from Anesidora just so I can better work on things. - toon.b_setTrackAccess([1, 1, 1, 1, 1, 1, 1]) + if not 1.0 <= speed <= 1000.0: + return "Can't set speed to {}! Specify a value between 1 and 1000.".format(toon.getName()) + if speed == OTPGlobals.ToonForwardSpeed: + base.localAvatar.currentSpeed = OTPGlobals.ToonForwardSpeed + base.localAvatar.currentReverseSpeed = OTPGlobals.ToonReverseSpeed + base.localAvatar.controlManager.setSpeeds(OTPGlobals.ToonForwardSpeed, OTPGlobals.ToonJumpForce, + OTPGlobals.ToonReverseSpeed, OTPGlobals.ToonRotateSpeed) + return "Your speed has been set to the default ({}).".format(OTPGlobals.ToonForwardSpeed) + else: + reverseSpeed = speed / 3 + base.localAvatar.currentSpeed = speed + base.localAvatar.currentReverseSpeed = reverseSpeed + base.localAvatar.controlManager.setSpeeds(speed, OTPGlobals.ToonJumpForce, reverseSpeed, + OTPGlobals.ToonRotateSpeed) + return "Your speed has been set to {}.".format(speed) + + +class MaxToon(MagicWord): + aliases = ["max"] + desc = "Maxes out the target's stats. You can provide a gag track to exclude from the target's unlocked tracks." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("missingTrack", str, False, '')] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + missingTrack = args[0] + + gagTracks = [8, 8, 8, 8, 8, 8, 8] # 8s to allow gag exp overflow + if missingTrack != '': + try: + index = ('toonup', 'trap', 'lure', 'sound', 'throw', + 'squirt', 'drop').index(missingTrack) + except: + return 'Missing Gag track is invalid!' + gagTracks[index] = 0 + toon.b_setTrackAccess(gagTracks) toon.b_setMaxCarry(ToontownGlobals.MaxCarryLimit) - toon.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit) - toon.experience.maxOutExp() - toon.d_setExperience(toon.experience.makeNetString()) + experience = Experience.Experience(toon.getExperience(), toon) + for i, track in enumerate(toon.getTrackAccess()): + if track: + experience.experience[i] = ToontownBattleGlobals.MaxSkill + toon.b_setExperience(experience.getCurrentExperience()) - toon.inventory.maxOutInv() - toon.d_setInventory(toon.inventory.makeNetString()) + toon.inventory.maxInventory(clearFirst=True) + toon.b_setInventory(toon.inventory.makeNetString()) + + toon.b_setBaseGagSkillMultiplier(50) + + toon.b_setMaxMoney(30000) + toon.b_setMoney(toon.getMaxMoney()) + toon.b_setBankMoney(ToontownGlobals.DefaultMaxBankMoney) toon.b_setMaxHp(ToontownGlobals.MaxHpLimit) - toon.b_setHp(ToontownGlobals.MaxHpLimit) + toon.toonUp(ToontownGlobals.MaxHpLimit) - toon.b_setMaxMoney(250) - toon.b_setMoney(toon.maxMoney) - toon.b_setBankMoney(toon.maxBankMoney) - - toon.b_setQuests([]) - toon.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit) - toon.b_setRewardHistory(Quests.LOOPING_FINAL_TIER, []) - - toon.b_setCogParts([*CogDisguiseGlobals.PartsPerSuitBitmasks]) - toon.b_setCogTypes([SuitDNA.suitsPerDept - 1] * 4) - toon.b_setCogLevels([ToontownGlobals.MaxCogSuitLevel] * 4) - - return f"Successfully maxed {toon.getName()}!" - -class Inventory(MagicWord): - # by default restock the inventory - aliases = ['gags', 'inv'] - desc = 'This allows you to modify your inventory in various ways.' - execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("command", str, False, ''), ("track", str, False, ""), ("level", int, False, 0), ("amount", int, False, 0)] - def handleWord(self, invoker, avId, toon, *args): - command = args[0] - # the list of words that can be used to restock the inventory - restockInvWords = ['restock', 'max', 'all', '', 'fill'] - # the list of words that can be used to empty the inventory - emptyInvWords = ['empty', 'zero', 'null', 'clear', 'none', 'reset'] - if command in restockInvWords: - toon.inventory.maxOutInv() - toon.d_setInventory(toon.inventory.makeNetString()) - return ("Maxing out inventory for " + toon.getName() + ".") - if command in emptyInvWords: - toon.inventory.zeroInv() - toon.d_setInventory(toon.inventory.makeNetString()) - return ("Zeroing inventory for " + toon.getName() + ".") - -class SetPinkSlips(MagicWord): - # this command gives the target toon the specified amount of pink slips - # default is 255 - aliases = ["pinkslips", "fires", 'setfires'] - desc = "Gives the target toon the specified amount of pink slips." - execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("amount", int, False, 255)] - - def handleWord(self, invoker, avId, toon, *args): - toon.b_setPinkSlips(args[0]) - return f"Gave {toon.getName()} {args[0]} pink slips!" - -class AbortMinigame(MagicWord): - aliases = ["exitgame", "exitminigame", "quitgame", "quitminigame", "skipgame", "skipminigame"] - desc = "Aborts an ongoing minigame." - execLocation = MagicWordConfig.EXEC_LOC_CLIENT - arguments = [] - - def handleWord(self, invoker, avId, toon, *args): - messenger.send("minigameAbort") - return "Requested minigame abort." - -class SkipMiniGolfHole(MagicWord): - aliases = ["skipgolfhole", "skipgolf", "skiphole"] - desc = "Skips the current golf hole." - execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [] - - def handleWord(self, invoker, avId, toon, *args): - from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI - course = None - for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it. - if isinstance(do, DistributedGolfCourseAI): - if invoker.doId in do.avIdList: - course = do - break - if not course: - return "You aren't in a golf course!" - - if course.isPlayingLastHole(): # If the Toon is on the final hole, calling holeOver() will softlock, so instead we move onto the reward screen. - course.demand('WaitReward') - else: - course.holeOver() - - return "Skipped the current hole." - -class AbortGolfCourse(MagicWord): - aliases = ["abortminigolf", "abortgolf", "abortcourse", "leavegolf", "leavecourse"] - desc = "Aborts the current golf course." - execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [] - - def handleWord(self, invoker, avId, toon, *args): - from toontown.golf.DistributedGolfCourseAI import DistributedGolfCourseAI - course = None - for do in simbase.air.doId2do.values(): # For all doids, check whether it's a golf course, then check if our target is part of it. - if isinstance(do, DistributedGolfCourseAI): - if invoker.doId in do.avIdList: - course = do - break - if not course: - return "You aren't in a golf course!" - - course.setCourseAbort() - - return "Aborted golf course." - -class Minigame(MagicWord): - aliases = ["mg"] - desc = "Teleport to or request the next trolley minigame." - execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("command", str, True), ("minigame", str, False, ''), ("difficulty", float, False, 0)] - - def handleWord(self, invoker, avId, toon, *args): - command = args[0] - minigame = args[1] - difficulty = args[2] - - from toontown.toonbase import ToontownGlobals - from toontown.hood import ZoneUtil - from toontown.minigame import MinigameCreatorAI - if command in ToontownGlobals.MinigameNames: - # Shortcut - minigame = args[0] - try: - difficulty = float(args[2]) - except ValueError: - difficulty = 0 - - if toon.zoneId in MinigameCreatorAI.MinigameZoneRefs: - # Already in minigame zone, assume request - command = "request" - elif toon.zoneId == ZoneUtil.getSafeZoneId(toon.zoneId): - # Assume teleport - command = "teleport" - else: - # Request by default - command = "request" - - isTeleport = command in ('teleport', 'tp') - isRequest = command in ('request', 'next') - - mgId = None - mgDiff = None if difficulty == 0 else difficulty - mgKeep = None - mgSzId = ZoneUtil.getSafeZoneId(toon.zoneId) if isTeleport else None - - if not any ((isTeleport, isRequest)): - return f"Unknown command or minigame \"{command}\". Valid commands: \"teleport\", \"request\", or a minigame to automatically teleport or request" - - try: - mgId = int(minigame) - if mgId not in ToontownGlobals.MinigameIDs: - return f"Unknown minigame ID {mgId}." - except: - if minigame not in ToontownGlobals.MinigameNames: - return f"Unknown minigame name \"{minigame}\"." - mgId = ToontownGlobals.MinigameNames.get(minigame) - - if any((isTeleport, isRequest)): - if isTeleport: - if ZoneUtil.isDynamicZone(toon.zoneId) or not toon.zoneId == mgSzId: - return "Target needs to be in a playground to teleport to a minigame." - mgSzId = ToontownGlobals.ToontownCentral if ZoneUtil.isWelcomeValley(mgSzId) else mgSzId - MinigameCreatorAI.RequestMinigame[avId] = (mgId, mgKeep, mgDiff, mgSzId) - if isTeleport: - try: - result = MinigameCreatorAI.createMinigame(self.air, [avId], mgSzId) - except: - return f"Unable to create \"{minigame}\" minigame" - - minigameZone = result['minigameZone'] - retStr = f"Teleporting {toon.getName()} to minigame \"{minigame}\"" - if mgDiff: - retStr += f" with difficulty {mgDiff}" - return retStr + "...", avId, ["minigame", "minigame", "", mgSzId, minigameZone, 0] - - # isRequest - retStr = f"Successfully requested minigame \"{minigame}\"" - if mgDiff: - retStr += f" with difficulty {mgDiff}" - return retStr + "." - - return f"Unknown command or minigame \"{command}\". Valid commands: \"teleport\", \"request\", or a minigame to automatically teleport or request" - -class Quests(MagicWord): - aliases = ["quest", "tasks", "task", "toontasks"] - desc = "Quest manupliation" - execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("command", str, True), ("index", int, False, -1)] - - def handleWord(self, invoker, avId, toon, *args): - command = args[0] - index = args[1] - """ - Commands: - - "finish": Finish a task (sets the progress to 1000), finishes all by default - """ - if command == "finish": - if index == -1: - self.air.questManager.completeAllQuestsMagically(toon) - return "Finished all quests." - else: - if self.air.questManager.completeQuestMagically(toon, index): - return f"Finished quest {index}." - return f"Quest {index} not found. (Hint: Quest indexes start at 0)" - else: - return "Valid commands: \"finish\"" - -class Factory(MagicWord): - desc = "Quickly start a Sellbot Factory." - execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [('sideEnterace', int, False, 0)] - - def handleWord(self, invoker, avId, toon, *args): - if not hasattr(self.air, "factoryMgr"): - return "No factory manager." - - from toontown.toonbase import ToontownGlobals - zoneId = self.air.factoryMgr.createFactory(ToontownGlobals.SellbotFactoryInt, 1 if args[0] > 0 else 0, [avId]) - return "Created factory, teleporting...", avId, ["cogHQLoader", "factoryInterior", "", ToontownGlobals.SellbotHQ, zoneId, 0] - -class BossBattle(MagicWord): - aliases = ["boss"] - desc = "Create a new or manupliate the current boss battle." - execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("command", str, True), ("type", str, False, ""), ("start", int, False, 1)] - - def handleWord(self, invoker, avId, toon, *args): - command = args[0].lower() - type = args[1].lower() - start = args[2] - - """ - Commands: - - create [type] [start: 1]: Creates a boss and teleports to it. - - start: Starts/Restarts the battle from the beginning. - - stop: Stops the battle by going to the Frolic state. - - skip: Skips the boss to the next state (needs getNextState to be implemented). - - final: Skips the boss to the final round. - - kill: Skips the boss to the Victory state. - """ - - # create command shortcut: - if command in ("vp", "cfo", "cj", "ceo"): - type = command - command = "create" - try: - start = int(args[1]) - except ValueError: - start = 1 - - from toontown.suit.DistributedBossCogAI import AllBossCogs - boss = None - for bc in AllBossCogs: - if bc.isToonKnown(invoker.doId): - boss = bc - break - - if command == "create": - if boss: - return "You're already in a boss battle. Please finish this one." - if type == "vp": - from toontown.suit.DistributedSellbotBossAI import DistributedSellbotBossAI - boss = DistributedSellbotBossAI(self.air) - elif type == "cfo": - from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI - boss = DistributedCashbotBossAI(self.air) - elif type == "cj": - from toontown.suit.DistributedLawbotBossAI import DistributedLawbotBossAI - boss = DistributedLawbotBossAI(self.air) - elif type == "ceo": - from toontown.suit.DistributedBossbotBossAI import DistributedBossbotBossAI - boss = DistributedBossbotBossAI(self.air) - else: - return f"Unknown boss type: \"{type}\"" - - zoneId = self.air.allocateZone() - boss.generateWithRequired(zoneId) - if start: - boss.addToon(avId) - boss.b_setState('WaitForToons') - else: - boss.b_setState('Frolic') - - self.acceptOnce(boss.uniqueName('BossDone'), self.__destroyBoss, extraArgs=[boss]) - - respText = f"Created {type.upper()} boss battle" - if not start: - respText += " in Frolic state" - - return respText + ", teleporting...", toon.doId, ["cogHQLoader", "cogHQBossBattle", "movie" if start else "teleportIn", boss.getHoodId(), boss.zoneId, 0] - - elif command == "list": - # List all the ongoing boss battles. - dept2name = {'c': 'ceo', - 'l': 'cj', - 'm': 'cfo', - 's': 'vp'} - name2dept = invertDict(dept2name) - - if not AllBossCogs: - return "No ongoing boss battles." - - respText = "\nBoss Battles:" - - if type: - # Filter by boss type - dept = name2dept.get(type) - if not dept: - return f"Can't filter by unknown type \"{type.upper()}\"" - bossBattles = (boss for boss in AllBossCogs if boss.dept == dept) - else: - bossBattles = AllBossCogs - - for boss in bossBattles: - index = AllBossCogs.index(boss) - respText += f"\n - #{index}: {dept2name.get(boss.dept, '???').upper()}, {boss.zoneId}, {boss.state}, {len(boss.involvedToons)}" - return respText - - elif command == "join": - # Join an ongoing boss battle. - if boss: - return "You're already in a boss battle. Please finish this one." - try: - index = int(type) - except ValueError: - return "Boss index not an integer!" - - if index not in range(len(AllBossCogs)): - return "Index out of range!" - - boss = AllBossCogs[index] - return "Teleporting to boss battle...", toon.doId, ["cogHQLoader", "cogHQBossBattle", "", boss.getHoodId(), boss.zoneId, 0] - - - # The following commands needs the invoker to be in a boss battle. - if not boss: - return "You ain't in a boss battle! Use the \"create\" command to create a boss battle." - - boss.acceptNewToons() - if command == "start": - boss.b_setState('WaitForToons') - return "Boss battle started!" - - elif command == "stop": - boss.b_setState("Frolic") - return "Boss battle stopped!" - - elif command == "skip": - try: - nextState = boss.getNextState() - except NotImplementedError: - return "\"getNextState\" is not implemented for this boss battle!" - if nextState: - boss.b_setState(nextState) - return f"Skipped to {nextState}!" - return f"Cannot skip \"{boss.getCurrentOrNextState()}\" state." - - elif command in ("final", "pie", "crane"): - if boss.dept == 'c': - boss.b_setState("BattleFour") - else: - boss.b_setState("BattleThree") - return "Skipped to final round!" - - elif command in ("kill", "victory", "finish"): - boss.b_setState("Victory") - return "Killed the boss!" - - # The create command is already described when the invoker is not in a battle. These are the commands - # they can use INSIDE the battle. - return f"Unknown command: \"{command}\". Valid commands: \"start\", \"stop\", \"skip\", \"final\", \"kill\"." - - def __destroyBoss(self, boss): - bossZone = boss.zoneId - boss.requestDelete() - self.air.deallocateZone(bossZone) - -class GlobalTeleport(MagicWord): - aliases = ["globaltp", "tpaccess"] - desc = "Enables teleport access to all zones." - execLocation = MagicWordConfig.EXEC_LOC_SERVER - - def handleWord(self, invoker, avId, toon, *args): - from toontown.toonbase import ToontownGlobals - toon.b_setHoodsVisited(ToontownGlobals.HoodsForTeleportAll) + toon.b_setHoodsVisited(ToontownGlobals.Hoods) toon.b_setTeleportAccess(ToontownGlobals.HoodsForTeleportAll) - return f"Enabled teleport access to all zones for {toon.getName()}." - -class Teleport(MagicWord): - aliases = ["tp", "goto"] - desc = "Teleport to a specified zone." + + toon.b_setCogParts([ + CogDisguiseGlobals.PartsPerSuitBitmasks[0], + CogDisguiseGlobals.PartsPerSuitBitmasks[1], + CogDisguiseGlobals.PartsPerSuitBitmasks[2], + CogDisguiseGlobals.PartsPerSuitBitmasks[3], + ]) + toon.b_setCogLevels([ToontownGlobals.MaxCogSuitLevel] * 4) + toon.b_setCogTypes([7] * 4) + + toon.b_setCogCount([CogPageGlobals.get_max_cog_quota(toon)] * 8 * 4) + cogStatus = [CogPageGlobals.COG_COMPLETE2] * SuitDNA.suitsPerDept + toon.b_setCogStatus(cogStatus * 4) + toon.b_setCogRadar([1] * 4) + toon.b_setBuildingRadar([1] * 4) + + for id in toon.getQuests(): + toon.removeQuest(id) + toon.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit) + toon.b_setRewardHistory(Quests.LOOPING_FINAL_TIER, toon.getRewardHistory()[1]) + + allFish = TTLocalizer.FishSpeciesNames + fishLists = [[], [], []] + for genus in list(allFish.keys()): + for species in range(len(allFish[genus])): + fishLists[0].append(genus) + fishLists[1].append(species) + fishLists[2].append(FishGlobals.getRandomWeight(genus, species)) + toon.b_setFishCollection(*fishLists) + toon.b_setFishingRod(FishGlobals.MaxRodId) + toon.b_setFishingTrophies(list(FishGlobals.TrophyDict.keys())) + + if not toon.hasKart() and simbase.wantKarts: + toon.b_setKartBodyType(list(KartDict.keys())[1]) + toon.b_setTickets(RaceGlobals.MaxTickets) + maxTrophies = RaceGlobals.NumTrophies + RaceGlobals.NumCups + toon.b_setKartingTrophies(list(range(1, maxTrophies + 1))) + toon.b_setTickets(99999) + + toon.b_setGolfHistory([600] * (GolfGlobals.MaxHistoryIndex * 2)) + + toon.b_setAccessKeys([_ for _ in range(100)]) + + return "Maxed out {}'s stats.".format(toon.getName()) + + +class UnlockEmotes(MagicWord): + aliases = ["emotes"] + desc = "Unlock all of the target's emotes." execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("zoneName", str, False, '')] + accessLevel = 'NO_ACCESS' def handleWord(self, invoker, avId, toon, *args): - from toontown.hood import ZoneUtil - from toontown.toonbase import ToontownGlobals - zoneName = args[0] + emoteAccess = list(toon.getEmoteAccess()) - # Can add stuff like streets to this too if you wanted, but if you do you'll want it to be a valid zone on that street. eg: 2100 is invalid, but any value 2101 to 2156 is fine. - # so if you wanted to add a silly street key, theroetically you could do something like this: 'sillystreet': ToontownGlobals.SillyStreet +1, - zoneName2Id = {'ttc': ToontownGlobals.ToontownCentral, - 'dd': ToontownGlobals.DonaldsDock, - 'dg': ToontownGlobals.DaisyGardens, - 'mml': ToontownGlobals.MinniesMelodyland, - 'tb': ToontownGlobals.TheBrrrgh, - 'ddl': ToontownGlobals.DonaldsDreamland, - 'gs': ToontownGlobals.GoofySpeedway, - 'oz': ToontownGlobals.OutdoorZone, - 'aa': ToontownGlobals.OutdoorZone, - 'gz': ToontownGlobals.GolfZone, - 'sbhq': ToontownGlobals.SellbotHQ, - 'factory': ToontownGlobals.SellbotFactoryExt, - 'cbhq': ToontownGlobals.CashbotHQ, - 'lbhq': ToontownGlobals.LawbotHQ, - 'bbhq': ToontownGlobals.BossbotHQ} - - try: - zone = zoneName2Id[zoneName] - except KeyError: - return "Unknown zone name!" + # Old version of command made emote access list shorter. Let's fix that. + if len(emoteAccess) < len(OTPLocalizer.EmoteFuncDict): + emoteAccess = [0] * len(OTPLocalizer.EmoteFuncDict) - return f"Requested to teleport {toon.getName()} to zone {zone}.", toon.doId, [ZoneUtil.getBranchLoaderName(zone), ZoneUtil.getToonWhereName(zone), "", ZoneUtil.getHoodId(zone), zone, 0] + for emoteId in list(OTPLocalizer.EmoteFuncDict.values()): + if emoteId > 25 or emoteId in [17, 18, 19]: + continue + emoteAccess[emoteId] = 1 -class ToggleSleep(MagicWord): - aliases = ["sleep", "nosleep", "neversleep", "togglesleeping", "insomnia"] - desc = "Toggles sleeping for the target." + toon.b_setEmoteAccess(emoteAccess) + return "Unlocked all of {}'s emotes.".format(toon.getName()) + + +class SetBeans(MagicWord): + aliases = ["beans", "setjellybeans", "jellybeans", "setmoney", "money"] + desc = "Sets the target's jellybean count." execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("beans", int, True)] + accessLevel = 'USER' def handleWord(self, invoker, avId, toon, *args): - toon.d_toggleSleep() - return f"Toggled sleeping for {toon.getName()}." - + beans = args[0] + + if not 0 <= beans <= toon.getMaxMoney(): + return ("Can't set {0}'s jellybean count to {1}! Specify a value between 0 and {0}'s jellybean jar size ({" + "2}).").format(toon.getName(), beans, toon.getMaxMoney()) + + toon.b_setMoney(beans) + return "{}'s jellybean count has been set to {}.".format(toon.getName(), beans) + + +class SetMaxBeans(MagicWord): + aliases = ["maxbeans", "setmaxjellybeans", "maxjellybeans", "setmaxmoney", "maxMoney"] + desc = "Sets the target's jellybean jar size." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("maxBeans", int, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + maxBeans = args[0] + + if not 0 <= maxBeans <= 9999: + return "Can't set {}'s jellybean jar size to {}! Specify a value between 0 and 9999.".format(toon.getName(), + maxBeans) + + toon.b_setMaxMoney(maxBeans) + return "{}'s jellybean jar size has been set to {}.".format(toon.getName(), maxBeans) + + +######################################################################################################################## +class SetEmblems(MagicWord): + aliases = ["emblems"] + desc = "Gives the target a specified amount of silver and gold emblems, respectively." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("silver", int, True), ("gold", int, True)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + emblems = (args[0], args[1]) + toon.addEmblems(emblems) + return "Gave {} {} silver and {} gold emblems!".format(toon.getName(), *emblems) + + class ToggleImmortal(MagicWord): - aliases = ["immortal", "invincible", "invulnerable"] - desc = "Toggle immortal mode. This makes the Toon immune to damage." + aliases = ["immortal"] + desc = "Makes the target immortal." execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' def handleWord(self, invoker, avId, toon, *args): - toon.setImmortalMode(not toon.immortalMode) - return f"Toggled immortal mode for {toon.getName()}" + toon.b_setImmortalMode(not toon.getImmortalMode()) + return "{} is {} immortal!".format(toon.getName(), "now" if toon.getImmortalMode() else "no longer") + + +class ToggleUnlimitedGags(MagicWord): + aliases = ["unlimitedgags"] + desc = "Toggles unlimited gags for the target." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + + from toontown.toon.InventoryBase import InventoryBase + + inventory: InventoryBase = toon.inventory + inventory.maxInventory(mode=InventoryBase.FillMode.ALL, clearFirst=True) + + invoker.b_setInventory(inventory.makeNetString()) + toon.b_setUnlimitedGags(not toon.getUnlimitedGags()) + + return "{} {} has unlimited gags!".format(toon.getName(), "now" if toon.getUnlimitedGags() else "no longer") + + +class ToggleInstaKill(MagicWord): + aliases = ["instakill"] + desc = "Lets the target instantly kill Cogs with any amount of damage." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + toon.b_setInstaKill(not toon.getInstaKill()) + return "{} can {} insta-kill Cogs!".format(toon.getName(), "now" if toon.getInstaKill() else "no longer") + +class SetDamageMultiplier(MagicWord): + aliases = ["damagemult", "mult", "dm"] + desc = "Sets the target's damage mult to the given %." + arguments = [("damageMult", int, True)] + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + mult = args[0] + if mult < 1 or mult > 500: + return "Your damage mult must be within the range 1-500!" + if invoker != toon: + return "You can only set your own damage mult!" + toon.b_setDamageMultiplier(mult) + return "{}'s damage mult is now at {}%!".format(toon.getName(), str(mult)) + + +class SetOverflowMultiplier(MagicWord): + aliases = ["overflow", "setoverflow", "om"] + desc = "Sets the target's overflow rate to the given %." + arguments = [("overflowMult", int, True)] + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + mult = args[0] + if mult < 1 or mult > 300: + return "Your overflow rate must be within the range 1-300!" + if invoker != toon: + return "You can only set your own overflow rate!" + toon.b_setOverflowMod(mult) + return "{}'s overflow rate is now at {}%!".format(toon.getName(), str(mult)) + + +class SkipMovie(MagicWord): + desc = "Skips the current round of animations in a Cog battle." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battleId = toon.getBattleId() + if not battleId: + return "You cannot skip a movie if you are not currently in battle!" + battle = self.air.doId2do.get(battleId) + if not battle: + return "{} is not a valid battle!".format(battleId) + battle._DistributedBattleBaseAI__movieDone() + return "Successfully skipped Battle movie!" + + +class ToggleGod(MagicWord): + aliases = ["god", 'godmode'] + desc = "Makes the target fast, immortal, all-powerful, and omnipotent." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + isGod = not toon.getImmortalMode() + toon.d_setRun() + toon.b_setImmortalMode(isGod) + toon.b_setUnlimitedGags(isGod) + toon.b_setInstaKill(isGod) + return "God mode {} for {}!".format("enabled" if isGod else "disabled", toon.getName()) + + +class ToggleCollisionsOff(MagicWord): + aliases = ['collisionsoff', 'noclip'] + desc = "Disables collisions for the target." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + return "This command is not supported. If you need to noclip, please use ~ghost." + + +class GetPos(MagicWord): + desc = "Get the current position of your toon." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + if not base.localAvatar: + return "Your Toon does not exist!" + return str(base.localAvatar.getPos()) + + +class SetPos(MagicWord): + desc = "Set the current position of your Toon." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + arguments = [("x", float, True), ("y", float, True), ("z", float, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + toonX = args[0] + toonY = args[1] + toonZ = args[2] + if not base.localAvatar: + return "Your Toon does not exist!" + for arg in args: + if not -2500 <= arg <= 2500: + return "This position is too far out!" + base.localAvatar.setPos(toonX, toonY, toonZ) + + +class GetHpr(MagicWord): + desc = "Returns the rotation value of your Toon." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + if not base.localAvatar: + return "No Toon found!" + return str(base.localAvatar.getHpr()) + + +class SetHpr(MagicWord): + desc = "Set the rotation value of your Toon." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + arguments = [("h", int, True), ("p", int, True), ("r", int, True), ("part", str, False, '')] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + toonH = args[0] + + if not base.localAvatar: + return "No Toon found!" + + part = args[3].lower() + + if part in ('head', 'torso', 'legs'): + for lod in toon.getLODNames(): + base.localAvatar.getPart(part, lod).setH(toonH) + + base.localAvatar.setHpr(args[0], args[1], args[2]) + + +class PrintPosHpr(MagicWord): + aliases = ['pph'] + desc = "Print Pos Hpr" + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + count = 0 + + def printPosHpr(count): + count += 1 + if count <= 100: + print(("%s %s" % (toon.getPos(), toon.getHpr()))) + taskMgr.doMethodLater(0.1, printPosHpr, "print the damn points", extraArgs=[count]) + else: + return + + printPosHpr(0) + + +class Camera(MagicWord): + aliases = ['cam'] + desc = "Set a movie sequence" + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + arguments = [("command", str, True), ("x", float, False, 0), ("y", float, False, 0), ("z", float, False, 0), + ("h", float, False, 0), ("p", float, False, 0), ("r", float, False, 0), ("duration", float, False, 1)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + command = args[0] + x = args[1] + y = args[2] + z = args[3] + h = args[4] + p = args[5] + r = args[6] + duration = args[7] + valid_commands = ['startpoint', 'addpoint', 'clear', 'play', 'list', 'ease'] + if command not in valid_commands: + return "Invalid command! Please use ~camera startpoint or ~camera addpoint." + if command == 'startpoint': + if None in [x, y, z, h, p, r]: + return "The command syntax is [~camera startpoint x y z h p r]!" + base.localAvatar.camStart = [x, y, z, h, p, r] + return "%d %d %d %d %d %d is set as the starting point!" % (x, y, z, h, p, r) + elif command == 'addpoint': + if None in [x, y, z, h, p, r]: + return "The command syntax is [~camera addpoint x y z h p r duration]!" + base.localAvatar.camPoints.append([x, y, z, h, p, r, duration]) + return "Point %d %d %d %d %d %d with a duration of %d was added!" % (x, y, z, h, p, r, duration) + elif command == 'clear': + base.localAvatar.camPoints = [] + return "Successfully cleared all camera points!" + elif command == 'play': + start = base.localAvatar.camStart + camera = base.camera + track = Sequence() + track.append(Func(base.localAvatar.stopUpdateSmartCamera)) + track.append(Func(camera.wrtReparentTo, render)) + track.append(Func(camera.setPosHpr, start[0], start[1], start[2], start[3], start[4], start[5])) + for point in base.localAvatar.camPoints: + track.append(camera.posHprInterval(point[6], Point3(point[0], point[1], point[2]), + Point3(point[3], point[4], point[5]))) + track.append(Func(camera.wrtReparentTo, base.localAvatar)) + track.append(Func(base.localAvatar.startUpdateSmartCamera)) + track.start() + elif command == 'ease': + start = base.localAvatar.camStart + camera = base.camera + track = Sequence() + track.append(Func(base.localAvatar.stopUpdateSmartCamera)) + track.append(Func(camera.wrtReparentTo, render)) + track.append(Func(camera.setPosHpr, start[0], start[1], start[2], start[3], start[4], start[5])) + for point in base.localAvatar.camPoints: + track.append(camera.posHprInterval(point[6], Point3(point[0], point[1], point[2]), + Point3(point[3], point[4], point[5]), blendType='easeInOut')) + track.append(Func(camera.wrtReparentTo, base.localAvatar)) + track.append(Func(base.localAvatar.startUpdateSmartCamera)) + track.start() + elif command == 'list': + start = base.localAvatar.camStart + points = "[x%d, y%d, z%d, h%d, p%d, r%d]" % (start[0], start[1], start[2], start[3], start[4], start[5]) + for point in base.localAvatar.camPoints: + points = points + "\n [x%d, y%d, z%d, h%d, p%d, r%d, dur%d]" % ( + point[0], point[1], point[2], point[3], point[4], point[5], point[6]) + return points + + +class PrintCamera(MagicWord): + aliases = ['pc'] + desc = "Prints the coordinates of the current camera position." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + print(f"current camera pos: {base.camera}") + print(f"current local av camera pos: {base.localAvatar.camera}") + return "Printed your current camera positions in your logs." + + +class ToggleCollisionsOn(MagicWord): + aliases = ['collisionson', 'clip', 'yesclip'] + desc = "Enables collisions for the target." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + return "This command is not supported. If you need to noclip, please use ~ghost." + + +class GlobalTP(MagicWord): + aliases = ["alltp"] + desc = "Allows you to teleport anywhere." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + hoodsVisited = [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 11000, 12000, 13000] + toon.b_setHoodsVisited(hoodsVisited) + toon.b_setTeleportAccess(hoodsVisited) + return "{} can now teleport anywhere!".format(toon.getName()) + + +class Help(MagicWord): + administrative = True + desc = "Tells you to come here for more info... but you're already here!" + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + try: + # make sure this is running on client + settings = base.settings + except NameError: + # someone made this a server MW, so default to '~' + mw_prefix = MagicWordConfig.PREFIX_ALLOWED[0] + else: + # we're actually client; get the actual prefix, or '~' if invalid + idx = settings.get('magic-word-activator') + idx = idx if 0 <= idx < len(MagicWordConfig.PREFIX_ALLOWED) else 0 + mw_prefix = MagicWordConfig.PREFIX_ALLOWED[idx] + return "Refer to your Shticker Book for a list of all commands! Some may require a higher access level. Clicking a Toon's nametag and then using 2 '{}' characters will run a command on them.".format( + mw_prefix) + + +class ToggleSleeping(MagicWord): + aliases = ["sleep", "sleeping"] + desc = "Enables or disables sleeping for your current session. This does not affect other Toons." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + if not base.localAvatar.neverSleep: + base.localAvatar.disableSleeping() + return "Sleeping has been deactivated for the current session." + else: + base.localAvatar.enableSleeping() + return "Sleeping has been activated for the current session." + + +class Teleport(MagicWord): + aliases = ["tp"] + desc = "Teleports the target to a specified location." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("hood", str, False, '')] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + hood = args[0] + + if not hood: + toon.d_doTeleport('GUI') + return "Teleport GUI opened!" + + try: + request = ToontownGlobals.hood2Id[hood.upper()] + except: + return "Invalid location!" + + hoodId = request[0] + + toon.d_doTeleport(hood) + return "Teleporting {0} to {1}!".format(toon.getName(), ToontownGlobals.hoodNameMap[hoodId][-1]) + + +class SetTrackAccess(MagicWord): + aliases = ["trackaccess"] + desc = "Set the tracks a toon has." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("wantTrack", int, True)] * 7 + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + # args = toonup, trap, lure, sound, throw, squirt, drop + tracks = ['toonup', 'trap', 'lure', 'sound', 'throw', 'squirt', 'drop'] + if len(args) == 7: + ret = '' + for track, accessLevel in enumerate(args): + ret += '\n' + toon.setTrackAccessLevel(track, accessLevel) + ret += f"{tracks[track]}: {accessLevel}" + + return f"Set your track access to the following:\n{ret}" + else: + return "Invalid amount of arguments! There must be 7..." + + +class SetTracks(MagicWord): + aliases = ["tracks"] + desc = "Grants all the gag tracks, with the option of leaving one out." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("leftOutTrack", str, False, '')] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + leftOutTrack = args[0] + + tracks = [("toonup", 1), ("trap", 1), ("lure", 1), ("sound", 1), ("throw", 1), ("squirt", 1), ("drop", 1)] + tracks = collections.OrderedDict(tracks) + if leftOutTrack in list(tracks.keys()): + tracks[leftOutTrack] = 0 + + toon.b_setTrackAccess(list(tracks.values())) + if leftOutTrack: + msg = "Set your gag tracks, %sless Toon!" % (leftOutTrack) + else: + msg = "Set your gag tracks, Toon!" + + return msg + + +class Catalog(MagicWord): + desc = "Gives the toon a new catalog." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + simbase.air.catalogManager.deliverCatalogFor(toon) + + +class GetAccId(MagicWord): + administrative = True + aliases = ["accId", "accountId"] + desc = "Get the accountId from the target player." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + accountId = toon.DISLid + return "%s has the accountId of %d" % (toon.getName(), accountId) + + +class GetAvId(MagicWord): + administrative = True + aliases = ["avId", "avatarId"] + desc = "Get the avId from the target player." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + return "%s has the avId of %d" % (toon.getName(), toon.getDoId()) + + +class SetGravity(MagicWord): + aliases = ["gravity"] + desc = "Set your gravity value." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + arguments = [("gravity", float, False, ToontownGlobals.GravityValue * 2.0), ("override", bool, False, False)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + gravityValue = args[0] + overrideWarning = args[1] + + if not base.localAvatar: + return "Your Toon does not exist!" + + if gravityValue < 1 and not overrideWarning: + return "A value lower than 1 may crash your client." + + base.localAvatar.controlManager.currentControls.setGravity(gravityValue) + if gravityValue == ToontownGlobals.GravityValue * 2.0: + return "Gravity returned to normal." + elif gravityValue == ToontownGlobals.GravityValue * 0.75: + return "April fools gravity enabled!" + + +class TrueFriend(MagicWord): + aliases = ["tf"] + desc = "Automatically add a Toon as a true friend." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("avIdShort", int, True)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + avIdShort = args[0] + avIdFull = 400000000 - (300000000 - avIdShort) + av = simbase.air.doId2do.get(avIdFull) + if not av: + return "avId not found/online!" + if int(str(avIdFull)[:2]) >= 40: # AI + return "%s is an NPC!" % av.getName() + if invoker == av: + return "Cannot true friend yourself!" + + invoker.extendFriendsList(av.getDoId(), 1) + av.extendFriendsList(invoker.getDoId(), 1) + + invoker.d_setFriendsList(invoker.getFriendsList()) + av.d_setFriendsList(av.getFriendsList()) + + +class ToggleOobeCull(MagicWord): + aliases = ["oobecull"] + desc = "Toggle 'out of body experience' view, with culling debugging." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + base.oobeCull() + + +class ToggleWire(MagicWord): + aliases = ["wire", "wireframe"] + desc = "Toggle wireframe view." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + base.toggleWireframe() + + +class ToggleTextures(MagicWord): + aliases = ["textures"] + desc = "Toggle textures." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + base.toggleTexture() + + +class ToggleFPS(MagicWord): + aliases = ["fps", "showfps"] + desc = "Toggle frame rate meter." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + base.setFrameRateMeter(not base.frameRateMeter) + + +class GetAccess(MagicWord): + desc = "Get the access level of a target." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + return "Access level: " + str(toon.getAccessLevel()) + + +class Aspect2D(MagicWord): + aliases = ["a2d"] + desc = "Toggles Aspect2d." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + if aspect2d.isHidden(): + aspect2d.show() + else: + aspect2d.hide() + + +class InvasionStatus(MagicWord): + desc = "Returns the number of cogs remaining in an invasion." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + invasionMgr = simbase.air.suitInvasionManager + + if not invasionMgr.getInvading(): + return "There is no invasion in progress!" + + invadingCog = invasionMgr.getInvadingCog() + simbase.air.newsManager.sendUpdateToAvatarId(invoker.getDoId(), 'setInvasionStatus', [ + ToontownGlobals.SuitInvasionUpdate, invadingCog[0], invasionMgr.numSuits, invadingCog[1]]) + + +class RevealMap(MagicWord): + desc = "Reveals map in the Sellbot Field Office maze game." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + mazeGame = None + from toontown.cogdominium.DistCogdoMazeGame import DistCogdoMazeGame + for do in list(base.cr.doId2do.values()): + if isinstance(do, DistCogdoMazeGame): + if invoker.doId in do.getToonIds(): + mazeGame = do + break + + if mazeGame: + mazeGame.game.guiMgr.mazeMapGui.showExit() + mazeGame.game.guiMgr.mazeMapGui.revealAll() + return "Map revealed!" + + return "You are not in a Maze Game!" + + +class EndMaze(MagicWord): + desc = "Ends the maze game in a Sellbot Field Office." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + mazeGame = None + from toontown.cogdominium.DistCogdoMazeGameAI import DistCogdoMazeGameAI + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistCogdoMazeGameAI): + if invoker.doId in do.getToonIds(): + mazeGame = do + break + + if mazeGame: + mazeGame.openDoor() + return "Completed Maze Game" + + return "You are not in a Maze Game!" + + +class SpawnBuilding(MagicWord): + aliases = ["building", "spawnbldg", "bldg"] + desc = "Spawns a Cog Building with the given suit index." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("department", str, True), ("floorCount", int, True)] + accessLevel = 'NO_ACCESS' + priority = True + + def handleWord(self, invoker, avId, toon, *args): + suitDeptToCode = { + 'boss': 'c', + 'bossbot': 'c', + 'law': 'l', + 'lawbot': 'l', + 'cash': 'm', + 'cashbot': 'm', + 'sell': 's', + 'sellbot': 's', + } + department = args[0] + floors = args[1] + + if department in suitDeptToCode: + departmentCode = suitDeptToCode[department] + else: + return "Couldn't spawn a building with department {0}.".format(department) + + if floors not in range(1, 6): + return "Building floor count must be a number from 1 to 5." + + returnCode = invoker.doBuildingTakeover(None, departmentCode, floors) + if returnCode[0] == 'success': + return "Successfully spawned a {0} building with '{1}' floor(s)!".format(department, floors) + return "Couldn't spawn a {0} building with '{1}' floor(s)!".format(department, floors) + + +class SpawnFO(MagicWord): + aliases = ["fo", "spawncogdo", "cogdo"] + desc = "Spawns a Field Office with the given type and difficulty." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("type", str, True), ("difficulty", int, False, 0)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + from toontown.building import SuitBuildingGlobals + track = args[0] + difficulty = args[1] + + tracks = ['s', 'l'] + if track not in tracks: + return "Invalid Field Office type! Supported types are 's' and 'l'" + if not 0 <= difficulty < len(SuitBuildingGlobals.SuitBuildingInfo): + return "Difficulty out of bounds!" + + try: + building = invoker.findClosestDoor() + except KeyError: + return "You\'re not on a street!" + if building is None: + return "Unable to spawn a %s Field Office with a difficulty of %d." % ( + ToontownGlobals.Dept2Dept.get(track), difficulty) + + building.cogdoTakeOver(track, difficulty, 2) + return "Successfully spawned a %s Field Office with a difficulty of %d!" % ( + ToontownGlobals.Dept2Dept.get(track), difficulty) + + +class SetCEIndex(MagicWord): + aliases = ["setce", "ce", "cheesyeffect"] + desc = "Set Cheesy Effect of the target." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("index", int, True), ("zoneId", int, False, 0), ("duration", int, False, 0)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + """Set Cheesy Effect of the target.""" + index = args[0] + zoneId = args[1] + duration = args[2] + + if not 0 <= index <= 17: + return "Invalid value %s specified for Cheesy Effect." % index + if index == 17 and (not hasattr(self.air, 'holidayManager') or not self.air.holidayManager.isHolidayRunning( + ToontownGlobals.APRIL_FOOLS)): + return "Invalid value %s specified for Cheesy Effect." % index + if zoneId != 0 and not 100 < zoneId < ToontownGlobals.DynamicZonesBegin: + return "Invalid zoneId specified." + toon.b_setCheesyEffect(index, zoneId, time.time() + duration) + + +class SetFishingRod(MagicWord): + aliases = ["rod", "setrod"] + desc = "Set target's fishing rod value." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("rodVal", int, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + rodVal = args[0] + if not 0 <= rodVal <= 4: + return "Rod value must be between 0 and 4." + toon.b_setFishingRod(rodVal) + return "Rod changed to " + str(rodVal) + + +class SetFishingBucket(MagicWord): + aliases = ["fishbucket", "bucket", "maxtank"] + desc = "Set target's max fish tank value." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("tankVal", int, True)] + accessLevel = 'USER' + def handleWord(self, invoker, avId, toon, *args): + tankVal = args[0] + if not 20 <= tankVal <= 99: + return "Max fish tank value must be between 20 and 99" + toon.b_setMaxFishTank(tankVal) + return "Max size of fish tank changed to " + str(tankVal) + + +class ClearFishCollection(MagicWord): + aliases = ["clearfishcollection"] + desc = "Clears target's fish collection." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + toon.b_setFishCollection([], [], []) + return "Cleared fish collection on target" + + +class SetPlayRate(MagicWord): + aliases = ["playrate"] + desc = "Set target's play rate." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("playRate", float, False, 1.0)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + rate = args[0] + toon.d_setAnimPlayRate(rate) + if rate == 1: + return "Set playrate to normal!" + + +class SetName(MagicWord): + aliases = ["name"] + desc = "Set target's name." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("name", str, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + nameStr = args[0] + oldName = toon.getName() + + if not nameStr: + return "Cannot change %s's name to nothing!" % oldName + elif ":" in nameStr: + return "Cannot change %s's name to %s. Invalid characters specified." % (oldName, nameStr) + + toon.b_setName(nameStr) + return "Changed %s's name to %s!" % (oldName, nameStr) + + +class SetColor(MagicWord): + aliases = ["tooncolor"] + desc = "Set shirt color of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("partName", str, True), ("colorId", int, False, 0)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(toon.getDNAString()) + toonParts = ['all', 'legs', 'arms', 'head'] + toonPart = args[0] + toonColor = args[1] + + if toonPart not in toonParts: + return "Invalid toon part specified!" + if toonColor not in ToonDNA.defaultBoyColorList: + return "Invalid toon color specified!" + if toonPart == toonParts[0]: + dna.headColor = toonColor + dna.armColor = toonColor + dna.legColor = toonColor + elif toonPart == toonParts[1]: + dna.legColor = toonColor + elif toonPart == toonParts[2]: + dna.armColor = toonColor + elif toonPart == toonParts[3]: + dna.headColor = toonColor + toon.b_setDNAString(dna.makeNetString()) + + +class SetTop(MagicWord): + aliases = ["shirt"] + desc = "Set shirt of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("textureId", int, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(toon.getDNAString()) + + topTex = args[0] + + if not 0 <= topTex <= len(ToonDNA.Shirts): + return "Invalid shirt texture specified!" + dna.topTex = topTex + toon.b_setDNAString(dna.makeNetString()) + + +class SetTopColor(MagicWord): + aliases = ["shirtcolor"] + desc = "Set shirt color of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("colorId", int, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(toon.getDNAString()) + + topTexColor = args[0] + + if not 0 <= topTexColor <= len(ToonDNA.ClothesColors): + return "Invalid shirt color specified!" + dna.topTexColor = topTexColor + toon.b_setDNAString(dna.makeNetString()) + + +class SetSleeves(MagicWord): + aliases = ["sleeves"] + desc = "Set sleeves of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("textureId", int, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(toon.getDNAString()) + + sleeveTex = args[0] + + if not 0 <= sleeveTex <= len(ToonDNA.Sleeves): + return "Invalid sleeves texture specified!" + dna.sleeveTex = sleeveTex + toon.b_setDNAString(dna.makeNetString()) + + +class SetSleevesColor(MagicWord): + aliases = ["sleevescolor"] + desc = "Set sleeves color of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("colorId", int, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(toon.getDNAString()) + + sleeveTexColor = args[0] + + if not 0 <= sleeveTexColor <= len(ToonDNA.ClothesColors): + return "Invalid sleeves color specified!" + dna.sleeveTexColor = sleeveTexColor + toon.b_setDNAString(dna.makeNetString()) + + +class SetBottoms(MagicWord): + aliases = ["bottoms"] + desc = "Set bottoms of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("textureId", int, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(toon.getDNAString()) + + if dna.gender == 'm': + bottoms = ToonDNA.BoyShorts + elif dna.gender == 'f': + bottoms = ToonDNA.GirlBottoms + else: + return 'Unknown gender... perhaps you\'re a helicopter?' + + botTex = args[0] + + if not 0 <= botTex <= len(bottoms): + return "Invalid bottoms texture specified!" + dna.botTex = botTex + toon.b_setDNAString(dna.makeNetString()) + + +class SetBottomsColor(MagicWord): + aliases = ["bottomscolor"] + desc = "Set bottoms color of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("colorId", int, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(toon.getDNAString()) + + botTexColor = args[0] + + if not 0 <= botTexColor <= len(ToonDNA.ClothesColors): + return "Invalid bottoms color specified!" + dna.botTexColor = botTexColor + toon.b_setDNAString(dna.makeNetString()) + + +class SetHat(MagicWord): + aliases = ["hat"] + desc = "Set hat of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, True), ("textureId", int, False, 0)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + hatId = args[0] + hatTex = args[1] + + if hatId == 58: + return "Invalid hat specified." + if not 0 <= hatId <= 60: + return "Invalid hat specified." + if not 0 <= hatTex <= 40: + return "Invalid hat texture specified." + toon.b_setHat(hatId, hatTex, 0) + + +class SetGlasses(MagicWord): + aliases = ["glasses"] + desc = "Set glasses of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, True), ("textureId", int, False, 0)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + glassesId = args[0] + glassesTex = args[1] + + if not 0 <= glassesId <= 24: + return "Invalid glasses specified." + if not 0 <= glassesTex <= 25: + return "Invalid glasses texture specified." + toon.b_setGlasses(glassesId, glassesTex, 0) + + +class SetBackpack(MagicWord): + aliases = ["backpack"] + desc = "Set backpack of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, True), ("textureId", int, False, 0)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + bpId = args[0] + bpTex = args[1] + + if not 0 <= bpId <= 26: + return "Invalid backpack specified." + if not 0 <= bpTex <= 22: + return "Invalid backpack texture specified." + toon.b_setBackpack(bpId, bpTex, 0) + + +class SetShoes(MagicWord): + aliases = ["shoes"] + desc = "Set shoes of target toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, True), ("textureId", int, False, 0)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + shoesId = args[0] + shoesTex = args[1] + + if not 0 <= shoesId <= 3: + return "Invalid shoe type specified." + if (shoesTex == 54 and not __debug__) or not 0 <= shoesTex <= 54: + return "Invalid shoe specified." + toon.b_setShoes(shoesId, shoesTex, 0) + + +class ClearAccessories(MagicWord): + aliases = ["removeallaccessories", "removeaccessories"] + desc = "Clear's all the accessories of the target." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + toon.b_setHat(0, 0, 0) + toon.b_setGlasses(0, 0, 0) + toon.b_setBackpack(0, 0, 0) + toon.b_setShoes(0, 0, 0) + return "Cleared the target's accessories." + + +class SetInventory(MagicWord): + aliases = ["inventory"] + desc = "Modify gag inventory. Can reset (clear) inventory or restock." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("command", str, True), ("level", int, False, 5), ("track", int, False, -1)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + type = args[0] + level = args[1] + track = args[2] + + inventory = toon.inventory + if type in ('reset', 'clear'): + maxLevelIndex = level or 5 + if not 0 <= maxLevelIndex < len(ToontownBattleGlobals.Levels[0]): + return "Invalid max level index: {0}".format(maxLevelIndex) + targetTrack = -1 or track + if not -1 <= targetTrack < len(ToontownBattleGlobals.Tracks): + return "Invalid target track index: {0}".format(targetTrack) + for track in range(0, len(ToontownBattleGlobals.Tracks)): + if (targetTrack == -1) or (track == targetTrack): + inventory.inventory[track][:maxLevelIndex + 1] = [0] * (maxLevelIndex + 1) + toon.b_setInventory(inventory.makeNetString()) + if targetTrack == -1: + return "Inventory cleared." + else: + return "Inventory cleared for target track index: {0}".format(targetTrack) + elif type == "restock": + maxLevelIndex = level or 5 + if not 0 <= maxLevelIndex < len(ToontownBattleGlobals.Levels[0]): + return "Invalid max level index: {0}".format(maxLevelIndex) + targetTrack = -1 or track + if not -1 <= targetTrack < len(ToontownBattleGlobals.Tracks): + return "Invalid target track index: {0}".format(targetTrack) + if (targetTrack != -1) and (not toon.hasTrackAccess(targetTrack)): + return "The target Toon doesn't have target track index: {0}".format(targetTrack) + inventory.maxInventory(maxLevel=level, clearFirst=True) + toon.b_setInventory(inventory.makeNetString()) + if targetTrack == -1: + return "Inventory restocked." + else: + return "Inventory restocked for target track index: {0}".format(targetTrack) + else: + try: + targetTrack = int(type) + except: + return "Invalid first argument." + if not toon.hasTrackAccess(targetTrack): + return "The target Toon doesn't have target track index: {0}".format(targetTrack) + maxLevelIndex = level or 6 + if not 0 <= maxLevelIndex < len(ToontownBattleGlobals.Levels[0]): + return "Invalid max level index: {0}".format(maxLevelIndex) + for _ in range(track): + inventory.addItem(targetTrack, maxLevelIndex) + toon.b_setInventory(inventory.makeNetString()) + return "Restored {0} Gags to: {1}, {2}".format(track, targetTrack, maxLevelIndex) + + +class ToggleGM(MagicWord): + desc = "Toggle the target's GM icon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + access = invoker.getAccessLevel() + if invoker.isGM(): + invoker.b_setGM(0) + return "You have disabled your GM icon." + else: + if access >= 800: + invoker.b_setGM(5) + elif access >= 700: + invoker.b_setGM(6) + elif access >= 600: + invoker.b_setGM(8) + invoker.b_setGM(7) + invoker.b_setGM(4) + elif access >= 500: + invoker.b_setGM(3) + elif access >= 400: + invoker.b_setGM(2) + elif access >= 200: + invoker.b_setGM(1) + return "You have enabled your GM icon." + + class ToggleGhost(MagicWord): - aliases = ["ghost", "invisible", "spy"] - desc = "Toggle ghost mode." + aliases = ["ghost"] + desc = "Set toon to invisible." execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' def handleWord(self, invoker, avId, toon, *args): - # 1 is for the attic, 2 enables you to see yourself other ghost toons. 0 is off. - toon.b_setGhostMode(2 if not toon.ghostMode else 0) # As it's primarily for moderation purposes, we set it to 2 here, or 0 if it's already on. - return f"Toggled ghost mode for {toon.getName()}" - + if invoker.ghostMode == 0: + invoker.b_setGhostMode(2) + return "Going ghost!" + else: + invoker.b_setGhostMode(0) + + class SetGM(MagicWord): - aliases = ["icon", "seticon", "gm", "gmicon", "setgmicon"] - desc = "Sets the GM icon on the target." + aliases = ["gmicon", "gm"] + desc = "Set the target's GM Icon." execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("iconRequest", int, False, 0),] + arguments = [("id", int, True), ("name", bool, False, False)] + accessLevel = 'USER' def handleWord(self, invoker, avId, toon, *args): - from toontown.toonbase import TTLocalizer + gmId = args[0] - iconRequest = args[0] - if iconRequest > len(TTLocalizer.GM_NAMES) or iconRequest < 0: - return "Invalid GM icon ID!" + # if gmId == 1: + # return 'This GM is reserved for the Toon Council. Use ~setGM 2 instead.' + + if not 0 <= gmId <= 8: + return "Invalid GM Icon specified." + + accessLevel = toon.getAccessLevel() + if gmId > 3 and accessLevel < 600: + return "Your access level is too low to use this GM icon." + else: + if (accessLevel < 400 and gmId > 2) or (accessLevel < 500 and gmId > 3): + return "Your access level is too low to use this GM icon." + + if toon.isGM() and gmId != 0: + toon.b_setGM(0) + elif toon.isGM and gmId == 0: + toon.b_setGM(0) + + toon.b_setGM(gmId) + + if __debug__: + pass + else: + toon.d_requestVerifyGM() + + return "You have set %s to GM type %s" % (toon.getName(), gmId) + + +class SetTickets(MagicWord): + aliases = ["tickets"] + desc = "Set the target's racing ticket's value." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("val", int, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + tixVal = args[0] + if not 0 <= tixVal <= 99999: + return "Ticket value out of range (0-99999)" + toon.b_setTickets(tixVal) + return "%s's tickets were set to %s." % (toon.getName(), tixVal) + + +class GrowFlowers(MagicWord): + desc = "Grow a target's flowers." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + estate = toon.air.estateMgr._lookupEstate(toon) + + if not estate: + return "Estate not found!" + + house = estate.getAvHouse(avId) + garden = house.gardenManager.gardens.get(toon.doId) + if not garden: + return "Garden not found!" + + now = int(time.time()) + i = 0 + for flower in garden.flowers: + flower.b_setWaterLevel(5) + flower.b_setGrowthLevel(2) + flower.update() + i += 1 + + return "%d flowers grown." % i + + +class PickAllFlowers(MagicWord): + aliases = ["pickflowers"] + desc = "Picks all of the target's flowers." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + estate = toon.air.estateMgr._lookupEstate(toon) + + if not estate: + return "Estate not found!" + + house = estate.getAvHouse(avId) + garden = house.gardenManager.gardens.get(toon.doId) + if not garden: + return "Garden not found!" + + i = 0 + for flower in garden.flowers.copy(): + if flower.getGrowthLevel() >= flower.growthThresholds[2]: + flower.removeItem(1) + i += 1 + + return "%d flowers picked." % i + + +class GrowTrees(MagicWord): + desc = "Grows the target's trees." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("track", str, True), ("index", int, True), ("amount", int, False, 21)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + track = args[0] + index = args[1] + grown = args[2] + + estate = toon.air.estateMgr._lookupEstate(toon) + + if not estate: + return "Estate not found!" + + house = estate.getAvHouse(avId) + garden = house.gardenManager.gardens.get(toon.doId) + if not garden: + return "Garden not found!" + + try: + trackIndex = ('toonup', 'trap', 'lure', 'sound', 'throw', + 'squirt', 'drop').index(track) + except ValueError: + # Backwards compatibility + try: + trackIndex = int(track) + except ValueError: + return 'Gag track is invalid.' + + if trackIndex > 6: + return 'Gag track is invalid.' + + tree = garden.getTree(trackIndex, index) + if not tree: + return "Tree not found!" + + result = tree.doGrow(grown) + return '%d trees grown.' % result + + +class PickTrees(MagicWord): + desc = "Picks the target's trees." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("track", str, True), ("index", int, True)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + track = args[0] + index = args[1] + estate = toon.air.estateMgr._lookupEstate(toon) + + if not estate: + return "Estate not found!" + + house = estate.getAvHouse(avId) + garden = house.gardenManager.gardens.get(toon.doId) + if not garden: + return "Garden not found!" + + try: + trackIndex = ('toonup', 'trap', 'lure', 'sound', 'throw', + 'squirt', 'drop').index(track) + except ValueError: + # Backwards compatibility + try: + trackIndex = int(track) + except ValueError: + return 'Gag track is invalid.' + + if trackIndex > 6: + return 'Gag track is invalid.' + + tree = garden.getTree(trackIndex, index) + if not tree: + return "Tree not found!" + + tree.calculate(0, tree.lastCheck) + tree.sendUpdate('setFruiting', [tree.getFruiting()]) + return "Trees picked." + + +class FlowerAll(MagicWord): + desc = "Flowers the target's flowers." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("species", int, False, 49), ("variety", int, False, 0)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + species = args[0] + variety = args[1] + + estate = toon.air.estateMgr._lookupEstate(toon) + if not estate: + return "Estate not found!" + + house = estate.getAvHouse(avId) + garden = house.gardenManager.gardens.get(toon.doId) + if not garden: + return "Garden not found!" + + from toontown.estate.DistributedGardenPlotAI import DistributedGardenPlotAI + i = 0 + for obj in garden.objects.copy(): + if isinstance(obj, DistributedGardenPlotAI): + if obj.plotType != GardenGlobals.FLOWER_TYPE: + continue + + if not obj.plantFlower(species, variety, 1): + return "Error on plot %d!" % i + + i += 1 + + return "%d flowers planted." % i + + +class RestockFlowerSpecials(MagicWord): + desc = "Give special flowers." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + toon.gardenSpecials = [] + for x in (100, 101, 102, 103, 105, 106, 107, 108, 109, 130, 131, 135): + toon.addGardenItem(x, 99) + + +class MaxDoodle(MagicWord): + desc = "Maxes the target's doodle." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + petId = invoker.getPetId() + pet = simbase.air.doId2do.get(petId) + if not pet: + return "You must be at your estate and own a doodle to use this magic word!" + + pet.b_setTrickAptitudes([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]) + return "Maxed your doodle!" + + +class LeaveRace(MagicWord): + desc = "Leave the current race you are in." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + messenger.send('leaveRace') + + +class RestartPieRound(MagicWord): + aliases = ['rsp', 'restartpie'] + desc = "Restarts the pie round" + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("round", str, False, "next")] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + from toontown.suit.DistributedSellbotBossAI import DistributedSellbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedSellbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a VP!" + + boss.exitIntroduction() + boss.b_setState('BattleThree') + return "Restarting Pie Round" + + +class SkipCFO(MagicWord): + desc = "Skips to the indicated round of the CFO." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("round", str, False, "next")] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + + from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedCashbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CFO!" + + battle = battle.lower() + + if battle == 'two': + if boss.state in ('PrepareBattleThree', 'BattleThree'): + return "You can not return to previous rounds!" + else: + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + return "Skipping to last round..." + + if battle == 'next': + if boss.state in ('PrepareBattleOne', 'BattleOne'): + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + return "Skipping current round..." + elif boss.state in ('PrepareBattleThree', 'BattleThree'): + boss.exitIntroduction() + boss.b_setState('Victory') + return "Skipping final round..." + + +class HitCFO(MagicWord): + desc = "Hits the CFO." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("damage", int, False, 0)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + dmg = args[0] + from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedCashbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CFO!" + + boss.magicWordHit(dmg, invoker.doId) + + +class RestartCraneRound(MagicWord): + aliases = ['rcr', 'restartcrane'] + desc = "Restarts the crane round" + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("round", str, False, "next")] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedCashbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CFO!" + + if boss.state == 'Elevator': + boss.sendUpdate('setState', ['Introduction']) + + boss.clearObjectSpeedCaching() + battle = battle.lower() + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + boss.b_setState('BattleThree') - toon.b_setGM(0) # Reset it first, otherwise the Toon keeps the old icon, but the name still changes. - toon.b_setGM(iconRequest) - return f"GM icon set to {iconRequest} for {toon.getName()}" - -class SetMaxCarry(MagicWord): - aliases = ["gagpouch", "pouch", "gagcapacity"] - desc = "Set a Toon's gag pouch size." + return "Restarting Crane Round" + + +class SetCFOModifiers(MagicWord): + aliases = ['cfomodifiers', 'cfomods'] + desc = "Dynamically tweak modifiers mid CFO" execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("pouchSize", int, True)] + accessLevel = 'USER' + VALID_SUBCOMMANDS = ['debug', 'amount', 'random', 'clear', 'add', 'remove', 'on', 'off'] + arguments = [ + ('subcommand', str, False, 'debug'), # subcommand + ('mod-id/yes/no', str, False, 'debug'), # modifier id/on/off/int + ('tier', int, False, 1), # tier + ] def handleWord(self, invoker, avId, toon, *args): - pouchSize = args[0] - if pouchSize > 255 or pouchSize < 0: - return "Specified pouch size must be between 1 and 255." + from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedCashbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break - toon.b_setMaxCarry(pouchSize) - return f"Set gag pouch size to {pouchSize} for {toon.getName()}" - -class ToggleInstantKill(MagicWord): - aliases = ["instantkill", "instakill"] - desc = "Toggle the ability to instantly kill a Cog with any gag." + if not boss: + return "You aren't in a CFO!" + + # Handle if no arguments given + if args[0].lower() == self.VALID_SUBCOMMANDS[0]: + return 'Valid subcommands: ' + ', '.join(self.VALID_SUBCOMMANDS) + + # Handle if they just want to turn them on or off + if args[0].lower() in ['on', 'off']: + + if args[0].lower() == 'off': + boss.numModsWanted = 0 + boss.rollModsOnStart = True + return 'Modifiers turned off!' + elif args[0].lower() == 'on': + boss.numModsWanted = 5 + boss.rollModsOnStart = True + return 'Modifiers turned on! Defaulted to 5 modifiers' + + return 'this should never happen lol' + + # Handle if setting amount of mods wanted + if args[0].lower() == self.VALID_SUBCOMMANDS[1]: + + try: + n = int(args[1]) + except: + return "Please specify a number!" + + range = (0, len(list(CraneLeagueGlobals.CFORulesetModifierBase.MODIFIER_SUBCLASSES.values()))) + + if n < range[0] or n > range[1]: + return "Number of modifiers must be in between %s and %s" % (range[0], range[1]) + + boss.numModsWanted = n + return "Set desired amount of modifiers to %s" % n + + # Handle if we wanna randomize mods + if args[0].lower() == self.VALID_SUBCOMMANDS[2]: + + response = args[1].lower() + valid_responses = ('on', 'off') + + if response not in valid_responses: + return "Please say on or off! ex: ~modifiers random on" + + map = {'on': True, 'off': False} + boss.rollModsOnStart = map[response] + return 'Randomize modifiers on restart set to: %s' % response + + # Handle if we want to clear the mods for the next round + if args[0].lower() == self.VALID_SUBCOMMANDS[3]: + ret = "Cleared modifiers, ~rcr to have changes take effect" + if boss.rollModsOnStart: + ret += ' warning: roll modifers on rcr active, use ~modifiers random off' + boss.modifiers = [] + return ret + + # Handle if we want to add a modifier + if args[0].lower() == self.VALID_SUBCOMMANDS[4]: + + try: + mod_id = int(args[1]) + except: + return "Please provide a number for modifier ID" + + mod = CraneLeagueGlobals.CFORulesetModifierBase.MODIFIER_SUBCLASSES.get(mod_id) + if not mod: + return "Invalid modifier ID provided" + + tier = args[2] + if tier <= 0: + return "Please provide a tier above 0!" + + m_instance = mod(tier) + + for m in list(boss.modifiers): + if m.MODIFIER_ENUM == m_instance.MODIFIER_ENUM: + boss.modifiers.remove(m) + + boss.modifiers.append(m_instance) + s = "Added modifier %s, ~rcr to take effect" % m_instance.getName() + if boss.rollModsOnStart: + s += ' warning: roll modifiers on rcr active, use ~modifiers random off' + + return s + + # Handle if we want to remove a modifier + if args[0].lower() == self.VALID_SUBCOMMANDS[5]: + + try: + mod_id = int(args[1]) + except: + return "Please provide a number for modifier ID" + + mod = CraneLeagueGlobals.CFORulesetModifierBase.MODIFIER_SUBCLASSES.get(mod_id) + if not mod: + return "Invalid modifier ID provided" + + for m in list(boss.modifiers): + if m.MODIFIER_ENUM == mod_id: + boss.modifiers.remove(m) + + s = "Removed modifier %s, ~rcr to take effect" % mod_id + if boss.rollModsOnStart: + s += ' warning: roll modifiers on rcr active, use ~modifiers random off' + + return s + + +class DisableGoons(MagicWord): + desc = "Stuns all of the goons in an area." execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' def handleWord(self, invoker, avId, toon, *args): - toon.setInstantKillMode(not toon.instantKillMode) - return f"Toggled instant-kill mode for {toon.getName()}" + from toontown.suit.DistributedGoonAI import DistributedGoonAI + for goon in simbase.air.doFindAllInstances(DistributedGoonAI): + goon.requestStunned(0) + return "Disabled all Goons!" -class Fireworks(MagicWord): - aliases = ["firework"] - desc = "Starts a firework show." + +class SkipCJ(MagicWord): + desc = "Skips to the indicated round of the CJ." execLocation = MagicWordConfig.EXEC_LOC_SERVER - arguments = [("name", str, False, "newyear"), ("hood", str, False, "")] + arguments = [("round", str, False, "next")] + accessLevel = 'USER' - # List of firework shows currently in progress - fireworkShows = {} + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + from toontown.suit.DistributedLawbotBossAI import DistributedLawbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedLawbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CJ!" + + battle = battle.lower() + + if battle == 'two': + if boss.state in ('RollToBattleTwo', 'PrepareBattleTwo', 'BattleTwo', 'PrepareBattleThree', 'BattleThree'): + return "You can not return to previous rounds!" + else: + boss.exitIntroduction() + boss.b_setState('RollToBattleTwo') + return "Skipping to second round..." + + if battle == 'three': + if boss.state in ('PrepareBattleThree', 'BattleThree'): + return "You can not return to previous rounds!" + else: + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + return "Skipping to final round..." + + if battle == 'next': + if boss.state in ('PrepareBattleOne', 'BattleOne'): + boss.exitIntroduction() + boss.b_setState('RollToBattleTwo') + return "Skipping current round..." + elif boss.state in ('RollToBattleTwo', 'PrepareBattleTwo', 'BattleTwo'): + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + return "Skipping current round..." + elif boss.state in ('PrepareBattleThree', 'BattleThree'): + boss.exitIntroduction() + boss.enterNearVictory() + boss.b_setState('Victory') + return "Skipping final round..." + + +class StunLawyers(MagicWord): + desc = "Stuns all the lawyers in the CJ Evidence round." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + boss = None + from toontown.suit.DistributedLawbotBossAI import DistributedLawbotBossAI + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedLawbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CJ!" + if not boss.state == 'BattleThree': + return "You aren't in the evidence round." + for suit in boss.lawyers: + suit.hitByToon(avIdOverride=avId) + + return "Stunned the lawyers!" + + +class RestartScaleRound(MagicWord): + aliases = ['rsc', 'restartscale'] + desc = "Restarts the scale round" + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("round", str, False, "next")] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + from toontown.suit.DistributedLawbotBossAI import DistributedLawbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedLawbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CJ!" + + battle = battle.lower() + boss.exitIntroduction() + boss.b_setState('BattleThree') + return "Restarting Scale Round" + + +class StartCannonRound(MagicWord): + aliases = ['cannons', 'startcannons'] + desc = "Enters the cannon round" + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("round", str, False, "next")] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + from toontown.suit.DistributedLawbotBossAI import DistributedLawbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedLawbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CJ!" + + battle = battle.lower() + boss.exitIntroduction() + boss.b_setState('BattleOne') + boss.b_setState('PrepareBattleTwo') + return "Entering Cannon Round" + + +class FillJury(MagicWord): + desc = "Fills all of the chairs in the CJ's Jury Round." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + boss = None + from toontown.suit.DistributedLawbotBossAI import DistributedLawbotBossAI + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedLawbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CJ!" + if not boss.state == 'BattleTwo': + return "You aren't in the cannon round." + for i in range(len(boss.chairs)): + boss.chairs[i].b_setToonJurorIndex(0) + boss.chairs[i].requestToonJuror() + return "Filled chairs." + + +class RestartSeltzerRound(MagicWord): + aliases = ['rss', 'restartseltzer'] + desc = "Restarts the seltzer round" + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("round", str, False, "next")] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + from toontown.suit.DistributedBossbotBossAI import DistributedBossbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedBossbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CEO!" + + boss.exitIntroduction() + boss.b_setState('BattleFour') + return "Restarting Seltzer Round" + + +class KillBoss(MagicWord): + aliases = ['skipaction', 'killboss', 'skipfinal'] + desc = "Skips the final round of a Cog Boss." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'NO_ACCESS' + priority = True + + def handleWord(self, invoker, avId, toon, *args): + from toontown.suit.DistributedSellbotBossAI import DistributedSellbotBossAI + from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI + from toontown.suit.DistributedLawbotBossAI import DistributedLawbotBossAI + from toontown.suit.DistributedBossbotBossAI import DistributedBossbotBossAI + boss = None + bossType = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedSellbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + bossType = 'sell' + break + elif isinstance(do, DistributedCashbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + bossType = 'cash' + break + elif isinstance(do, DistributedLawbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + bossType = 'law' + break + elif isinstance(do, DistributedBossbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + bossType = 'boss' + break + if not boss: + return "You aren't in a Cog Boss!" + + if bossType == 'sell': + if boss.state in ('PrepareBattleThree', 'BattleThree'): + boss.exitIntroduction() + boss.b_setState('Victory') + return "Skipping VP Final..." + else: + return "You aren't in the final round!" + elif bossType == 'cash': + if boss.state in ('PrepareBattleThree', 'BattleThree'): + boss.exitIntroduction() + boss.b_setState('Victory') + return "Skipping CFO Final..." + else: + return "You aren't in the final round!" + elif bossType == 'law': + if boss.state in ('PrepareBattleThree', 'BattleThree'): + boss.exitIntroduction() + boss.b_setState('Victory') + return "Skipping CJ Final..." + else: + return "You aren't in the final round!" + elif bossType == 'boss': + if boss.state in ('PrepareBattleFour', 'BattleFour'): + boss.exitIntroduction() + boss.b_setState('Victory') + return "Skipping CEO Final..." + else: + return "You aren't in the final round!" + else: + return "Got an invalid/no boss!" + + +class SkipVP(MagicWord): + desc = "Skips to the indicated round of the VP." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("round", str, False, "next")] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + from toontown.suit.DistributedSellbotBossAI import DistributedSellbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedSellbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a VP!" + + battle = battle.lower() + + if battle == 'one': + boss.b_setState("Introduction") + boss.b_setState('BattleOne') + return "Starting battle one!" + + if battle == 'three': + if boss.state in ('PrepareBattleThree', 'BattleThree'): + return "You can not return to previous rounds!" + else: + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + return "Skipping to final round..." + + if battle == 'next': + if boss.state in ('PrepareBattleOne', 'BattleOne'): + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + return "Skipping current round..." + elif boss.state in ('PrepareBattleThree', 'BattleThree'): + boss.exitIntroduction() + boss.b_setState('Victory') + return "Skipping final round..." + elif boss.state in ("Introduction", "Elevator"): + if boss.state in ("Elevator"): + boss.b_setState("Introduction") + boss.b_setState('BattleOne') + return "Skipping introduction!" + + +class StunVP(MagicWord): + desc = "Stuns the VP in the final round of his battle." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + from toontown.suit.DistributedSellbotBossAI import DistributedSellbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedSellbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a VP!" + currState = boss.getCurrentOrNextState() + if currState != 'BattleThree': + return "You aren't in the final round of a VP!" + boss.b_setAttackCode(ToontownGlobals.BossCogDizzyNow) + boss.b_setBossDamage(boss.getBossDamage(), 0, 0) + + +class SkipCEO(MagicWord): + desc = "Skips to the indicated round of the CEO." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("round", str, False, "next")] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + battle = args[0] + from toontown.suit.DistributedBossbotBossAI import DistributedBossbotBossAI + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedBossbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CEO!" + + battle = battle.lower() + + if battle == 'two': + if boss.state in ( + 'PrepareBattleFour', 'BattleFour', 'PrepareBattleThree', 'BattleThree', 'PrepareBattleTwo', + 'BattleTwo'): + return "You can not return to previous rounds!" + else: + boss.exitIntroduction() + boss.b_setState('PrepareBattleTwo') + return "Skipping to second round..." + + if battle == 'three': + if boss.state in ('PrepareBattleFour', 'BattleFour', 'PrepareBattleThree', 'BattleThree'): + return "You can not return to previous rounds!" + else: + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + return "Skipping to third round..." + + if battle == 'four': + if boss.state in ('PrepareBattleFour', 'BattleFour'): + return "You can not return to previous rounds!" + else: + boss.exitIntroduction() + boss.b_setState('PrepareBattleFour') + return "Skipping to last round..." + + if battle == 'next': + if boss.state in ('PrepareBattleOne', 'BattleOne'): + boss.exitIntroduction() + boss.b_setState('PrepareBattleTwo') + return "Skipping current round..." + elif boss.state in ('PrepareBattleTwo', 'BattleTwo'): + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + return "Skipping current round..." + elif boss.state in ('PrepareBattleThree', 'BattleThree'): + boss.exitIntroduction() + boss.b_setState('PrepareBattleFour') + return "Skipping current round..." + elif boss.state in ('PrepareBattleFour', 'BattleFour'): + boss.exitIntroduction() + boss.b_setState('Victory') + return "Skipping final round..." + + +class FeedDiners(MagicWord): + desc = "Feed the diners in the CEO battle." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + boss = None + from toontown.suit.DistributedBossbotBossAI import DistributedBossbotBossAI + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedBossbotBossAI): + if invoker.doId in do.involvedToons: + boss = do + break + if not boss: + return "You aren't in a CEO!" + + if boss.state != 'BattleTwo': + return "You aren't in the waiter round!" + + for table in boss.tables: + for chairIndex in list(table.dinerInfo.keys()): + dinerStatus = table.getDinerStatus(chairIndex) + if dinerStatus in (table.HUNGRY, table.ANGRY): + table.foodServed(chairIndex) + + return "All diners have been fed!" + + +class AbortGame(MagicWord): + aliases = ["abortminigame", "leaveminigame"] + desc = "Abort any minigame you are currently in." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + messenger.send('minigameAbort') + + +class RequestMinigame(MagicWord): + aliases = ['request', 'wantgame', 'requestgame'] + desc = "Request a specific minigame for the trolley." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [('game', str, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + + requested_name = args[0].lower() + wanted_game_id = ToontownGlobals.MinigameNames.get(requested_name) + + if wanted_game_id is None: + return f"There is no game registered with the name: {requested_name}\nValid choices are: {', '.join(ToontownGlobals.MinigameNames.keys())}" + + self.air.minigameMgr.storeRequest(toon.doId, wanted_game_id) + return f"The next trolley game that {toon.getName()} plays will be {requested_name}!" + + +class ToggleSuitPaths(MagicWord): + aliases = ['suitpaths'] + desc = "Toggles visualization of suit paths if they exist in your current zone." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + + # Attempt to find a SuitPlanner in the zone. + from toontown.suit.DistributedSuitPlanner import DistributedSuitPlanner + suitPlanners: Dict[int, DistributedSuitPlanner] = base.cr.getObjectsOfClass(DistributedSuitPlanner) + + # Are there none? + if len(suitPlanners) <= 0: + return "No suit planners found in your zone!" + + # Are we turning it off? + if list(suitPlanners.values())[0].pathViz is not None: + for suitPlanner in suitPlanners.values(): + suitPlanner.hidePaths() + return f"Now un-visualizing {len(suitPlanners)} suit planners!" + + # Visualize them! + for suitPlanner in suitPlanners.values(): + suitPlanner.showPaths() + return f"Now visualizing {len(suitPlanners)} suit planners!" + + +class SpawnCog(MagicWord): + aliases = ["cog"] + desc = "Spawns a cog with the defined level" + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("suit", str, True), ("level", int, False, 1), ("specialSuit", int, False, 0), ("revives", int, False, 0)] + accessLevel = 'NO_ACCESS' + priority = True def handleWord(self, invoker, avId, toon, *args): name = args[0] - hood = args[1] + level = args[1] + specialSuit = args[2] + revives = args[3] + if name not in SuitDNA.suitHeadTypes: + return "Suit %s is not a valid suit!" % name + if level not in ToontownGlobals.SuitLevels: + return "Invalid Cog Level." - from toontown.toonbase import ToontownGlobals - from toontown.parties import PartyGlobals - name2showId = { - 'newyear': ToontownGlobals.NEWYEARS_FIREWORKS, - 'newyears': ToontownGlobals.NEWYEARS_FIREWORKS, - 'summer': ToontownGlobals.JULY4_FIREWORKS, - 'combo': ToontownGlobals.COMBO_FIREWORKS, - 'party': PartyGlobals.FireworkShows.Summer - } + streetId = ZoneUtil.getBranchZone(invoker.zoneId) + if streetId not in invoker.air.suitPlanners: + return "Unable to spawn a level %d %s in current zone." % (level, name) + sp = invoker.air.suitPlanners[streetId] + map = sp.getZoneIdToPointMap() + zones = [invoker.zoneId, invoker.zoneId - 1, invoker.zoneId + 1] - if name not in name2showId: - return f"Unknown firework name \"{name}\". Valid names: {list(name2showId.keys())}" - showId = name2showId[name] + if not sp: + return "Unable to spawn a level %d %s in current zone." % (level, name) + try: + # This probably wont be needed, but in the rare case that we somehow try spawning a cog while the area is capped + # We will remove one + sp.commandCheckFlyRandomSuit() + for zoneId in zones: + if zoneId in map: + points = map[zoneId][:] + sp.createNewSuit([], points, suitName=name, suitLevel=level, skelecog=specialSuit, revives=revives) + return "Spawned a level %d %s in current zone." % (level, name) + except IndexError: + return "Level %d is out of range for %s." % (level, name) - zoneToStyleDict = { - ToontownGlobals.DonaldsDock : 5, - ToontownGlobals.ToontownCentral : 0, - ToontownGlobals.TheBrrrgh : 4, - ToontownGlobals.MinniesMelodyland : 3, - ToontownGlobals.DaisyGardens : 1, - ToontownGlobals.OutdoorZone : 0, - ToontownGlobals.GoofySpeedway : 0, - ToontownGlobals.DonaldsDreamland : 2 - } - - from toontown.hood import ZoneUtil - zones = [] - if not hood: - zones = (toon.zoneId,) - elif hood == "all": - zones = zoneToStyleDict.keys() + +class SpawnInvasion(MagicWord): + aliases = ["invasion"] + desc = "Spawn an invasion on the current AI if one doesn't exist." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("command", str, True), ("suit", str, False, "f"), ("amount", int, False, 1000), + ("skelecog", bool, False, False)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + cmd = args[0] + name = args[1] + num = args[2] + skeleton = args[3] + + if not 10 <= num <= 25000: + return "Can't the invasion amount to {}! Specify a value between 10 and 25,000.".format(num) + + invMgr = simbase.air.suitInvasionManager + if cmd == 'start': + if invMgr.getInvading(): + return "There is already an invasion on the current AI!" + if not name in SuitDNA.suitHeadTypes: + return "This cog does not exist!" + invMgr.startInvasion(name, num, skeleton) + elif cmd == 'stop': + if not invMgr.getInvading(): + return "There is no invasion on the current AI!" + # elif invMgr.undergoingMegaInvasion: + # return "The current invasion is a mega invasion, you must stop the holiday to stop the invasion." + invMgr.stopInvasion() else: - return "Missing hood argument." - - # Generate our firework shows - from toontown.effects.DistributedFireworkShowAI import DistributedFireworkShowAI - count = 0 - for zone in zones: - if zone not in self.fireworkShows: - show = DistributedFireworkShowAI(self.air, self) - show.generateWithRequired(zone) - self.fireworkShows[zone] = show - show.d_startShow(showId, zoneToStyleDict.get(zone, 0)) - count += 1 - - return f"Started firework {'show' if count == 1 else 'shows'} in {count} {'zone' if count == 1 else 'zones'}!" - - def stopShow(self, zoneId): - if zoneId in self.fireworkShows: - show = self.fireworkShows[zoneId] - show.requestDelete() - del self.fireworkShows[zoneId] + return "You didn't enter a valid command! Commands are ~invasion start or stop." -# Instantiate all classes defined here to register them. -# A bit hacky, but better than the old system -for item in list(globals().values()): - if isinstance(item, type) and issubclass(item, MagicWord): - i = item() +class SetTrophyScore(MagicWord): + aliases = ["trophyscore"] + desc = "Set the trophy score of target." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("val", int, True)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + amt = args[0] + + if not 0 <= amt <= 255: + return "The amount must be between 0 and 255!" + simbase.air.trophyMgr.addTrophy(toon.doId, toon.name, amt, True) + + +class GivePies(MagicWord): + aliases = ["pies"] + desc = "Gives the target pies to throw." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("type", int, True), ("amount", int, False, -1)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + pieType = args[0] + numPies = args[1] + if pieType == -1: + toon.b_setNumPies(0) + return "Removed %s's pies." % toon.getName() + if not 0 <= pieType <= 7: + return "You can only specify between pie types 0 and 7." + if numPies == -1: + toon.b_setPieType(pieType) + toon.b_setNumPies(ToontownGlobals.FullPies) + return "Gave %s an infinite amount of pies" % toon.getName() + if not 0 <= numPies <= 99: + return "You can only specify between 0 and 99 pies." + toon.b_setPieType(pieType) + toon.b_setNumPies(numPies) + + +class SetQP(MagicWord): + aliases = ["qp", "questprogress"] + desc = "Get and set the targets quest progress." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, False, 0), ("progress", int, False, 0)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + questId = args[0] + progress = args[1] + questIds = "" + for index in range(len(toon.quests)): + questIds += "{0} ".format(toon.quests[index][0]) + if toon.quests[index][0] == questId: + toon.quests[index][4] = progress + toon.b_setQuests(toon.quests) + return questIds + + +class CompleteQuests(MagicWord): + aliases = ["cq", "completequest"] + desc = "Completes all quests on the target." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + + for index in range(len(toon.quests)): + toon.quests[index][4] = 2**16 + toon.b_setQuests(toon.quests) + return f"Completed {len(toon.quests)} of {toon.getName()}'s quests!" + + +class SetUnites(MagicWord): + aliases = ["unites", "restockunites"] + desc = "Restocks the target's unites. The default amount is 999." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("amount", int, False, 999)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + amt = args[0] + if not 1 <= amt <= 999: + return "Unite amount must be between 0 and 999!" + toon.restockAllResistanceMessages(amt) + return "Restocked " + str(amt) + " unites successfully!" + + +class UnlockTricks(MagicWord): + aliases = ["tricks"] + desc = "Unlock all the target's pet trick phrases." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + invoker.b_setPetTrickPhrases(list(range(7))) + return "Unlocked pet tricks!" + + +class RestockSummons(MagicWord): + desc = "Restock all of the target's CJ summons." + aliases = ["summons"] + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + # Make sure we have every cog in our Cog Page. + cogCount = [] + for deptIndex in range(5): + for cogIndex in range(9): + cogCount.append(CogPageGlobals.get_max_cog_quota(invoker) if cogIndex != 8 else 0) + invoker.b_setCogCount(cogCount) + invoker.b_setCogStatus(([CogPageGlobals.COG_COMPLETE2] * 8 + [0]) * 5) + invoker.restockAllCogSummons() + return "Restocked all cog summons successfully!" + + +class SetPinkSlips(MagicWord): + aliases = ["pinkslips", "setfires", "fires"] + desc = "Sets the target's pink slips. The default amount is 255." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("amount", int, False, 255)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + amt = args[0] + plural = 's' + if not 0 <= amt <= 255: + return "The amount must be between 0 and 255!" + if amt == 1: + plural = '' + toon.b_setPinkSlips(amt) + return "Restocked {0} pink slip{1} successfully!".format(amt, plural) + + +class QuestTier(MagicWord): + desc = "Sets the target's quest tier to specified value." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("tier", int, True)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + tier = args[0] + + if not 0 <= tier <= 255: + return "Can't set {}'s quest tier to {}! Specify a value between 0 and 255.".format(toon.getName(), tier) + + toon.b_setQuests([]) + toon.b_setRewardHistory(tier, []) + return "Set %s's quest tier to %d." % (toon.getName(), tier) + + +class SetExp(MagicWord): + aliases = ["exp"] + desc = "Sets the target's experience for the specified gag track. The default amount is 0." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("track", str, True), ("amount", int, False, 0)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + track = args[0] + amt = args[1] + + tracks = ['toon-up', 'trap', 'lure', 'sound', 'throw', 'squirt', 'drop'] + maxed = ['max', 'maxxed'] + + if track not in tracks + maxed: + return "Invalid gag track specified!" + + if not 0 <= amt <= ToontownBattleGlobals.MaxSkill: + return "Can't set {0}'s jellybean count to {1}! Specify a value between 0 and 999,9999.".format( + toon.getName(), amt) + + if track in maxed: + for track in tracks: + toon.experience.setExp(track, amt if amt > 0 else 10000) + toon.b_setExperience(toon.experience.getCurrentExperience()) + return "Maxed all of {}'s gag tracks.".format(toon.getName()) + else: + trackIndex = TTLocalizer.BattleGlobalTracks.index(track) + toon.experience.setExp(trackIndex, amt) + toon.b_setExperience(toon.experience.getCurrentExperience()) + return "Set %s exp to %d successfully." % (track, amt) + + +class TrackBonus(MagicWord): + desc = "Modify the invoker's track bonus level. " + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("track", int, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + track = args[0] + + trackLength = ToontownBattleGlobals.UBER_GAG_LEVEL_INDEX + numTracks = ToontownBattleGlobals.NUM_GAG_TRACKS + gagAccess = invoker.getTrackAccess() + + trackBonusLevel = [-1] * numTracks + if track == 8: + bonusAccess = lambda x: trackLength if x > 0 else x + trackBonusLevel = list(map(bonusAccess, gagAccess)) + elif 0 <= track < numTracks: + if gagAccess[track]: + trackBonusLevel[track] = trackLength + else: + return "You don\'t have that track!" + else: + return "Invalid track!" + + invoker.b_setTrackBonusLevel(trackBonusLevel) + return "Track bonus set!" + + +class SetCogSuit(MagicWord): + aliases = ["cogsuit"] + desc = "Set disguise type and level." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("corp", str, True), ("type", str, True), ("level", int, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + corp = args[0] + type = args[1] + level = args[2] + corps = ['bossbot', 'lawbot', 'cashbot', 'sellbot'] + corp = corp.lower() + if corp not in corps: + return "Invalid cog corp. specified." + corpIndex = corps.index(corp) + + if type == "nosuit": + # They want to reset their suit entirely. + parts = toon.getCogParts() + parts[corpIndex] = 0 + toon.b_setCogParts(parts) + types = toon.getCogTypes() + types[corpIndex] = 0 + toon.b_setCogTypes(types) + levels = toon.getCogLevels() + levels[corpIndex] = 0 + toon.b_setCogLevels(levels) + merits = toon.getCogMerits() + merits[corpIndex] = 0 + toon.b_setCogMerits(merits) + return "Reset %s disguise and removed all earned parts." % corp.capitalize() + + # Get the cog type from the specified short-hand value. + types = SuitDNA.suitHeadTypes[8 * corpIndex:(8 * corpIndex) + 8] + if type not in types: + return "Invalid cog type specified.." + typeIndex = types.index(type) + + # Make sure they gave a level that is in range. + if typeIndex == 7: + # The final suit can go up to Level 50. + levelRange = list(range(8, 51)) # Last digit is exclusive. + else: + levelRange = list(range((typeIndex + 1), (typeIndex + 6))) + if level not in levelRange: + return "Invalid level specified for %s disguise %s." % ( + corp.capitalize(), SuitBattleGlobals.getSuitAttributes(type).name) + + # Reset their merits to 0. + merits = toon.getCogMerits() + merits[corpIndex] = 0 + toon.b_setCogMerits(merits) + + # Ensure they have all the parts. + parts = toon.getCogParts() + parts[corpIndex] = CogDisguiseGlobals.PartsPerSuitBitmasks[corpIndex] + toon.b_setCogParts(parts) + + # Find out if they need laff boosts or laff points removed. + # for levelBoost in [14, 19, 29, 39, 49]: + # if level <= levelBoost and not levelBoost > toon.getCogLevels()[corpIndex]: + # if toon.getMaxHp() <= 15: + # continue + # toon.b_setMaxHp(toon.getMaxHp() - 1) + # elif level > levelBoost and not levelBoost <= toon.getCogLevels()[corpIndex]: + # if toon.getMaxHp() >= 137: + # continue + # toon.b_setMaxHp(toon.getMaxHp() + 1) + # # Lets be nice and give them a toonup or make them suffer. + # if toon.getHp() > toon.getMaxHp(): + # toon.takeDamage(toon.getHp() - toon.getMaxHp()) + # else: + # toon.toonUp(toon.getMaxHp() - toon.getHp()) + + # Set their type and level that they specified. + types = toon.getCogTypes() + types[corpIndex] = typeIndex + toon.b_setCogTypes(types) + levels = toon.getCogLevels() + levels[corpIndex] = level - 1 # -1 because it starts at 0 + toon.b_setCogLevels(levels) + + return "Set %s disguise to %s Level %d." % ( + corp.capitalize(), SuitBattleGlobals.getSuitAttributes(type).name, level) + + +class Merits(MagicWord): + desc = "Set the target's merits to the value specified." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("corp", str, True), ("amount", int, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + corp = args[0] + amount = args[1] + corps = ['bossbot', 'lawbot', 'cashbot', 'sellbot'] + if corp not in corps: + return "Invalid cog corp. specified." + corpIndex = corps.index(corp) + + merits = toon.getCogMerits() + merits[corpIndex] = amount + toon.b_setCogMerits(merits) + + +class Pouch(MagicWord): + desc = "Set the target's max gag limit." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("amount", int, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + amt = args[0] + + if not 1 <= amt <= 255: + return "Can't set {0}'s pouch size to {1}! Specify a value between 1 and 255.".format(toon.getName(), amt) + + toon.b_setMaxCarry(amt) + return "Set %s's pouch size to %d" % (toon.getName(), amt) + + +class SetNametagStyle(MagicWord): + aliases = ["setnametag", "nametag", "nametagstyle"] + desc = "Set the style of the target's nametag to the specified ID." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("style", str, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + styleName = args[0] + + nametag_list = list(TTLocalizer.NametagFontNames) + for index, item in enumerate(nametag_list): + nametag_list[index] = item.lower() + styleName = styleName.lower() + + if styleName in nametag_list: + index = nametag_list.index(styleName) + elif styleName == "basic": + index = 100 + else: + return "Invalid nametag name entered." + + toon.b_setNametagStyle(index) + return "Set %s's nametag style successfully." % toon.getName() + + +class SetNametagType(MagicWord): + aliases = ["nametagtype", "lacker"] + desc = "Set the style of the target's nametag to the specified ID." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("type", int, False, 0)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + type = args[0] + + nametagTypeList = [(0, NametagGroup.CCNormal), (1, NametagGroup.CCNonPlayer), (3, NametagGroup.CCSuit)] + for nametag in nametagTypeList: + if type == nametag[0]: + toon.b_setNametagType(nametag[0]) + return "Changed %s's nametag type successfully." % toon.getName() + + return "Invalid nametag type specified!" + + +class Phrase(MagicWord): + desc = "Unlocks a new phrase and adds it to target's list of 'My Phrases'." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + phraseId = args[0] + + strings = OTPLocalizer.CustomSCStrings + scId = int(phraseId) * 10 + id = None + if scId in iter(strings.keys()): + id = scId + + if id: + if toon.customMessages.count(id) != 0: + return "%s already has this custom phrase!" % toon.getName() + if len(toon.customMessages) >= ToontownGlobals.MaxCustomMessages: + toon.customMessages = toon.customMessages[1:] + toon.customMessages.append(id) + toon.d_setCustomMessages(toon.customMessages) + return "Added new phrase to %s's custom phrases." % toon.getName() + + return "Invalid phrase id!" + + +class SetSos(MagicWord): + aliases = ["sos"] + desc = "Sets the target's SOS cards. The default is 1 Flippy card." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("amount", int, False, 1), ("name", str, False, 'Flippy')] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + amt = args[0] + name = args[1] + + if not 0 <= amt <= 100: + return "The amount must be between 0 and 100!" + + for npcId, npcName in TTLocalizer.NPCToonNames.items(): + if name.lower() == npcName.lower(): + if npcId not in NPCToons.npcFriends: + continue + break + else: + return "The {0} SOS card was not found!".format(name) + if (amt == 0) and (npcId in invoker.NPCFriendsDict): + del toon.NPCFriendsDict[npcId] + else: + toon.NPCFriendsDict[npcId] = amt + toon.d_setNPCFriendsDict(toon.NPCFriendsDict) + return "Restocked {0} {1} SOS cards successfully!".format(amt, npcName) + + +class FreeBldg(MagicWord): + desc = "Closest cog building gets freed." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + + returnCode = invoker.doBuildingFree() + if returnCode[0] == 'success': + return "Successfully took back building!" + elif returnCode[0] == 'busy': + return "Toons are currently taking back the building!" + return "Couldn't free building." + + +class MaxGarden(MagicWord): + desc = "Maxes your garden." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + invoker.b_setShovel(3) + invoker.b_setWateringCan(3) + invoker.b_setShovelSkill(639) + invoker.b_setWateringCanSkill(999) + invoker.b_setGardenTrophies(list(GardenGlobals.TrophyDict.keys())) + # invoker.b_setFlowerCollection([1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6, 7, 8, 9]) + # print invoker.flowerCollection.getNetLists() + + +class InstaDelivery(MagicWord): + aliases = ["fastdel"] + desc = "Instant delivery of an item." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + invoker.instantDelivery = not invoker.instantDelivery + for item in toon.onOrder: + item.deliveryDate = int(time.time() / 60) # Deliver all the packages that they already ordered, too. + return "Instant Delivery has been turned {0}.".format('on' if invoker.instantDelivery else 'off') + + +class SetMuzzle(MagicWord): + aliases = ["muzzle"] + desc = "Modify the targets muzzle." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, False, 0)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + muzzle = args[0] + if not 0 <= muzzle <= 5: + return "Invalid muzzle. (0-5)" + + toon.b_setMuzzle(muzzle) + if muzzle == 0: + return "Returned muzzle to normal!" + + +class SetEyes(MagicWord): + aliases = ["eyes"] + desc = "Modify the targets eyes." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("id", int, False, 0)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + type = args[0] + if not 0 <= type <= 3: + return "The type must be between 0 and 3!" + + toon.b_setEyes(type) + if type == 0: + return "Returned eyes to normal!" + + +class SetTaskCarryLimit(MagicWord): + aliases = ["taskcarrylimit", "settaskcarry", "taskcarry", "setcarry"] + desc = "Set the amount of tasks a toon can carry." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("limit", int, False, 1)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + amt = args[0] + plural = 's' + + if not 1 <= amt <= 6: + return "The amount must be between 1 and 6!" + if amt == 1: + plural = '' + toon.b_setQuestCarryLimit(amt) + return "You can now carry {0} task{1}!".format(amt, plural) + + +class SetAlwaysHitCogs(MagicWord): + aliases = ["alwayshitcogs", "hitcogs"] + desc = "Enable/Disable always hitting cogs." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + if not toon: + return + + if not toon.getAlwaysHitSuits(): + toon.setAlwaysHitSuits(True) + else: + toon.setAlwaysHitSuits(False) + + return "Toggled always hitting Cogs %s for %s" % ('ON' if toon.getAlwaysHitSuits() else 'OFF', toon.getName()) + + +class EndFlying(MagicWord): + desc = "Ends the flying game in a Lawbot Field Office." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + from toontown.cogdominium.DistCogdoFlyingGameAI import DistCogdoFlyingGameAI + flyingGame = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistCogdoFlyingGameAI): + if invoker.doId in do.getToonIds(): + flyingGame = do + break + + if flyingGame: + flyingGame._handleGameFinished() + return "Completed the flying game." + + return "You are not in a flying game!" + + +class Ping(MagicWord): + desc = "Pong!" + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + return "Pong!" + + +class GardenGame(MagicWord): + aliases = ["gardendrop"] + desc = "Start the garden drop mini-game." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + from toontown.estate import GardenDropGame + base.localAvatar.game = GardenDropGame.GardenDropGame() + + +class WinGame(MagicWord): + aliases = ["winminigame"] + desc = "Win the trolley game you are in." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + messenger.send("minigameVictory") + return "Trolley game won." + + +class GetZone(MagicWord): + aliases = ["getzoneid"] + desc = "Returns the target's zone ID." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + return "{}'s zone ID is {}.".format(toon.getName(), str(toon.zoneId)) + + +class SetAccessLevel(MagicWord): + aliases = ["accesslevel", "access", "setaccess"] + desc = "Sets the target's access level." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("rank", int, False, 100)] + affectRange = [MagicWordConfig.AFFECT_OTHER] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + rank = args[0] + + if not -100 <= rank <= 800: + return "Can't set {0}'s speed to {1}! Specify a value between -100 and 800.".format(toon.getName(), rank) + + if invoker.getAccessLevel() == rank: + return "You cannot set the target to your own Access Level!" + + rank = OTPGlobals.AccessLevelInt2Name.get(rank) + toon.b_setAccessLevel(rank) + return "Set {0}'s Access Level to {1}.".format(toon.getName(), rank) + + +class RestrictAccess(MagicWord): + aliases = ["restrict", "remove", "block", "remove"] + desc = "Restricts the target's access for the rest of the session." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + affectRange = [MagicWordConfig.AFFECT_OTHER] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + if invoker == toon: + return "You can't restrict access for yourself!" + toon.b_setAccessLevel(OTPGlobals.AccessLevelName2Int.get('RESTRICTED')) + return "Restricted {0}'s access for the rest of their session.".format(toon.getName()) + + +class ToggleCheats(MagicWord): + aliases = ["cheat", "cheats", "command", "commands"] + desc = "Toggles cheats on and off for the Toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + if invoker != toon: + return "You can only toggle cheats for yourself!" + if toon.getAccessLevel() >= OTPGlobals.AccessLevelName2Int.get('USER'): + toon.b_setAccessLevel(OTPGlobals.AccessLevelName2Int.get('NO_ACCESS')) + return "Toggled cheats OFF." + toon.b_setAccessLevel(OTPGlobals.AccessLevelName2Int.get('USER')) + return "Toggled cheats ON. WARNING: Some commands may be unstable and cause bugs/crashes!" + + +class ToggleAdmin(MagicWord): + aliases = ["admin", "setAdmin"] + desc = "Toggles admin on and off for the toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + if invoker != toon: + return "You can only toggle admin for yourself!" + if toon.getAccessLevel() >= OTPGlobals.AccessLevelName2Int.get('TTOFF_DEVELOPER'): + toon.b_setAccessLevel(OTPGlobals.AccessLevelName2Int.get('USER')) + return "Toggled admin OFF." + toon.b_setAccessLevel(OTPGlobals.AccessLevelName2Int.get('TTOFF_DEVELOPER')) + return "Toggled admin ON. WARNING: Some commands may be unstable and cause bugs/crashes!" + + +class PrintChildren(MagicWord): + aliases = ["children"] + desc = "Prints all of render's children to the client log." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + arguments = [("type", int, False, 0), ("mode", int, False, 0)] + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + type = args[0] + mode = args[1] + + if not type: + node = render + else: + node = render2d + + if not mode: + print(node.getChildren()) + elif mode == 2: + for child in node.getChildren(): + for secondaryChild in child.getChildren(): + print(secondaryChild.getChildren()) + elif mode == 3: + for child in node.getChildren(): + for secondaryChild in child.getChildren(): + for thirdChild in secondaryChild.getChildren(): + print(thirdChild.getChildren()) + elif mode == 4: + for child in node.getChildren(): + for secondaryChild in child.getChildren(): + for thirdChild in secondaryChild.getChildren(): + for fourthChild in thirdChild.getChildren(): + print(fourthChild.getChildren()) + elif mode == 5: + for child in node.getChildren(): + for secondaryChild in child.getChildren(): + for thirdChild in secondaryChild.getChildren(): + for fourthChild in thirdChild.getChildren(): + for fifthChild in fourthChild.getChildren(): + print(fifthChild.getChildren()) + else: + for child in node.getChildren(): + print(child.getChildren()) + + +class StartBoss(MagicWord): + aliases = ['startvp', 'startcfo', 'startcj', 'startceo'] + desc = 'Starts a boss battle.' + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + + from ..toon.DistributedToonAI import DistributedToonAI + + hood_zones = [ToontownGlobals.BossbotHQ, ToontownGlobals.LawbotHQ, + ToontownGlobals.CashbotHQ, ToontownGlobals.SellbotHQ] + if toon.zoneId not in hood_zones: + return 'Toon is not in the correct zone! Expected a Cog HQ courtyard, got {}.'.format(toon.zoneId) + + # Get all players online + online_toons: List[DistributedToonAI] = simbase.air.getAllOfType(DistributedToonAI) + for online_toon in list(online_toons): + if not online_toon.isPlayerControlled(): + online_toons.remove(online_toon) + + for hood in simbase.air.hoods: + if hood.zoneId == toon.zoneId: + + toons_in_zone = [online_toon for online_toon in online_toons if online_toon.zoneId == toon.zoneId] + + # If there are more than 8 players, just grab the first 7 we find and assure that invoker is in it + if len(toons_in_zone) > 8: + if toon.getDoId() in toons_in_zone: + toons_in_zone.remove(toon) + + toons_in_zone = toons_in_zone[0:7] + toons_in_zone.append(toon) + + zone = hood.lobbyMgr.createBossOffice([t.getDoId() for t in toons_in_zone]) + for t in toons_in_zone: + t.sendUpdate('forceEnterBoss', [t.zoneId, zone]) + return f'Successfully created a boss for {len(toons_in_zone)} toons!!' + + return 'Cog HQ hood data not found!' + + +class CFOFight(MagicWord): + aliases = ['cfo', 'cfofight', 'cfoboss'] + desc = 'Teleports you into a fresh CFO boss instance.' + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + # Create a new Cashbot boss office instance and force the invoker into it. + # NOTE: forceEnterBoss relies on the CogHQ loader flow client-side, so we must + # first get the toon into the Cashbot HQ courtyard, then enter the boss instance, + # then fast-forward to the crane round (Battle Three). + cashbotHoodId = ToontownGlobals.CashbotHQ + + for hood in simbase.air.hoods: + if hood.zoneId != cashbotHoodId: + continue + + zone = hood.lobbyMgr.createBossOffice([toon.getDoId()]) + # If we're not already in Cashbot HQ courtyard, teleport there first. + if getattr(toon, 'zoneId', None) != cashbotHoodId: + toon.d_doTeleport('CFO') + + def __enterBossInstance(attempt=0): + # Wait until the toon is actually in Cashbot HQ before forcing boss entry. + if getattr(toon, 'zoneId', None) != cashbotHoodId: + if attempt >= 50: + return + taskMgr.doMethodLater(0.1, __enterBossInstance, f'cfo-enter-boss-{toon.doId}', extraArgs=[attempt + 1]) + return + + toon.sendUpdate('forceEnterBoss', [cashbotHoodId, zone]) + + def __skipToCraneRound(attempt=0): + from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI + + boss = None + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedCashbotBossAI) and getattr(do, 'zoneId', None) == zone: + boss = do + break + + # Boss and/or toon may not have generated/entered yet; retry briefly. + # We only fast-forward once the toon is actually inside the boss instance zone. + if (not boss) or (getattr(toon, 'zoneId', None) != zone): + if attempt >= 80: + return + taskMgr.doMethodLater(0.1, __skipToCraneRound, f'cfo-skip-to-crane-{zone}', extraArgs=[attempt + 1]) + return + + # Ensure we are treated as involved for state logic. + if toon.doId not in boss.involvedToons: + boss.involvedToons.append(toon.doId) + + # Mirror the existing restart/skip logic used by ~rcr. + if boss.state == 'Elevator': + boss.sendUpdate('setState', ['Introduction']) + + # If we're already in crane round, don't spam state changes. + if boss.state in ('PrepareBattleThree', 'BattleThree'): + return + + boss.clearObjectSpeedCaching() + boss.exitIntroduction() + boss.b_setState('PrepareBattleThree') + boss.b_setState('BattleThree') + + taskMgr.doMethodLater(0.1, __enterBossInstance, f'cfo-enter-boss-{toon.doId}', extraArgs=[0]) + taskMgr.doMethodLater(0.1, __skipToCraneRound, f'cfo-skip-to-crane-{zone}', extraArgs=[0]) + return 'Created a new CFO instance and teleported you into the crane round.' + + return 'Cashbot HQ hood data not found!' + + +class ExitCFOCrane(MagicWord): + aliases = ['exitcfo', 'leavecfo', 'cfoexit', 'cfotc'] + desc = 'Exits the CFO crane round and returns you to Toontown Central.' + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + # If the toon is currently involved in a CFO instance, remove them cleanly. + try: + from toontown.suit.DistributedCashbotBossAI import DistributedCashbotBossAI + for do in list(simbase.air.doId2do.values()): + if isinstance(do, DistributedCashbotBossAI) and invoker.doId in getattr(do, 'involvedToons', []): + do.removeToon(invoker.doId, died=False) + break + except Exception: + # Always fall back to teleporting out. + pass + + toon.d_doTeleport('TTC') + return 'Teleported you back to Toontown Central.' + + +class SetDNA(MagicWord): + aliases = ['dna'] + desc = 'Modify a DNA part on the invoker' + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("part", str, True), ("value", str, True)] + accessLevel = 'NO_ACCESS' + + def handleWord(self, invoker, avId, toon, *args): + dna = ToonDNA.ToonDNA() + dna.makeFromNetString(invoker.getDNAString()) + + part = args[0] + value = args[1] + + part = part.lower() + if part.endswith('color') or part.endswith('tex') or part.endswith('size') or part.endswith( + 'gloves') or part.endswith('torso'): + try: + value = int(value) + except: + return 'Invalid value type for: ' + part + + if part == 'gender': + if value not in ('m', 'f', 'male', 'female'): + return 'Invalid gender: ' + value + dna.gender = value[0] + invoker.b_setDNAString(dna.makeNetString()) + return 'Gender set to: ' + dna.gender + + if part in ('head', 'species'): + speciesNames = ( + 'dog', 'cat', 'horse', 'mouse', 'rabbit', 'duck', 'monkey', 'bear', + 'pig', 'deer', 'beaver', 'alligator', 'fox', 'bat', 'raccoon', 'turkey', + 'koala', 'kangaroo', 'kiwi', 'armadillo' + ) + if value in speciesNames: + speciesIndex = speciesNames.index(value) + value = ToonDNA.toonSpeciesTypes[speciesIndex] + if value not in ToonDNA.toonSpeciesTypes: + return 'Invalid species: ' + value + dna.head = value + dna.head[1:3] + invoker.b_setDNAString(dna.makeNetString()) + return 'Species set to: ' + dna.head[0] + + if part == 'headsize': + sizes = ('ls', 'ss', 'sl', 'll') + if not 0 <= value <= len(sizes): + return 'Invalid head size index: ' + str(value) + dna.head = dna.head[0] + sizes[value] + invoker.b_setDNAString(dna.makeNetString()) + return 'Head size index set to: ' + dna.head[1:] + + if part == 'torso': + if dna.gender not in ('m', 'f'): + return 'Unknown gender.' + try: + value = int(value) + except: + return 'Invalid value type for: ' + part + if (dna.gender == 'm') and (not 0 <= value <= 2): + return 'Male torso index out of range (0-2).' + if (dna.gender == 'f') and (not 3 <= value <= 8): + return 'Female torso index out of range (3-8).' + dna.torso = ToonDNA.toonTorsoTypes[value] + invoker.b_setDNAString(dna.makeNetString()) + return 'Torso set to: ' + dna.torso + + if part == 'legs': + try: + value = int(value) + except: + return 'Invalid value type for: ' + part + if not 0 <= value <= len(ToonDNA.toonLegTypes): + return 'Legs index out of range (0-%d).' % len(ToonDNA.toonLegTypes) + dna.legs = ToonDNA.toonLegTypes[value] + invoker.b_setDNAString(dna.makeNetString()) + return 'Legs set to: ' + dna.legs + + if part == 'headcolor': + if dna.gender not in ('m', 'f'): + return 'Unknown gender.' + if (dna.gender == 'm') and (value not in ToonDNA.defaultBoyColorList): + return 'Invalid male head color index: ' + str(value) + if (dna.gender == 'f') and (value not in ToonDNA.defaultGirlColorList): + return 'Invalid female head color index: ' + str(value) + dna.headColor = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Head color index set to: ' + str(dna.headColor) + + if part == 'armcolor': + if dna.gender not in ('m', 'f'): + return 'Unknown gender.' + if (dna.gender == 'm') and (value not in ToonDNA.defaultBoyColorList): + return 'Invalid male arm color index: ' + str(value) + if (dna.gender == 'f') and (value not in ToonDNA.defaultGirlColorList): + return 'Invalid female arm color index: ' + str(value) + dna.armColor = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Arm color index set to: ' + str(dna.armColor) + + if part == 'legcolor': + if dna.gender not in ('m', 'f'): + return 'Unknown gender.' + if (dna.gender == 'm') and (value not in ToonDNA.defaultBoyColorList): + return 'Invalid male leg color index: ' + str(value) + if (dna.gender == 'f') and (value not in ToonDNA.defaultGirlColorList): + return 'Invalid female leg color index: ' + str(value) + dna.legColor = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Leg color index set to: ' + str(dna.legColor) + + if part == 'color': + if dna.gender not in ('m', 'f'): + return 'Unknown gender.' + if (dna.gender == 'm') and (value not in ToonDNA.defaultBoyColorList): + return 'Invalid male leg color index: ' + str(value) + if (dna.gender == 'f') and (value not in ToonDNA.defaultGirlColorList): + return 'Invalid female leg color index: ' + str(value) + dna.headColor = value + dna.armColor = value + dna.legColor = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Color index set to: ' + str(dna.headColor) + + if part == 'gloves': + try: + value = int(value) + except: + return 'Invalid value type for: ' + part + if value != 0: + return 'Invalid glove color: ' + str(value) + dna.gloveColor = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Glove color set to: ' + str(dna.gloveColor) + + if part == 'toptex': + if not 0 <= value <= len(ToonDNA.Shirts): + return 'Top texture index out of range (0-%d).' % len(ToonDNA.Shirts) + dna.topTex = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Top texture index set to: ' + str(dna.topTex) + + if part == 'toptexcolor': + if not 0 <= value <= len(ToonDNA.ClothesColors): + return 'Top texture color index out of range(0-%d).' % len(ToonDNA.ClothesColors) + dna.topTexColor = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Top texture color index set to: ' + str(dna.topTexColor) + + if part == 'sleevetex': + if not 0 <= value <= len(ToonDNA.Sleeves): + return 'Sleeve texture index out of range(0-%d).' % len(ToonDNA.Sleeves) + dna.sleeveTex = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Sleeve texture index set to: ' + str(dna.sleeveTex) + + if part == 'sleevetexcolor': + if not 0 <= value <= len(ToonDNA.ClothesColors): + return 'Sleeve texture color index out of range(0-%d).' % len(ToonDNA.ClothesColors) + dna.sleeveTexColor = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Sleeve texture color index set to: ' + str(dna.sleeveTexColor) + + if part == 'bottex': + if dna.gender not in ('m', 'f'): + return 'Unknown gender.' + if dna.gender == 'm': + bottoms = ToonDNA.BoyShorts + else: + bottoms = ToonDNA.GirlBottoms + if not 0 <= value <= len(bottoms): + return 'Bottom texture index out of range (0-%d).' % len(bottoms) + dna.botTex = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Bottom texture index set to: ' + str(dna.botTex) + + if part == 'bottexcolor': + if not 0 <= value <= len(ToonDNA.ClothesColors): + return 'Bottom texture color index out of range(0-%d).' % len(ToonDNA.ClothesColors) + dna.botTexColor = value + invoker.b_setDNAString(dna.makeNetString()) + return 'Bottom texture color index set to: ' + str(dna.botTexColor) + + # these arguments don't work + # if part == 'save': + # backup = simbase.backups.load('toon', (invoker.doId,), default={}) + # backup.setdefault('dna', {})[value] = invoker.getDNAString() + # simbase.backups.save('toon', (invoker.doId,), backup) + # return 'Saved a DNA backup for %s under the name: %s' % (invoker.getName(), value) + + # if part == 'restore': + # backup = simbase.backups.load('toon', (invoker.doId,), default={}) + # if value not in backup.get('dna', {}): + # return "Couldn't find a DNA backup for %s under the name: %s" % (invoker.getName(), value) + # invoker.b_setDNAString(backup['dna'][value]) + # return 'Restored a DNA backup for %s under the name: %s' % (invoker.getName(), value) + + return 'Invalid part: ' + part + + +# Start new magic words implemented for crane league debugging +class ShowScoreboard(MagicWord): + aliases = ['scoreboard'] + desc = 'make scoreboard appear' + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'TTOFF_DEVELOPER' + + def getNearbyToons(self): + from toontown.toon.Toon import Toon + t = [] + for disObj in list(base.cr.doId2do.values()): + if isinstance(disObj, Toon): + t.append(disObj) + + return t + + def handleWord(self, invoker, avId, toon, *args): + + global DEBUG_SCOREBOARD + global DEBUG_HEAT + + if not DEBUG_SCOREBOARD: + from toontown.coghq.CogBossScoreboard import CogBossScoreboard + DEBUG_SCOREBOARD = CogBossScoreboard() + DEBUG_SCOREBOARD.show() + DEBUG_HEAT = CraneLeagueHeatDisplay() + # for toon in self.getNearbyToons(): + # DEBUG_SCOREBOARD.addToon(toon.getDoId()) + + return "scoreboard created!" + + n = random.randint(100, 1500) + m = random.randint(5, 80) + if random.randint(0, 99) <= 50: + s = "BONUS!" + else: + s = '' + + DEBUG_HEAT.set_heat(n) + DEBUG_SCOREBOARD.addScore(avId, m, reason=s) + + return "set heat to " + str(n) + + +class ShowActivityLog(MagicWord): + aliases = ['log', 'activitylog'] + desc = 'make debug log appear' + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'NO_ACCESS' + + CHOICES = [ + 'ye', + 'as;lkdfj;laksdfj;lkasdjf;lkasdfl;kasdljk;fasdf', + 'aaaaaaaaaaaaaaaaaaaaa', + 'words words words words words words words words words words words', + 'msg_labelmsg_labelmsg_labelmsg_labelmsg_labelmsg_label msg_label msg_labelmsg_label msg_label' + + ] + + def handleWord(self, invoker, avId, toon, *args): + global DEBUG_LOG + + if not DEBUG_LOG: + DEBUG_LOG = ActivityLog() + + DEBUG_LOG.addToLog(random.choice(self.CHOICES)) + + return 'modified log' + + +# Command that forces your state to the 'Walk' state. +# Used in rare circumstances where your toon gets softlocked to prevent having to restart the game. +# Note: This is still a cheat so it should only be used when needed, as there are many scenarios where you +# can break the game by running this command when you shouldn't. +class FreeLocalToon(MagicWord): + aliases = ['free', 'unstuck', 'freeme', 'unstick', 'imstuck', 'stuck'] + desc = "Forces your toon to be set in the 'walk' state where you can regain control of your toon and walk around freely. Use at your own risk." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + + # Check for common errors when trying to do this + if not hasattr(self.cr, 'playGame') or not hasattr(self.cr.playGame, 'place') or self.cr.playGame.getPlace() is None: + return "Your toon is in an invalid state to run this command!" + + self.cr.playGame.getPlace().setState('walk') + return "Freed your toon!" + + +# Command to start a "sandbox" battle. +# Used for toying with battle mechanics. +class SandboxBattle(MagicWord): + aliases = ['sandbox', 'custombattle'] + desc = 'Starts a sandbox turn based battle. Used for testing battle mechanics and gives control of a dynamic suit planner.' + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'TTOFF_DEVELOPER' + + def handleWord(self, invoker, avId, toon, *args): + from ..battle.DistributedBattleSandboxAI import DistributedBattleSandboxAI + from ..toon.DistributedToonAI import DistributedToonAI + + if toon.isBattling(): + return f"{toon.getName()} is battling! Cannot start a new one." + + teammates: List[int] = [] + for otherToonId, otherToon in self.air.getObjectsOfClassInZone(self.air.districtId, toon.zoneId, DistributedToonAI).items(): + if otherToon.isPlayerControlled() and toon != otherToon: + teammates.append(otherToonId) + + if len(teammates) > 3: + teammates = teammates[:3] + + zoneId = toon.zoneId + + # Start a new battle for them. + battle: DistributedBattleSandboxAI = DistributedBattleSandboxAI(self.air, zoneId) + battle.generateWithRequired(zoneId) + battle.start(toon.doId, otherToons=teammates) + suff = f" and {len(teammates)} others!" if len(teammates) > 0 else '!' + return f"Started a sandbox battle for {toon.getName()}{suff}" + + +# Use this command template for spawning objects client side to tweak attributes quickly +# class SpawnObject(MagicWord): +# aliases = ["sb"] +# desc = "Spawn barrel." +# execLocation = MagicWordConfig.EXEC_LOC_CLIENT +# +# def handleWord(self, invoker, avId, toon, *args): +# from ..coghq.DistributedAPCheckBarrel import DistributedAPCheckBarrel +# b = DistributedAPCheckBarrel(base.cr) +# b.generateInit() +# b.reparentTo(render) +# b.loadModel() +# b.applyLabel() +# pos = base.localAvatar.getPos() +# b.setPos(pos[0]+5, pos[1], pos[2]) +# return "Spawned a barrel" + + +class QMSetInventory(MagicWord): + aliases = ["qmsetinventory"] + desc = "Sets inventory for quick match toon." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + arguments = [("invStr", str, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + invStr = args[0] + try: + counts = [int(x) for x in invStr.split(",")] + if len(counts) != 49: + return "Invalid gag count length!" + except Exception as e: + return f"Error parsing inventory: {str(e)}" + + inventory = toon.inventory + idx = 0 + for track in range(7): + for level in range(7): + inventory.inventory[track][level] = counts[idx] + idx += 1 + + inventory.calcTotalProps() + toon.b_setInventory(inventory.makeNetString()) + return "Quick match inventory set successfully." + + +class QMStartCogBattle(MagicWord): + aliases = ["qmstartcogbattle"] + desc = "Starts the quick match cog battle." + execLocation = MagicWordConfig.EXEC_LOC_SERVER + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + zoneId = self.air.allocateZone() + from toontown.building.DistributedQuickMatchCogInteriorAI import DistributedSuitInteriorAI + interior = DistributedSuitInteriorAI(self.air, zoneId, toon.doId) + interior.generateWithRequired(zoneId) + + if not hasattr(self.air, 'debos'): + self.air.debos = {} + self.air.debos[toon.doId] = interior + + self.air.magicWordManager.sendClientCommand( + avId, 'qmteleporttobattle', 'QMTeleportToBattle', + targetIds=[avId], parsedArgList=[zoneId] + ) + return "Quick match battle started." + + +class QMTeleportToBattle(MagicWord): + aliases = ["qmteleporttobattle"] + desc = "Client side teleport to quick match cog battle." + execLocation = MagicWordConfig.EXEC_LOC_CLIENT + arguments = [("zoneId", int, True)] + accessLevel = 'USER' + + def handleWord(self, invoker, avId, toon, *args): + zoneId = args[0] + requestStatus = { + 'hoodId': ToontownGlobals.ToontownCentral, + 'zoneId': zoneId, + 'loader': 'suitInterior', + 'where': 'suitInterior', + 'how': 'teleportIn', + 'shardId': None, + 'avId': -1 + } + place = base.cr.playGame.getPlace() + if place: + place.fsm.request('teleportOut', [requestStatus]) + + +# Loop through every registered subclass of MagicWord and instantiate it. +for magicWordSubclass in MagicWord.__subclasses__(): + _ = magicWordSubclass() diff --git a/toontown/spellbook/MagicWordServerCommands.py b/toontown/spellbook/MagicWordServerCommands.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/spellbook/TTOffMagicWordManager.py b/toontown/spellbook/TTOffMagicWordManager.py new file mode 100644 index 0000000..999313f --- /dev/null +++ b/toontown/spellbook/TTOffMagicWordManager.py @@ -0,0 +1,153 @@ +from direct.directnotify import DirectNotifyGlobal +from direct.distributed import DistributedObject + +from libotp import WhisperPopup +from libotp.nametag.WhisperGlobals import WhisperType +from otp.otpbase.OTPGlobals import * + +from toontown.spellbook.MagicWordConfig import * +from toontown.spellbook.MagicWordIndex import * + +import json +import random +import string + +class TTOffMagicWordManager(DistributedObject.DistributedObject): + notify = DirectNotifyGlobal.directNotify.newCategory('TTOffMagicWordManager') + neverDisable = 1 + + def __init__(self, cr): + DistributedObject.DistributedObject.__init__(self, cr) + base.cr.magicWordManager = self + self.chatPrefix = PREFIX_DEFAULT + self.wizardName = WIZARD_DEFAULT + self.lastClickedAvId = 0 + + def announceGenerate(self): + DistributedObject.DistributedObject.announceGenerate(self) + activatorIndex = base.settings.get('magic-word-activator') + if 0 <= activatorIndex <= (len(PREFIX_ALLOWED) - 1): + self.chatPrefix = PREFIX_ALLOWED[activatorIndex] + + self.accept(INCOMING_CHAT_MESSAGE_NAME, self.checkMagicWord) + self.accept('clickedNametag', self.__handleClickedNametag) + + def disable(self): + DistributedObject.DistributedObject.disable(self) + self.ignore(INCOMING_CHAT_MESSAGE_NAME) + self.ignore('clickedNametag') + + def setChatPrefix(self, chatPrefix): + self.chatPrefix = chatPrefix + + def __handleClickedNametag(self, avatar): + if avatar: + self.lastClickedAvId = avatar.getDoId() + + def checkMagicWord(self, magicWord): + if not magicWord.startswith(self.chatPrefix): + return + + if base.localAvatar.getAccessLevel() < AccessLevelName2Int.get('NO_ACCESS'): + self.generateResponse(responseType="NoAccess") + return + + self.handleMagicWord(magicWord) + + def generateResponse(self, responseType, magicWord='', args=None, returnValue=None, affectRange=None, + affectType=None, affectExtra=None, lastClickedAvId=None, extraMessageData = None): + response = self.generateMagicWordResponse(responseType, magicWord, args, returnValue, affectRange, affectType, + affectExtra, lastClickedAvId, extraMessageData) + base.localAvatar.setSystemMessage(0, response, WhisperType.WTMagicWord) + self.notify.info(response) + + def generateMagicWordResponse(self, responseType, magicWord, args, returnValue, affectRange, affectType, + affectExtra, lastClickedAvId, extraMessageData): + response = '' + if responseType == "SuccessNoResp" and magicWord: + response = '' + successExclaim = random.choice(MAGIC_WORD_SUCCESS_PHRASES) + response += successExclaim + return response + elif responseType == "Success": + response += returnValue + return response + response += MAGIC_WORD_RESPONSES.get(responseType, "...I don't know how to respond!") + if responseType in MagicWordConfig.HAS_EXTRA_MESSAGE_DATA: + response = response.format(extraMessageData) + return response + + def handleMagicWord(self, magicWord): + affectRange = AFFECT_NONE + affectType = AFFECT_SINGULAR + affectExtra = -1 + for x in range(3): + if magicWord.startswith(self.chatPrefix * (3 - x)): + affectRange = 2 - x + break + + if affectRange == AFFECT_NONE: + self.generateResponse(responseType = "NoEffect") + return + elif affectRange == AFFECT_OTHER: + toon = base.cr.doId2do.get(self.lastClickedAvId) + if not toon: + self.generateResponse(responseType = "NoTarget") + return + if toon.getTransitioning(): + self.generateResponse(responseType = "OtherTeleporting") + return + + activatorLength = affectRange + 1 + magicWordNoPrefix = magicWord[activatorLength:] + for type in AFFECT_TYPES: + if magicWordNoPrefix.startswith(type): + magicWordNoPrefix = magicWordNoPrefix[len(type):] + affectType = AFFECT_TYPES.index(type) + break + + if affectType == AFFECT_RANK: + for level in list(OTPGlobals.AccessLevelName2Int.values()): + if magicWordNoPrefix.startswith(str(level)): + try: # FIXME: !?!?!?!?!??!?! + int(magicWordNoPrefix[len(str(level)):][:1]) + self.generateResponse(responseType = "BadTarget") # ???? + return + except: + pass + + magicWordNoPrefix = magicWordNoPrefix[len(str(level)):] + affectExtra = level + break + + if affectExtra == -1: + self.generateResponse(responseType = "BadTarget") + return + + word = magicWordNoPrefix.split(' ', 1)[0].lower() + if word not in magicWordIndex: + for magicWord in list(magicWordIndex.keys()): + if word in magicWord: + self.generateResponse(responseType = "CloseWord", extraMessageData = magicWord) + return + self.generateResponse(responseType = "BadWord") + return + + magicWordInfo = magicWordIndex[word] + if magicWordInfo['execLocation'] == EXEC_LOC_INVALID: + raise ValueError("execLocation not set for magic word {}!".format(magicWordInfo['classname'])) + elif magicWordInfo['execLocation'] in [EXEC_LOC_SERVER, EXEC_LOC_CLIENT]: + self.sendUpdate('requestExecuteMagicWord', + [affectRange, affectType, affectExtra, self.lastClickedAvId, magicWordNoPrefix]) + + def executeMagicWord(self, word, commandName, targetIds, args, affectRange, affectType, affectExtra, lastClickedAvId): + command = getMagicWord(commandName) + command = command(None, self.cr, base.localAvatar.getDoId(), targetIds, args) + + returnValue = command.executeWord() + if returnValue: + self.generateResponse(responseType = "Success", returnValue=returnValue) + else: + self.generateResponse(responseType = "SuccessNoResp", magicWord=word, args=args, + affectRange=affectRange, affectType=affectType, affectExtra=affectExtra, + lastClickedAvId=lastClickedAvId) diff --git a/toontown/spellbook/TTOffMagicWordManagerAI.py b/toontown/spellbook/TTOffMagicWordManagerAI.py new file mode 100644 index 0000000..5b3fc42 --- /dev/null +++ b/toontown/spellbook/TTOffMagicWordManagerAI.py @@ -0,0 +1,204 @@ +from direct.directnotify import DirectNotifyGlobal +from direct.distributed import DistributedObjectAI + +from otp.avatar.DistributedPlayerAI import DistributedPlayerAI +from otp.otpbase import OTPGlobals + +from toontown.spellbook.MagicWordConfig import * +from toontown.spellbook.MagicWordIndex import * + +import json +import os + + +MagicWordIndex = magicWordIndex.copy() + +spellbookJsonDefaultValues = '''{ + "words": + [ + { + "name": "SetPos", + "access": "USER" + }, + { + "name": "GetPos", + "access": "USER" + } + ] +} +''' + +if not os.path.exists('config/'): + os.mkdir('config/') + +if not os.path.isfile('config/spellbook.json'): + with open('config/spellbook.json', 'w') as data: + data.write(spellbookJsonDefaultValues) + data.close() + +with open('config/spellbook.json') as data: + spellbook = json.load(data) + +for word in spellbook['words']: + name = word['name'] + accessLevel = word['access'] + + if accessLevel not in list(OTPGlobals.AccessLevelName2Int.keys()): + break + + try: + wordInfo = MagicWordIndex[str(name.lower())] + for alias in wordInfo['aliases']: + MagicWordIndex[alias]['access'] = accessLevel + except: + pass + + +class TTOffMagicWordManagerAI(DistributedObjectAI.DistributedObjectAI): + notify = DirectNotifyGlobal.directNotify.newCategory('TTOffMagicWordManagerAI') + + def requestExecuteMagicWord(self, affectRange, affectType, affectExtra, lastClickedAvId, magicWord): + avId = self.air.getAvatarIdFromSender() + toon = self.air.doId2do.get(avId) + if not toon: + self.notify.warning('requestExecuteMagicWord: Magic Word use requested but invoker avatar is non-existant!') + return + + targetIds = [] + if affectRange in [AFFECT_SINGLE, AFFECT_BOTH]: + targetIds.append(avId) + + if (affectRange in [AFFECT_OTHER, AFFECT_BOTH]) and affectType == AFFECT_SINGULAR: + if lastClickedAvId: + targetIds.append(lastClickedAvId) + else: + self.generateResponse(avId=avId, responseType = "NoTarget") + return + + if affectType in [AFFECT_ZONE, AFFECT_SERVER, AFFECT_RANK]: + toonIds = [] + for doId in list(self.air.doId2do.keys())[:]: + do = self.air.doId2do.get(doId) + if isinstance(do, DistributedPlayerAI) and do.isPlayerControlled() and do != toon: + if affectType == AFFECT_ZONE and do.zoneId == toon.zoneId: + toonIds.append(doId) + elif affectType == AFFECT_SERVER: + toonIds.append(doId) + elif affectType == AFFECT_RANK and do.getAccessLevel() == affectExtra: + toonIds.append(doId) + + if not toonIds and not targetIds: + self.generateResponse(avId=avId, responseType = "NoTarget") + return + + targetIds += toonIds + + if not targetIds: + self.generateResponse(avId=avId, responseType = "NoTarget") + return + + for targetId in targetIds: + if targetId == avId: + continue + targetToon = self.air.doId2do.get(targetId) + if not targetToon: + continue + # if targetToon.getAccessLevel() >= toon.getAccessLevel(): + # targetIds.remove(targetId) + # continue + + if len(targetIds) == 0: + self.generateResponse(avId=avId, responseType="NoAccessToTarget") + + magicWord, args = (magicWord.split(' ', 1) + [''])[:2] + magicWord = magicWord.lower() + magicWordInfo = MagicWordIndex.get(magicWord) + + # Is the client out of sync with the server? + if magicWordInfo is None: + self.generateResponse(avId=avId, responseType="BadWord") + return + + if affectRange not in magicWordInfo['affectRange']: + self.generateResponse(avId=avId, responseType="RestrictionOther") + return + + if toon.getAccessLevel() < OTPGlobals.AccessLevelName2Int.get(magicWordInfo['access'], 0): + if toon.getAccessLevel() == OTPGlobals.AccessLevelName2Int.get("NO_ACCESS") and \ + OTPGlobals.AccessLevelName2Int.get(magicWordInfo['access'], 0) == OTPGlobals.AccessLevelName2Int.get("USER"): + self.generateResponse(avId=avId, responseType="NoCheatAccess") + return + self.generateResponse(avId=avId, responseType="NoAccess") + return + + if self.air.defaultAccessLevel == 'NO_ACCESS': + if not magicWordInfo['administrative']: + self.generateResponse(avId=avId, responseType="LegitServer") + return + + commandArgs = magicWordInfo['args'] + maxArgs = len(commandArgs) + minArgs = 0 + argList = args.split(None, maxArgs-1) + for argSet in commandArgs: + isRequired = argSet[ARGUMENT_REQUIRED] + if isRequired: + minArgs += 1 + + if len(argList) < minArgs: + self.generateResponse(avId=avId, responseType = "NotEnoughArgs", extraMessageData = "{} argument{}".format(minArgs, "s" if minArgs != 1 else '')) + return + elif len(argList) > maxArgs: + self.generateResponse(avId=avId, responseType = "TooManyArgs", extraMessageData = "{} argument{}".format(maxArgs, "s" if maxArgs != 1 else '')) + return + + parsedArgList = [] + + if len(argList) < maxArgs: + for x in range(minArgs,maxArgs): + if commandArgs[x][ARGUMENT_REQUIRED] or len(argList) >= x+1: + continue + argList.append(commandArgs[x][ARGUMENT_DEFAULT]) + + for x in range(len(argList)): + arg = argList[x] + argType = commandArgs[x][ARGUMENT_TYPE] + try: + parsedArg = argType(arg) + except: + self.generateResponse(avId=avId, responseType = "BadArgs") + return + + parsedArgList.append(parsedArg) + + if magicWordInfo['execLocation'] == EXEC_LOC_CLIENT: + self.sendClientCommand(avId, magicWord, magicWordInfo['classname'], targetIds=targetIds, parsedArgList=parsedArgList, affectRange=affectRange, + affectType=affectType, affectExtra=affectExtra, lastClickedAvId=lastClickedAvId) + else: + command = getMagicWord(magicWordInfo['classname']) + command = command(self.air, None, avId, targetIds, parsedArgList) + returnValue = command.executeWord() + if returnValue: + self.generateResponse(avId=avId, responseType = "Success", returnValue=returnValue) + else: + self.generateResponse(avId=avId, responseType = "SuccessNoResp", magicWord=magicWord, parsedArgList=parsedArgList, + affectRange=affectRange, affectType=affectType, affectExtra=affectExtra, + lastClickedAvId=lastClickedAvId) + + def generateResponse(self, avId, responseType = "BadWord", magicWord='', parsedArgList=None, returnValue='', affectRange=0, + affectType=0, affectExtra=0, lastClickedAvId=0, extraMessageData=''): + if not parsedArgList: + parsedArgList = [] + + parsedArgList = json.dumps(parsedArgList) + self.sendUpdateToAvatarId(avId, 'generateResponse', + [responseType, magicWord, parsedArgList, returnValue, affectRange, affectType, + affectExtra, lastClickedAvId, extraMessageData]) + + def sendClientCommand(self, avId, word, commandName, targetIds=[], parsedArgList=None, affectRange=0, + affectType=0, affectExtra=0, lastClickedAvId=0): + if not parsedArgList: + parsedArgList = [] + + parsedArgList = json.dumps(parsedArgList) + self.sendUpdateToAvatarId(avId, "executeMagicWord", [word, commandName, targetIds, parsedArgList, affectRange, affectType, affectExtra, lastClickedAvId]) diff --git a/toontown/toon/LocalToon.py b/toontown/toon/LocalToon.py index 4a00e2f..a41a11e 100644 --- a/toontown/toon/LocalToon.py +++ b/toontown/toon/LocalToon.py @@ -33,6 +33,7 @@ from toontown.shtiker import DisguisePage from toontown.shtiker import PhotoAlbumPage from toontown.shtiker import FishPage from toontown.shtiker import NPCFriendPage +from toontown.shtiker import WordPage from toontown.shtiker import EventsPage from toontown.shtiker import TIPPage from toontown.quest import Quests @@ -377,6 +378,9 @@ class LocalToon(DistributedToon.DistributedToon, LocalAvatar.LocalAvatar): self.suitPage = SuitPage.SuitPage() self.suitPage.load() self.book.addPage(self.suitPage, pageName=TTLocalizer.SuitPageTitle) + self.wordPage = WordPage.WordPage() + self.wordPage.load() + self.book.addPage(self.wordPage, pageName=TTLocalizer.SpellbookPageTitle) if base.config.GetBool('want-photo-album', 0): self.photoAlbumPage = PhotoAlbumPage.PhotoAlbumPage() self.photoAlbumPage.load() diff --git a/toontown/toonbase/TTLocalizerEnglish.py b/toontown/toonbase/TTLocalizerEnglish.py index 688bcd9..60b8249 100644 --- a/toontown/toonbase/TTLocalizerEnglish.py +++ b/toontown/toonbase/TTLocalizerEnglish.py @@ -9899,3 +9899,32 @@ OptionDisabled = "Disabled" OptionRefresh = "Refresh" WinConditionMet = 'Congratulations, you have completed your goal! You may now talk to Flippy to redeem your reward!' RefreshDialogMsg = "Refreshing audio devices..." + +SpellbookPageTitle = 'Spellbook' +WordPageTabTitle = 'Spellbook' +WordPageHelp = 'This page contains a list of all of the Magic Words that can be used in-game.' +WordPageSearch = 'Search' +WordPageActivator = 'Activator: ' +WordPageTotal = 'Total Words: ' +WordPageCurrent = 'Selected: ' +WordPageNA = 'N/A' +WordPageCopyToChat = 'Copy To Chat' +WordPageMoreInfo = 'More Info' +WordPageDescription = 'Description: ' +WordPageExample = 'Example: ' +WordPageAliases = 'Aliases: ' +WordPageAccessLevel = 'Required Access Level: ' +ClothingPageTitle = 'Clothing' +ClothingPageShirt = 'Shirt IDs' +ClothingPageSleeve = 'Sleeve IDs' +ClothingPageShort = 'Shorts IDs' +AccessoriesPageHeadTab = 'Head\nAccessories' +AccessoriesPageBodyTab = 'Body\nAccessories' +AccessoriesPageHead = 'Head Accessories' +AccessoriesPageBody = 'Body Accessories' +AccessoriesPageHat = 'Hat IDs' +AccessoriesPageGlasses = 'Glasses IDs' +AccessoriesPageBackpack = 'Backpack IDs' +AccessoriesPageShoes = 'Shoes IDs' +TeleportGUITitle = 'Teleport' +TeleportGUITeleport = 'Go!' diff --git a/toontown/toonbase/ToontownGlobals.py b/toontown/toonbase/ToontownGlobals.py index da6e354..0ca0f08 100644 --- a/toontown/toonbase/ToontownGlobals.py +++ b/toontown/toonbase/ToontownGlobals.py @@ -1671,3 +1671,4 @@ AV_TOUCH_CHECK_DIST_Z = 5.0 AV_TOUCH_CHECK_TIMELIMIT_CL = 0.002 AV_TOUCH_COUNT_LIMIT = 5 AV_TOUCH_COUNT_TIME = 300 +GravityValue = 32.174 diff --git a/toontown/util/HotReloadWatcher.py b/toontown/util/HotReloadWatcher.py new file mode 100644 index 0000000..d52a195 --- /dev/null +++ b/toontown/util/HotReloadWatcher.py @@ -0,0 +1,77 @@ +import importlib +import os +import sys +import time + +from direct.showbase.MessengerGlobal import messenger + + +class HotReloadWatcher: + def __init__(self, roots, interval=0.5): + self.roots = [os.path.normpath(r) for r in roots] + self.interval = float(interval) + self._mtimes = {} + self._nextCheck = 0.0 + + def _scan(self): + changed = [] + for root in self.roots: + if not os.path.isdir(root): + continue + for dirpath, _, filenames in os.walk(root): + for fn in filenames: + if not fn.endswith('.py'): + continue + path = os.path.join(dirpath, fn) + try: + mtime = os.path.getmtime(path) + except OSError: + continue + old = self._mtimes.get(path) + if old is None: + self._mtimes[path] = mtime + continue + if mtime != old: + self._mtimes[path] = mtime + changed.append(path) + return changed + + def _pathToModule(self, path): + path = os.path.normpath(path) + for prefix in self.roots: + prefix = os.path.normpath(prefix) + if not path.startswith(prefix + os.sep): + continue + rel = path[len(prefix) + 1:] + if rel.endswith('.py'): + rel = rel[:-3] + rel = rel.replace(os.sep, '.') + if prefix.endswith('toontown'): + return f"toontown.{rel}" + if prefix.endswith('otp'): + return f"otp.{rel}" + return None + + def reloadChanged(self): + changedPaths = self._scan() + if not changedPaths: + return + modules = [] + for p in changedPaths: + mod = self._pathToModule(p) + if mod and mod in sys.modules: + modules.append(mod) + # Reload deepest modules first. + modules = sorted(set(modules), key=lambda m: (-m.count('.'), m)) + reloaded = [] + for mod in modules: + try: + importlib.reload(sys.modules[mod]) + reloaded.append(mod) + except Exception: + # If reload fails, let caller decide next action. + messenger.send('hot-reload-failed', [mod]) + return + if reloaded: + messenger.send('hot-reload-applied', [reloaded]) + diff --git a/toontown/util/__init__.py b/toontown/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/util/astron/AstronDict.py b/toontown/util/astron/AstronDict.py new file mode 100644 index 0000000..481f84e --- /dev/null +++ b/toontown/util/astron/AstronDict.py @@ -0,0 +1,18 @@ +from toontown.util.astron.AstronStruct import AstronStruct +import json + + +class AstronDict(dict, AstronStruct): + + def toStruct(self): + return [json.dumps(self)] + + @classmethod + def fromStruct(cls, struct): + if not struct[0]: + return cls() + return cls(json.loads(struct[0])) + + @classmethod + def fromDict(cls, d: dict): + return cls(d) diff --git a/toontown/util/astron/AstronStruct.py b/toontown/util/astron/AstronStruct.py new file mode 100644 index 0000000..9a41638 --- /dev/null +++ b/toontown/util/astron/AstronStruct.py @@ -0,0 +1,19 @@ +class AstronStruct: + + def toStruct(self): + raise NotImplementedError + + @classmethod + def fromStruct(cls, struct): + if struct: + return cls(*struct) + else: + return cls() + + @staticmethod + def toStructList(astronStructs): + return [astronStruct.toStruct() for astronStruct in astronStructs] + + @classmethod + def fromStructList(cls, struct): + return [cls.fromStruct(substruct) for substruct in struct] diff --git a/toontown/util/astron/__init__.py b/toontown/util/astron/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/util/dev/Injector.py b/toontown/util/dev/Injector.py new file mode 100644 index 0000000..589b63f --- /dev/null +++ b/toontown/util/dev/Injector.py @@ -0,0 +1,36 @@ +import traceback + +from direct.showbase.DirectObject import DirectObject + + +INJECTOR_KEY = 'f12' +INJECTOR_FILE = './toontown/util/dev/inject.py' + + +class DeveloperInjector(DirectObject): + + def start(self): + print(f'Now listening for injector events. When {INJECTOR_KEY} is pressed all contents in {INJECTOR_FILE} are ran.') + self.accept(INJECTOR_KEY, self.__inject) + + def stop(self): + self.ignoreAll() + + def __inject(self): + + try: + with open(INJECTOR_FILE, 'r'): + pass + except FileNotFoundError: + print(f"File {INJECTOR_FILE} does not exist. Please create it and try again.") + traceback.print_exc() + return + + with open(INJECTOR_FILE, 'r') as f: + code = f.read() + try: + exec(code, globals()) + except Exception as e: + print(f"Exception thrown while injecting: {e}") + traceback.print_exc() + return diff --git a/toontown/util/dev/__init__.py b/toontown/util/dev/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toontown/util/dev/inject.py b/toontown/util/dev/inject.py new file mode 100644 index 0000000..86eb16c --- /dev/null +++ b/toontown/util/dev/inject.py @@ -0,0 +1,7 @@ +from panda3d.core import * + +# Write some code here and press f12 :3 +# You have access to reference global vars (mainly base) +from otp.otpbase.OTPLocalizerEnglish import EmoteFuncDict +base.localAvatar.playEmote(EmoteFuncDict['Dance'], 1, None) +base.localAvatar.broadcastHpString('i did it mom im cheating', 1, 1, 1) diff --git a/toontown/util/ui.py b/toontown/util/ui.py new file mode 100644 index 0000000..075fc14 --- /dev/null +++ b/toontown/util/ui.py @@ -0,0 +1,13 @@ +from direct.gui import DirectGuiGlobals +from direct.gui.DirectScrolledList import DirectScrolledList + + +def make_dsl_scrollable(dsl: DirectScrolledList): + affected_ui = [dsl.itemFrame, dsl] + for item in dsl['items']: + if not isinstance(item, str): + affected_ui.append(item) + + for ui in affected_ui: + ui.bind(DirectGuiGlobals.WHEELUP, lambda *_: dsl.scrollTo(dsl.index - 1)) + ui.bind(DirectGuiGlobals.WHEELDOWN, lambda *_: dsl.scrollTo(dsl.index + 1)) From 44f5fe87a5746797a0e00085aac74c2e018881b3 Mon Sep 17 00:00:00 2001 From: Toontown Super <108632596+ToontownSuperForeverMVP@users.noreply.github.com> Date: Sun, 2 Aug 2026 03:03:31 -0400 Subject: [PATCH 2/2] Docs: Update GEMINI.md with completed magic word system work and PR #12 details --- GEMINI.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/GEMINI.md b/GEMINI.md index ffaff5e..45ca287 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -68,10 +68,13 @@ Target: `C:\Users\Shadow\Desktop\ttbtn` - [x] Ported `toontown/settings/Settings.py`: Persistent JSON-backed settings manager with controls mapping, graphics/audio defaults, and property getters/setters. - [x] Ported revamped `toontown/shtiker/OptionsPage.py` & `OptionsPageGUI.py`: Tabbed options page (Gameplay, Privacy, Controls, Video, Audio) with resolution pickers, anti-aliasing, FPS limiters, and lighting toggles. - [x] Integrated `toontown/shtiker/ShtikerBook.py` & `toontown/toontowngui/ToontownScrolledFrame.py` with full OptionsPage support and localizations in `TTLocalizerEnglish.py`. -- [x] Submitted Pull Request #9 (`feature/modern-loading-system`), Pull Request #10 (`feature/outdoor-lighting-and-procedural-quests`), and Pull Request #11 (`feature/settings-and-shtikerbook-revamp`) to GitHub repository. +- [x] Ported Magic Word System (`toontown/spellbook/`): Integrated `MagicWordIndex.py` (340 magic words registered), `MagicWordConfig.py`, `TTOffMagicWordManager.py`, `TTOffMagicWordManagerAI.py`, and `ToontownMagicWordManager.py`. +- [x] Ported Spellbook Shtiker Page (`toontown/shtiker/WordPage.py`): Tabbed Spellbook page with search, category filters, copy-to-chat, argument inspectors, clothing ID tab, and accessory ID tabs. +- [x] Integrated `LocalToon.py` & `toontown/util/ui.py`: Registered `WordPage` in ShtikerBook and implemented `make_dsl_scrollable` wheel scrolling. +- [x] Submitted Pull Request #9 (`feature/modern-loading-system`), Pull Request #10 (`feature/outdoor-lighting-and-procedural-quests`), Pull Request #11 (`feature/settings-and-shtikerbook-revamp`), and Pull Request #12 (`feature/magic-words-and-spellbook-page`) to GitHub repository. # TODO List for Future AI Instances -- [ ] Merge and review PR #9, PR #10, and PR #11 into `develop`. +- [ ] Merge and review PR #9, PR #10, PR #11, and PR #12 into `develop`. - [ ] Verify `TaskAutoer.py` quest automation compatibility with the procedural quest dict during live server sessions. - [ ] Validate server stability with Astron local launcher under heavy zone switching.