Merge pull request #12 from ToontownSuperForeverMVP/feature/magic-words-and-spellbook-page

MagicWords & UI: Full magic word system and Spellbook WordPage in ShtikerBook
This commit is contained in:
Toontown Super 2026-08-02 16:21:18 -04:00 committed by GitHub
commit f2caf10594
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 7937 additions and 708 deletions

View File

@ -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.

883
otp/ai/MagicWordManager.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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 <zoneNum>')
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 <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

View File

@ -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

1851
toontown/shtiker/WordPage.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
]
}
'''

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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])

View File

@ -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()

View File

@ -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!'

View File

@ -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

View File

@ -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])

View File

View File

@ -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)

View File

@ -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]

View File

View File

@ -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

View File

View File

@ -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)

13
toontown/util/ui.py Normal file
View File

@ -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))