commit 0e7bfc1fe29fd595df0b982e40f94c30befb1ec7
Author: satire6 <66761962+satire6@users.noreply.github.com>
Date: Fri Sep 11 12:58:04 2020 -0400
Initial commit
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..758c1ff
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,3 @@
+Toontown Online is a MMORPG created by Disney Interactive, shut down in 2013.
+
+This is a collection of various source code for the game.
\ No newline at end of file
diff --git a/otp/.cvsignore b/otp/.cvsignore
new file mode 100644
index 0000000..6f5a0ae
--- /dev/null
+++ b/otp/.cvsignore
@@ -0,0 +1,5 @@
+.cvsignore
+__init__.py
+Makefile
+pp.dep
+built
diff --git a/otp/.project b/otp/.project
new file mode 100644
index 0000000..9ceab4a
--- /dev/null
+++ b/otp/.project
@@ -0,0 +1,11 @@
+
+ "
+ pageText += "
You must specify an avatarId.\r\n")
+ return
+
+ try:
+ id = int(avatarId)
+ except:
+ replyTo.respondHTTP("400 Bad Request","Error 400: Bad Request
Error parsing avatarId.\r\n")
+ return
+
+ self.requestRender(id,replyTo)
+
+ def handleHTTPQueueSnapshot(self,replyTo,**kw):
+ avatarId = kw.get("avatarId",None)
+ if avatarId is None:
+ replyTo.respondHTTP("400 Bad Request","Error 400: Bad Request
You must specify an avatarId.\r\n")
+ return
+
+ try:
+ id = int(avatarId)
+ except:
+ replyTo.respondHTTP("400 Bad Request","Error 400: Bad Request
Error parsing avatarId.\r\n")
+ return
+
+ self.requestRender(id)
+
+ replyTo.respond("Queue successful.\r\n")
+
+
+ # -- Distributed Methods --
+
+ def requestRender(self,avatarId,replyTo=None):
+ """
+ Only outside entry point to the snapshot system.
+ 'Please render this avatar' method.
+ Called from DC space or in response to an HTTP query.
+ Work is queued up and later retrieved for processing
+ by a call from a SnapshotRenderer, which will report
+ back when it's finished the task (or failed to).
+ """
+ if avatarId in self.recentlyDeletedAvatars:
+ self.notify.debug("Ignoring requestRender for deleted avatar %s." % avatarId)
+ if replyTo is not None:
+ replyTo.respondHTTP("400 Bad Request",
+ "Error 400: Bad Request
The avatar you specified could not be found.\r\n")
+ return
+ self.numServed += 1
+ jobId = self.numServed
+ writeToFile = self._idToFilename(avatarId)
+ self.jobQueue.put_nowait(RenderJob(jobId,replyTo,None,avatarId,writeToFile))
+ self.notify.debug("Job %d: Queued" % jobId)
+
+
+ def avatarDeleted(self,avatarId):
+ """
+ Message sent by the AvatarManager when an avatar gets deleted.
+ 'Please ignore any requests to render this guy.'
+ """
+ self.notify.debug("Creating deletion record for %s." % avatarId)
+ self.recentlyDeletedAvatars[avatarId] = 1
+ uber.taskMgr.doMethodLater(1800.0,self.clearRecentDeleteRecord,'clearRecentDeleteRecord-%s'%avatarId,[avatarId])
+
+
+ def requestNewWork(self,rendererLoc):
+ """
+ Update received from a SnapshotRenderer.
+ 'I am idle, give me work!'
+ Send the SnapshotRenderer some work.
+ If we've already sent him work, ignore this request
+ until he responds or times out.
+ """
+ if self.rendererIsBusy.setdefault(rendererLoc,False):
+ self.notify.debug("Ignoring work request from %d because he already has work outstanding." % rendererLoc)
+ return
+ try:
+ job = self.jobQueue.get_nowait()
+ except Queue.Empty:
+ # No work to give! Do nothing.
+ return
+
+ job.assignedTo = rendererLoc
+ self.rendererIsBusy[rendererLoc] = True
+ self.jobsInProgress[job.jobId] = job
+ self.air.sendUpdateToGlobalDoId("SnapshotRendererUD",
+ "requestRender",
+ rendererLoc,
+ [job.jobId,
+ job.avatarId,
+ job.writeToFile])
+ self.notify.debug("Job %d: Sent to renderer %d" % (job.jobId,rendererLoc))
+ # insert a task to report failure if we don't hear back
+ uber.taskMgr.doMethodLater(20.0,self.jobTimedOut,"rendertimeout-%d"%job.jobId,[job.jobId])
+
+
+ def jobTimedOut(self,jobId,task=None):
+ self.notify.warning("Timed out waiting for a response from job %d!" % jobId)
+ self.numErrors += 1
+ job = self.jobsInProgress.pop(jobId,None)
+ if job is not None:
+ self.notify.warning("Job %d was attempting to render avId %d." % (jobId,job.avatarId))
+ self.rendererIsBusy[job.assignedTo] = False
+ if job.replyTo is not None:
+ job.replyTo.timeout()
+ else:
+ self.notify.warning("Didn't have a job %d listed when we got the timeout." % jobId)
+ return Task.done
+
+ def cancelTimeout(self,jobId):
+ uber.taskMgr.remove("rendertimeout-%d" % jobId)
+
+ def errorFetchingAvatar(self,rendererLoc,jobId):
+ """
+ Update received from a SnapshotRenderer.
+ 'I had an error trying to get this avatar's data.'
+ The avatar probably doesn't exist so we should
+ report back that we failed (if someone is waiting)
+ and give up.
+
+ Also, send the SnapshotRenderer more work.
+ """
+ self.numErrors += 1
+ job = self.jobsInProgress.pop(jobId,None)
+ self.cancelTimeout(jobId)
+ if job is None:
+ self.notify.warning("Got back an error for an unrecognized job: %d" % jobId)
+ return
+
+ self.notify.warning("errorFetchingAvatar from renderer %d for job %d on avatar %d." % (rendererLoc,jobId,job.avatarId))
+
+ self.rendererIsBusy[job.assignedTo] = False
+
+ if job.replyTo is not None:
+ job.replyTo.respondHTTP("400 Bad Request",
+ "Error 400: Bad Request
The avatar you specified could not be found.\r\n")
+
+ self.requestNewWork(rendererLoc)
+
+
+ def errorRenderingAvatar(self,rendererLoc,jobId):
+ """
+ Update received from a SnapshotRenderer.
+ 'I had an error trying to render this avatar.'
+ The avatar exists but we couldn't render him
+ for some reason. Report back that we failed
+ (if someone is waiting) and give up.
+
+ Also, send the SnapshotRenderer more work.
+ """
+ self.numErrors += 1
+ job = self.jobsInProgress.pop(jobId,None)
+ self.cancelTimeout(jobId)
+ if job is None:
+ self.notify.warning("Got back an error for an unrecognized job: %d" % jobId)
+ return
+
+ self.notify.warning("errorRenderingAvatar from renderer %d for job %d on avatar %d." % (rendererLoc,jobId,job.avatarId))
+
+ self.rendererIsBusy[job.assignedTo] = False
+
+ if job.replyTo is not None:
+ job.replyTo.respondHTTP("503 Internal Server Error",
+ "Error 503: Internal Server Error
There was an error rendering your avatar.")
+
+ self.requestNewWork(rendererLoc)
+
+
+ def renderSuccessful(self,rendererLoc,jobId):
+ """
+ Update received from a SnapshotRenderer.
+ 'I successfully rendered this avatar.'
+ Report back that we succeeded (if someone is
+ waiting).
+
+ Also, send the SnapshotRenderer more work.
+ """
+ job = self.jobsInProgress.pop(jobId,None)
+ self.cancelTimeout(jobId)
+ if job is None:
+ self.notify.warning("Got back success for an unrecognized job: %d" % jobId)
+ return
+
+ self.notify.debug("Job %d: Successfully rendered avatar %d" % (jobId,job.avatarId))
+
+ self.rendererIsBusy[job.assignedTo] = False
+
+ if job.replyTo is not None:
+ job.replyTo.respond("%s"%job.writeToFile)
+
+ self.requestNewWork(rendererLoc)
+
+
diff --git a/otp/src/snapshot/SnapshotRenderer.py b/otp/src/snapshot/SnapshotRenderer.py
new file mode 100644
index 0000000..fa69b32
--- /dev/null
+++ b/otp/src/snapshot/SnapshotRenderer.py
@@ -0,0 +1,5 @@
+from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal
+from direct.directnotify.DirectNotifyGlobal import directNotify
+
+class SnapshotRenderer(DistributedObjectGlobal):
+ pass
diff --git a/otp/src/snapshot/SnapshotRendererAI.py b/otp/src/snapshot/SnapshotRendererAI.py
new file mode 100644
index 0000000..f951f1a
--- /dev/null
+++ b/otp/src/snapshot/SnapshotRendererAI.py
@@ -0,0 +1,5 @@
+from direct.distributed.DistributedObjectGlobalAI import DistributedObjectGlobalAI
+from direct.directnotify.DirectNotifyGlobal import directNotify
+
+class SnapshotRendererAI(DistributedObjectGlobalAI):
+ pass
diff --git a/otp/src/snapshot/SnapshotRendererUD.py b/otp/src/snapshot/SnapshotRendererUD.py
new file mode 100644
index 0000000..5eece00
--- /dev/null
+++ b/otp/src/snapshot/SnapshotRendererUD.py
@@ -0,0 +1,149 @@
+from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
+#from otp.otpbase import OTPGlobals
+from otp.distributed import OtpDoGlobals
+from otp.ai import AIMsgTypes
+#from otp.uberdog.UberDogUtil import ManagedAsyncRequest
+from direct.directnotify.DirectNotifyGlobal import directNotify
+
+from direct.task import Task
+import Queue
+from direct.distributed.AsyncRequest import AsyncRequest
+from pandac.PandaModules import Thread
+
+notify = directNotify.newCategory('SnapshotRendererUD')
+
+
+#--------------------------------------------------
+
+class AvatarDNARequest(AsyncRequest):
+ notify = notify
+
+ def __init__(self,air,jobId,avatarId,writeToFile):
+ assert self.notify.debugCall()
+ self.__deleted = False
+ AsyncRequest.__init__(self,air)
+
+ self.avatarId = avatarId
+ self.jobId = jobId
+ self.writeToFile = writeToFile
+
+ self.neededObjects[avatarId] = self.air.doId2do.get(avatarId)
+ if self.neededObjects[avatarId] is not None:
+ self.finish()
+ else:
+ self.askForObject(avatarId)
+
+ def timeout(self,task):
+ self.air.snapshotRenderer.errorFetchingAvatar(self.jobId,self.avatarId)
+ self.delete()
+
+ def finish(self):
+ av = self.neededObjects.get(self.avatarId,None)
+ if av is None:
+ self.air.snapshotRenderer.errorFetchingAvatar(self.jobId,self.avatarId)
+ else:
+ self.air.snapshotRenderer.renderSnapshot(self.jobId,self.avatarId,av.dna,self.writeToFile)
+
+ self.delete()
+
+#--------------------------------------------------
+
+class SnapshotRendererUD(DistributedObjectGlobalUD):
+ """
+ Uberdog object for rendering avatar pictures in response
+ to DC requests.
+ """
+ notify = notify
+
+ def __init__(self, air):
+ assert self.notify.debugCall()
+ DistributedObjectGlobalUD.__init__(self, air)
+
+ self.air = air
+
+ self.dispatcherLoc = OtpDoGlobals.OTP_DO_ID_SNAPSHOT_DISPATCHER
+ self.myLoc = 0
+
+
+ def announceGenerate(self):
+ assert self.notify.debugCall()
+ DistributedObjectGlobalUD.announceGenerate(self)
+ self.sendUpdateToChannel(
+ AIMsgTypes.CHANNEL_CLIENT_BROADCAST, "online", [])
+ self.sendUpdateToChannel(
+ AIMsgTypes.OTP_CHANNEL_AI_AND_UD_BROADCAST, "online", [])
+ self.myLoc = self.doId
+ self.startAskingForWork()
+
+ def delete(self):
+ assert self.notify.debugCall()
+ uber.taskMgr.remove('pollForWorkTask')
+ self.ignoreAll()
+ DistributedObjectGlobalUD.delete(self)
+
+ # -- Internal methods --
+
+ def renderSnapshot(self,jobId,avatarId,avatarDNA,writeToFile):
+ print "OTP-level renderSnapshot method called! You should override this!"
+
+ def errorFetchingAvatar(self,jobId,avatarId):
+ """
+ Tell the dispatcher we had an error in the current task.
+ It should send us back new work or not respond if there's nothing to do.
+ """
+ self.notify.warning("Error fetching DNA for avatar %d, reporting failure to the dispatcher." % avatarId)
+ self.air.sendUpdateToGlobalDoId("SnapshotDispatcherUD","errorFetchingAvatar",self.dispatcherLoc,[self.myLoc,jobId])
+ self.startAskingForWork()
+
+ def errorRenderingAvatar(self,jobId,avatarId,avatarDNA):
+ """
+ Tell the dispatcher we had an error in the current task.
+ It should send us back new work or not response if there's nothing to do.
+ """
+ self.notify.warning("Error rendering an image for avatar %d, reporting failure to the dispatcher. DNA: %s" % (avatarId,avatarDNA))
+ self.air.sendUpdateToGlobalDoId("SnapshotDispatcherUD","errorRenderingAvatar",self.dispatcherLoc,[self.myLoc,jobId])
+ self.startAskingForWork()
+
+ def renderSuccessful(self,jobId,avatarId,writeToFile):
+ """
+ Tell the dispatcher we finished the current task.
+ If we don't hear back immediately, there's no new work
+ so we need to start polling again
+ """
+ self.notify.debug("Successfully rendered avatar %d to %s" % (avatarId,writeToFile))
+ self.air.sendUpdateToGlobalDoId("SnapshotDispatcherUD","renderSuccessful",self.dispatcherLoc,[self.myLoc,jobId])
+ self.startAskingForWork()
+
+
+
+ # -- Distributed Methods --
+
+ def requestRender(self,jobId,avatarId,writeToFile):
+ """
+ 'Please render this avatar' method.
+ Called from DC space by a SnapshotDispatcher,
+ results in an image being written to disk
+ sometime after this function returns.
+ """
+ self.stopAskingForWork()
+ AvatarDNARequest(self.air,jobId,avatarId,writeToFile)
+
+
+ # -- Task Methods --
+
+ def startAskingForWork(self):
+ uber.taskMgr.remove('pollForWorkTask')
+ uber.taskMgr.doMethodLater(2.0,self.pollForWorkTask,'pollForWorkTask')
+
+ def stopAskingForWork(self):
+ uber.taskMgr.remove('pollForWorkTask')
+
+ def pollForWorkTask(self,task):
+ """
+ Task that polls the SnapshotDispatcher for rendering jobs.
+ If there is work, the dispatcher calls back to requestRender.
+ """
+ # query the dispatcher for work
+ self.air.sendUpdateToGlobalDoId("SnapshotDispatcherUD","requestNewWork",self.dispatcherLoc,[self.myLoc])
+ return Task.again
+
diff --git a/otp/src/snapshot/SnapshotWebServer.py b/otp/src/snapshot/SnapshotWebServer.py
new file mode 100644
index 0000000..677a3c7
--- /dev/null
+++ b/otp/src/snapshot/SnapshotWebServer.py
@@ -0,0 +1,36 @@
+import cherrypy
+import atexit
+
+class SnapshotWebServer(object):
+ def __init__(self,requestQueue):
+ config = "C:\\cygwin\\home\\igraham\\player\\otp\\src\\snapshot\\cherrypy.conf"
+
+ self.requestQueue = requestQueue
+
+ atexit.register(self.stopThreads)
+
+ cherrypy._global_conf_alias.update(config)
+ cherrypy.tree.mount(self,"",config)
+ cherrypy.server.quickstart()
+ cherrypy.engine.start(blocking=False)
+
+ def stopThreads(self):
+ cherrypy.engine.stop()
+ cherrypy.server.stop()
+
+ @cherrypy.expose
+ def getSnapshot(self,avatarId):
+ """
+ Render this avatar's picture and give me the location (URL) of the image.
+ Accessed via an HTTP query.
+ """
+ try:
+ avatarId = int(avatarId)
+ except:
+ return "Error parsing argument avatarId. Gimme an integer!"
+
+ print "getSnapshot %s" % avatarId
+
+ self.requestQueue.put_nowait(avatarId)
+
+ return "%s" % avatarId
diff --git a/otp/src/snapshot/Sources.pp b/otp/src/snapshot/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/otp/src/snapshot/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/otp/src/snapshot/__init__.py b/otp/src/snapshot/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/otp/src/snapshot/cherrypy.conf b/otp/src/snapshot/cherrypy.conf
new file mode 100644
index 0000000..920e87f
--- /dev/null
+++ b/otp/src/snapshot/cherrypy.conf
@@ -0,0 +1,6 @@
+[global]
+server.socket_host = "localhost"
+server.socket_port = 8080
+server.thread_pool = 10
+engine.autoreload_on = False
+
diff --git a/otp/src/speedchat/.cvsignore b/otp/src/speedchat/.cvsignore
new file mode 100644
index 0000000..b30778f
--- /dev/null
+++ b/otp/src/speedchat/.cvsignore
@@ -0,0 +1,5 @@
+.cvsignore
+Makefile
+*.pyc
+pp.dep
+sc.zip
diff --git a/otp/src/speedchat/ColorSpace.py b/otp/src/speedchat/ColorSpace.py
new file mode 100644
index 0000000..b2c6bb4
--- /dev/null
+++ b/otp/src/speedchat/ColorSpace.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+"""ColorSpace.py: contains utility functions to convert between color spaces"""
+
+import math
+
+"""
+ r,g,b values are from 0. to 1.
+ h = [0,360], s = [0,1], v = [0,1]
+ if s == 0, then h = -1 (undefined)
+ 'The coordinate system is cylindrical, and the colors are defined inside
+ a hexcone. The hue value H runs from 0 to 360º. The saturation S is the
+ degree of strength or purity and is from 0 to 1. Purity is how much white
+ is added to the color, so S=1 makes the purest color (no white).
+ Brightness V also ranges from 0 to 1, where 0 is the black.'
+"""
+def rgb2hsv(r, g, b):
+ _min = float(min(r, g, b))
+ _max = float(max(r, g, b))
+ v = _max
+ delta = _max - _min
+
+ if (delta != 0.):
+ s = delta / _max
+ else:
+ # r = g = b = N
+ # s = 0, h is undefined
+ s = 0.
+ h = -1.
+ return h,s,v
+
+ if (r == _max):
+ h = (g - b) / delta # between yellow & magenta
+ elif (g == _max):
+ h = 2. + ((b - r) / delta) # between cyan & yellow
+ else:
+ h = 4. + ((r - g) / delta) # between magenta & cyan
+ h *= 60. # degrees
+ if(h < 0.):
+ h += 360.
+ return h,s,v
+
+def hsv2rgb(h, s, v):
+ if(s == 0.):
+ # achromatic (grey)
+ return v,v,v
+
+ h %= 360.
+ h /= 60. # sector 0 to 5
+ i = int(math.floor(h))
+ f = h - i # factorial part of h
+ p = v * (1. - s)
+ q = v * (1. - s * f)
+ t = v * (1. - s * (1. - f))
+ if i == 0:
+ return v,t,p
+ elif i == 1:
+ return q,v,p
+ elif i == 2:
+ return p,v,t
+ elif i == 3:
+ return p,q,v
+ elif i == 4:
+ return t,p,v
+ else:
+ return v,p,q
+
+"""
+ y is luminance/brightness
+ u and v describe color in a non-intuitive manner
+"""
+def rgb2yuv(r,g,b):
+ y = .299*r + .587*g + .114*b
+ u = -.169*r - .331*g + .500*b + .5
+ v = .500*r - .419*g - .081*b + .5
+ return tuple(map(lambda x: min(max(x,0),1), (y,u,v)))
+
+def yuv2rgb(y,u,v):
+ r = y - 0.0009267*(u-.5) + 1.4016868*(v-.5)
+ g = y - 0.3436954*(u-.5) - 0.7141690*(v-.5)
+ b = y + 1.7721604*(u-.5) + 0.0009902*(v-.5)
+ return tuple(map(lambda x: min(max(x,0),1), (r,g,b)))
diff --git a/otp/src/speedchat/SCColorPanel.py b/otp/src/speedchat/SCColorPanel.py
new file mode 100644
index 0000000..a135528
--- /dev/null
+++ b/otp/src/speedchat/SCColorPanel.py
@@ -0,0 +1,93 @@
+from SCColorScheme import SCColorScheme
+from direct.tkwidgets.Valuator import *
+
+colors = {}
+panels = {}
+
+def scRgbPanel(callback, title, initColor):
+ def getScaledColor(color, s):
+ return tuple(map(lambda x: x*s, color))
+ sInitColor = getScaledColor(initColor, 255.)
+ vgp = ValuatorGroupPanel(title=title,
+ dim=3,
+ labels=['R','G','B'],
+ value=[int(sInitColor[0]),
+ int(sInitColor[1]),
+ int(sInitColor[2])],
+ type = 'slider',
+ valuator_style='mini',
+ valuator_min=0,
+ valuator_max=255,
+ valuator_resolution=1,
+ fDestroy=1)
+ # Add a print button which will also serve as a color tile
+ pButton = Button(vgp.interior(), text = '',
+ bg = getTkColorString(sInitColor))
+ pButton.pack(expand = 1, fill = BOTH)
+
+ def acceptColor(color):
+ pButton['bg'] = getTkColorString(color)
+ # scale colors to 0..1
+ callback(getScaledColor(color, 1/255.))
+
+ def getDefaultFrameColor():
+ global colors
+ # set the color scheme without the frameColor
+ base.speedChat.setColorScheme(SCColorScheme(
+ arrowColor=colors['arrowColor'],
+ rolloverColor=colors['rolloverColor'],
+ ))
+ # update the colors dictionary
+ colors['frameColor'] = \
+ base.speedChat.getColorScheme().getFrameColor()
+ p = panels['frameColor'].component('valuatorGroup')
+ c = colors['frameColor']
+ p.component('valuator0').set(math.floor(c[0]*255))
+ p.component('valuator1').set(math.floor(c[1]*255))
+ p.component('valuator2').set(math.floor(c[2]*255))
+
+ def updateAllPanels():
+ global colors
+ # update the colors dictionary
+ cs = base.speedChat.getColorScheme()
+ colors['arrowColor'] = cs.getArrowColor()
+ colors['rolloverColor'] = cs.getRolloverColor()
+ colors['frameColor'] = cs.getFrameColor()
+
+ # update the panels
+ for panelName in colors.keys():
+ p = panels[panelName].component('valuatorGroup')
+ c = colors[panelName]
+ p.component('valuator0').set(math.floor(c[0]*255))
+ p.component('valuator1').set(math.floor(c[1]*255))
+ p.component('valuator2').set(math.floor(c[2]*255))
+
+ # Update menu
+ menu = vgp.component('menubar').component('Valuator Group-menu')
+ # Some helper functions
+ menu.insert_command(index = 0, label = 'Get Default FrameColor',
+ command = getDefaultFrameColor)
+ menu.insert_command(index = 1, label = 'Update All Panels',
+ command = updateAllPanels)
+
+
+ vgp['command'] = acceptColor
+ return vgp
+
+# adjust the main and contrasting speedchat colors
+def adjustSCColors():
+ global colors
+ base.startTk()
+ cs = base.speedChat.getColorScheme()
+ colors = {
+ 'arrowColor': cs.getArrowColor(),
+ 'rolloverColor': cs.getRolloverColor(),
+ 'frameColor': cs.getFrameColor(),
+ }
+ for colorName in colors.keys():
+ def handleCallback(color, colorName=colorName):
+ global colors
+ colors[colorName] = tuple(color)
+ base.speedChat.setColorScheme(SCColorScheme(**colors))
+
+ panels[colorName] = scRgbPanel(handleCallback, colorName, colors[colorName])
diff --git a/otp/src/speedchat/SCColorScheme.py b/otp/src/speedchat/SCColorScheme.py
new file mode 100644
index 0000000..91b5d37
--- /dev/null
+++ b/otp/src/speedchat/SCColorScheme.py
@@ -0,0 +1,117 @@
+"""SCColorScheme.py: contains the SCColorScheme class"""
+
+from ColorSpace import *
+
+class SCColorScheme:
+ """ SCColorScheme is a class that holds all the information
+ that a SpeedChat tree needs to display itself with a particular
+ color scheme.
+
+ This object is intentionally immutable; if you want to change the
+ color scheme of a SpeedChat tree, create a new SCColorScheme object.
+ """
+ def __init__(self,
+ arrowColor=(.5,.5,1),
+ rolloverColor=(.53,.9,.53),
+ # derived from arrowColor
+ frameColor=None,
+ # derived from rolloverColor
+ pressedColor=None,
+ menuHolderActiveColor=None,
+ emoteIconColor=None,
+ textColor=(0,0,0),
+ emoteIconDisabledColor=(.5,.5,.5),
+ textDisabledColor=(.4,.4,.4),
+ alpha=.95,
+ ):
+ def scaleColor(color, s):
+ y,u,v = rgb2yuv(*color)
+ return yuv2rgb(y*s,u,v)
+ def scaleIfNone(color, srcColor, s):
+ if color is not None:
+ return color
+ else:
+ return scaleColor(srcColor, s)
+
+ self.__arrowColor = arrowColor
+ self.__rolloverColor = rolloverColor
+
+ self.__frameColor = frameColor
+ if self.__frameColor is None:
+ # the frame color should be a whited-out version of the
+ # arrow color; reduce the saturation
+ h,s,v = rgb2hsv(*arrowColor)
+ self.__frameColor = hsv2rgb(h,.2*s,v)
+
+ # TEMP
+ # there seems to be a change to the Panda/DirectGui (alpha?)
+ # behavior, where the speedchat frame is darker than it used to
+ # be. This is problem because the rollover color no longer
+ # contrasts; see 'blue' as an example.
+ # Brighten up the frame color so that it's close to what it used to
+ # be.
+ h,s,v = rgb2hsv(*self.__frameColor)
+ self.__frameColor = hsv2rgb(h,.5*s,v)
+
+ # pressed and menuHolderActive are rollover with slightly
+ # (and progressively) lower luminance
+ self.__pressedColor = scaleIfNone(pressedColor,
+ self.__rolloverColor, .92)
+ self.__menuHolderActiveColor = scaleIfNone(menuHolderActiveColor,
+ self.__rolloverColor, .84)
+
+ self.__emoteIconColor = emoteIconColor
+ if self.__emoteIconColor is None:
+ # base the emote icon color off of the rollover color
+ # max out the saturation to get a deep color
+ # bring the value down slightly
+ h,s,v = rgb2hsv(*self.__rolloverColor)
+ self.__emoteIconColor = hsv2rgb(h,1.,.8*v)
+ self.__emoteIconDisabledColor = emoteIconDisabledColor
+
+ self.__textColor = textColor
+ self.__textDisabledColor = textDisabledColor
+ self.__alpha = alpha
+
+ def getArrowColor(self):
+ return self.__arrowColor
+ def getRolloverColor(self):
+ return self.__rolloverColor
+ def getFrameColor(self):
+ return self.__frameColor
+ def getPressedColor(self):
+ return self.__pressedColor
+ def getMenuHolderActiveColor(self):
+ return self.__menuHolderActiveColor
+ def getEmoteIconColor(self):
+ return self.__emoteIconColor
+ def getTextColor(self):
+ return self.__textColor
+ def getEmoteIconDisabledColor(self):
+ return self.__emoteIconDisabledColor
+ def getTextDisabledColor(self):
+ return self.__textDisabledColor
+ def getAlpha(self):
+ return self.__alpha
+
+ def __str__(self):
+ members = ('arrowColor',
+ 'rolloverColor',
+ 'frameColor',
+ 'pressedColor',
+ 'menuHolderActiveColor',
+ 'emoteIconColor',
+ 'textColor',
+ 'emoteIconDisabledColor',
+ 'textDisabledColor',
+ 'alpha')
+ result = ''
+ for member in members:
+ result += '%s = %s' % (member, self.__dict__[
+ '_%s__%s' % (self.__class__.__name__, member)])
+ if member is not members[-1]:
+ result += '\n'
+ return result
+
+ def __repr__(self):
+ return str(self)
diff --git a/otp/src/speedchat/SCConstants.py b/otp/src/speedchat/SCConstants.py
new file mode 100644
index 0000000..48483de
--- /dev/null
+++ b/otp/src/speedchat/SCConstants.py
@@ -0,0 +1,4 @@
+""" SCConstants.py: contains SpeedChat constants """
+
+SCMenuFinalizePriority = 48
+SCElementFinalizePriority = 47
diff --git a/otp/src/speedchat/SCCustomMenu.py b/otp/src/speedchat/SCCustomMenu.py
new file mode 100644
index 0000000..8cbd191
--- /dev/null
+++ b/otp/src/speedchat/SCCustomMenu.py
@@ -0,0 +1,30 @@
+"""SCCustomMenu.py: contains the SCCustomMenu class"""
+
+from SCMenu import SCMenu
+from SCCustomTerminal import SCCustomTerminal
+from otp.otpbase.OTPLocalizer import CustomSCStrings
+
+class SCCustomMenu(SCMenu):
+ """ SCCustomMenu represents a menu of SCCustomTerminals. """
+ def __init__(self):
+ SCMenu.__init__(self)
+ # listen for changes to localtoon's custom speedchat messages
+ self.accept("customMessagesChanged", self.__customMessagesChanged)
+ self.__customMessagesChanged()
+
+ def destroy(self):
+ SCMenu.destroy(self)
+
+ def __customMessagesChanged(self):
+ # clear out everything from our menu
+ self.clearMenu()
+
+ # if local toon has not been created, don't panic
+ try:
+ lt = base.localAvatar
+ except:
+ return
+
+ for msgIndex in lt.customMessages:
+ if CustomSCStrings.has_key(msgIndex):
+ self.append(SCCustomTerminal(msgIndex))
diff --git a/otp/src/speedchat/SCCustomTerminal.py b/otp/src/speedchat/SCCustomTerminal.py
new file mode 100644
index 0000000..db24fde
--- /dev/null
+++ b/otp/src/speedchat/SCCustomTerminal.py
@@ -0,0 +1,23 @@
+"""SCCustomTerminal.py: contains the SCCustomTerminal class"""
+
+from SCTerminal import SCTerminal
+from otp.otpbase.OTPLocalizer import CustomSCStrings
+
+# args: textId
+SCCustomMsgEvent = 'SCCustomMsg'
+
+def decodeSCCustomMsg(textId):
+ return CustomSCStrings.get(textId, None)
+
+class SCCustomTerminal(SCTerminal):
+ """ SCCustomTerminal represents a terminal SpeedChat entry that
+ contains a phrase that was purchased from the catalog. """
+ def __init__(self, textId):
+ SCTerminal.__init__(self)
+ self.textId = textId
+ self.text = CustomSCStrings[self.textId]
+
+ def handleSelect(self):
+ SCTerminal.handleSelect(self)
+ messenger.send(self.getEventName(SCCustomMsgEvent),
+ [self.textId])
diff --git a/otp/src/speedchat/SCDecoders.py b/otp/src/speedchat/SCDecoders.py
new file mode 100644
index 0000000..5be94e5
--- /dev/null
+++ b/otp/src/speedchat/SCDecoders.py
@@ -0,0 +1,10 @@
+"""SCDecoders.py: contains functions to decode SpeedChat messages """
+
+"""
+Each of these functions normally returns the ready-to-display text
+string that corresponds to the encoded message. If there is a problem,
+None is returned.
+"""
+from SCStaticTextTerminal import decodeSCStaticTextMsg
+from SCCustomTerminal import decodeSCCustomMsg
+from SCEmoteTerminal import decodeSCEmoteWhisperMsg
diff --git a/otp/src/speedchat/SCElement.py b/otp/src/speedchat/SCElement.py
new file mode 100644
index 0000000..1ec3bce
--- /dev/null
+++ b/otp/src/speedchat/SCElement.py
@@ -0,0 +1,260 @@
+"""SCElement.py: contains the SCElement class"""
+
+from pandac.PandaModules import *
+from direct.gui.DirectGui import *
+from direct.task import Task
+from SCConstants import *
+from SCObject import SCObject
+from direct.showbase.PythonUtil import boolEqual
+from otp.otpbase import OTPGlobals
+
+class SCElement(SCObject, NodePath):
+ """ SCElement is the base class for all entities that can appear
+ as entries in a SpeedChat menu. """
+
+ font = OTPGlobals.getInterfaceFont()
+
+ SerialNum = 0
+
+ def __init__(self, parentMenu=None):
+ SCObject.__init__(self)
+
+ self.SerialNum = SCElement.SerialNum
+ SCElement.SerialNum += 1
+ node = hidden.attachNewNode('SCElement%s' % self.SerialNum)
+ NodePath.__init__(self, node)
+
+ self.FinalizeTaskName = 'SCElement%s_Finalize' % self.SerialNum
+
+ self.parentMenu = parentMenu
+ self.__active = 0
+ self.__viewable = 1
+
+ # these are used to detect changes to the size of this element
+ # to avoid unnecessary button rebuilds
+ self.lastWidth = 0
+ self.lastHeight = 0
+
+ self.setDimensions(0,0)
+
+ # how much space to put in around the edges of the button
+ self.padX = .25
+ self.padZ = .1
+
+ def destroy(self):
+ if self.isActive():
+ self.exitActive()
+ # this calls exitVisible if necessary
+ SCObject.destroy(self)
+ if hasattr(self, 'button'):
+ self.button.destroy()
+ del self.button
+ self.parentMenu = None
+ self.detachNode()
+
+ def setParentMenu(self, parentMenu):
+ self.parentMenu = parentMenu
+ def getParentMenu(self):
+ return self.parentMenu
+
+ def getDisplayText(self):
+ """ derived classes should override and return the text that
+ should be visually displayed on this item. Note that elements
+ that must do non-trivial processing to produce this text
+ should cache the text when they can. """
+ self.notify.error(
+ 'getDisplayText is pure virtual, derived class must override')
+
+ # input event handlers that derived classes can override
+ def onMouseEnter(self, event):
+ """ the mouse has just entered this entity """
+ if self.parentMenu is not None:
+ self.parentMenu.memberGainedInputFocus(self)
+
+ def onMouseLeave(self, event):
+ """ the mouse has just left this entity """
+ if self.parentMenu is not None:
+ self.parentMenu.memberLostInputFocus(self)
+
+ def onMouseClick(self, event):
+ """ the user just clicked on this entity """
+ pass
+
+ """ inheritors should override these methods and perform whatever
+ actions are appropriate when this element becomes 'active' and
+ 'inactive' (for example, menu holders should show/hide their menu;
+ other element types might play some sort of animation on activation).
+ 'active' generally corresponds to having the input focus, but not
+ always; see 'hasStickyFocus' below. """
+ def enterActive(self):
+ self.__active = 1
+ def exitActive(self):
+ self.__active = 0
+
+ def isActive(self):
+ return self.__active
+
+ def hasStickyFocus(self):
+ """ Inheritors should override and return non-zero if they
+ should remain active until a sibling becomes active, even
+ if they lose the input focus. For example, menu holders should
+ remain open until a sibling becomes active, even if the user
+ moves the mouse out of the menu holder, or even completely away
+ from the SpeedChat menus. """
+ return 0
+
+ """ If this element is marked as 'not viewable', it will disappear from
+ its parent menu, and it will not be possible for the user to
+ interact with this element. """
+ def setViewable(self, viewable):
+ if (not boolEqual(self.__viewable, viewable)):
+ self.__viewable = viewable
+
+ # inform our parent that our visibility state has changed
+ if self.parentMenu is not None:
+ self.parentMenu.memberViewabilityChanged(self)
+
+ def isViewable(self):
+ return self.__viewable
+
+ def getMinDimensions(self):
+ """ Should return the width/height that this element would
+ ideally like to be. We may be asked to display ourselves
+ larger than this, never smaller.
+ returns (width, height)
+ """
+ text = TextNode('SCTemp')
+ text.setFont(SCElement.font)
+ dText = self.getDisplayText()
+ text.setText(dText)
+ bounds = text.getCardActual()
+ # there's already padding on the right, apparently
+ width = abs(bounds[1] - bounds[0]) + self.padX
+ # the height will always be the same regardless of the string
+ height = abs(bounds[3] - bounds[2]) + 2.*self.padZ
+ return width, height
+
+ def setDimensions(self, width, height):
+ """ Call this to tell this element how big it should be. Must be
+ called before calling finalize. """
+ self.width = float(width)
+ self.height = float(height)
+ if (self.lastWidth, self.lastHeight) != (self.width, self.height):
+ self.invalidate()
+
+ def invalidate(self):
+ """ call this if something about our appearance has changed and
+ we need to re-create our button """
+ SCObject.invalidate(self)
+ parentMenu = self.getParentMenu()
+ if parentMenu is not None:
+ # if our parent menu caused us to become invalid during its
+ # finalization, don't re-invalidate it
+ if not parentMenu.isFinalizing():
+ parentMenu.invalidate()
+
+ # from SCObject
+ def enterVisible(self):
+ SCObject.enterVisible(self)
+ self.privScheduleFinalize()
+
+ def exitVisible(self):
+ SCObject.exitVisible(self)
+ self.privCancelFinalize()
+
+ def privScheduleFinalize(self):
+ # spawn a task to finalize ourselves before we render.
+ def finalizeElement(task, self=self):
+ # if our parent menu is dirty, it will be finalizing us shortly;
+ # our size may change in the process, so don't bother
+ # finalizing yet.
+ if self.parentMenu is not None:
+ if self.parentMenu.isDirty():
+ return Task.done
+ self.finalize()
+ return Task.done
+ taskMgr.remove(self.FinalizeTaskName)
+ taskMgr.add(finalizeElement, self.FinalizeTaskName,
+ priority=SCElementFinalizePriority)
+
+ def privCancelFinalize(self):
+ taskMgr.remove(self.FinalizeTaskName)
+
+ def finalize(self, dbArgs={}):
+ """ 'dbArgs' can contain parameters (and parameter overrides) for
+ the DirectButton.
+ """
+ if not self.isDirty():
+ return
+
+ SCObject.finalize(self)
+
+ if hasattr(self, 'button'):
+ self.button.destroy()
+ del self.button
+
+ halfHeight = self.height/2.
+
+ # if we're given a 'center' value for the text alignment,
+ # calculate the appropriate text X position
+ textX = 0
+ if dbArgs.has_key('text_align'):
+ if dbArgs['text_align'] == TextNode.ACenter:
+ textX = self.width/2.
+
+ args = {
+ 'text': self.getDisplayText(),
+ 'frameColor': (0,0,0,0),
+ 'rolloverColor': self.getColorScheme().getRolloverColor()+(1,),
+ 'pressedColor': self.getColorScheme().getPressedColor()+(1,),
+ 'text_font': OTPGlobals.getInterfaceFont(),
+ 'text_align': TextNode.ALeft,
+ 'text_fg': self.getColorScheme().getTextColor()+(1,),
+ 'text_pos': (textX,-.25-halfHeight,0),
+ 'relief': DGG.FLAT,
+ # disable the 'press effect' (slight scale-down)
+ 'pressEffect': 0,
+ }
+ # add external parameters and apply any overrides
+ args.update(dbArgs)
+
+ # these can't be passed directly to DirectButton
+ rolloverColor = args['rolloverColor']
+ pressedColor = args['pressedColor']
+ del args['rolloverColor']
+ del args['pressedColor']
+
+ """
+ from direct.gui.DirectGui import *;import OTPGlobals;
+ btn.destroy();w=3;h=2;btn = DirectButton(parent=aspect2d,frameSize=(0,w,-h,0),text='TEST',frameColor=(.8,.8,1,1),text_font=OTPGlobals.getInterfaceFont(),text_align=TextNode.ALeft,text_fg=(0,0,0,1),text_pos=(0,-.25-(h/2.),0),image=('phase_3/models/props/page-arrow', 'poly'),image_pos=(w*.9,0,-h/2.),state=DGG.NORMAL,relief=DGG.RAISED);btn.setPos(-.5,0,0);btn.setScale(.5)"""
+
+ btn = DirectButton(
+ parent = self,
+ frameSize = (0,self.width,
+ -self.height,0),
+ # this doesn't trigger until mouse-up. We want to trigger
+ # on mouse-down; see the 'bind' calls below
+ #command = self.onMouseClick,
+ **args
+ )
+
+ # Set frame color for rollover and pressed states
+ btn.frameStyle[DGG.BUTTON_ROLLOVER_STATE].setColor(*rolloverColor)
+ btn.frameStyle[DGG.BUTTON_DEPRESSED_STATE].setColor(*pressedColor)
+ btn.updateFrameStyle()
+
+ # listen for input events
+ btn.bind(DGG.ENTER, self.onMouseEnter)
+ btn.bind(DGG.EXIT, self.onMouseLeave)
+ btn.bind(DGG.B1PRESS, self.onMouseClick)
+ self.button = btn
+
+ # store the new display params
+ self.lastWidth = self.width
+ self.lastHeight = self.height
+
+ self.validate()
+
+ def __str__(self):
+ return '%s: %s' % (self.__class__.__name__, self.getDisplayText())
+
diff --git a/otp/src/speedchat/SCEmoteMenu.py b/otp/src/speedchat/SCEmoteMenu.py
new file mode 100644
index 0000000..a510669
--- /dev/null
+++ b/otp/src/speedchat/SCEmoteMenu.py
@@ -0,0 +1,29 @@
+"""SCEmoteMenu.py: contains the SCEmoteMenu class"""
+
+from SCMenu import SCMenu
+from SCEmoteTerminal import SCEmoteTerminal
+
+class SCEmoteMenu(SCMenu):
+ """ SCEmoteMenu represents a menu of SCEmoteTerminals. """
+ def __init__(self):
+ SCMenu.__init__(self)
+ self.accept('emotesChanged', self.__emoteAccessChanged)
+ self.__emoteAccessChanged()
+
+ def destroy(self):
+ SCMenu.destroy(self)
+
+ def __emoteAccessChanged(self):
+ # clear out everything from our menu
+ self.clearMenu()
+
+ # if local toon has not been created, don't panic
+ try:
+ lt = base.localAvatar
+ except:
+ return
+
+ for i in range(len(lt.emoteAccess)):
+ # Only add entries we actually have.
+ if lt.emoteAccess[i]:
+ self.append(SCEmoteTerminal(i))
diff --git a/otp/src/speedchat/SCEmoteTerminal.py b/otp/src/speedchat/SCEmoteTerminal.py
new file mode 100644
index 0000000..2fdeb85
--- /dev/null
+++ b/otp/src/speedchat/SCEmoteTerminal.py
@@ -0,0 +1,143 @@
+"""SCEmoteTerminal.py: contains the SCEmoteTerminal class"""
+
+from direct.gui.DirectGui import *
+from SCTerminal import SCTerminal
+from otp.otpbase.OTPLocalizer import EmoteList, EmoteWhispers
+from otp.avatar import Emote
+
+# args: emoteId
+SCEmoteMsgEvent = 'SCEmoteMsg'
+# args: none
+SCEmoteNoAccessEvent = 'SCEmoteNoAccess'
+
+# emote terminals don't produce any spoken text
+# when whispered, they produce msgs like 'Flippy waves'
+def decodeSCEmoteWhisperMsg(emoteId, avName):
+ if emoteId >= len(EmoteWhispers):
+ return None
+ return EmoteWhispers[emoteId] % avName
+
+class SCEmoteTerminal(SCTerminal):
+ """ SCEmoteTerminal represents a terminal SpeedChat node that
+ contains an emotion. """
+ def __init__(self, emoteId):
+ SCTerminal.__init__(self)
+ self.emoteId = emoteId
+ # look up the emote name that should be displayed
+ if not self.__ltHasAccess():
+ self.text = '?'
+ else:
+ self.text = EmoteList[self.emoteId]
+
+ def __ltHasAccess(self):
+ # returns non-zero if localToon has access to this emote
+ #
+ # Note that the current design stipulates that this object
+ # will be destroyed and replaced by the SCEmoteMenu if and
+ # when the lt's emote-access state changes. Therefore the
+ # result of this function should be constant throughout
+ # the lifetime of the object.
+ try:
+ lt = base.localAvatar
+ return lt.emoteAccess[self.emoteId]
+ except:
+ return 0
+
+ def __emoteEnabled(self):
+ # all of the emotes are always available for whispering
+ if self.isWhispering():
+ return 1
+ assert Emote.globalEmote != None
+ return Emote.globalEmote.isEnabled(self.emoteId)
+
+ def finalize(self, dbArgs={}):
+ if not self.isDirty():
+ return
+
+ args = {}
+
+ if ((not self.__ltHasAccess()) or
+ (not self.__emoteEnabled())):
+ # make the button 'unclickable'
+ args.update({
+ 'rolloverColor': (0,0,0,0),
+ 'pressedColor': (0,0,0,0),
+ 'rolloverSound': None,
+ 'text_fg': self.getColorScheme().getTextDisabledColor()+(1,),
+ })
+ if not self.__ltHasAccess():
+ # if localToon doesn't have access to this emote, the
+ # button has a '?' on it. make sure it's centered
+ args.update({
+ 'text_align': TextNode.ACenter,
+ })
+ elif not self.__emoteEnabled():
+ # don't play a sound if user clicks on a disabled emote
+ # that they have access to
+ args.update({
+ 'clickSound': None,
+ })
+
+ self.lastEmoteEnableState = self.__emoteEnabled()
+
+ args.update(dbArgs)
+ SCTerminal.finalize(self, dbArgs=args)
+
+ def __emoteEnableStateChanged(self):
+ if self.isDirty():
+ # we're marked as dirty, don't bother trying to change
+ # our appearance here; we may not even have a button yet
+ self.notify.info(
+ "skipping __emoteEnableStateChanged; we're marked as dirty")
+ return
+ elif not hasattr(self, 'button'):
+ self.notify.error(
+ "SCEmoteTerminal is not marked as dirty, but has no button!")
+
+ btn = self.button
+ if self.__emoteEnabled():
+ rolloverColor = self.getColorScheme().getRolloverColor()+(1,)
+ pressedColor = self.getColorScheme().getPressedColor()+(1,)
+ btn.frameStyle[DGG.BUTTON_ROLLOVER_STATE].setColor(*rolloverColor)
+ btn.frameStyle[DGG.BUTTON_DEPRESSED_STATE].setColor(*pressedColor)
+ btn.updateFrameStyle()
+ btn['text_fg'] = (
+ self.getColorScheme().getTextColor()+(1,))
+ btn['rolloverSound'] = DGG.getDefaultRolloverSound()
+ btn['clickSound'] = DGG.getDefaultClickSound()
+ else:
+ btn.frameStyle[DGG.BUTTON_ROLLOVER_STATE].setColor(0,0,0,0)
+ btn.frameStyle[DGG.BUTTON_DEPRESSED_STATE].setColor(0,0,0,0)
+ btn.updateFrameStyle()
+ btn['text_fg'] = (
+ self.getColorScheme().getTextDisabledColor()+(1,))
+ btn['rolloverSound'] = None
+ btn['clickSound'] = None
+
+ def enterVisible(self):
+ SCTerminal.enterVisible(self)
+ if self.__ltHasAccess():
+ # if the emote enable state has changed since the last time
+ # we were finalized, invalidate
+ if hasattr(self, 'lastEmoteEnableState'):
+ if self.lastEmoteEnableState != self.__emoteEnabled():
+ self.invalidate()
+
+ if not self.isWhispering():
+ self.accept(Emote.globalEmote.EmoteEnableStateChanged,
+ self.__emoteEnableStateChanged)
+
+ def exitVisible(self):
+ SCTerminal.exitVisible(self)
+ self.ignore(Emote.globalEmote.EmoteEnableStateChanged)
+
+ # from SCTerminal
+ def handleSelect(self):
+ if not self.__ltHasAccess():
+ messenger.send(self.getEventName(SCEmoteNoAccessEvent))
+ else:
+ if self.__emoteEnabled():
+ # calling this makes the speedchat menu go away
+ SCTerminal.handleSelect(self)
+ messenger.send(self.getEventName(SCEmoteMsgEvent),
+ [self.emoteId])
diff --git a/otp/src/speedchat/SCGMTextTerminal.py b/otp/src/speedchat/SCGMTextTerminal.py
new file mode 100644
index 0000000..e112e33
--- /dev/null
+++ b/otp/src/speedchat/SCGMTextTerminal.py
@@ -0,0 +1,25 @@
+"""SCGMTextTerminal.py: contains the SCGMTextTerminal class"""
+
+from SCTerminal import SCTerminal
+from otp.speedchat import SpeedChatGMHandler
+
+# args: textId
+SCGMTextMsgEvent = 'SCGMTextMsg'
+
+class SCGMTextTerminal(SCTerminal):
+ """ SCGMTextTerminal represents a terminal SpeedChat entry that
+ contains a piece of static (never-changing/constant) text sent
+ from the GM.
+
+ When selected, generates a 'SCGMTextMsg' event, with arguments:
+ - textId (32-bit; use as index)
+ """
+ def __init__(self, textId):
+ SCTerminal.__init__(self)
+ gmHandler = SpeedChatGMHandler.SpeedChatGMHandler()
+ self.textId = textId
+ self.text = gmHandler.getPhrase(textId)
+
+ def handleSelect(self):
+ SCTerminal.handleSelect(self)
+ messenger.send(self.getEventName(SCGMTextMsgEvent), [self.textId])
diff --git a/otp/src/speedchat/SCMenu.py b/otp/src/speedchat/SCMenu.py
new file mode 100644
index 0000000..f56d9d0
--- /dev/null
+++ b/otp/src/speedchat/SCMenu.py
@@ -0,0 +1,703 @@
+"""SCMenu.py: contains the SCMenu class"""
+
+from pandac.PandaModules import *
+from direct.gui.DirectGui import *
+from direct.task import Task
+from SCConstants import *
+from direct.interval.IntervalGlobal import *
+from SCObject import SCObject
+from direct.showbase.PythonUtil import makeTuple
+import types
+
+class SCMenu(SCObject, NodePath):
+ """ SCMenu is a menu of SCElements """
+
+ # The speedchat will wait this long before switching to a new
+ # active menu member when the mouse rolls over the new member.
+ # Set to zero to disable this behavior.
+ config = getConfigShowbase()
+ SpeedChatRolloverTolerance = config.GetFloat(
+ 'speedchat-rollover-tolerance', .08)
+
+ # should we fade in the menus?
+ WantFade = config.GetBool('want-speedchat-fade', 0)
+
+ # How long it should take for a menu to fade in.
+ FadeDuration = config.GetFloat('speedchat-fade-duration', .2)
+
+ SerialNum = 0
+
+ # This should be filled in by SpeedChat.py to indicate the
+ # appropriate model file to load for the background and the gui
+ # objects, respectively. Also the names of the gui icon nodes.
+ BackgroundModelName = None
+ GuiModelName = None
+
+ def __init__(self, holder=None):
+ SCObject.__init__(self)
+
+ self.SerialNum = SCMenu.SerialNum
+ SCMenu.SerialNum += 1
+
+ node = hidden.attachNewNode('SCMenu%s' % self.SerialNum)
+ NodePath.__init__(self, node)
+
+ # if 'None', this menu is a top-level menu (it's not under anything)
+ self.setHolder(holder)
+
+ self.FinalizeTaskName = 'SCMenu%s_Finalize' % self.SerialNum
+ self.ActiveMemberSwitchTaskName = (
+ 'SCMenu%s_SwitchActiveMember' % self.SerialNum)
+
+ self.bg = loader.loadModel(self.BackgroundModelName)
+ def findNodes(names, model=self.bg):
+ results = []
+ for name in names:
+ # if name is a tuple we need to look for the first match
+ for nm in makeTuple(name):
+ node = model.find('**/%s' % nm)
+ if not node.isEmpty():
+ results.append(node)
+ break
+ return results
+
+ # Maya doesn't allow nodes to be named 'top' so it's named
+ # 'top1' in the Pirates model
+ (self.bgTop, self.bgBottom, self.bgLeft, self.bgRight,
+ self.bgMiddle, self.bgTopLeft, self.bgBottomLeft,
+ self.bgTopRight, self.bgBottomRight) = findNodes(
+ [('top', 'top1'), 'bottom', 'left', 'right',
+ 'middle', 'topLeft', 'bottomLeft',
+ 'topRight', 'bottomRight'])
+
+ # give the bg frame a render order that puts it behind the rest
+ # of our children
+ self.bg.reparentTo(self, -1)
+
+ # the member elements in this menu
+ # note that our member list should be manipulated by applying
+ # list operations to 'self'
+ self.__members = []
+
+ # Each menu can have a maximum of one 'active' member ('active'
+ # being interpreted based on the member's element type; menu holders
+ # show their menu when active, etc.)
+ self.activeMember = None
+ self.activeCandidate = None
+
+ self.fadeIval = None
+
+ # initialized; only used for horizonal centering
+ self.width = 1
+
+ # to prevent recursive invalidates from members
+ self.inFinalize = 0
+
+ def destroy(self):
+ self.stopFade()
+ SCObject.destroy(self)
+ del self.bgTop
+ del self.bgBottom
+ del self.bgLeft
+ del self.bgRight
+ del self.bgMiddle
+ del self.bgBottomLeft
+ del self.bgTopRight
+ del self.bgBottomRight
+ self.bg.removeNode()
+ del self.bg
+
+ self.holder = None
+ for member in self.__members:
+ member.destroy()
+ del self.__members
+ self.removeNode()
+
+ taskMgr.remove(self.FinalizeTaskName)
+ taskMgr.remove(self.ActiveMemberSwitchTaskName)
+
+ def clearMenu(self):
+ """ This will empty our menu, and destroy all of the current
+ member elements. """
+ while len(self):
+ # It is important to del the item from the list *before*
+ # destroying it, because the __delitem__ method may
+ # perform some additional cleanup.
+ item = self[0]
+ del self[0]
+ item.destroy()
+
+ def rebuildFromStructure(self, structure, title=None):
+ """ This will destroy the current content of this menu and replace
+ it with the tree described by 'structure'."""
+ self.clearMenu()
+
+ if title:
+ holder = self.getHolder()
+ if holder:
+ holder.setTitle(title)
+
+ self.appendFromStructure(structure)
+
+ def appendFromStructure(self, structure):
+ """ This will add the tree elements described by 'structure' to the
+ existing menu elements.
+
+ structure should be a list of Python objects that represent SpeedChat
+ elements. Here is the mapping of Python objects to SpeedChat elements:
+
+ Integers represent static-text terminal elements. They should be valid
+ indices into OTPLocalizer.SpeedChatStaticText.
+
+ TODO: how to represent different terminals
+
+ Lists represent menus: the format is
+ [menuType, title, elem1, elem2, ..]
+ 'menuType' is the desired menu class (if omitted, defaults to SCMenu).
+ 'title' is the text that should appear on the menu's holder element.
+ elem1, etc. are the elements that should appear in the menu.
+
+ Emotes are attached to terminal elements using dictionaries:
+ {terminal:emoteId}
+ """
+ from SpeedChatTypes import SCMenuHolder, SCStaticTextTerminal, SCGMTextTerminal
+ from otp.otpbase import OTPLocalizer
+
+ def addChildren(menu, childList):
+ """ this recursive function adds children to an SCMenu
+ according to the specification in 'childList'. See above
+ for the format of childList (it matches the format of
+ 'structure'). """
+ for child in childList:
+ # if it's a dictionary, there's an emote attached
+ emote = None
+ if type(child) == type({}):
+ assert len(child.keys()) == 1
+ item = child.keys()[0]
+ emote = child[item]
+ child = item
+
+ # use this func to add terminal nodes;
+ # takes care of linking emotes
+ # def addTerminal(terminal, menu=menu, emote=emote):
+ # if emote is not None:
+ # terminal.setLinkedEmote(emote)
+ # menu.append(terminal)
+
+ if type(child) == type(0):
+ # it's a static text ID
+ assert child in OTPLocalizer.SpeedChatStaticText
+ terminal = SCStaticTextTerminal(child)
+ if emote is not None:
+ terminal.setLinkedEmote(emote)
+ menu.append(terminal)
+ # addTerminal(SCStaticTextTerminal(child))
+ elif type(child) == type([]):
+ # we've got a menu holder and a menu to be held
+ # if first element of list is string, it's a plain SCMenu
+ if type(child[0]) == type(''):
+ holderTitle = child[0]
+ subMenu = SCMenu()
+ subMenuChildren = child[1:]
+ else:
+ # otherwise, first element of list is class
+ menuType, holderTitle = child[0], child[1]
+ subMenu = menuType()
+ subMenuChildren = child[2:]
+ if emote:
+ print ('warning: tried to link emote %s '
+ 'to a menu holder' % emote)
+ holder = SCMenuHolder(holderTitle, menu=subMenu)
+ menu.append(holder)
+ addChildren(subMenu, subMenuChildren)
+ elif type(child) == type('') and child[:2] == 'gm':
+ terminal = SCGMTextTerminal(child)
+ menu.append(terminal)
+ else:
+ raise ('error parsing speedchat structure. '
+ 'invalid child: %s' % child)
+
+ addChildren(self, structure)
+ # clean up memory leak
+ addChildren = None
+
+ def fadeFunc(self, t):
+ cs = self.getColorScale()
+ self.setColorScale(cs[0],cs[1],cs[2],t)
+
+ def stopFade(self):
+ if self.fadeIval is not None:
+ self.fadeIval.pause()
+ self.fadeIval = None
+
+ def enterVisible(self):
+ SCObject.enterVisible(self)
+ self.privScheduleFinalize()
+
+ # tell our members that they're visible now
+ for member in self:
+ if member.isViewable():
+ if not member.isVisible():
+ member.enterVisible()
+
+ # we are just becoming visible, so reset our child fade flag
+ self.childHasFaded = 0
+
+ # if a sibling menu has already faded in, don't fade in again
+ alreadyFaded = 0
+ parentMenu = None
+ if self.holder is not None:
+ if self.holder.parentMenu is not None:
+ parentMenu = self.holder.parentMenu
+ alreadyFaded = parentMenu.childHasFaded
+
+ if SCMenu.WantFade:
+ if alreadyFaded:
+ self.fadeFunc(1.)
+ else:
+ self.stopFade()
+ self.fadeIval = LerpFunctionInterval(
+ self.fadeFunc, fromData=0., toData=1.,
+ duration = SCMenu.FadeDuration)
+ self.fadeIval.play()
+ if parentMenu is not None:
+ parentMenu.childHasFaded = 1
+
+ def exitVisible(self):
+ SCObject.exitVisible(self)
+ self.stopFade()
+ self.privCancelFinalize()
+ self.__cancelActiveMemberSwitch()
+ # if there is a member that is active, deactive it
+ self.__setActiveMember(None)
+ # tell all of our visible members that they're about to
+ # not be visible anymore
+ for member in self:
+ if member.isVisible():
+ member.exitVisible()
+
+ """ The 'holder' is an element that 'owns' this menu; if 'None', this
+ is a free-standing menu. """
+ def setHolder(self, holder):
+ self.holder = holder
+ def getHolder(self):
+ return self.holder
+
+ def isTopLevel(self):
+ return (self.holder == None)
+
+ def memberSelected(self, member):
+ """ non-terminal member elements should call this when they are
+ clicked on; immediately makes them the active member. """
+ assert member in self
+
+ # cancel any pending active candidate
+ self.__cancelActiveMemberSwitch()
+
+ self.__setActiveMember(member)
+
+ def __setActiveMember(self, member):
+ """ this function actually does the work of switching the active
+ member. """
+ # if this member is already the active member, ignore
+ if self.activeMember is member:
+ return
+ # if there is a member element that was active, deactive it
+ if self.activeMember is not None:
+ self.activeMember.exitActive()
+ # set up with the new member as the active member
+ self.activeMember = member
+ if self.activeMember is not None:
+ # reparent the active member to us, so that it (and its children,
+ # if any) will be rendered on top of all of its siblings
+ self.activeMember.reparentTo(self)
+ self.activeMember.enterActive()
+
+ """ Member elements will call these functions to inform us that they
+ have gained/lost the input focus. Based on calls to these functions,
+ we will instruct our elements to go active or inactive. """
+ def memberGainedInputFocus(self, member):
+ """ member elements will call this function to let us know that
+ they have gained the input focus """
+ assert member in self
+
+ # kill any candidate-active-member wait task
+ self.__cancelActiveMemberSwitch()
+
+ # this could be the currently-active member, if it has sticky
+ # focus (elements with sticky focus don't necessarily become
+ # inactive when the mouse leaves)
+ if member is self.activeMember:
+ return
+
+ # we can improve the feel and ease-of-use of the menus by
+ # delaying the switching of the active member state, to make
+ # sure that the user actually intends to give the element the
+ # focus, and is not just grazing past it on the way to something
+ # else.
+
+ # if there is no active member, just make the candidate member
+ # active right away. Also, switch immediately if this member
+ # is above the candidate in the menu.
+ if ((self.activeMember is None) or
+ (SCMenu.SpeedChatRolloverTolerance == 0) or
+ (member.posInParentMenu < self.activeMember.posInParentMenu)):
+ self.__setActiveMember(member)
+ else:
+ # otherwise, don't switch the active member right away.
+ # if this element maintains the input focus for N seconds,
+ # make it active
+
+ def doActiveMemberSwitch(task, self=self, member=member):
+ self.activeCandidate = None
+ self.__setActiveMember(member)
+ return Task.done
+
+ # If we just spawn a simple doLater task, the task is guaranteed
+ # to run no sooner than after the next display update, and thus
+ # the visual results are guaranteed to show up no sooner than two
+ # display updates from now. This is a minimum time delay of two
+ # full frame cycles, from the time that the mouse enters the member
+ # to the time that the user sees that member as active.
+ #
+ # At a certain threshold of slow framerate, we want to cut our
+ # losses, assume that the mouse has been over the candidate member
+ # long enough, and switch to that member during this frame.
+
+ # +-- dt ---+
+ # |---------|---------|---------|---------|
+ # +--------+ | | |
+ # mouse reg doLater results
+ # enter doLater run visible
+
+ minFrameRate = 1./SCMenu.SpeedChatRolloverTolerance
+ if globalClock.getAverageFrameRate() > minFrameRate:
+ taskMgr.doMethodLater(SCMenu.SpeedChatRolloverTolerance,
+ doActiveMemberSwitch,
+ self.ActiveMemberSwitchTaskName)
+ # keep a record of the candidate member that wants to be active
+ self.activeCandidate = member
+ else:
+ # do the switch this frame
+ self.__setActiveMember(member)
+
+ def __cancelActiveMemberSwitch(self):
+ """ Call this to clean up a delayed active-member switch without
+ switching to the candidate. Okay to call even if there currently
+ is no candidate.
+ """
+ taskMgr.remove(self.ActiveMemberSwitchTaskName)
+ self.activeCandidate = None
+
+ def memberLostInputFocus(self, member):
+ """ member elements will call this function to let us know that
+ they have lost the input focus """
+ assert member in self
+
+ if member is self.activeCandidate:
+ # this member was waiting to become active; kill the wait task
+ self.__cancelActiveMemberSwitch()
+
+ # if this member is not *the* active member in this menu,
+ # we don't really care
+ if member is not self.activeMember:
+ """ this can occur now that we delay switching of the active
+ member to ensure that the user actually wants the switch
+ to occur. """
+ assert not member.isActive()
+ else:
+ # this is currently the active member. if it doesn't have
+ # sticky focus, de-activate it now
+ if not member.hasStickyFocus():
+ self.__setActiveMember(None)
+
+ def memberViewabilityChanged(self, member):
+ """ member elements will call this if their viewability state
+ changes. """
+ # Keep in mind that the viewability of this member may change back
+ # before the frame ends. If the active member calls this saying
+ # that it's not viewable, don't deactivate it... yet.
+
+ # our menu is no longer valid
+ self.invalidate()
+
+ def invalidate(self):
+ """ Call this if something has changed and we should reconstruct
+ ourselves:
+ - member added
+ - member removed
+ - member visibility state change
+ etc.
+ """
+ SCObject.invalidate(self)
+
+ # If we're visible, we need to spawn a task to rebuild our menu
+ # before we render. We could rebuild the menu immediately, but
+ # that would get expensive if there are many consecutive member
+ # adds and/or removes.
+ if self.isVisible():
+ self.privScheduleFinalize()
+
+ def privScheduleFinalize(self):
+ # spawn a task to finalize our menu before we render.
+ def finalizeMenu(task, self=self):
+ self.finalize()
+ return Task.done
+ taskMgr.remove(self.FinalizeTaskName)
+ taskMgr.add(finalizeMenu, self.FinalizeTaskName,
+ priority=SCMenuFinalizePriority)
+
+ def privCancelFinalize(self):
+ taskMgr.remove(self.FinalizeTaskName)
+
+ def isFinalizing(self):
+ return self.inFinalize
+
+ def finalize(self):
+ if not self.isDirty():
+ return
+
+ self.inFinalize = 1
+
+ SCObject.finalize(self)
+
+ if __debug__:
+ # make sure all of our members know that we are their parent menu
+ # (who's yo daddy?)
+ for member in self:
+ assert member.getParentMenu() is self
+
+ # we aren't interested in members that aren't viewable.
+ # build a list of viewable members. Also parent viewable
+ # members to us, parent non-viewable members to hidden.
+ visibleMembers = []
+ for member in self:
+ if member.isViewable():
+ visibleMembers.append(member)
+ member.reparentTo(self)
+ else:
+ member.reparentTo(hidden)
+ # if this is the active member, deactivate it
+ if self.activeMember is member:
+ self.__setActiveMember(None)
+
+ # survey the members to find out their ideal dimensions, and
+ # determine the maximum dimensions
+ maxWidth = 0.
+ maxHeight = 0.
+ for member in visibleMembers:
+ width,height = member.getMinDimensions()
+ maxWidth = max(maxWidth, width)
+ maxHeight = max(maxHeight, height)
+
+ # make sure that we cover our parent menu all the way out past its
+ # right edge
+ holder = self.getHolder()
+ if holder is not None:
+ # how wide do we need to be to cover our parent menu?
+ widthToCover = holder.getMinSubmenuWidth()
+ maxWidth = max(maxWidth, widthToCover)
+
+ # all of the menu members will be at least as big as the biggest
+ # member, in each dimension
+ memberWidth, memberHeight = maxWidth, maxHeight
+ # Store this so that we can do horizonal centering
+ self.width = maxWidth
+
+ # put the members in the right place, and tell them what size
+ # they should be
+ for i in xrange(len(visibleMembers)):
+ member = visibleMembers[i]
+ member.setPos(0,0,-i * maxHeight)
+ member.setDimensions(memberWidth, memberHeight)
+ member.finalize()
+
+ if len(visibleMembers) > 0:
+ z1 = visibleMembers[0].getZ(aspect2d)
+ visibleMembers[0].setZ(-maxHeight)
+ z2 = visibleMembers[0].getZ(aspect2d)
+ visibleMembers[0].setZ(0)
+
+ actualHeight = (z2-z1) * len(visibleMembers)
+
+ # keep the menu from going off the bottom of the screen
+ bottomZ = self.getZ(aspect2d) + actualHeight
+ if bottomZ < -1.:
+ overlap = bottomZ - (-1.)
+ self.setZ(aspect2d, self.getZ(aspect2d) - overlap)
+ # keep the menu from going off the top of the screen
+ if self.getZ(aspect2d) > 1.:
+ self.setZ(aspect2d, 1.)
+
+ # set up the background frame
+ sX = memberWidth
+ sZ = memberHeight * len(visibleMembers)
+ self.bgMiddle.setScale(sX,1,sZ)
+ self.bgTop.setScale(sX,1,1)
+ self.bgBottom.setScale(sX,1,1)
+ self.bgLeft.setScale(1,1,sZ)
+ self.bgRight.setScale(1,1,sZ)
+ self.bgBottomLeft.setZ(-sZ)
+ self.bgBottom.setZ(-sZ)
+ self.bgTopRight.setX(sX)
+ self.bgRight.setX(sX)
+ self.bgBottomRight.setX(sX)
+ self.bgBottomRight.setZ(-sZ)
+ # scale the border wrt aspect2d
+ # note: changing the Y-scale from literal '1' wrt parent
+ # is not necessary and was causing visibility problems
+ sB = .15
+ self.bgTopLeft.setSx(aspect2d, sB)
+ self.bgTopLeft.setSz(aspect2d, sB)
+ self.bgBottomRight.setSx(aspect2d, sB)
+ self.bgBottomRight.setSz(aspect2d, sB)
+ self.bgBottomLeft.setSx(aspect2d, sB)
+ self.bgBottomLeft.setSz(aspect2d, sB)
+ self.bgTopRight.setSx(aspect2d, sB)
+ self.bgTopRight.setSz(aspect2d, sB)
+ self.bgTop.setSz(aspect2d, sB)
+ self.bgBottom.setSz(aspect2d, sB)
+ self.bgLeft.setSx(aspect2d, sB)
+ self.bgRight.setSx(aspect2d, sB)
+
+ r,g,b = self.getColorScheme().getFrameColor()
+ a = self.getColorScheme().getAlpha()
+ self.bg.setColorScale(r,g,b,a)
+
+ # if we have an active member, reparent it to us, so that
+ # it and its children show up over the rest of the menu elements
+ if self.activeMember is not None:
+ self.activeMember.reparentTo(self)
+
+ self.validate()
+
+ self.inFinalize = 0
+
+ # Functions to make the SCMenu object act just like a Python
+ # list of SCElements
+ #
+ # Note that removing an element does not remove the element from
+ # the menu visually; the owner of the element is responsible for
+ # parenting it away somewhere else and/or destroying the element.
+ # If you just want to clear out the entire menu, see clearMenu()
+ # above.
+ def append(self, element):
+ # Appends a single element to the list so far.
+ if isinstance(self.__members, types.TupleType):
+ self.__members = list(self.__members)
+ self.__members.append(element)
+ self.privMemberListChanged(added=[element])
+
+ def extend(self, elements):
+ # Appends a list of elements to the list so far.
+ # note that this operation invokes __iadd__
+ self += elements
+
+ def index(self, element):
+ return self.__members.index(element)
+
+ def __len__(self):
+ return len(self.__members)
+
+ def __getitem__(self, index):
+ return self.__members[index]
+
+ def __setitem__(self, index, value):
+ if isinstance(self.__members, types.TupleType):
+ self.__members = list(self.__members)
+ removedMember = self.__members[index]
+ self.__members[index] = value
+ self.privMemberListChanged(added=[value], removed=[removedMember])
+
+ def __delitem__(self, index):
+ if isinstance(self.__members, types.TupleType):
+ self.__members = list(self.__members)
+ removedMember = self.__members[index]
+ del self.__members[index]
+ self.privMemberListChanged(removed=[removedMember])
+
+ def __getslice__(self, i, j):
+ if isinstance(self.__members, types.TupleType):
+ self.__members = list(self.__members)
+ return self.__members[i:j]
+
+ def __setslice__(self, i, j, s):
+ if isinstance(self.__members, types.TupleType):
+ self.__members = list(self.__members)
+ removedMembers = self.__members[i:j]
+ self.__members[i:j] = list(s)
+ self.privMemberListChanged(added=list(s), removed=removedMembers)
+
+ def __delslice__(self, i, j):
+ if isinstance(self.__members, types.TupleType):
+ self.__members = list(self.__members)
+ removedMembers = self.__members[i:j]
+ del self.__members[i:j]
+ self.privMemberListChanged(removed=removedMembers)
+
+ def __iadd__(self, other):
+ if isinstance(self.__members, types.TupleType):
+ self.__members = list(self.__members)
+ if isinstance(other, SCMenu):
+ otherMenu = other
+ other = otherMenu.__members
+ del otherMenu[:]
+ self.__members += list(other)
+ self.privMemberListChanged(added=list(other))
+ return self
+
+ def privMemberListChanged(self, added=None, removed=None):
+ assert added or removed
+
+ if removed is not None:
+ for element in removed:
+ # if this element is our active member, we no longer have an
+ # active member
+ if element is self.activeMember:
+ self.__setActiveMember(None)
+ # if this element has not been reassigned to a different
+ # menu, make sure it knows it's not visible, and set its
+ # parent to None
+ if element.getParentMenu() is self:
+ if element.isVisible():
+ element.exitVisible()
+ element.setParentMenu(None)
+ element.reparentTo(hidden)
+
+ if added is not None:
+ for element in added:
+ self.privAdoptSCObject(element)
+ element.setParentMenu(self)
+
+ # it's possible that we are now empty, or have become not-empty
+ # tell our holder item to update its 'viewability' property
+ if self.holder is not None:
+ self.holder.updateViewability()
+
+ # set a 'posInParentMenu' index value on each member
+ for i in range(len(self.__members)):
+ self.__members[i].posInParentMenu = i
+
+ self.invalidate()
+
+ def privSetSettingsRef(self, settingsRef):
+ SCObject.privSetSettingsRef(self, settingsRef)
+ # propogate the settings reference to our children
+ for member in self:
+ member.privSetSettingsRef(settingsRef)
+
+ def invalidateAll(self):
+ SCObject.invalidateAll(self)
+ for member in self:
+ member.invalidateAll()
+
+ def finalizeAll(self):
+ SCObject.finalizeAll(self)
+ for member in self:
+ member.finalizeAll()
+
+ def getWidth(self):
+ return self.width
+
+ def __str__(self):
+ return '%s: menu%s' % (self.__class__.__name__, self.SerialNum)
diff --git a/otp/src/speedchat/SCMenuHolder.py b/otp/src/speedchat/SCMenuHolder.py
new file mode 100644
index 0000000..3b937d9
--- /dev/null
+++ b/otp/src/speedchat/SCMenuHolder.py
@@ -0,0 +1,233 @@
+"""SCMenuHolder.py: contains the SCMenuHolder class"""
+
+from pandac.PandaModules import *
+from direct.gui.DirectGui import *
+from SCObject import SCObject
+from SCElement import SCElement
+from SCMenu import SCMenu
+import types
+
+class SCMenuHolder(SCElement):
+ """
+ SCMenuHolder is an SCElement that owns an SCMenu and is
+ responsible for displaying it.
+ """
+
+ # our default background color should simply darken the underlying
+ # pixels
+ #
+ # The alpha-blending equation:
+ #
+ # Cout = Asrc*Csrc + (1-Asrc)*Cdst
+ #
+ # where Cout is the result, Csrc is the color of the transparent
+ # pixel being drawn, Asrc is the alpha value of said pixel,
+ # and Cdst is the color that is currently in the framebuffer.
+ #
+ # We will use Csrc = (0,0,0), since we only want to darken the
+ # framebuffer, not change its color/hue. This gives us:
+ #
+ # Cout = (1-Asrc)*Cdst
+ #
+ # Intuitively, we want to be able to say 'Make the existing color N%
+ # of its current brightness.' Assuming N in 0..1:
+ #
+ # Cout = N*Cdst
+ # thus
+ # N = (1-Asrc),
+ # Asrc = (1-N)
+ N = .9
+ DefaultFrameColor = (0,0,0,1.-N)
+ del N
+
+ # how much darker a child menu should be than its parent
+ MenuColorScaleDown = .95
+
+ def __init__(self, title, menu=None):
+ SCElement.__init__(self)
+ self.title = title
+
+ scGui = loader.loadModel(SCMenu.GuiModelName)
+ self.scArrow = scGui.find('**/chatArrow')
+
+ self.menu = None
+ self.setMenu(menu)
+
+ def destroy(self):
+ if self.menu is not None:
+ self.menu.destroy()
+ self.menu = None
+ SCElement.destroy(self)
+
+ def setTitle(self, title):
+ self.title = title
+ self.invalidate()
+ def getTitle(self):
+ return self.title
+
+ def setMenu(self, menu):
+ if self.menu is not None:
+ self.menu.destroy()
+ self.menu = menu
+ if self.menu is not None:
+ self.privAdoptSCObject(self.menu)
+ self.menu.setHolder(self)
+ # make sure the menu shows up over us
+ self.menu.reparentTo(self, 1)
+ self.menu.hide()
+ self.updateViewability()
+ def getMenu(self):
+ return self.menu
+
+ def showMenu(self):
+ """use this if we go back to a sorted bin
+ # make sure the menu shows up over us
+ drawOrder = self.getNetState().getDrawOrder()
+ self.menu.setBin('fixed', drawOrder + 1)
+ """
+ if self.menu is not None:
+ cS = SCMenuHolder.MenuColorScaleDown
+ self.menu.setColorScale(cS,cS,cS,1)
+ self.menu.enterVisible()
+ self.menu.show()
+
+ def hideMenu(self):
+ if self.menu is not None:
+ self.menu.hide()
+ self.menu.exitVisible()
+
+ def getMenuOverlap(self):
+ """returns a value in 0..1 representing the percentage
+ of our width that submenus should cover"""
+ if self.parentMenu.isTopLevel():
+ return self.getTopLevelOverlap()
+ else:
+ return self.getSubmenuOverlap()
+
+ def getMenuOffset(self):
+ """should return a Point3 offset at which the menu should be
+ positioned relative to this element"""
+ xOffset = self.width * (1. - self.getMenuOverlap())
+ return Point3(xOffset, 0, 0)
+
+ # from SCElement
+ def onMouseClick(self, event):
+ SCElement.enterActive(self)
+ self.parentMenu.memberSelected(self)
+
+ # 'active-state' state-change handlers; called by parent menu
+ def enterActive(self):
+ SCElement.enterActive(self)
+ self.showMenu()
+
+ # set the frame color to show that this menuHolder is active
+ if hasattr(self, 'button'):
+ r,g,b = self.getColorScheme().getMenuHolderActiveColor()
+ a = self.getColorScheme().getAlpha()
+ self.button.frameStyle[DGG.BUTTON_READY_STATE].setColor(r,g,b,a)
+ self.button.updateFrameStyle()
+ else:
+ self.notify.warning("SCMenuHolder has no button (has finalize been called?).")
+
+ def exitActive(self):
+ SCElement.exitActive(self)
+ self.hideMenu()
+
+ # reset the frame color
+ self.button.frameStyle[DGG.BUTTON_READY_STATE].setColor(
+ *SCMenuHolder.DefaultFrameColor)
+ self.button.updateFrameStyle()
+
+ def getDisplayText(self):
+ return self.title
+
+ def updateViewability(self):
+ if self.menu is None:
+ self.setViewable(0)
+ return
+ # if our menu is empty or none of our children are
+ # viewable, we should not be viewable
+ isViewable = False
+ for child in self.menu:
+ if child.isViewable():
+ isViewable = True
+ break
+ self.setViewable(isViewable)
+
+ def getMinSubmenuWidth(self):
+ # return the minimum width for a submenu, so that it covers
+ # this menu out past its right edge
+ parentMenu = self.getParentMenu()
+ # if we are in a menu, use the menu's width, since we are also
+ # that wide
+ if parentMenu is None:
+ myWidth, myWeight = self.getMinDimensions()
+ else:
+ myWidth = parentMenu.getWidth()
+ # this assumes offsetMult in [0,1]
+ return .15 + (myWidth * self.getMenuOverlap())
+
+ def getMinDimensions(self):
+ width, height = SCElement.getMinDimensions(self)
+ # add space for the arrow
+ width += 1.
+ return width, height
+
+ def invalidate(self):
+ SCElement.invalidate(self)
+ # invalidate our menu, since our width may have changed and
+ # the menu may be stretched to cover our width
+ if self.menu is not None:
+ self.menu.invalidate()
+
+ def finalize(self, dbArgs={}):
+ """catch this call and influence the appearance of our button"""
+ if not self.isDirty():
+ return
+
+ r,g,b = self.getColorScheme().getArrowColor()
+ a = self.getColorScheme().getAlpha()
+ self.scArrow.setColorScale(r,g,b,a)
+
+ if self.menu is not None:
+ # adjust the position of the menu
+ self.menu.setPos(self.getMenuOffset())
+
+ if self.isActive():
+ r,g,b = self.getColorScheme().getMenuHolderActiveColor()
+ a = self.getColorScheme().getAlpha()
+ frameColor = (r,g,b,a)
+ else:
+ frameColor = SCMenuHolder.DefaultFrameColor
+
+ args = {
+ 'image': self.scArrow,
+ 'image_pos': (self.width-.5,0,-self.height*.5),
+ 'frameColor': frameColor,
+ }
+
+ args.update(dbArgs)
+ SCElement.finalize(self, dbArgs=args)
+
+ def hasStickyFocus(self):
+ """menu holders have sticky focus. Once a menu holder gets
+ activated, it stays active until a sibling becomes active."""
+ return 1
+
+ # from SCObject
+ def privSetSettingsRef(self, settingsRef):
+ SCObject.privSetSettingsRef(self, settingsRef)
+ # propogate the settings reference to our menu
+ if self.menu is not None:
+ self.menu.privSetSettingsRef(settingsRef)
+
+ def invalidateAll(self):
+ SCObject.invalidateAll(self)
+ if self.menu is not None:
+ self.menu.invalidateAll()
+
+ def finalizeAll(self):
+ SCObject.finalizeAll(self)
+ if self.menu is not None:
+ self.menu.finalizeAll()
+
diff --git a/otp/src/speedchat/SCObject.py b/otp/src/speedchat/SCObject.py
new file mode 100644
index 0000000..7fe0094
--- /dev/null
+++ b/otp/src/speedchat/SCObject.py
@@ -0,0 +1,91 @@
+"""SCObject.py: contains the SCObject class"""
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.showbase.DirectObject import DirectObject
+
+class SCObject(DirectObject):
+ """ SCObject is the base class for all entities that make up a
+ SpeedChat tree. """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('SpeedChat')
+
+ def __init__(self):
+ self.settingsRef = None
+ self.__visible = 0
+ self.__dirty = 1
+
+ def destroy(self):
+ self.ignoreAll()
+ if self.isVisible():
+ self.exitVisible()
+
+ """ The owner of this SCObject should call enter/exitVisible when
+ this object appears on screen and no longer appears on the screen,
+ respectively. Derived classes can override these functions and
+ perform the appropriate actions """
+ def enterVisible(self):
+ #print 'enterVisible: %s' % self
+ self.__visible = 1
+ def exitVisible(self):
+ #print 'exitVisible: %s' % self
+ self.__visible = 0
+ def isVisible(self):
+ return self.__visible
+
+ # call 'invalidate' to indicate that the appearance of this object
+ # has changed, such that it needs to rebuild itself before being shown
+ def invalidate(self):
+ self.__dirty = 1
+ def isDirty(self):
+ return self.__dirty
+ def validate(self):
+ self.__dirty = 0
+
+ def finalize(self):
+ """ subclasses should override this function and perform whatever
+ processing is necessary to 'finalize' the appearance of this
+ object so that it's ready to be displayed """
+ pass
+
+ def getEventName(self, name):
+ """ the names of all events that pertain to a specific SpeedChat
+ object should come from this function """
+ return '%s%s' % (self.settingsRef.eventPrefix, name)
+
+ def getColorScheme(self):
+ return self.settingsRef.colorScheme
+
+ def isWhispering(self):
+ return self.settingsRef.whisperMode
+
+ def getSubmenuOverlap(self):
+ return self.settingsRef.submenuOverlap
+
+ def getTopLevelOverlap(self):
+ if self.settingsRef.topLevelOverlap is None:
+ return self.getSubmenuOverlap()
+ else:
+ return self.settingsRef.topLevelOverlap
+
+ def privSetSettingsRef(self, settingsRef):
+ """ Subclasses that contain nested SCObjects are responsible for
+ overriding this function, calling it for all of their children,
+ and calling this function. This propogates the reference through
+ the entire tree. """
+ self.settingsRef = settingsRef
+
+ def privAdoptSCObject(self, scObj):
+ """ Subclasses that contain nested SCObjects must call this
+ function whenever they gain a new child, with a reference to
+ the new child. """
+ scObj.privSetSettingsRef(self.settingsRef)
+
+ def invalidateAll(self):
+ """ inheritors should call down to this function, and also call
+ invalidateAll for all of their child objects """
+ self.invalidate()
+
+ def finalizeAll(self):
+ """ inheritors should call down to this function, and also call
+ finalizeAll for all of their child objects """
+ self.finalize()
diff --git a/otp/src/speedchat/SCSettings.py b/otp/src/speedchat/SCSettings.py
new file mode 100644
index 0000000..b1abdca
--- /dev/null
+++ b/otp/src/speedchat/SCSettings.py
@@ -0,0 +1,28 @@
+"""SCSettings.py: contains the SCSettings class"""
+
+from SCColorScheme import SCColorScheme
+from otp.otpbase import OTPLocalizer
+
+class SCSettings:
+ """ SCSettings holds values that are global for an entire SpeedChat
+ tree. By convention, these values are accessed through functions
+ defined in the SCObject base class (e.g. self.getColorScheme()).
+
+ eventPrefix: string to prepend to all the event names that pertain to
+ this specific SpeedChat instance
+ whisperMode: are we whispering?
+ colorScheme: an SCColorScheme for this SpeedChat
+ submenuOverlap: how much of their parent menu submenus should overlap,
+ in [0..1]
+ topLevelOverlap: same as submenuOverlap but for submenus of the top-level
+ menu; None means 'same as submenuOverlap'
+ """
+ def __init__(self, eventPrefix, whisperMode=0, colorScheme=None,
+ submenuOverlap=OTPLocalizer.SCOsubmenuOverlap, topLevelOverlap=None):
+ self.eventPrefix = eventPrefix
+ self.whisperMode = whisperMode
+ if colorScheme is None:
+ colorScheme = SCColorScheme()
+ self.colorScheme = colorScheme
+ self.submenuOverlap = submenuOverlap
+ self.topLevelOverlap = topLevelOverlap
diff --git a/otp/src/speedchat/SCStaticTextTerminal.py b/otp/src/speedchat/SCStaticTextTerminal.py
new file mode 100644
index 0000000..71c3319
--- /dev/null
+++ b/otp/src/speedchat/SCStaticTextTerminal.py
@@ -0,0 +1,26 @@
+"""SCStaticTextTerminal.py: contains the SCStaticTextTerminal class"""
+
+from SCTerminal import SCTerminal
+from otp.otpbase.OTPLocalizer import SpeedChatStaticText
+
+# args: textId
+SCStaticTextMsgEvent = 'SCStaticTextMsg'
+
+def decodeSCStaticTextMsg(textId):
+ return SpeedChatStaticText.get(textId, None)
+
+class SCStaticTextTerminal(SCTerminal):
+ """ SCStaticTextTerminal represents a terminal SpeedChat entry that
+ contains a piece of static (never-changing/constant) text.
+
+ When selected, generates a 'SCStaticTextMsg' event, with arguments:
+ - textId (16-bit; use as index into OTPLocalizer.SpeedChatStaticText)
+ """
+ def __init__(self, textId):
+ SCTerminal.__init__(self)
+ self.textId = textId
+ self.text = SpeedChatStaticText[self.textId]
+
+ def handleSelect(self):
+ SCTerminal.handleSelect(self)
+ messenger.send(self.getEventName(SCStaticTextMsgEvent), [self.textId])
diff --git a/otp/src/speedchat/SCTerminal.py b/otp/src/speedchat/SCTerminal.py
new file mode 100644
index 0000000..cb213e6
--- /dev/null
+++ b/otp/src/speedchat/SCTerminal.py
@@ -0,0 +1,224 @@
+"""SCTerminal.py: contains the SCTerminal class"""
+
+from SCElement import SCElement
+from SCObject import SCObject
+from SCMenu import SCMenu
+from direct.fsm.StatePush import StateVar, FunctionCall
+from direct.showbase.DirectObject import DirectObject
+from otp.avatar import Emote
+
+# this event is generated whenever any SpeedChat terminal is chosen
+# args: none
+SCTerminalSelectedEvent = 'SCTerminalSelected'
+# this event is generated if the selected terminal has a linked emote
+# args: emoteId
+SCTerminalLinkedEmoteEvent = 'SCTerminalLinkedEmoteEvent'
+# someone needs to generate this event whenever the speedchat tree goes
+# in or out of whisper mode
+SCWhisperModeChangeEvent = 'SCWhisperModeChange'
+
+class SCTerminal(SCElement):
+ """ SCTerminal is the base class for all 'terminal' speedchat
+ entities """
+ def __init__(self, linkedEmote=None):
+ SCElement.__init__(self)
+ self.setLinkedEmote(linkedEmote)
+
+ scGui = loader.loadModel(SCMenu.GuiModelName)
+ self.emotionIcon = scGui.find('**/emotionIcon')
+ self.setDisabled(False)
+ self.__numCharges = -1
+
+ # should we listen for whisper mode changes?
+ self._handleWhisperModeSV = StateVar(False)
+ # can't set this up until we're ready to have the handler func called
+ self._handleWhisperModeFC = None
+
+ def destroy(self):
+ self._handleWhisperModeSV.set(False)
+ if self._handleWhisperModeFC:
+ self._handleWhisperModeFC.destroy()
+ self._handleWhisperModeSV.destroy()
+ SCElement.destroy(self)
+
+ def privSetSettingsRef(self, settingsRef):
+ SCElement.privSetSettingsRef(self, settingsRef)
+ if self._handleWhisperModeFC is None:
+ self._handleWhisperModeFC = FunctionCall(self._handleWhisperModeSVChanged,
+ self._handleWhisperModeSV)
+ self._handleWhisperModeFC.pushCurrentState()
+ # if this terminal is not whisperable, we need to listen for whisper mode changes
+ self._handleWhisperModeSV.set((self.settingsRef is not None) and
+ (not self.isWhisperable()))
+
+ def _handleWhisperModeSVChanged(self, handleWhisperMode):
+ if handleWhisperMode:
+ # this terminal can't be whispered. we need to reconstruct
+ # our GUI element when the whisper mode changes
+ # listen for that mode change
+ # create a DirectObject to avoid conflicts with other parts of this
+ # object that are listening for this event
+ self._wmcListener = DirectObject()
+ self._wmcListener.accept(self.getEventName(SCWhisperModeChangeEvent),
+ self._handleWhisperModeChange)
+ else:
+ if hasattr(self, '_wmcListener'):
+ # we no longer need to listen for whisper mode changes
+ self._wmcListener.ignoreAll()
+ del self._wmcListener
+ # make sure our GUI element is appropriate
+ self.invalidate()
+
+ def _handleWhisperModeChange(self, whisperMode):
+ # whisper mode changed, we need to change our GUI element
+ self.invalidate()
+
+ # the meat of SCTerminal; inheritors should override this
+ # and perform the appropriate action
+ def handleSelect(self):
+ """ called when the user selects this node """
+ # send the generic 'something was selected' event
+ messenger.send(self.getEventName(SCTerminalSelectedEvent))
+
+ # if we have a linked emote, and it isn't disabled, generate a msg
+ if self.hasLinkedEmote() and self.linkedEmoteEnabled():
+ messenger.send(self.getEventName(SCTerminalLinkedEmoteEvent),
+ [self.linkedEmote])
+
+ def isWhisperable(self):
+ # can this terminal be sent as a whisper message?
+ return True
+
+ # Some terminal nodes have an emote associated with them, which
+ # should be invoked when the node is selected.
+ def getLinkedEmote(self):
+ return self.linkedEmote
+ def setLinkedEmote(self, linkedEmote):
+ self.linkedEmote = linkedEmote
+ # TODO: we should make sure we're listening for emote
+ # enable state changes if this is set while we're visible
+ self.invalidate()
+ def hasLinkedEmote(self):
+ return (self.linkedEmote is not None)
+ def linkedEmoteEnabled(self):
+ if Emote.globalEmote:
+ return Emote.globalEmote.isEnabled(self.linkedEmote)
+
+ def getCharges(self):
+ return self.__numCharges
+
+ def setCharges(self, nCharges):
+ self.__numCharges = nCharges
+ if (nCharges is 0):
+ self.setDisabled(True)
+
+ # support for disabled terminals
+ def isDisabled(self):
+ return self.__disabled or (self.isWhispering() and not self.isWhisperable())
+
+ def setDisabled(self, bDisabled):
+ # make the button 'unclickable'
+ self.__disabled = bDisabled
+
+ # from SCElement
+ def onMouseClick(self, event):
+ if not self.isDisabled():
+ SCElement.onMouseClick(self, event)
+ self.handleSelect()
+
+ def getMinDimensions(self):
+ width, height = SCElement.getMinDimensions(self)
+ if self.hasLinkedEmote():
+ # add space for the emotion icon
+ width += 1.3
+ return width, height
+
+ def finalize(self, dbArgs={}):
+ """ catch this call and influence the appearance of our button """
+ if not self.isDirty():
+ return
+
+ args = {}
+
+ if self.hasLinkedEmote():
+ self.lastEmoteIconColor = self.getEmoteIconColor()
+ self.emotionIcon.setColorScale(*self.lastEmoteIconColor)
+ args.update({
+ 'image': self.emotionIcon,
+ 'image_pos': (self.width-.6,0,-self.height*.5),
+ })
+
+ if self.isDisabled():
+ args.update({
+ 'rolloverColor': (0,0,0,0),
+ 'pressedColor': (0,0,0,0),
+ 'rolloverSound': None,
+ 'clickSound': None,
+ 'text_fg': self.getColorScheme().getTextDisabledColor()+(1,),
+ })
+
+ args.update(dbArgs)
+ SCElement.finalize(self, dbArgs=args)
+
+ def getEmoteIconColor(self):
+ if self.linkedEmoteEnabled() and (not self.isWhispering()):
+ r,g,b = self.getColorScheme().getEmoteIconColor()
+ else:
+ r,g,b = self.getColorScheme().getEmoteIconDisabledColor()
+ return (r,g,b,1)
+
+ def updateEmoteIcon(self):
+ if hasattr(self, 'button'):
+ self.lastEmoteIconColor = self.getEmoteIconColor()
+ for i in range(self.button['numStates']):
+ self.button['image%s_image' % i].setColorScale(
+ *self.lastEmoteIconColor)
+ else:
+ self.invalidate()
+
+ # from SCObject
+ def enterVisible(self):
+ SCElement.enterVisible(self)
+
+ # Check if the emote state has changed since the last time
+ # we were finalized, and invalidate if it's different.
+ if hasattr(self, 'lastEmoteIconColor'):
+ if self.getEmoteIconColor() != self.lastEmoteIconColor:
+ self.invalidate()
+
+ # listen for whisper-mode changes
+ def handleWhisperModeChange(whisperMode, self=self):
+ if self.hasLinkedEmote():
+ # we are leaving or entering whisper mode;
+ # the appearance of our emote icon needs to change
+ # (no linked emotes on whispers)
+ if self.isVisible() and not self.isWhispering():
+ self.updateEmoteIcon()
+ self.accept(self.getEventName(SCWhisperModeChangeEvent),
+ handleWhisperModeChange)
+
+ # listen for emote-enable state changes
+ def handleEmoteEnableStateChange(self=self):
+ if self.hasLinkedEmote():
+ # emotions have just become enabled/disabled
+ # update our emote icon
+ # (no emotes when whispering)
+ if self.isVisible() and not self.isWhispering():
+ self.updateEmoteIcon()
+ if self.hasLinkedEmote():
+ if Emote.globalEmote:
+ self.accept(Emote.globalEmote.EmoteEnableStateChanged,
+ handleEmoteEnableStateChange)
+
+ def exitVisible(self):
+ SCElement.exitVisible(self)
+ self.ignore(self.getEventName(SCWhisperModeChangeEvent))
+ if Emote.globalEmote:
+ self.ignore(Emote.globalEmote.EmoteEnableStateChanged)
+
+ def getDisplayText(self):
+ if self.getCharges() is not -1:
+ return self.text + " (%s)" % self.getCharges()
+ else:
+ return self.text
+
diff --git a/otp/src/speedchat/Sources.pp b/otp/src/speedchat/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/otp/src/speedchat/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/otp/src/speedchat/SpeedChat.py b/otp/src/speedchat/SpeedChat.py
new file mode 100644
index 0000000..96f2f3f
--- /dev/null
+++ b/otp/src/speedchat/SpeedChat.py
@@ -0,0 +1,224 @@
+"""SpeedChat.py: contains the SpeedChat class"""
+
+from direct.showbase.PythonUtil import boolEqual
+from SpeedChatTypes import *
+from SCSettings import SCSettings
+from SCTerminal import SCWhisperModeChangeEvent
+from otp.otpbase import OTPLocalizer
+
+# for speedchat tech details, see the bottom of this file.
+
+class SpeedChat(SCMenu):
+ """
+ SpeedChat object. Create one of these to make a complete SpeedChat menu.
+
+ If you provide a name for the SpeedChat, the name will be prepended onto
+ all events that the SpeedChat generates.
+
+ The entire structure of the menu may be specified upon construction,
+ through the 'structure' argument to SpeedChat.__init__. Alternatively,
+ you may add SpeedChat elements manually using the standard Python list
+ interfaces implemented by SCMenu.
+
+ See SCMenu.appendFromStructure for the format of 'structure'.
+ """
+
+ def __init__(self, name='', structure=None, backgroundModelName = None,
+ guiModelName = None):
+ SCMenu.BackgroundModelName = backgroundModelName
+ SCMenu.GuiModelName = guiModelName
+
+ SCMenu.__init__(self)
+ self.name = name
+
+ self.settings = SCSettings(
+ eventPrefix = self.name,
+ )
+ self.privSetSettingsRef(self.settings)
+
+ if structure is not None:
+ self.rebuildFromStructure(structure)
+
+ # this is used to detect if the menu needs to be invalidated
+ # because of a change in position or scale etc.
+ self._lastTransform = None
+
+ def destroy(self):
+ if self.isVisible():
+ self.exitVisible()
+ self._lastTransform = None
+ SCMenu.destroy(self)
+
+ def __str__(self):
+ return "%s: '%s'" % (self.__class__.__name__, self.name)
+
+ def enter(self):
+ self._detectTransformChange()
+ self.enterVisible()
+
+ def exit(self):
+ self.exitVisible()
+
+ def _detectTransformChange(self):
+ # check if our transform has changed
+ newTransform = self.getTransform(aspect2d)
+ if self._lastTransform is not None:
+ if newTransform != self._lastTransform:
+ self.invalidateAll()
+ self._lastTransform = newTransform
+
+ def setWhisperMode(self, whisperMode):
+ if not boolEqual(self.settings.whisperMode, whisperMode):
+ self.settings.whisperMode = whisperMode
+ messenger.send(self.getEventName(SCWhisperModeChangeEvent),
+ [whisperMode])
+
+ def setColorScheme(self, colorScheme):
+ self.settings.colorScheme = colorScheme
+ # this affects pretty much every element, and it's not a common
+ # occurence. invalidate the entire tree.
+ self.invalidateAll()
+
+ def setSubmenuOverlap(self, submenuOverlap):
+ self.settings.submenuOverlap = submenuOverlap
+ self.invalidateAll()
+
+ def setTopLevelOverlap(self, topLevelOverlap):
+ self.settings.topLevelOverlap = topLevelOverlap
+ self.invalidateAll()
+
+ def finalizeAll(self):
+ self.notify.debug('finalizing entire SpeedChat tree')
+ self._detectTransformChange()
+ SCMenu.finalizeAll(self)
+
+"""
+SpeedChat tech
+==============
+
+The SpeedChat comprises cascading menus of 'elements'. Some elements
+act as holders for submenus, others represent phrases or emotions that
+the user can select to make their avatar speak and/or animate.
+
+The classes that make up the basic SpeedChat system are:
+SCObject: base class for all SpeedChat entities, contains support functions
+SCColorScheme: contains a set of colors for the elements of the SpeedChat
+SCMenu: a single menu that contains a number of elements
+SCElement: base class for all entities that may appear within a menu
+SCMenuHolder: element that has a submenu attached to it
+SCTerminal: base class for all elements that can be clicked on to perform
+ an action; once selected, the SpeedChat session is over
+SCStaticTextTerminal: basic terminal, contains a phrase that can be spoken
+SpeedChat: the top-level menu that contains the entire SpeedChat and provides
+ interface functions to the user of the system
+
+To support the needs of Toontown, the following classes are derived:
+SCCustomMenu: menu that holds purchased custom phrases
+SCCustomTerminal: element of an SCCustomMenu
+SCEmoteMenu: menu that holds the set of emotions that local toon has access to
+SCEmoteTerminal: element of an SCEmoteMenu
+SCToontaskMenu: menu that contains phrases related to local toon's toontasks
+SCToontaskTerminal: element of an SCToontaskMenu
+
+GRAPHICAL REPRESENTATION
+Graphically, menus are responsible for displaying a transparent frame around
+and behind its elements. The background is a flat area with borders around it,
+with rounded corners. The geometry of the background image is a model split
+into 9 pieces: a 1x1 square middle section, top/left/right/bottom sections,
+each 1 foot long or high, and four corner pieces. The origin of the model is
+at the intersection of the top-left corner, top, left, and middle pieces.
+
+To make this background big enough for a menu that must contain elements that
+take up a WxH-feet area, the pieces are scaled and moved in an intuitive
+manner. The middle piece is scaled by W and H, the top/left/right/bottom
+pieces are scaled similarly but along one dimension, and the top-right,
+bottom-left, and bottom-right corners are moved by W or H appropriately.
+Finally, all border elements are scaled wrt aspect2d in the appropriate
+dimensions in order to maintain an appropriate border width regardless of
+the scale of the overall SpeedChat. See SCMenu.finalize() for the
+implementation.
+
+Each menu element contains a DirectButton that takes care of interaction
+with the mouse. The DirectButton is also used as the graphical representation
+of the element. (In the future, it would be a good idea to rework the code
+so that SCElements *are* DirectButtons.)
+
+Each element creates its button so that it contains some text. For terminals,
+this is usually the phrase that the local toon would say if selected. Some
+elements also add a graphical element to their button; for instance, menu
+holders contain an arrow graphic, and terminals that have emotions that will
+be triggered upon selection (linked emotes) contain an 'emote icon' graphic.
+
+The background of the button is made completely transparent most of the time,
+since the menu already has created a background frame underneath the elements.
+Menu holders use a slight alpha on their background, to darken the menu frame
+behind them. Solid, opaque colors are used to indicate rollover.
+
+GRAPHICAL DYNAMICS
+Every graphical object in the SpeedChat (everything that derives from
+SCObject) inherits an interface that is designed to minimize
+graphics-related processing. The methods that SCObject defines are:
+
+invalidate(): mark this object as 'dirty' (needs processing before being shown)
+isDirty(): returns true if this object has been marked dirty
+validate(): reset the 'dirty' flag (presumably after updating the object's
+ graphical representation)
+finalize(): performs the necessary operations to ensure that the object's
+ graphical representation is up-to-date
+
+invalidate() should be a light operation; it is acceptable for finalize() to
+be a heavy operation. Dirty objects only need to be finalized once per frame,
+regardless of how many times it is marked as dirty. It should be OK to call
+finalize() on a non-dirty object, and that should be a light operation.
+
+In the current implementation, SCElements destroy their button and rebuild it
+from scratch every time they are invalidated. For performance reasons,
+some subclasses of SCElement avoid this by making dynamic modifications to
+their button when they can. If and when the SpeedChat objects are made into
+first-class DirectGui elements, dynamic modifications will become a necessity;
+presumably, this will result in slightly better performance (and slightly
+more complex code).
+
+MENU DYNAMICS
+As the user moves the mouse cursor through an open menu, different elements
+of the menu gain the input focus. Only one element can have the input focus
+at any instant. Correspondingly, only one element should be 'active' at any
+instant, where 'active' is interpreted according to the type of element:
+menu holders show their submenu when active, terminals simply show their
+rollover state, etc.
+
+Naively, we can say that the element that has the input focus should be the
+active element. This works OK, until the user moves the mouse cursor away
+from the menu -- or into a submenu! What we really want is for some elements,
+such as menu holders, to have 'sticky focus' -- that is to say, once activated,
+elements with sticky focus will stay active until a sibling becomes
+activated, even if they lose the input focus.
+
+The SpeedChat takes advantage of this separation of 'active' and 'focus-having'
+elements in order to make the menus easier to use. When an element
+gains the input focus, it is only activated if the element maintains the
+input focus for a specific period of time. By calibrating the duration of
+the wait period, we create a 'tolerance' for 'sloppy' use of the menu: the
+user can drag the mouse across large sections of an open menu without causing
+any of the elements that are dragged over to become active.
+
+COLOR SCHEMES
+To change the color scheme of a SpeedChat, you must create a new SCColorScheme
+object and pass it to the SpeedChat with its setColorScheme() method.
+SCColorScheme objects are immutable.
+
+There are some crude utility functions to dynamically change the color scheme
+of a SpeedChat in SCColorPanel.py.
+
+SpeedChat color schemes are basically described by two colors: a main color
+and a contrasting color. The main color ('arrowColor') is used directly on
+the menuHolder arrows, and a lightened version of it is used for the menu
+backgrounds. The contrasting color is used directly as the element rollover
+color, and variations of it are calculated for the pressed and 'active'
+element colors, as well as the emote icon color.
+
+The color modifications and calculations are done by converting RGB colors
+into the YUV and HSV color spaces, manipulating those values, and
+converting back to RGB. The conversion functions and descriptions of the
+YUV and HSV color spaces are in ColorSpace.py
+"""
diff --git a/otp/src/speedchat/SpeedChatGMHandler.py b/otp/src/speedchat/SpeedChatGMHandler.py
new file mode 100644
index 0000000..035f797
--- /dev/null
+++ b/otp/src/speedchat/SpeedChatGMHandler.py
@@ -0,0 +1,49 @@
+"""SpeedChatGMHandler.py: Handles the GM menu for speed chats"""
+
+
+from pandac.PandaModules import *
+from direct.showbase import DirectObject
+from otp.otpbase import OTPLocalizer
+
+class SpeedChatGMHandler(DirectObject.DirectObject):
+
+ scStructure = None
+ scList = {}
+
+ def __init__(self):
+ if SpeedChatGMHandler.scStructure is None:
+ self.generateSCStructure()
+
+ # Generates the structure containing all GM speed chat entries
+ def generateSCStructure(self):
+ SpeedChatGMHandler.scStructure = [OTPLocalizer.PSCMenuGM]
+ phraseCount = 0
+ numGMCategories = base.config.GetInt('num-gm-categories', 0)
+ for i in range(0, numGMCategories):
+ categoryName = base.config.GetString('gm-category-%d' % i,'')
+ if categoryName == '':
+ continue
+ categoryStructure = [categoryName]
+ numCategoryPhrases = base.config.GetInt('gm-category-%d-phrases' % i, 0)
+ for j in range(0, numCategoryPhrases):
+ phrase = base.config.GetString('gm-category-%d-phrase-%d' % (i, j),'')
+ if phrase!= '':
+ idx = 'gm%d' % phraseCount
+ SpeedChatGMHandler.scList[idx] = phrase
+ categoryStructure.append(idx)
+ phraseCount += 1
+ SpeedChatGMHandler.scStructure.append(categoryStructure)
+ numGMPhrases = base.config.GetInt('num-gm-phrases', 0)
+ for i in range(0, numGMPhrases):
+ phrase = base.config.GetString('gm-phrase-%d' % i,'')
+ if phrase != '':
+ idx = 'gm%d' % phraseCount
+ SpeedChatGMHandler.scList[idx] = phrase
+ SpeedChatGMHandler.scStructure.append(idx)
+ phraseCount += 1
+
+ def getStructure(self):
+ return SpeedChatGMHandler.scStructure
+
+ def getPhrase(self, id):
+ return SpeedChatGMHandler.scList[id]
diff --git a/otp/src/speedchat/SpeedChatGlobals.py b/otp/src/speedchat/SpeedChatGlobals.py
new file mode 100644
index 0000000..027cab1
--- /dev/null
+++ b/otp/src/speedchat/SpeedChatGlobals.py
@@ -0,0 +1,22 @@
+"""SpeedChatGlobals.py: global SpeedChat data """
+
+# If you just want to know when a speedchat message is selected,
+# see the events in ChatManager.py.
+
+# These are the base names of the events that may be generated by
+# a SpeedChat object.
+# To get the actual event name, use:
+# speedChat.getEventName(eventBaseName)
+# where 'speedChat' is your SpeedChat object, and 'eventBaseName' is
+# one of the following:
+from SCTerminal import SCTerminalSelectedEvent
+from SCTerminal import SCTerminalLinkedEmoteEvent
+from SCStaticTextTerminal import SCStaticTextMsgEvent
+from SCGMTextTerminal import SCGMTextMsgEvent
+from SCCustomTerminal import SCCustomMsgEvent
+from SCEmoteTerminal import SCEmoteMsgEvent, SCEmoteNoAccessEvent
+
+# SCColorSchemeChangeMsgEvent
+
+# See the individual SC*Terminal.py files for documentation of the
+# arguments included with each message.
diff --git a/otp/src/speedchat/SpeedChatTypes.py b/otp/src/speedchat/SpeedChatTypes.py
new file mode 100644
index 0000000..8282f1c
--- /dev/null
+++ b/otp/src/speedchat/SpeedChatTypes.py
@@ -0,0 +1,19 @@
+"""SpeedChatTypes.py: SpeedChat types """
+
+from SCObject import SCObject
+
+from SCMenu import SCMenu
+
+from SCElement import SCElement
+from SCMenuHolder import SCMenuHolder
+from SCTerminal import SCTerminal
+
+from SCCustomMenu import SCCustomMenu
+from SCEmoteMenu import SCEmoteMenu
+
+from SCStaticTextTerminal import SCStaticTextTerminal
+from SCGMTextTerminal import SCGMTextTerminal
+from SCCustomTerminal import SCCustomTerminal
+from SCEmoteTerminal import SCEmoteTerminal
+
+from SCColorScheme import SCColorScheme
diff --git a/otp/src/speedchat/__init__.py b/otp/src/speedchat/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/otp/src/status/Sources.pp b/otp/src/status/Sources.pp
new file mode 100644
index 0000000..e69de29
diff --git a/otp/src/status/StatusDatabase.py b/otp/src/status/StatusDatabase.py
new file mode 100644
index 0000000..5de6bec
--- /dev/null
+++ b/otp/src/status/StatusDatabase.py
@@ -0,0 +1,80 @@
+import datetime
+import time
+from direct.distributed.DistributedObjectGlobal import DistributedObjectGlobal
+from direct.directnotify.DirectNotifyGlobal import directNotify
+#from otp.uberdog.RejectCode import RejectCode
+#from otp.otpbase import OTPGlobals
+from otp.otpbase import OTPLocalizer
+
+
+class StatusDatabase(DistributedObjectGlobal):
+ """
+
+ """
+
+ notify = directNotify.newCategory('StatusDatabase')
+
+ def __init__(self, cr):
+ assert self.notify.debugCall()
+ DistributedObjectGlobal.__init__(self, cr)
+ self.avatarData = {}
+ self.avatarQueue = []
+ self.avatarRequestTaskName = "StatusDataBase_RequestAvatarLastOnline"
+ self.avatarRetreiveTaskName = "StatusDataBase_GetAvatarLastOnline"
+ self.avatarDoneTaskName = "StatusDataBase GotAvatarData"
+
+
+ # CL -> UD
+ def requestOfflineAvatarStatus(self,avIds):
+ self.notify.debugCall()
+ self.sendUpdate("requestOfflineAvatarStatus",[avIds])
+
+ def queueOfflineAvatarStatus(self,avIds):
+ for avId in avIds:
+ if not (avId in self.avatarQueue):
+ self.avatarQueue.append(avId)
+
+ while taskMgr.hasTaskNamed(self.avatarRequestTaskName):
+ taskMgr.remove(self.avatarRequestTaskName)
+
+ task = taskMgr.doMethodLater(1.0, self.requestAvatarQueue, self.avatarRequestTaskName)
+
+
+ def requestAvatarQueue(self, task):
+ self.sendUpdate("requestOfflineAvatarStatus",[self.avatarQueue])
+ self.avatarQueue = []
+
+
+ # UD -> CL
+ def recvOfflineAvatarStatus(self, avId, lastOnline):
+ self.notify.debugCall()
+ # This is where we update our data, repaint GUI, etc etc etc.
+
+ self.notify.debug("Got an update for offline avatar %s who was last online %s" % (avId, self.lastOnlineString(lastOnline)))
+
+ self.avatarData[avId] = lastOnline
+
+ while taskMgr.hasTaskNamed(self.avatarRetreiveTaskName):
+ taskMgr.remove(self.avatarRetreiveTaskName)
+
+ task = taskMgr.doMethodLater(1.0, self.announceNewAvatarData, self.avatarRetreiveTaskName)
+
+
+
+ #base.cr.avatarFriendsManager.avatarId2Info[avId].timestamp = lastOnline
+
+ def announceNewAvatarData(self, task):
+ messenger.send(self.avatarDoneTaskName)
+
+
+
+ # Helper function to translate from a timestamp in seconds to "x hours ago", etc
+ def lastOnlineString(self, timestamp):
+ if timestamp == 0:
+ return ""
+
+ now = datetime.datetime.utcnow()
+
+ td = abs(now - datetime.datetime.fromtimestamp(timestamp))
+
+ return OTPLocalizer.timeElapsedString(td)
diff --git a/otp/src/status/StatusDatabaseUD.py b/otp/src/status/StatusDatabaseUD.py
new file mode 100644
index 0000000..6d28545
--- /dev/null
+++ b/otp/src/status/StatusDatabaseUD.py
@@ -0,0 +1,185 @@
+import MySQLdb
+import _mysql_exceptions
+
+from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from otp.uberdog.DBKeepAlive import DBKeepAlive
+from otp.uberdog.DBInterface import DBInterface
+
+class StatusDatabaseUD(DistributedObjectGlobalUD,DBInterface):
+ """
+ StatusDatabase is a lightweight DB wrapper for status information about avatars.
+ This initial version only stores last online times by recording timestamps each
+ time an avatar comes online or goes offline.
+
+ Currently, he client must pull all desired information using requestOfflineAvatarStatus.
+ This can easily change to a push interface later on, if desired.
+ """
+
+ notify = directNotify.newCategory('StatusDatabaseUD')
+
+ def __init__(self, air):
+ DistributedObjectGlobalUD.__init__(self, air)
+
+ self.avatarId2LastOnline = {}
+
+ self.DBuser = uber.config.GetString("mysql-user", "ud_rw")
+ self.DBpasswd = uber.config.GetString("mysql-passwd", "r3adwr1te")
+
+ self.DBhost = "localhost"
+ self.DBport = 3306
+ self.DBname = self.processDBName("status")
+
+ self.db = MySQLdb.connect(host=self.DBhost,
+ port=self.DBport,
+ user=self.DBuser,
+ passwd=self.DBpasswd)
+
+ self.notify.info("Connected to MySQL server at %s:%d."%(self.DBhost,self.DBport))
+
+ cursor = self.db.cursor()
+
+ try:
+ cursor.execute("CREATE DATABASE `%s`"%self.DBname)
+ self.notify.info("Database '%s' did not exist, created a new one!"%self.DBname)
+ except _mysql_exceptions.ProgrammingError,e:
+ pass
+
+ cursor.execute("USE `%s`"%self.DBname)
+ self.notify.debug("Using database '%s'"%self.DBname)
+
+ try:
+ cursor.execute("""
+ CREATE TABLE `offlineAvatarStatus` (
+ `avatarId` int(32) UNSIGNED NOT NULL,
+ `lastOnlineTime` TIMESTAMP NOT NULL,
+ PRIMARY KEY (`avatarId`)
+ ) ENGINE=InnoDB
+ """)
+ self.notify.info("Table offlineAvatarStatus did not exist, created a new one!")
+ except _mysql_exceptions.OperationalError,e:
+ pass
+
+ if __dev__:
+ self.dbkeep = DBKeepAlive(self.db)
+
+ taskMgr.doMethodLater(1.0,self._lazyCommit,'lazyCommit')
+
+
+ def announceGenerate(self):
+ self.accept("avatarOnline", self.avatarOnline, [])
+ self.accept("avatarOffline", self.avatarOffline, [])
+ DistributedObjectGlobalUD.announceGenerate(self)
+
+ def delete(self):
+ self.ignoreAll()
+ DistributedObjectGlobalUD.delete(self)
+
+
+ def avatarOnline(self, avatarId, avatarType):
+ self._updateLastOnlineTime(avatarId)
+
+ def avatarOffline(self, avatarId):
+ self._updateLastOnlineTime(avatarId)
+
+
+ # CL -> UD
+ def requestOfflineAvatarStatus(self, avatarIds):
+ """
+ CL->UD message to request details for a list of offline avatars.
+ Results in a set of UD->CL recvOfflineAvatarStatus messages, one for each avatar.
+ """
+ if not avatarIds: #return if empty
+ return
+
+ senderId = self.air.getAvatarIdFromSender()
+
+ if len(avatarIds) > 1000:
+ self.notify.warning("Ignoring huge avatarIds list sent to requestOfflineAvatarStatus from sender %s: %s" % (senderId,avatarIds))
+ return
+
+ onlineTimes = self._getLastOnlineTimes(avatarIds)
+
+ for (avId,onlineTime) in onlineTimes:
+ self.sendUpdateToAvatarId(senderId,
+ "recvOfflineAvatarStatus",
+ [avId, onlineTime])
+
+
+ # ----- Handy internal functions -----
+
+
+ def _lazyCommit(self,task):
+ self.db.commit()
+ return task.again
+
+
+ def _valueList(self, numVals):
+ assert numVals > -1
+ if numVals == 0:
+ return "()"
+ else:
+ return "(" + ("%s," * (numVals-1)) + "%s)"
+
+
+ def _getLastOnlineTimes(self, avatarIds):
+ """
+ Returns a list of pairs of (avatarId,lastOnlineTime) for the given avatarIds.
+ If we have no data for an avatarId, (avatarId,0) is returned.
+ """
+ if len(avatarIds) < 1:
+ return
+
+ missing = []
+
+ # Determine who we don't have cached in RAM
+ for id in avatarIds:
+ if (not id in self.avatarId2LastOnline) and (id > 0):
+ missing.append(id)
+
+ # Get the missing people from the DB into RAM
+ if len(missing) > 0:
+ cursor = self.db.cursor()
+ cursor.execute("SELECT avatarId,UNIX_TIMESTAMP(lastOnlineTime) from offlineAvatarStatus WHERE avatarId in " + self._valueList(len(missing)), missing)
+ while cursor.rownumber < cursor.rowcount:
+ id,lastOnline = cursor.fetchone()
+ self.avatarId2LastOnline[id] = lastOnline
+
+ result = []
+
+ # Return results from RAM
+ for id in avatarIds:
+ lastOnline = self.avatarId2LastOnline.get(id,0)
+
+ result.append((id,lastOnline))
+
+ return result
+
+
+ def _updateLastOnlineTime(self,avatarId):
+ """
+ Sets the last online time for the specified avatar to NOW (UTC).
+ Value is updated in MySQL and in our in-memory cache.
+ """
+ assert avatarId > 0
+
+ cursor = self.db.cursor()
+
+ # Update in DB, also read back the timestamp that SQL just recorded so we can cache it
+ cursor.execute("""
+ INSERT INTO offlineAvatarStatus (avatarId,lastOnlineTime) VALUES (%s,UTC_TIMESTAMP)
+ ON DUPLICATE KEY UPDATE lastOnlineTime=UTC_TIMESTAMP;
+ SELECT UNIX_TIMESTAMP(lastOnlineTime) from offlineAvatarStatus WHERE avatarId = %s
+ """, [avatarId, avatarId])
+
+ # Skip empty result set from INSERT/UPDATE
+ cursor.nextset()
+
+ # Get the timestamp our SELECT retrieved and store it in RAM
+ assert cursor.rowcount == 1
+
+ lastOnline = cursor.fetchone()[0]
+ self.avatarId2LastOnline[avatarId] = lastOnline
+
+ # For 10x performance improvement: Do not commit here!
+ # lazyCommit will be called in 1 second or less and flush our changes to disk then.
diff --git a/otp/src/status/__init__.py b/otp/src/status/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/otp/src/switchboard/FriendManager.wsdl b/otp/src/switchboard/FriendManager.wsdl
new file mode 100644
index 0000000..0af34b0
--- /dev/null
+++ b/otp/src/switchboard/FriendManager.wsdl
@@ -0,0 +1,620 @@
+
+Switchboard Monitor
+
+:) " % self.getRowClassString()
+ else:
+ pageText = " \n"
+ else:
+ pageText += ":( "
+
+ pageText += "%s%s %s %d " % (prefix,name,prefix,name,uri.address,uri.port)
+
+ if healthy:
+ pageText += "Not responding \n"
+
+ return pageText
+
+ def orphanWedgeString(self,name,prefix):
+ uri = self.ns.resolve(prefix + name)
+ return " \n" % \
+ (self.getRowClassString(),prefix,name,uri.address,uri.port)
+
+ def nodeTable(self):
+ self.rowCount = 0
+ pageText = "? %s%s %s %d Orphaned \n
\n"
+ pageText += "\n\n"
+ pageText += "\n"
+ nodeList = self.ns.list(":sb.node")
+ for node in nodeList:
+ assert node[1] == 1,"whoa, someone put a subgroup in :sb.node!"
+ self.nodeNames.add(node[0])
+ pageText += self.serverStatusString(node[0],":sb.node.")
+
+ pageText += "? Name Host Port Notes
"
+ #pageText += "\n"
+ pageText += "\n
\n"
+ pageText += "\n\n"
+ wedgeList = self.ns.list(":sb.wedge")
+ for wedge in wedgeList:
+ assert wedge[1] == 1,"whoa, someone put a subgroup in :sb.wedge!"
+ if wedge[0] in self.nodeNames:
+ pageText += self.serverStatusString(wedge[0],":sb.wedge.")
+ else:
+ pageText+= self.orphanWedgeString(wedge[0],":sb.wedge.")
+
+ pageText += "? Name Host Port Notes
"
+ pageText += "\n"
+ pageText += "\n
\n"
+ pageText += "\n\n"
+ for table in tables:
+ pageText += "Table Name Engine Rows Data Length Avg Row Length \n" % (table['Name'],table['Engine'],table['Rows'],table['Data_length'],table['Avg_row_length'])
+
+ pageText += "%s %s %s %s %s
\n" % target + + if 1: + uri = self.ns.resolve(target) + proxy = Pyro.core.getProxyForURI(uri) + proxy._setTimeout(0.5) + pageText += proxy.getLogTail() + #except: + # pageText += "Error retrieving log!\n" + + pageText += "\n
\n" % node[0] + try: + stats = proxy.statCheck() + pageText += str(stats) + except: + pageText += "Unreachable!" + pageText += "\n
\n" % wedge[0] + try: + self.printlog.debug("Enter statCheck") + stats = proxy.statCheck() + pageText += str(stats) + except: + pageText += "Unreachable!" + pageText += "\n
\n" % target + + uri = self.ns.resolve(target) + proxy = Pyro.core.getProxyForURI(uri) + proxy._setTimeout(1) + pageText += str(proxy.statCheck()) + + pageText += "\n
'
+ self.setMagicWordResponse(response)
+
+ 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':
+ # We need to know if we're about to plan a party upon entering the
+ # teleportIn state of the Party.py fsm. Following the pet tutorial's
+ # lead by setting a boolean on the avatar.
+ base.localAvatar.aboutToPlanParty = True
+ base.localAvatar.creatingNewPartyWithMagicWord = False
+ elif action == 'new':
+ # Let's tell ourselves we're creating a new party, so we know what
+ # to do with the response
+ 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')
+ # We need to find the lowest populated shard and have the party there
+ curShardTuples = base.cr.listActiveShards()
+ # Tuple is of form : (shardId, name, pop, WelcomeValleyPopulation)
+ 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
+
+ if response is not None:
+ self.setMagicWordResponse(response)
+
+ def doCatalog(self, word):
+ """Handle the ~catalog magic word: manage catalogs. Most of
+ these are handled by the AI."""
+
+ args = word.split()
+
+
+ if len(args) == 1:
+ # No parameter. Handled by AI.
+ 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:
+ # Some other parameter. Handled by AI.
+ 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):
+ """requestTeleport(self, int hoodId, int zoneId, int avId)
+
+ The AI tells the avatar go somewhere. This is probably in
+ response to a magic word requesting transfer to a zone.
+ """
+ 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}])
+
+
+ def exit_rogues(self):
+ self.rogues.exit()
+ del self.rogues
+ self.rogues = None
+
+ def identifyDistributedObjects(self, name):
+ # Find the nearby distributed object or objects (of any type)
+ # with the given name. Returns a list of (name, obj) pairs.
+
+ 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):
+ # Decompose the string into keywords, and return the
+ # corresponding collision bitmask, suitable for passing to
+ # NodePath.showCS() or hideCS().
+ 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):
+ """Handle the ~bossBattle magic word: manage a final boss
+ battle."""
+
+ args = word.split()
+
+ # Find the particular Boss Cog that's in the same zone with
+ # the avatar.
+ bossCog = None
+ for distObj in self.cr.doId2do.values():
+ if isinstance(distObj, DistributedBossCog.DistributedBossCog):
+ bossCog = distObj
+ break
+
+ response = None
+ if len(args) == 1:
+ # No parameter: ignore (handled by AI).
+ pass
+
+ elif args[1] == "safe":
+ # ~bossBattle safe [flag]: Ignore hits to the toon during the pie
+ # scene. This acts as a toggle.
+ 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":
+ # ~bossBattle stun: stun all of the goons in the CFO battle.
+ bossCog.stunAllGoons()
+
+ elif args[1] == "destroy":
+ # ~bossBattle destroy: destroy all of the goons in the CFO battle.
+ bossCog.destroyAllGoons()
+
+ elif args[1] == "avatarEnter":
+ # ~bossBattle avatarEnter: run this if you get No Toons error.
+ bossCog.d_avatarEnter()
+ response = 'called d_avatarEnter'
+
+ if response:
+ self.setMagicWordResponse(response)
+
+
+ def doGolf(self, word):
+ """Handle the ~golf magic word for valid client side stuff."""
+ args = word.split()
+ response = None
+ if len(args) == 1:
+ # No parameter: ignore (handled by AI).
+ 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)
+
+ 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)
diff --git a/toontown/src/ai/ToontownMagicWordManagerAI.py b/toontown/src/ai/ToontownMagicWordManagerAI.py
new file mode 100644
index 0000000..45eef26
--- /dev/null
+++ b/toontown/src/ai/ToontownMagicWordManagerAI.py
@@ -0,0 +1,3619 @@
+# python imports
+import fpformat
+import string
+import time
+import random
+import datetime
+from sets import Set
+
+# panda3d imports
+from pandac.PandaModules import *
+from direct.showbase import PythonUtil
+from direct.task import Task
+
+# toontown imports
+from otp.ai.AIBaseGlobal import *
+from otp.ai.AIZoneData import AIZoneData
+from direct.distributed import DistributedObjectAI
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toon import InventoryBase
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import ToontownBattleGlobals
+from toontown.suit import DistributedSuitPlannerAI
+from toontown.battle import DistributedBattleBaseAI
+from toontown.toon import DistributedToonAI
+import WelcomeValleyManagerAI
+from toontown.hood import ZoneUtil
+from toontown.battle import SuitBattleGlobals
+from toontown.quest import Quests
+from toontown.minigame import MinigameCreatorAI
+from toontown.estate import DistributedPhoneAI
+from toontown.suit import DistributedBossCogAI
+from toontown.suit import DistributedSellbotBossAI
+from toontown.suit import DistributedCashbotBossAI
+from toontown.suit import DistributedLawbotBossAI
+from toontown.suit import DistributedBossbotBossAI
+from toontown.catalog import CatalogItemList
+from toontown.pets import PetTricks
+from toontown.suit import SuitDNA
+from toontown.toon import ToonDNA
+from toontown.toonbase import TTLocalizer
+from otp.ai import MagicWordManagerAI
+from toontown.estate import GardenGlobals
+from otp.otpbase import OTPGlobals
+from toontown.golf import GolfManagerAI
+from toontown.golf import GolfGlobals
+from toontown.parties import PartyGlobals
+from toontown.parties import PartyUtils
+from toontown.uberdog.DataStoreAIClient import DataStoreAIClient
+from toontown.uberdog import DataStoreGlobals
+
+if (simbase.wantKarts):
+ from toontown.racing.KartDNA import *
+
+class ToontownMagicWordManagerAI(MagicWordManagerAI.MagicWordManagerAI):
+ notify = DirectNotifyGlobal.directNotify.newCategory("ToontownMagicWordManagerAI")
+
+ GameAvatarClass = DistributedToonAI.DistributedToonAI
+
+ # is it a safezone?
+ Str2szId = {
+ 'ttc': ToontownGlobals.ToontownCentral,
+ 'tt': ToontownGlobals.ToontownCentral,
+ 'tc': ToontownGlobals.ToontownCentral,
+ 'dd': ToontownGlobals.DonaldsDock,
+ 'dg': ToontownGlobals.DaisyGardens,
+ 'mml': ToontownGlobals.MinniesMelodyland,
+ 'mm': ToontownGlobals.MinniesMelodyland,
+ 'br': ToontownGlobals.TheBrrrgh,
+ 'ddl': ToontownGlobals.DonaldsDreamland,
+ 'dl': ToontownGlobals.DonaldsDreamland,
+ }
+
+
+ def __init__(self, air):
+ MagicWordManagerAI.MagicWordManagerAI.__init__(self, air)
+ self.__bossBattleZoneId = [None, None, None, None]
+ self.__bossCog = [None, None, None, None]
+
+ def doMagicWord(self, word, av, zoneId, senderId):
+ def wordIs(w, word=word):
+ return word[:(len(w)+1)] == ('%s ' % w) or word == w
+
+ if (MagicWordManagerAI.MagicWordManagerAI.doMagicWord(self, word, av,
+ zoneId, senderId) == 1):
+ pass
+ elif word == "~allstuff":
+ av.inventory.maxOutInv()
+ av.d_setInventory(av.inventory.makeNetString())
+ self.notify.debug("Maxing out inventory for " + av.name)
+ elif word == "~nostuff":
+ av.inventory.zeroInv(1)
+ av.d_setInventory(av.inventory.makeNetString())
+ self.notify.debug("Zeroing inventory for " + av.name)
+ elif word == "~restock":
+ av.doRestock(1)
+ elif word == "~restockUber":
+ av.doRestock(0)
+
+ elif word == "~rich":
+ av.b_setMoney(av.maxMoney)
+ av.b_setBankMoney(av.maxBankMoney)
+ self.notify.debug(av.name + " is now rich")
+ elif word == "~poor":
+ av.b_setMoney(0)
+ av.b_setBankMoney(0)
+ self.notify.debug(av.name + " is now poor")
+ elif wordIs("~jelly"):
+ args = word.split()
+ if len(args) > 1:
+ count = int(args[1])
+ # this will just fill up the pocketbook,
+ # but wont add to the bank
+ av.b_setMoney(min(count, av.getMaxMoney()))
+ else:
+ av.b_setMoney(av.getMaxMoney())
+ elif wordIs("~bank"):
+ args = word.split()
+ if len(args) > 1:
+ count = int(args[1])
+ av.b_setBankMoney(count)
+ else:
+ av.b_setBankMoney(av.getMaxBankMoney())
+ elif wordIs("~maxBankMoney"):
+ args = word.split()
+ if len(args) > 1:
+ count = int(args[1])
+ av.b_setMaxBankMoney(count)
+ response = "Max bank money set to %s" % (av.getMaxBankMoney())
+ self.down_setMagicWordResponse(senderId, response)
+ else:
+ response = "Max bank money is %s" % (av.getMaxBankMoney())
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs("~pie"):
+ # Give ourselves a pie. Or four.
+ count = 0
+ type = None
+ args = word.split()
+ if len(args) == 1:
+ count = 1
+ for arg in args[1:]:
+ from toontown.toonbase import ToontownBattleGlobals
+ if arg in ToontownBattleGlobals.pieNames:
+ type = ToontownBattleGlobals.pieNames.index(arg)
+ else:
+ try:
+ count = int(arg)
+ except:
+ response = "Invalid pie argument: %s" % (arg)
+ self.down_setMagicWordResponse(senderId, response)
+ return
+
+ if type != None:
+ av.b_setPieType(type)
+ av.b_setNumPies(av.numPies + count)
+
+ elif word == "~amateur":
+ av.b_setTrackAccess([0, 0, 0, 0, 1, 1, 0])
+ av.b_setMaxCarry(20)
+ av.b_setQuestCarryLimit(1)
+ av.experience.zeroOutExp()
+ av.d_setExperience(av.experience.makeNetString())
+ av.b_setMaxHp(15)
+ av.b_setHp(15)
+ newInv = InventoryBase.InventoryBase(av)
+ newInv.maxOutInv()
+ av.inventory.setToMin(newInv.inventory)
+ av.d_setInventory(av.inventory.makeNetString())
+ self.notify.debug("Default exp for " + av.name)
+ elif word == "~amateur+":
+ av.experience.setAllExp(9)
+ av.d_setExperience(av.experience.makeNetString())
+ av.b_setMaxHp(30)
+ av.b_setHp(30)
+ # make sure we're not over maxProps too
+ newInv = InventoryBase.InventoryBase(av)
+ newInv.maxOutInv()
+ av.inventory.setToMin(newInv.inventory)
+ av.d_setInventory(av.inventory.makeNetString())
+ self.notify.debug("Setting exp to 9 for " + av.name)
+ elif word == "~professional":
+ av.b_setTrackAccess([1, 1, 1, 1, 1, 1, 1])
+ av.b_setMaxCarry(ToontownGlobals.MaxCarryLimit)
+ av.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit)
+ av.experience.maxOutExp()
+ av.d_setExperience(av.experience.makeNetString())
+ av.b_setMaxHp(ToontownGlobals.MaxHpLimit)
+ av.b_setHp(ToontownGlobals.MaxHpLimit)
+ self.notify.debug("Max exp for " + av.name)
+ elif word == "~professional--":
+ av.b_setTrackAccess([1, 1, 1, 1, 1, 1, 1])
+ av.b_setMaxCarry(ToontownGlobals.MaxCarryLimit)
+ av.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit)
+ av.experience.makeExpHigh()
+ av.d_setExperience(av.experience.makeNetString())
+ av.b_setMaxHp(ToontownGlobals.MaxHpLimit-15)
+ av.b_setHp(ToontownGlobals.MaxHpLimit-15)
+ self.notify.debug("High exp for " + av.name)
+ elif word == "~regularToon":
+ pickTrack = ([1, 1, 1, 1, 1, 1, 0],
+ [1, 1, 1, 0, 1, 1, 1],
+ [0, 1, 1, 1, 1, 1, 1],
+ [1, 0, 1, 1, 1, 1, 1])
+ av.b_setTrackAccess(random.choice(pickTrack))
+ av.b_setMaxCarry(ToontownGlobals.MaxCarryLimit)
+ av.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit)
+ av.experience.makeExpRegular()
+ av.d_setExperience(av.experience.makeNetString())
+ laughminus = int(random.random() * 20.0) + 10.0
+ av.b_setMaxHp(ToontownGlobals.MaxHpLimit-laughminus)
+ av.b_setHp(ToontownGlobals.MaxHpLimit-laughminus)
+ self.notify.debug("regular exp for " + av.name)
+ elif word == "~maxexp--":
+ av.b_setTrackAccess([1, 1, 1, 1, 1, 1, 1])
+ av.b_setMaxCarry(ToontownGlobals.MaxCarryLimit)
+ av.b_setQuestCarryLimit(ToontownGlobals.MaxQuestCarryLimit)
+ av.experience.maxOutExpMinusOne()
+ av.d_setExperience(av.experience.makeNetString())
+ av.b_setMaxHp(ToontownGlobals.MaxHpLimit)
+ av.b_setHp(ToontownGlobals.MaxHpLimit)
+ self.notify.debug("Max exp-- for " + av.name)
+ elif wordIs('~mintRaider'):
+ av.experience.maxOutExp()
+ av.d_setExperience(av.experience.makeNetString())
+ av.b_setQuestHistory([])
+ av.b_setRewardHistory(Quests.DL_TIER+2, [])
+ av.fixAvatar()
+ # 7LP for fishing, + 5LP for SellbotHQ, /2
+ av.b_setMaxHp(av.getMaxHp()+6)
+ av.b_setHp(av.getMaxHp())
+ trackAccess = [1, 1, 1, 1, 1, 1, 1]
+ trackAccess[random.choice((0, 1, 2, 3, 6))] = 0
+ av.b_setTrackAccess(trackAccess)
+
+ elif word[:4] == "~exp":
+ self.doExp(word, av, zoneId, senderId)
+
+ elif word[:7] == "~trophy":
+ self.doTrophy(word, av, zoneId, senderId)
+
+ elif word == "~trophies":
+ # Report the top 10 trophy holders.
+ scores = self.air.trophyMgr.getSortedScores()
+ response = ''
+ for i in range(min(len(scores), 10)):
+ score, avId = scores[i]
+ av = self.air.doId2do.get(avId, None)
+ if av:
+ avName = av.name
+ else:
+ avName = avId
+ response += '%s %s\n' % (score, avName)
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif word[:8] == "~effect ":
+ # Apply a cheesy rendering effect.
+ self.doCheesyEffect(word, av, zoneId, senderId)
+
+ elif word[:12] == "~cogTakeOver":
+ self.doCogTakeOver(word, av, zoneId, senderId)
+
+ elif wordIs("~cogdoTakeOver"):
+ if simbase.air.wantCogdominiums:
+ self.doCogdoTakeOver(word, av, zoneId, senderId)
+
+ elif word[:13] == "~toonTakeOver":
+ self.doToonTakeOver(word, av, zoneId, senderId)
+
+ elif word[:8] == "~welcome":
+ self.doWelcome(word, av, zoneId, senderId)
+
+ elif word == "~finishTutorial":
+ av.b_setTutorialAck(1)
+ av.b_setQuests([])
+ av.b_setQuestHistory([])
+ av.b_setRewardHistory(2, [])
+ av.fixAvatar()
+ self.down_setMagicWordResponse(senderId, "Finished tutorial.")
+
+ elif word == "~finishQuests":
+ self.air.questManager.completeAllQuestsMagically(av)
+ self.down_setMagicWordResponse(senderId, "Finished quests.")
+
+ elif word[:12] == "~finishQuest":
+ args = word.split()
+ index = int(args[1])
+ result = self.air.questManager.completeQuestMagically(av, index)
+ if result:
+ self.down_setMagicWordResponse(senderId, ("Finished quest %s." % (index)))
+ else:
+ self.down_setMagicWordResponse(senderId, ("Quest %s not found." % (index)))
+
+ elif word == "~clearQuests":
+ # Reset all quest fields as if this were a new toon
+ av.b_setQuests([])
+ av.b_setQuestHistory([])
+ currentTier = av.getRewardTier()
+ av.b_setRewardHistory(currentTier, [])
+ self.down_setMagicWordResponse(senderId, "Cleared quests.")
+
+ elif word == "~getQuestTier":
+ # Report the current quest tier
+ response = "tier %d" % (av.getRewardTier())
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif word[:13] == "~setQuestTier":
+ # Sets reward tier and optionally index
+ args = word.split()
+ tier = int(args[1])
+ tier = min(tier, Quests.getNumTiers())
+ av.b_setQuestHistory([])
+ av.b_setRewardHistory(tier, [])
+ av.fixAvatar()
+
+ elif word[:12] == "~assignQuest":
+ # Intelligently assigns a quest
+ args = word.split()
+ questId = int(args[1])
+
+ # Make sure this quest exists
+ questDesc = Quests.QuestDict.get(questId)
+ if questDesc is None:
+ self.down_setMagicWordResponse(senderId, "Quest %s not found" % (questId))
+ return
+
+ # Make sure the av is in that tier
+ avTier = av.getRewardTier()
+ tier = questDesc[Quests.QuestDictTierIndex]
+ if tier != avTier:
+ self.down_setMagicWordResponse(senderId, "Avatar not in that tier: %s. You can ~setQuestTier %s, if you want." % (tier, tier))
+ return
+
+ # Make sure the av has room for this quest
+ if not self.air.questManager.needsQuest(av):
+ self.down_setMagicWordResponse(senderId, "Quests are already full")
+ return
+
+ # Make sure the av does not already have this quest
+ for questDesc in av.quests:
+ if questId == questDesc[0]:
+ self.down_setMagicWordResponse(senderId, "Already has quest: %s" % (questId))
+ return
+
+ # Should we check your reward history too?
+
+ fromNpcId = Quests.ToonHQ # A reasonable default
+
+ rewardId = Quests.getQuestReward(questId, av)
+ # Some quests do not have a reward specified. Instead they have
+ # the keyword which tells the quest system to try to
+ # match something up. In our case, we are not going through
+ # normal channels, so just pick some reward so things do not
+ # crash. How about some jellybeans?
+ if rewardId == Quests.Any:
+ # Just give 100 jellybeans (rewardId = 604 from Quests.py)
+ rewardId = 604
+
+ toNpcId = Quests.getQuestToNpcId(questId)
+ # Account for some trickery in the quest description
+ # If the toNpcId is marked or let's just use ToonHQ
+ if toNpcId == Quests.Any:
+ toNpcId = Quests.ToonHQ
+ elif toNpcId == Quests.Same:
+ toNpcId = Quests.ToonHQ
+
+ startingQuest = Quests.isStartingQuest(questId)
+ self.air.questManager.assignQuest(av.doId,
+ fromNpcId,
+ questId,
+ rewardId,
+ toNpcId,
+ startingQuest,
+ )
+ self.down_setMagicWordResponse(senderId, "Quest %s assigned" % (questId))
+
+ elif wordIs("~nextQuest"):
+ # forces NPCs to offer you a particular quest
+ args = word.split()
+ if len(args) == 1:
+ # clear any existing request
+ questId = self.air.questManager.cancelNextQuest(av.doId)
+ if questId:
+ self.down_setMagicWordResponse(
+ senderId, "Cancelled request for quest %s" % (questId))
+ return
+
+ questId = int(args[1])
+
+ # Make sure this quest exists
+ questDesc = Quests.QuestDict.get(questId)
+ if questDesc is None:
+ self.down_setMagicWordResponse(
+ senderId, "Quest %s not found" % (questId))
+ return
+
+ # Make sure the av is in that tier
+ avTier = av.getRewardTier()
+ tier = questDesc[Quests.QuestDictTierIndex]
+ if tier != avTier:
+ self.down_setMagicWordResponse(senderId, "Avatar not in that tier: %s. You can ~setQuestTier %s, if you want." % (tier, tier))
+ return
+
+ # Make sure the av does not already have this quest
+ for questDesc in av.quests:
+ if questId == questDesc[0]:
+ self.down_setMagicWordResponse(
+ senderId, "Already has quest: %s" % (questId))
+ return
+
+ self.air.questManager.setNextQuest(av.doId, questId)
+ self.down_setMagicWordResponse(senderId,
+ "Quest %s queued" % (questId))
+
+ elif word == "~visitHQ":
+ # Sets quests to return to HQ Officer instead of whomever.
+ # Saves walking all over the map to test quests.
+ for quest in av.quests:
+ quest[2] = Quests.ToonHQ
+ av.b_setQuests(av.quests)
+
+ elif wordIs('~teleportAll'):
+ av.b_setHoodsVisited(ToontownGlobals.HoodsForTeleportAll)
+ av.b_setTeleportAccess(ToontownGlobals.HoodsForTeleportAll)
+
+ elif word[:10] == "~buildings":
+ self.doBuildings(word, av, zoneId, senderId)
+
+ elif word[:16] == "~buildingPercent":
+ self.doBuildingPercent(word, av, zoneId, senderId)
+
+ elif wordIs('~call'):
+ self.doCall(word, av, zoneId, senderId)
+
+ elif wordIs('~battle'):
+ self.doBattle(word, av, zoneId, senderId)
+
+ elif word[:5] == "~cogs":
+ self.doCogs(word, av, zoneId, senderId)
+
+ # Suit Invasions
+ elif word[:12] == "~getInvasion":
+ self.getCogInvasion(word, av, zoneId, senderId)
+ elif word[:14] == "~startInvasion":
+ self.doCogInvasion(word, av, zoneId, senderId)
+ elif word[:13] == "~stopInvasion":
+ self.stopCogInvasion(word, av, zoneId, senderId)
+
+ # Fireworks
+ elif word[:18] == "~startAllFireworks":
+ self.startAllFireworks(word, av, zoneId, senderId)
+ elif word[:15] == "~startFireworks":
+ self.startFireworks(word, av, zoneId, senderId)
+ elif word[:14] == "~stopFireworks":
+ self.stopFireworks(word, av, zoneId, senderId)
+ elif word[:17] == "~stopAllFireworks":
+ self.stopAllFireworks(word, av, zoneId, senderId)
+
+ elif word == "~save":
+ # Save the AI state. Presently, this is just the set of
+ # buildings.
+ self.air.saveBuildings()
+
+ response = "Building state saved."
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif word[:9] == "~minigame":
+ self.doMinigame(word, av, zoneId, senderId)
+
+ elif wordIs('~factory'):
+ # select which factory to enter
+ # AI-global for now
+ from toontown.coghq import FactoryManagerAI
+ args = word.split()
+ if len(args) == 1:
+ # no arguments given
+ if FactoryManagerAI.FactoryManagerAI.factoryId is None:
+ self.down_setMagicWordResponse(
+ senderId, "usage: ~factory [id]")
+ else:
+ fId = FactoryManagerAI.FactoryManagerAI.factoryId
+ FactoryManagerAI.FactoryManagerAI.factoryId = None
+ self.down_setMagicWordResponse(
+ senderId, "cancelled request for factory %s" % fId)
+ else:
+ factoryId = int(args[1])
+ if not factoryId in ToontownGlobals.factoryId2factoryType:
+ self.down_setMagicWordResponse(
+ senderId,
+ "unknown factory '%s'" % factoryId)
+ else:
+ FactoryManagerAI.FactoryManagerAI.factoryId = factoryId
+ self.down_setMagicWordResponse(
+ senderId,
+ "selected factory %s" % factoryId)
+
+ elif wordIs('~mintId'):
+ args = word.split()
+ postName = 'mintId-%s' % av.doId
+ if len(args) < 2:
+ if bboard.has(postName):
+ bboard.remove(postName)
+ response = 'cleared mint request'
+ else:
+ response = '~mintId id'
+ else:
+ try:
+ id = int(args[1])
+ # make sure it's a valid id
+ foo = ToontownGlobals.MintNumRooms[id]
+ except:
+ response = 'bad mint id: %s' % args[1]
+ else:
+ bboard.post(postName, id)
+ response = 'selected mint %s' % id
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~mintFloor'):
+ args = word.split()
+ postName = 'mintFloor-%s' % av.doId
+ if len(args) < 2:
+ if bboard.has(postName):
+ bboard.remove(postName)
+ response = 'cleared mint floor request'
+ else:
+ response = '~mintFloor num'
+ else:
+ try:
+ floor = int(args[1])
+ except:
+ response = 'bad floor index: %s' % args[1]
+ else:
+ bboard.post(postName, floor)
+ response = 'selected floor %s' % floor
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~mintRoom'):
+ args = word.split()
+ postName = 'mintRoom-%s' % av.doId
+ if len(args) < 2:
+ if bboard.has(postName):
+ bboard.remove(postName)
+ response = 'cleared mint room request'
+ else:
+ response = '~mintRoom '
+ else:
+ from toontown.coghq import MintRoomSpecs
+ id = None
+ name = None
+ try:
+ id = int(args[1])
+ name = MintRoomSpecs.CashbotMintRoomId2RoomName[id]
+ except:
+ if args[1] in MintRoomSpecs.CashbotMintRoomName2RoomId:
+ name = args[1]
+ id = MintRoomSpecs.CashbotMintRoomName2RoomId[name]
+ else:
+ response = 'invalid room: %s' % args[1]
+ bboard.post(postName, id)
+ response = 'selected mint room %s: %s' % (id, name)
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~stageRoom'):
+ args = word.split()
+ postName = 'stageRoom-%s' % av.doId
+ if len(args) < 2:
+ if bboard.has(postName):
+ bboard.remove(postName)
+ response = 'cleared stage room request'
+ else:
+ response = '~stageRoom '
+ else:
+ from toontown.coghq import StageRoomSpecs
+ id = None
+ name = None
+ try:
+ id = int(args[1])
+ name = StageRoomSpecs.CashbotStageRoomId2RoomName[id]
+ except:
+ if args[1] in StageRoomSpecs.CashbotStageRoomName2RoomId:
+ name = args[1]
+ id = StageRoomSpecs.CashbotStageRoomName2RoomId[name]
+ else:
+ response = 'invalid room: %s' % args[1]
+ bboard.post(postName, id)
+ response = 'selected stage room %s: %s' % (id, name)
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs("~autoRestock"):
+ args = word.split()
+ postName = 'autoRestock-%s' % av.doId
+ enable = not bboard.get(postName, 0)
+ # are they explicitly setting the state?
+ if len(args) > 1:
+ try:
+ enable = int(args[1])
+ except:
+ self.down_setMagicWordResponse(senderId,
+ 'invalid state flag: %s' %
+ args[1])
+ return
+ if enable:
+ state = 'ON'
+ bboard.post(postName, 1)
+ else:
+ state = 'OFF'
+ bboard.remove(postName)
+ self.down_setMagicWordResponse(senderId, 'autoRestock %s' % state)
+
+ elif wordIs("~resistanceRestock"):
+ from toontown.chat import ResistanceChat
+ args = word.split()
+ if len(args) < 2:
+ charges = 10
+ else:
+ charges = int(args[1])
+ msgs = []
+ for menuIndex in ResistanceChat.resistanceMenu:
+ for itemIndex in ResistanceChat.getItems(menuIndex):
+ textId = ResistanceChat.encodeId(menuIndex, itemIndex)
+ msgs.append([textId, charges])
+ av.b_setResistanceMessages(msgs)
+ response = 'Resistance phrases restocked - %d charges' % charges
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs("~restockSummons"):
+ numSuits = len(SuitDNA.suitHeadTypes)
+ fullSetForSuit = 0x01 | 0x02 | 0x04
+ allSummons = numSuits * [fullSetForSuit]
+ av.b_setCogSummonsEarned(allSummons)
+ self.down_setMagicWordResponse(senderId, "Summons restocked")
+
+ elif wordIs("~clearSummons"):
+ numSuits = len(SuitDNA.suitHeadTypes)
+ allSummons = numSuits * [0]
+ av.b_setCogSummonsEarned(allSummons)
+ self.down_setMagicWordResponse(senderId, "Summons emptied")
+
+ elif wordIs("~newSummons"):
+ args = word.split()
+
+ level = None
+ if len(args) > 1:
+ level = int(args[1])
+
+ if level and (level < 0 or level >= SuitDNA.suitsPerDept):
+ self.down_setMagicWordResponse(
+ senderId, "usage: ~newSummons [level:0-11]")
+ else:
+ (suitIndex, type) = av.assignNewCogSummons(level)
+ suitName = SuitDNA.suitHeadTypes[suitIndex]
+ suitFullName = SuitBattleGlobals.SuitAttributes[suitName]['name']
+ self.down_setMagicWordResponse(
+ senderId, "%s summons added for %s" % (type, suitFullName))
+
+ elif wordIs("~treasures"):
+ self.doTreasures(word, av, zoneId, senderId)
+
+ elif wordIs("~emote"):
+ self.doEmotes(word, av, zoneId, senderId)
+
+ elif wordIs("~catalog"):
+ self.doCatalog(word, av, zoneId, senderId)
+
+ elif word == "~resetFurniture":
+ house = None
+ if av.houseId:
+ house = self.air.doId2do.get(av.houseId)
+ if house:
+ house.setInitialFurniture()
+ house.resetFurniture()
+ response = "Furniture reset."
+ else:
+ response = "Could not find house."
+ self.down_setMagicWordResponse(senderId, response)
+
+ # Fishing
+ elif word == "~clearFishTank":
+ av.b_setFishTank([], [], [])
+ self.down_setMagicWordResponse(senderId, "Cleared fish tank.")
+
+ elif word == "~sellFishTank":
+ # Add up the total value of all the fish
+ totalValue = 0
+ totalValue = av.fishTank.getTotalValue()
+ # Credit the money
+ av.addMoney(totalValue)
+ # Clear the fish tank
+ av.b_setFishTank([], [], [])
+ # Feedback to sender
+ self.down_setMagicWordResponse(senderId, ("Sold fish in tank for %s jellbeans." % totalValue))
+
+ elif word == "~clearFishCollection":
+ av.b_setFishCollection([], [], [])
+ av.b_setFishingTrophies([])
+ self.down_setMagicWordResponse(senderId, "Cleared fish collection.")
+
+ elif word == "~completeFishCollection":
+ from toontown.fishing import FishGlobals
+ genusList = []
+ speciesList = []
+ weightList = []
+ for genus in FishGlobals.getGenera():
+ numSpecies = len(FishGlobals.getSpecies(genus))
+ for species in range(numSpecies):
+ weight = FishGlobals.getRandomWeight(genus, species)
+ genusList.append(genus)
+ speciesList.append(species)
+ weightList.append(weight)
+
+ av.b_setFishCollection(genusList, speciesList, weightList)
+ self.down_setMagicWordResponse(senderId, "Complete fish collection.")
+
+ elif word == "~randomFishTank":
+ av.makeRandomFishTank()
+ self.down_setMagicWordResponse(senderId, "Created random fishtank")
+
+ elif word == "~allFishingTrophies":
+ from toontown.fishing import FishGlobals
+ allTrophyList = FishGlobals.TrophyDict.keys()
+ av.b_setFishingTrophies(allTrophyList)
+ self.down_setMagicWordResponse(senderId, "All fishing trophies")
+
+ elif word[:4] == "~rod":
+ from toontown.fishing import FishGlobals
+ # Sets reward tier and optionally index
+ args = word.split()
+ rodId = int(args[1])
+ if ((rodId > FishGlobals.MaxRodId) or
+ (rodId < 0)):
+ self.down_setMagicWordResponse(senderId, ("Invalid rod: %s" % (rodId)))
+ else:
+ av.b_setFishingRod(rodId)
+ self.down_setMagicWordResponse(senderId, ("New fishing rod: %s" % (rodId)))
+
+
+ elif wordIs('~inGameEdit'):
+ # edit a level in the game engine
+ # Note - do not call this as a toon - call ~edit instead. That
+ # will automatically append your level doId so the AI knows
+ # which one you want to edit, along with your edit username
+
+ # make sure this AI is set up for editing
+ if not __dev__:
+ self.down_setMagicWordResponse(senderId,
+ "AI not running in dev mode")
+ return
+ from otp.level import EditorGlobals
+ msg = EditorGlobals.checkNotReadyToEdit()
+ if msg is not None:
+ self.down_setMagicWordResponse(senderId, msg)
+ return
+
+ args = word.split()
+ levelDoId = int(args[1])
+ editUsername = args[2]
+
+ level = self.air.doId2do.get(levelDoId)
+ if not level:
+ self.down_setMagicWordResponse(
+ senderId, ("Level %s not found" % levelDoId))
+ return
+
+ from toontown.coghq import DistributedInGameEditorAI
+ editor = DistributedInGameEditorAI.DistributedInGameEditorAI(
+ self.air, level, senderId, editUsername)
+
+ self.down_setMagicWordResponse(
+ senderId, ("Editing level %s as %s" % (
+ levelDoId, editUsername)))
+
+ elif word[:13] == "~setNPCFriend":
+ args = word.split()
+ npcId = int(args[1])
+ numCalls = int(args[2])
+ if self.doNpcFriend(av, npcId, numCalls):
+ self.down_setMagicWordResponse(senderId, "added NPC friend")
+ else:
+ self.down_setMagicWordResponse(senderId, "invalid NPC name")
+
+ elif wordIs("~pianos"):
+ if self.doNpcFriend(av, 1116, 100):
+ self.down_setMagicWordResponse(senderId, "got pianos")
+ else:
+ self.down_setMagicWordResponse(senderId, "error getting pianos")
+
+ elif wordIs("~resetNPCFriendsDict"):
+ av.resetNPCFriendsDict()
+ self.down_setMagicWordResponse(senderId, "Reset NPC Friends Dict")
+
+ elif wordIs('~uberDrop'):
+ from toontown.toon import NPCToons
+ if (av.attemptAddNPCFriend(
+ 1116, DistributedToonAI.DistributedToonAI.maxCallsPerNPC
+ ) == 1):
+ self.down_setMagicWordResponse(senderId, "added NPC friend")
+ else:
+ self.down_setMagicWordResponse(senderId, "failed")
+
+ elif wordIs("~bossBattle"):
+ self.doBossBattle(word, av, zoneId, senderId)
+
+ elif wordIs('~disguisePage'):
+ args = word.split()
+ flag = 1
+ if len(args) >= 2:
+ flag = int(args[1])
+ av.b_setDisguisePageFlag(flag)
+ self.down_setMagicWordResponse(senderId, "Disguise page = %s" % (flag))
+
+ elif wordIs("~allParts"):
+ from toontown.coghq import CogDisguiseGlobals
+ args = word.split()
+ for dept in self.getDepts(args):
+ parts = av.getCogParts()
+ parts[dept] = CogDisguiseGlobals.PartsPerSuitBitmasks[dept]
+
+ av.b_setCogParts(parts)
+ self.down_setMagicWordResponse(senderId, "Set cog parts: %s" % (parts))
+
+ elif wordIs("~noParts"):
+ args = word.split()
+ for dept in self.getDepts(args):
+ parts = av.getCogParts()
+ parts[dept] = 0
+
+ av.b_setCogParts(parts)
+ self.down_setMagicWordResponse(senderId, "Set cog parts: %s" % (parts))
+
+ elif wordIs("~part"):
+ args = word.split()
+ depts = self.getDepts(args)
+ if len(args) > 1:
+ # trust that user typed the factory type correctly...
+ factoryType = args[1]
+ else:
+ factoryType = ToontownGlobals.FT_FullSuit
+
+ for dept in depts:
+ av.giveGenericCogPart(factoryType, dept)
+
+ self.down_setMagicWordResponse(senderId, "Set cog parts: %s" % (av.getCogParts()))
+
+ elif wordIs("~merits"):
+ args = word.split()
+ depts = self.getDepts(args)
+ if len(args) > 1:
+ numMerits = int(args[1])
+ if numMerits > 32767:
+ numMerits = 32767
+ else:
+ self.down_setMagicWordResponse(senderId, "Specify number of merits to set.")
+ return
+
+ merits = av.getCogMerits()[:]
+ for dept in depts:
+ merits[dept] = numMerits
+ av.b_setCogMerits(merits)
+
+ self.down_setMagicWordResponse(senderId, "Set cog merits: %s" % (merits))
+
+ elif wordIs("~promote"):
+ args = word.split()
+ depts = self.getDepts(args)
+
+ for dept in depts:
+ av.b_promote(dept)
+
+ self.down_setMagicWordResponse(senderId, "Set cogTypes: %s and cogLevels: %s" % (av.getCogTypes(), av.getCogLevels()))
+
+ elif wordIs("~cogSuit"):
+ args = word.split()
+ if len(args) > 1:
+ cogType = args[1]
+ else:
+ self.down_setMagicWordResponse(senderId, "Specify cog type, or 'clear'.")
+ return
+
+ if cogType == 'clear':
+ av.b_setCogTypes([0, 0, 0, 0])
+ av.b_setCogLevels([0, 0, 0, 0])
+
+ elif cogType == 'on':
+ if len(args) > 2 and args[2] in SuitDNA.suitDepts:
+ dept = SuitDNA.suitDepts.index(args[2])
+ av.b_setCogIndex(dept)
+ else:
+ self.down_setMagicWordResponse(senderId, "Specify dept.")
+ return
+
+ elif cogType == 'off':
+ av.b_setCogIndex(-1)
+ return
+
+ else:
+ dept = SuitDNA.getSuitDept(cogType)
+ if dept == None:
+ self.down_setMagicWordResponse(senderId, "Unknown cog type: %s" % (cogType))
+ return
+
+ deptIndex = SuitDNA.suitDepts.index(dept)
+ type = SuitDNA.getSuitType(cogType)
+ minLevel = SuitBattleGlobals.SuitAttributes[cogType]['level']
+
+ # determine max level (usually minLevel + 4, but 50 for last cog)
+ if type >= (SuitDNA.suitsPerDept - 1):
+ maxLevel = ToontownGlobals.MaxCogSuitLevel + 1
+ else:
+ maxLevel = minLevel + 4
+
+ if len(args) > 2:
+ level = int(args[2]) - 1
+ if level < minLevel or level > maxLevel:
+ self.down_setMagicWordResponse(senderId, "Invalid level for %s (should be %s to %s)" % (cogType, minLevel + 1, minLevel + 5))
+ return
+ else:
+ level = minLevel
+
+ cogTypes = av.getCogTypes()[:]
+ cogLevels = av.getCogLevels()[:]
+ cogTypes[deptIndex] = type - 1
+ cogLevels[deptIndex] = level
+ av.b_setCogTypes(cogTypes)
+ av.b_setCogLevels(cogLevels)
+
+ self.down_setMagicWordResponse(senderId, "Set cogTypes: %s and cogLevels: %s" % (av.getCogTypes(), av.getCogLevels()))
+
+ elif wordIs("~pinkSlips"):
+ args = word.split()
+ depts = self.getDepts(args)
+ if len(args) > 1:
+ numSlips = int(args[1])
+ if numSlips > 255:
+ numSlips = 255
+ else:
+ self.down_setMagicWordResponse(senderId, "Specify number of pinkSlips to set.")
+ return
+
+ av.b_setPinkSlips(numSlips)
+
+ self.down_setMagicWordResponse(senderId, "Set PinkSlips: %s" % (numSlips))
+
+
+ elif wordIs("~setPaid"):
+ args = word.split()
+ depts = self.getDepts(args)
+ if len(args) > 1:
+ paid = int(args[1])
+ if paid:
+ paid = 1
+ av.b_setAccess(OTPGlobals.AccessFull)
+ else:
+ paid = 0
+ av.b_setAccess(OTPGlobals.AccessVelvetRope)
+ else:
+ self.down_setMagicWordResponse(senderId, "0 for unpaid 1 for paid")
+ return
+
+ self.down_setMagicWordResponse(senderId, "setPaid: %s" % (paid))
+
+
+
+ elif wordIs("~holiday"):
+ # Start or stop a holiday
+ holiday = 5
+ fStart = 1
+ args = word.split()
+ if len(args) == 1:
+ self.down_setMagicWordResponse(
+ senderId, "Usage:\n~holiday id\n~holiday id start\n~holiday id end\n~holiday list")
+ return
+ elif len(args) > 1:
+ if args[1] == 'list':
+ self.down_setMagicWordResponse(
+ senderId,
+ "1: July 4\n2: New Years\n3: Halloween\n4: Winter Decorations\n5: Skelecog Invades\n6: Mr. Holly Invades\n7: Fish Bingo\n8: Species Election\n9: Black Cat\n10: Resistance Event\n11: Reset Daily Recs\n12: Reset Weekly Recs\n13: Trick-or-Treat\n14: Grand Prix\n17: Trolley Metagame")
+ return
+ else:
+ holiday = int(args[1])
+ doPhase = None
+ stopForever = False
+ if len(args) > 2:
+ if args[2] == 'start':
+ fStart = 1
+ elif args[2] == 'end':
+ fStart = 0
+ if len(args) > 3:
+ if args[3] == 'forever':
+ stopForever = True
+ elif args[2] == 'phase':
+ if len(args) >3 :
+ doPhase = args[3]
+ else:
+ self.down_setMagicWordResponce(senderId,"need a number after phase")
+ else:
+ self.down_setMagicWordResponse(
+ senderId, 'Arg 2 should be "start" or "end" or "end forever" or "phase"')
+ return
+ if doPhase:
+ result = self.air.holidayManager.forcePhase(holiday, doPhase)
+ self.down_setMagicWordResponse(senderId, "succeeded=%s forcing holiday %d to phase %s" %(result,holiday,doPhase))
+ elif fStart:
+ self.down_setMagicWordResponse(
+ senderId, "Starting holiday %d" % holiday)
+ self.air.holidayManager.startHoliday(holiday)
+ else:
+ self.down_setMagicWordResponse(
+ senderId, "Ending holiday %d stopForever=%s" % (holiday, stopForever))
+ self.air.holidayManager.endHoliday(holiday, stopForever)
+
+ elif wordIs('~pet') and simbase.wantPets:
+ def summonPet(petId, callback=None, zoneId=zoneId):
+ def handleGetPet(success, pet, petId=petId, zoneId=zoneId):
+ if success:
+ pet.generateWithRequiredAndId(petId, self.air.districtId, zoneId)
+ if callback is not None:
+ callback(success, pet)
+ self.air.petMgr.getPetObject(petId, handleGetPet)
+
+ petId = av.getPetId()
+ if petId == 0:
+ def handleCreate(success, petId, zoneId=zoneId):
+ if success:
+ self.air.petMgr.assignPetToToon(petId, av.doId)
+ def handlePetGenerated(success, pet, avId=av.doId,
+ zoneId=zoneId):
+ if success:
+ pet._initDBVals(
+ avId,
+ traitSeed=PythonUtil.randUint31())
+ pet.sendSetZone(zoneId)
+ pet.delete()
+ # since this is the first time the pet is being
+ # created, and we're going to be setting properties
+ # on the pet, generate it in the Quiet zone first,
+ # then move it to the requested zone.
+ summonPet(petId, callback=handlePetGenerated,
+ zoneId=ToontownGlobals.QuietZone)
+ self.air.petMgr.createNewPetObject(handleCreate)
+ response = 'creating new pet...'
+ else:
+ if petId in self.air.doId2do:
+ response = 'pet %s already active' % petId
+ else:
+ summonPet(petId)
+ response = 'summoning pet %s...' % petId
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~dismiss') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ pet.requestDelete()
+ response = "pet %s dismissed" % pet.doId
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~deletePet') and simbase.wantPets:
+ petId = av.getPetId()
+ if petId == 0:
+ response = "don't have a pet"
+ else:
+ # if the pet is active, dismiss it
+ pet = self.air.doId2do.get(petId)
+ if pet is not None:
+ pet.requestDelete()
+ self.air.petMgr.deleteToonsPet(av.doId)
+ response = "deleted pet %s" % petId
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~petName') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ args = word.split()
+ if len(args) < 2:
+ response = "~petName name"
+ else:
+ pet.b_setPetName(args[1])
+ response = 'name changed'
+ self.down_setMagicWordResponse(senderId, response)
+
+
+ elif wordIs('~feed') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ pet.feed(av)
+ response = "fed pet"
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~attend') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ pet.scratch(av)
+ response = "attended pet"
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~callPet') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ pet.call(av)
+ response = "called pet"
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~stay') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ pet.handleStay(av)
+ response = "Stay, %s." % pet.getPetName()
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~shoo') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ pet.handleShoo(av)
+ response = "Shoo, %s." % pet.getPetName()
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~maxMood') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ pet.maxMood()
+ response = "pet mood maxed"
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~minMood') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ pet.minMood()
+ response = "pet mood minimized"
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~petMood') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ args = word.split()
+ if len(args) < 3:
+ response = '~petMood component value'
+ else:
+ from toontown.pets import PetMood
+ comp = args[1]
+ value = float(args[2])
+ if comp not in PetMood.PetMood.Components:
+ response = "unknown mood '%s'" % comp
+ else:
+ pet.mood.setComponent(comp, value)
+ response = "mood set"
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~moodTimescale') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ args = word.split()
+ if len(args) < 2:
+ response = '~moodTimescale timescale'
+ else:
+ simbase.petMoodTimescale = float(args[1])
+ # make the new timescale take effect immediately
+ pet.mood._driftMoodTask()
+ response = 'pet mood timescale = %s' % args[1]
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~typicalTraits') and simbase.wantPets:
+ szId = None
+ args = word.split()
+ if len(args) > 1:
+ szId = self.Str2szId.get(args[1])
+ if szId == None:
+ szId = ToontownGlobals.ToontownCentral
+
+ pet, response = self.getPet(av)
+ if pet:
+ pet._setTypicalTraits(szId)
+ # delete him to make sure we don't have a pet whose
+ # internal state is out-of-sync
+ pet.requestDelete()
+ response = 'set typical traits for %s' % szId
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~medianTraits') and simbase.wantPets:
+ szId = None
+ args = word.split()
+ if len(args) > 1:
+ szId = self.Str2szId.get(args[1])
+ if szId == None:
+ szId = ToontownGlobals.ToontownCentral
+
+ pet, response = self.getPet(av)
+ if pet:
+ pet._setMedianTraits(szId)
+ # delete him to make sure we don't have a pet whose
+ # internal state is out-of-sync
+ pet.requestDelete()
+ response = 'set median traits for %s' % szId
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~lowTraits') and simbase.wantPets:
+ szId = None
+ args = word.split()
+ if len(args) > 1:
+ szId = self.Str2szId.get(args[1])
+ if szId == None:
+ szId = ToontownGlobals.ToontownCentral
+
+ pet, response = self.getPet(av)
+ if pet:
+ pet._setLowTraits(szId)
+ # delete him to make sure we don't have a pet whose
+ # internal state is out-of-sync
+ pet.requestDelete()
+ response = 'set low traits for %s' % szId
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~highTraits') and simbase.wantPets:
+ szId = None
+ args = word.split()
+ if len(args) > 1:
+ szId = self.Str2szId.get(args[1])
+ if szId == None:
+ szId = ToontownGlobals.ToontownCentral
+
+ pet, response = self.getPet(av)
+ if pet:
+ pet._setHighTraits(szId)
+ # delete him to make sure we don't have a pet whose
+ # internal state is out-of-sync
+ pet.requestDelete()
+ response = 'set high traits for %s' % szId
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~leash') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ response = pet.toggleLeash(av.doId)
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~lockPet') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ if pet.isLockedDown():
+ response = 'pet already locked down'
+ else:
+ pet.lockPet()
+ response = 'pet locked down'
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~unlockPet') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ if not pet.isLockedDown():
+ response = 'pet already not locked down'
+ else:
+ pet.unlockPet()
+ response = 'pet no longer locked down'
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~attendPet') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ response = 'attendPet'
+ pet.handleAvPetInteraction(4, av.getDoId())
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~feedPet') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ response = 'feedPet'
+ pet.handleAvPetInteraction(3, av.getDoId())
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~petStress') and simbase.wantPets:
+ from toontown.pets import DistributedPetAI
+ args = word.split()
+ numPets = 56 # 50 friends, six Toons/acct #100 * 3 # 500 toons, 100 at estate, 3 pets per
+ if len(args) > 1:
+ numPets = int(args[1])
+ for i in xrange(numPets):
+ pet = DistributedPetAI.DistributedPetAI(self.air)
+ # make up a fake owner doId
+ pet._initFakePet(100+i, 'StressPet%s' % i)
+ pet.generateWithRequired(zoneId)
+ pet.setPos(randFloat(-30, 30),
+ randFloat(-30, 30), 0)
+ pet.b_setParent(ToontownGlobals.SPRender)
+
+ elif wordIs('~petTricks') and simbase.wantPets:
+ from toontown.pets import PetConstants, PetTricks
+ args = word.split()
+ trickIds = []
+ invalid = 0
+ for num in args[1:]:
+ id = int(num)
+ if id not in PetTricks.Tricks:
+ invalid = 1
+ response = 'invalid trick ID: %s' % id
+ break
+ trickIds.append(id)
+ if not invalid:
+ av.b_setPetTrickPhrases(trickIds)
+ response = 'set pet trick phrases: %s' % trickIds
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~trickAptitude') and simbase.wantPets:
+ pet, response = self.getPet(av)
+ if pet:
+ args = word.split()
+ if len(args) < 2:
+ response = '~trickAptitude aptitude'
+ else:
+ from toontown.pets import PetTricks
+ pet.b_setTrickAptitudes([float(args[1])] *
+ (len(PetTricks.Tricks)-1))
+ response = 'trick aptitude = %s' % args[1]
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~spamTrick'):
+ # spam the nearby pets with N commands to do the 'jump' trick
+ from toontown.pets import PetObserve
+ for i in xrange(20):
+ PetObserve.send(av.zoneId,
+ PetObserve.getSCObserve(21200, av.doId))
+
+ elif wordIs('~blackCat'):
+ response = 'done'
+ error = av.makeBlackCat()
+ if error:
+ response = error
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~bingoCheat') and simbase.wantBingo:
+ av.bingoCheat = not av.bingoCheat
+ if av.bingoCheat:
+ response = "Bingo cheating turned on. You will always catch boots."
+ else:
+ response = "Bingo cheating turned off"
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif __dev__ and wordIs('~airender'):
+ showZone = zoneId
+ args = word.split()
+ if len(args) > 1:
+ showZone = int(args[1])
+ from otp.ai import ShowBaseAI
+ base = ShowBaseAI.ShowBaseAI('zone %s' % showZone)
+ base.zoneData = AIZoneData(self.air.districtId, showZone)
+ render = base.zoneData.getRender()
+ base.camNode.setScene(render)
+ base.camera.reparentTo(render)
+ render.showCS()
+ axis = loader.loadModel('models/misc/xyzAxis')
+ axis.reparentTo(render)
+ axis.setPosHpr(render, 0, 0, 0, 0, 0, 0)
+ self.down_setMagicWordResponse(
+ senderId, 'rendering AI zone %s' % showZone)
+
+ elif __dev__ and wordIs("~aics"):
+ showZone = zoneId
+ args = word.split()
+ if len(args) > 1:
+ showZone = int(args[1])
+ zoneData = AIZoneData(self.air.districtId, showZone)
+ render = zoneData.getRender(showZone)
+
+ csVisible = 0
+ if hasattr(render, 'csVisible'):
+ csVisible = render.csVisible
+
+ if csVisible:
+ render.hideCS()
+ action = 'hidden'
+ else:
+ render.showCS()
+ action = 'shown'
+
+ render.csVisible = not csVisible
+ self.down_setMagicWordResponse(
+ senderId, 'collisions %s for %s' % (action, showZone))
+
+ ### Karting ###
+ elif(word[:8] == '~BuyKart'):
+ if (simbase.wantKarts):
+ accList = av.getKartAccessoriesOwned()
+ #accList[-1] = getDefaultColor()
+ accList[-2] = getDefaultRim()
+
+ response = "Av %s now owns accessories: %s" % (av.getDoId(), accList)
+ av.b_setKartAccessoriesOwned(accList)
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif(word[:13] == '~BuyAccessory'):
+ if (simbase.wantKarts):
+ response = "Av %s attempting to buy accessory %s" % (av.getDoId(), word[14:])
+ av.addOwnedAccessory(int(word[14:]))
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif(word[:8] == '~Tickets'):
+ if (simbase.wantKarts):
+ response = "Av %s now has %s tickets" % (av.getDoId(), word[9:])
+ av.b_setTickets(int(word[9:]))
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~allowSoloRace'):
+ if (simbase.wantKarts):
+ av.setAllowSoloRace(not av.allowSoloRace)
+ if av.allowSoloRace:
+ response = "Av %s can now race solo" % (av.getDoId())
+ else:
+ response = "Av %s can no longer Race solo" % (av.getDoId())
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~allowRaceTimeout'):
+ if (simbase.wantKarts):
+ av.setAllowRaceTimeout(not av.allowRaceTimeout)
+ if av.allowRaceTimeout:
+ response = "Av %s can timeout of races" % (av.getDoId())
+ else:
+ response = "Av %s can not timeout of races" % (av.getDoId())
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~drive'):
+ # Execute an arbitrary Python command on the AI.
+ av.takeOutKart()
+ self.down_setMagicWordResponse(
+ senderId, "I feel the need... the need for SPEED!")
+
+ # Golf
+ elif wordIs("~golf"):
+ self.doGolf(word, av, zoneId, senderId)
+
+ # Mail
+ elif wordIs("~mail"):
+ self.doMail(word, av, zoneId, senderId)
+
+ # Parties
+ elif wordIs("~party"):
+ self.doParty(word, av, zoneId, senderId)
+
+ #Gardening
+ elif wordIs("~garden"):
+ self.doGarden(word, av, zoneId, senderId)
+
+ elif word == "~clearGardenSpecials":
+ newWord = '~garden clearCarriedSpecials'
+ self.doGarden(newWord, av, zoneId, senderId)
+
+ elif word == "~clearFlowerCollection":
+ newWord = '~garden clearCollection'
+ self.doGarden(newWord, av, zoneId, senderId)
+
+ elif word == "~completeFlowerCollection":
+ newWord = '~garden completeCollection'
+ self.doGarden(newWord, av, zoneId, senderId)
+
+ elif wordIs('~startGarden'):
+ newWord = '~garden start'
+ self.doGarden(newWord, av, zoneId, senderId)
+
+ elif wordIs('~clearGarden'):
+ newWord = '~garden clear'
+ self.doGarden(newWord, av, zoneId, senderId)
+
+ elif wordIs('~cannons'):
+ sender = simbase.air.doId2do.get(senderId)
+ if sender:
+ zoneId = sender.zoneId
+ estateOwnerDoId = simbase.air.estateMgr.zone2owner.get(zoneId)
+ estate = simbase.air.estateMgr.estate.get(estateOwnerDoId)
+
+ if hasattr(estate, "cannonFlag"):
+ if estate.cannonFlag:
+ estate.endCannons()
+ response = "Cannons Ended"
+ else:
+ estate.startCannons()
+ response = "Cannons Started"
+
+ elif wordIs('~gameTable'):
+ sender = simbase.air.doId2do.get(senderId)
+ if sender:
+ zoneId = sender.zoneId
+ estateOwnerDoId = simbase.air.estateMgr.zone2owner.get(zoneId)
+ estate = simbase.air.estateMgr.estate.get(estateOwnerDoId)
+
+ if hasattr(estate, 'gameTableFlag'):
+ if estate.gameTableFlag:
+ estate.endGameTable()
+ response = 'Game Table Ended'
+ else:
+ estate.startGameTable()
+ response = 'Game Table Started'
+
+ elif wordIs('~plantGarden'):
+ newWord = '~garden plant'
+ args = word.split()
+ for i in range(1, len(args)):
+ newWord += ' '
+ newWord += args[i]
+ self.doGarden(newWord, av, zoneId, senderId)
+
+ elif wordIs('~trialerCount'):
+ total = 0
+ trialer =0
+ paid = 0
+ unknown = 0
+ allToons = simbase.air.doFindAll('DistributedToonAI')
+ for toon in allToons:
+ total += 1
+ if toon.getGameAccess() == ToontownGlobals.AccessFull:
+ paid += 1
+ elif toon.getGameAccess() == ToontownGlobals.AccessVelvetRope:
+ trialer += 1
+ else:
+ unknown += 1
+ response = "%d trialers, %d paid, %d total" % (trialer, paid, total)
+ if unknown:
+ response += "%d unknown" % unknown
+ self.down_setMagicWordResponse(senderId, response)
+
+ elif wordIs('~deleteBackupStores'):
+ storeClient = DataStoreAIClient(self.air, DataStoreGlobals.GEN, None)
+ storeClient.deleteBackupStores()
+ storeClient.closeStore()
+
+ elif wordIs("~autoRich"):
+ # Available only __dev__ and GMs. Guard against hacked clients sending this. If a non-GM
+ # needs to do this on LIVE, simply use ~rich instead.
+ if __dev__ or av.hasGMName():
+ # Basically this is a signal that a GM has logged in.
+ if av.hasGMName():
+ self.air.writeServerEvent('GM', av.doId, 'GM %s used auto-rich' % av.getName())
+ assert self.notify.debug('GM %s %s used auto-rich' % (av.doId, av.getName()))
+
+ args = word.split()
+ postName = 'autoRich-%s' % av.doId
+ enable = not bboard.get(postName, 0)
+ # are they explicitly setting the state?
+ if len(args) > 1:
+ try:
+ enable = int(args[1])
+ except:
+ self.down_setMagicWordResponse(senderId,
+ 'invalid state flag: %s' %
+ args[1])
+ return
+ if enable:
+ state = 'ON'
+ av.b_setMoney(av.maxMoney)
+ av.b_setBankMoney(av.maxBankMoney)
+ bboard.post(postName, 1)
+ else:
+ state = 'OFF'
+ bboard.remove(postName)
+ self.down_setMagicWordResponse(senderId, 'autoRich %s' % state)
+ else:
+ self.air.writeServerEvent('suspicious', av.doId, 'non-GM toon with name %s using ~autoRich' % av.getName())
+
+ else:
+ # The word is not an AI-side magic word. If the sender is
+ # different than the target avatar, then pass the magic
+ # word down to the target client-side MagicWordManager to
+ # execute a client-side magic word.
+ if (senderId != av.doId):
+ self.sendUpdateToAvatarId(av.doId, 'setMagicWord', [word, av.doId, zoneId])
+
+ def doNpcFriend(self, av, npcId, numCalls):
+ return av.attemptAddNPCFriend(npcId, numCalls) == 1
+
+ def getDepts(self, args):
+ # Returns a list of the dept indices specified by args[1], or
+ # all depts if nothing is specified. If a dept is specified,
+ # args[1] is removed from the list.
+
+ depts = []
+ if len(args) > 1:
+ if args[1] == 'all':
+ depts = [0, 1, 2, 3]
+ del args[1]
+ else:
+ allLettersGood = 1
+ for letter in args[1]:
+ if letter in SuitDNA.suitDepts:
+ dept = SuitDNA.suitDepts.index(letter)
+ depts.append(dept)
+ else:
+ allLettersGood = 0
+ break
+
+ if allLettersGood:
+ del args[1]
+ else:
+ depts = []
+
+ if depts:
+ return depts
+ else:
+ return [0, 1, 2, 3]
+
+
+ def doExp(self, word, av, zoneId, senderId):
+ """Handle the ~exp magic word."""
+ track = None
+ args=word.split()
+ trackIndex = -1
+ increment = 0
+ gotIncrement = 0
+
+ if len(args) > 1:
+ trackStr = args[1]
+ if trackStr != "all":
+ trackIndex = ToontownBattleGlobals.Tracks.index(trackStr)
+
+ if len(args) > 2:
+ increment = int(args[2])
+ gotIncrement = 1
+
+ if trackIndex == -1:
+ for trackIndex in range(ToontownBattleGlobals.MAX_TRACK_INDEX + 1):
+ if av.hasTrackAccess(trackIndex):
+ if not gotIncrement:
+ # No increment specified; the default is whatever
+ # it takes to get to the next track.
+ increment = av.experience.getNextExpValue(trackIndex) - av.experience.getExp(trackIndex)
+
+ self.notify.debug("Adding %d to %s track for %s." % (increment, ToontownBattleGlobals.Tracks[trackIndex], av.name))
+ av.experience.addExp(trackIndex, increment)
+ else:
+ if not gotIncrement:
+ # No increment specified; the default is whatever
+ # it takes to get to the next track.
+ increment = av.experience.getNextExpValue(trackIndex) - av.experience.getExp(trackIndex)
+
+ self.notify.debug("Adding %d to %s track for %s." % (increment, ToontownBattleGlobals.Tracks[trackIndex], av.name))
+ av.experience.addExp(trackIndex, increment)
+
+ av.d_setExperience(av.experience.makeNetString())
+
+
+ def doTrophy(self, word, av, zoneId, senderId):
+ """Handle the ~trophy magic word: artificially set (or
+ restore) the trophy score."""
+
+ args = word.split()
+ if len(args) > 1:
+ score = int(args[1])
+
+ response = "Set trophy score to %s." % (score)
+ self.down_setMagicWordResponse(senderId, response)
+ else:
+ # No score specified; restore the actual trophy score.
+ score = self.air.trophyMgr.getTrophyScore(av.doId)
+
+ response = "Trophy score is %s." % (score)
+ self.down_setMagicWordResponse(senderId, response)
+
+ av.d_setTrophyScore(score)
+
+
+ def doCheesyEffect(self, word, av, zoneId, senderId):
+ effect = None
+ if zoneId >= ToontownGlobals.DynamicZonesBegin:
+ hoodId = 1
+ else:
+ hoodId = ZoneUtil.getCanonicalHoodId(zoneId)
+ timeLimit = 10
+
+ args = word.split()
+ try:
+ effect = eval("ToontownGlobals.CE" + args[1])
+ except:
+ try:
+ effect = eval(args[1])
+ except:
+ effect = None
+
+ if effect == None:
+ self.down_setMagicWordResponse(senderId, "Unknown effect %s." % (args[1]))
+ return
+
+ if len(args) > 2:
+ timeLimit = int(args[2])
+
+ # Let it expire in timeLimit minutes.
+ expireTime = (int)(time.time() / 60 + 0.5) + timeLimit
+ av.b_setCheesyEffect(effect, hoodId, expireTime)
+
+ def doCogTakeOver(self, word, av, zoneId, senderId):
+ """Handle the ~cogTakeOver magic word."""
+ track = None
+ level = None
+ streetId = ZoneUtil.getBranchZone(zoneId)
+ args = word.split()
+
+ now = args.count("now")
+ if now:
+ args.remove("now")
+ slow = args.count("slow")
+ if slow:
+ args.remove("slow")
+
+ if len(args)>1:
+ track=args[1]
+ if track == 'x':
+ track = None
+
+ if len(args)>2:
+ if args[2] != 'x':
+ level=int(args[2])
+ level = max(min(level, 9), 1)
+
+ if len(args)>4:
+ if args[4] == 'all':
+ streetId = 'all'
+ else:
+ streetId=int(args[4])
+
+ if not self.air.suitPlanners.has_key(streetId):
+ response = "Street %d is not known." % (streetId)
+ self.down_setMagicWordResponse(senderId, response)
+ return
+
+ if streetId == 'all':
+ # All buildings, everywhere.
+ blockMap = {}
+ for sp in self.air.suitPlanners.values():
+ if sp.buildingMgr:
+ blockMap[sp.buildingMgr] = sp.buildingMgr.getToonBlocks()
+
+ else:
+ # Just the buildings on this street.
+ sp = self.air.suitPlanners[streetId]
+ bm = sp.buildingMgr
+
+ blocks = None
+ if len(args)>3:
+ if (args[3] == "all"):
+ blocks = bm.getToonBlocks()
+ elif (args[3] == "this"):
+ blocks = None
+ else:
+ blocks=[int(args[3])]
+
+ if blocks == None:
+ # Try to figure out what doors we're standing in front
+ # of.
+ blocks = []
+ for i in bm.getToonBlocks():
+ building = bm.getBuilding(i)
+ if hasattr(building, "door"):
+ if building.door.zoneId == zoneId:
+ blocks.append(i)
+
+ blockMap = { bm: blocks }
+
+ points = sp.streetPointList[:]
+ failureCount = 0
+ minPathLen = 2
+ maxPathLen = 10
+ if slow:
+ minPathLen = None
+ maxPathLen = None
+
+ total = 0
+ for bm, blocks in blockMap.items():
+ total += len(blocks)
+ for i in blocks:
+ if now:
+ # Take over the building immediately.
+ level, type, track = \
+ sp.pickLevelTypeAndTrack(level, None, track)
+ building = bm.getBuilding(i)
+ building.suitTakeOver(track, level - 1, None)
+
+ else:
+ # Dispatch a suit to take over the building.
+ if not slow:
+ # Let the suit take its time.
+ points = sp.getStreetPointsForBuilding(i)
+
+ suit = None
+ retryCount = 0
+ while suit == None and len(points) > 0 and retryCount < 20:
+ suit = sp.createNewSuit([], points,
+ suitTrack = track,
+ suitLevel = level,
+ toonBlockTakeover = i,
+ minPathLen = minPathLen,
+ maxPathLen = maxPathLen)
+ retryCount += 1
+ if suit == None:
+ failureCount += 1
+
+ self.notify.debug("cogTakeOver %s %s %d %d" %
+ (track, level, i, zoneId))
+
+ response = "%d buildings." % (total)
+ if (failureCount > 0):
+ response += " %d failed." % (failureCount)
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def doCogdoTakeOver(self, word, av, zoneId, senderId):
+ """Handle the ~cogdoTakeOver magic word."""
+ level = None
+ streetId = ZoneUtil.getBranchZone(zoneId)
+ args = word.split()
+
+ now = args.count("now")
+ if now:
+ args.remove("now")
+ slow = args.count("slow")
+ if slow:
+ args.remove("slow")
+
+ if len(args)>1:
+ if args[1] != 'x':
+ level=int(args[2])
+ level = max(min(level, 9), 1)
+
+ if len(args)>4:
+ if args[4] == 'all':
+ streetId = 'all'
+ else:
+ streetId=int(args[4])
+
+ if not self.air.suitPlanners.has_key(streetId):
+ response = "Street %d is not known." % (streetId)
+ self.down_setMagicWordResponse(senderId, response)
+ return
+
+ if streetId == 'all':
+ # All buildings, everywhere.
+ blockMap = {}
+ for sp in self.air.suitPlanners.values():
+ if sp.buildingMgr:
+ blockMap[sp.buildingMgr] = sp.buildingMgr.getToonBlocks()
+
+ else:
+ # Just the buildings on this street.
+ sp = self.air.suitPlanners[streetId]
+ bm = sp.buildingMgr
+
+ blocks = None
+ if len(args)>3:
+ if (args[3] == "all"):
+ blocks = bm.getToonBlocks()
+ elif (args[3] == "this"):
+ blocks = None
+ else:
+ blocks=[int(args[3])]
+
+ if blocks == None:
+ # Try to figure out what doors we're standing in front
+ # of.
+ blocks = []
+ for i in bm.getToonBlocks():
+ building = bm.getBuilding(i)
+ if hasattr(building, "door"):
+ if building.door.zoneId == zoneId:
+ blocks.append(i)
+
+ blockMap = { bm: blocks }
+
+ points = sp.streetPointList[:]
+ failureCount = 0
+ minPathLen = 2
+ maxPathLen = 10
+ if slow:
+ minPathLen = None
+ maxPathLen = None
+
+ total = 0
+ for bm, blocks in blockMap.items():
+ total += len(blocks)
+ for i in blocks:
+ if now:
+ # Take over the building immediately.
+ level, type, track = \
+ sp.pickLevelTypeAndTrack(level, None, track)
+ building = bm.getBuilding(i)
+ building.cogdoTakeOver(level - 1, None)
+
+ else:
+ # Dispatch a suit to take over the building.
+ if not slow:
+ # Let the suit take its time.
+ points = sp.getStreetPointsForBuilding(i)
+
+ suit = None
+ retryCount = 0
+ while suit == None and len(points) > 0 and retryCount < 20:
+ track = random.choice(SuitDNA.suitDepts)
+ suit = sp.createNewSuit([], points,
+ suitTrack = track,
+ suitLevel = level,
+ toonBlockTakeover = i,
+ cogdoTakeover = True,
+ minPathLen = minPathLen,
+ maxPathLen = maxPathLen)
+ retryCount += 1
+ if suit == None:
+ failureCount += 1
+
+ self.notify.debug("cogdoTakeOver %s %s %d %d" %
+ (track, level, i, zoneId))
+
+ response = "%d cogdos." % (total)
+ if (failureCount > 0):
+ response += " %d failed." % (failureCount)
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def doToonTakeOver(self, word, av, zoneId, senderId):
+ """Handle the ~toonTakeOver magic word."""
+ streetId = ZoneUtil.getBranchZone(zoneId)
+ args=word.split()
+ if len(args)>2:
+ if args[2] == 'all':
+ streetId = 'all'
+ else:
+ streetId=int(args[2])
+
+ if not self.air.suitPlanners.has_key(streetId):
+ response = "Street %d is not known." % (streetId)
+ self.down_setMagicWordResponse(senderId, response)
+ return
+
+ if streetId == 'all':
+ # All buildings, everywhere.
+ blockMap = {}
+ for sp in self.air.suitPlanners.values():
+ if sp.buildingMgr:
+ blockMap[sp.buildingMgr] = sp.buildingMgr.getSuitBlocks()
+
+ else:
+ # Just the buildings on this street.
+ bm=self.air.buildingManagers[streetId]
+ blocks = None
+ if len(args)>1:
+ if (args[1] == "all"):
+ blocks = bm.getSuitBlocks()
+ elif (args[1] == "this"):
+ blocks = None
+ else:
+ blocks=[int(args[1])]
+
+ if blocks == None:
+ # Try to figure out what doors we're standing in front
+ # of.
+ blocks = []
+ for i in bm.getSuitBlocks():
+ building = bm.getBuilding(i)
+ if hasattr(building, "elevator"):
+ if building.elevator.zoneId == zoneId and \
+ building.elevator.fsm.getCurrentState().getName() == 'waitEmpty':
+ blocks.append(i)
+ blockMap = { bm: blocks }
+
+ total = 0
+ for bm, blocks in blockMap.items():
+ total += len(blocks)
+ for i in blocks:
+ building = bm.getBuilding(i)
+ building.b_setVictorList([0, 0, 0, 0])
+ building.updateSavedBy([(av.doId, av.name, av.dna.asTuple())])
+ building.toonTakeOver()
+ self.notify.debug("Toon take over %s %s" % (i, streetId))
+
+ response = "%d buildings." % (total)
+ self.down_setMagicWordResponse(senderId, response)
+
+ def formatWelcomeValley(self, hood):
+ mgr = self.air.welcomeValleyManager
+
+ if hood == mgr.newHood:
+ return "%s N" % (hood[0].zoneId)
+ elif hood in mgr.removingHoods:
+ return "%s R" % (hood[0].zoneId)
+ else:
+ return "%s" % (hood[0].zoneId)
+
+ def doWelcome(self, word, av, zoneId, senderId):
+ """Handle the ~welcome magic word, for managing Welcome Valley."""
+ args = word.split()
+
+ hoodId = ZoneUtil.getHoodId(zoneId)
+ mgr = self.air.welcomeValleyManager
+
+ # if this is a GS hoodId, just grab the TT hood
+ if (hoodId % 2000) < 1000:
+ hood = mgr.welcomeValleys.get(hoodId)
+ else:
+ hood = mgr.welcomeValleys.get(hoodId - 1000)
+
+ if len(args) == 1:
+ # No parameter: report the current zone and population.
+ if hood == None:
+ response = "Not in Welcome Valley."
+ else:
+ response = "%s, pg = %s, hood = %s" % (
+ self.formatWelcomeValley(hood),
+ hood[0].getPgPopulation(),
+ hood[0].getHoodPopulation())
+
+ elif args[1] == "pg":
+ # ~welcome pg: list the playground population of all
+ # Welcome Valleys.
+
+ response = ""
+ hoodIds = mgr.welcomeValleys.keys()
+ hoodIds.sort()
+ for hoodId in hoodIds:
+ hood = mgr.welcomeValleys[hoodId]
+ response += "\n%s %s" % (
+ self.formatWelcomeValley(hood),
+ hood[0].getPgPopulation())
+
+ if response == "":
+ response = "No Welcome Valleys."
+ else:
+ response = response[1:]
+
+ elif args[1] == "hood":
+ # ~welcome hood: list the hood population of all
+ # Welcome Valleys.
+
+ response = ""
+ hoodIds = mgr.welcomeValleys.keys()
+ hoodIds.sort()
+ for hoodId in hoodIds:
+ hood = mgr.welcomeValleys[hoodId]
+ response += "\n%s %s" % (
+ self.formatWelcomeValley(hood),
+ hood[0].getHoodPopulation())
+
+ if response == "":
+ response = "No Welcome Valleys."
+ else:
+ response = response[1:]
+
+ elif args[1] == "login":
+ # ~welcome login avId: simulate a new player logging in.
+ # avId is an arbitrary number which should probably not
+ # match any real avatars.
+ avId = int(args[2])
+
+ zoneId = mgr.avatarRequestZone(avId, ToontownGlobals.WelcomeValleyToken)
+ response = "%s assigned to %s." % (avId, zoneId)
+
+ elif args[1] == "logout":
+ # ~welcome logout avId: simulate a player logging out.
+ # avId is an arbitrary number which should probably not
+ # match any real avatars.
+ avId = int(args[2])
+
+ avZoneId = mgr.avatarZones.get(avId)
+ if avZoneId:
+ mgr.avatarLogout(avId)
+ response = "%s logged out from %s." % (avId, avZoneId)
+ else:
+ response = "%s is unknown." % (avId)
+
+ elif args[1] == "zone":
+ # ~welcome zone avId [zoneId]: simulate a player switching
+ # zones. avId is an arbitrary number which should
+ # probably not match any real avatars.
+ avId = int(args[2])
+ if len(args) > 3:
+ newZoneId = int(args[3])
+ else:
+ newZoneId = zoneId
+
+ zoneId = mgr.avatarRequestZone(avId, newZoneId)
+ response = "%s redirected to %s." % (avId, zoneId)
+
+ elif args[1] == "new":
+ # ~welcome new [hoodId]: immediately move the indicated
+ # (or current) hood to the New pool.
+ if len(args) > 2:
+ hoodId = int(args[2])
+ else:
+ hoodId = zoneId
+ response = mgr.makeNew(hoodId)
+
+ elif args[1] == "stable":
+ # ~welcome stable [hoodId]: immediately move the indicated
+ # (or current) hood to the Stable pool.
+ if len(args) > 2:
+ hoodId = int(args[2])
+ else:
+ hoodId = zoneId
+ response = mgr.makeStable(hoodId)
+
+ elif args[1] == "remove":
+ # ~welcome remove [hoodId]: immediately move the indicated
+ # (or current) hood to the Removing pool.
+ if len(args) > 2:
+ hoodId = int(args[2])
+ else:
+ hoodId = zoneId
+ response = mgr.makeRemoving(hoodId)
+
+ elif args[1] == "check":
+ # ~welcome check: check that our record logged-in avatars
+ # are actually real avatars; logs out any others.
+ removed = mgr.checkAvatars()
+ if removed:
+ response = "Logged out %s phony avatars." % (len(removed))
+ else:
+ response = "All avatars real."
+
+ elif args[1] == "parms":
+ # ~welcome parms [min stable max]: report or adjust the
+ # balancing parameters.
+ if len(args) == 5:
+ PGminimum = int(args[2])
+ PGstable = int(args[3])
+ PGmaximum = int(args[4])
+ WelcomeValleyManagerAI.PGminimum = PGminimum
+ WelcomeValleyManagerAI.PGstable = PGstable
+ WelcomeValleyManagerAI.PGmaximum = PGmaximum
+
+ response = "min = %s, stable = %s, max = %s" % (
+ WelcomeValleyManagerAI.PGminimum,
+ WelcomeValleyManagerAI.PGstable,
+ WelcomeValleyManagerAI.PGmaximum)
+
+ else:
+ response = "Invalid welcome command: %s" % (args[1])
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def __sortBuildingDist(self, a, b):
+ return a[0] - b[0]
+
+ def doBuildings(self, word, av, zoneId, senderId):
+ """Handle the ~buildings magic word."""
+ streetId = ZoneUtil.getBranchZone(zoneId)
+
+ if word == "~buildings where":
+ # "~buildings where": report the distribution of buildings.
+ dist = {}
+ for sp in self.air.suitPlanners.values():
+ if sp.buildingMgr:
+ numActual = len(sp.buildingMgr.getSuitBlocks())
+ if not dist.has_key(numActual):
+ dist[numActual] = []
+ dist[numActual].append(sp.zoneId)
+
+ # Sort the distribution by number of buildings.
+ sorted = []
+ for tuple in dist.items():
+ sorted.append(tuple)
+ sorted.sort(self.__sortBuildingDist)
+
+ # Now format the distribution into a text response.
+ response = ""
+ for numActual, zones in sorted:
+ if numActual != 0:
+ response += "\n%s: %d" % (zones, numActual)
+
+ if response == "":
+ response = "No cog buildings."
+ else:
+ response = response[1:]
+
+ else:
+ # "~buildings zoneId" or "~buildings all"
+
+ args=word.split()
+ if len(args) > 1:
+ if args[1] == "all":
+ streetId = "all"
+ else:
+ streetId = int(args[1])
+
+ if streetId == "all":
+ numTarget = 0
+ numActual = 0
+ numTotalBuildings = 0
+ numAttempting = 0
+ numPerTrack = {}
+ numPerHeight = {}
+ for sp in self.air.suitPlanners.values():
+ numTarget += sp.targetNumSuitBuildings
+ if sp.buildingMgr:
+ numActual += len(sp.buildingMgr.getSuitBlocks())
+ numTotalBuildings += len(sp.frontdoorPointList)
+ numAttempting += sp.numAttemptingTakeover
+ sp.countNumBuildingsPerTrack(numPerTrack)
+ sp.countNumBuildingsPerHeight(numPerHeight)
+
+ response = "Overall, %d cog buildings (%s, %s) out of %d; target is %d. %d cogs are attempting takeover." % (
+ numActual, sp.formatNumSuitsPerTrack(numPerTrack),
+ sp.formatNumSuitsPerTrack(numPerHeight),
+ numTotalBuildings, numTarget, numAttempting)
+
+ elif not self.air.suitPlanners.has_key(streetId):
+ response = "Street %d is not known." % (streetId)
+
+ else:
+ sp = self.air.suitPlanners[streetId]
+
+ numTarget = sp.targetNumSuitBuildings
+ if sp.buildingMgr:
+ numActual = len(sp.buildingMgr.getSuitBlocks())
+ else:
+ numActual = 0
+ numTotalBuildings = len(sp.frontdoorPointList)
+ numAttempting = sp.numAttemptingTakeover
+ numPerTrack = {}
+ numPerHeight = {}
+ sp.countNumBuildingsPerTrack(numPerTrack)
+ sp.countNumBuildingsPerHeight(numPerHeight)
+
+ response = "Street %d has %d cog buildings (%s, %s) out of %d; target is %d. %d cogs are attempting takeover." % (
+ streetId, numActual,
+ sp.formatNumSuitsPerTrack(numPerTrack),
+ sp.formatNumSuitsPerTrack(numPerHeight),
+ numTotalBuildings, numTarget, numAttempting)
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def doBuildingPercent(self, word, av, zoneId, senderId):
+ """Handle the ~buildingPercent magic word."""
+ percent = None
+
+ args=word.split()
+ if len(args) > 1:
+ percent = int(args[1])
+
+ if percent == None:
+ response = "Suit building target percentage is %d" % (DistributedSuitPlannerAI.DistributedSuitPlannerAI.TOTAL_SUIT_BUILDING_PCT)
+ else:
+ DistributedSuitPlannerAI.DistributedSuitPlannerAI.TOTAL_SUIT_BUILDING_PCT = percent
+ sp = self.air.suitPlanners.values()[0]
+ sp.assignInitialSuitBuildings()
+ response = "Reset target percentage to %d" % (percent)
+
+ self.down_setMagicWordResponse(senderId, response)
+
+
+ def doCall(self, word, av, zoneId, senderId):
+ """Handle the ~call magic word."""
+ streetId = ZoneUtil.getBranchZone(zoneId)
+
+ args=word.split()
+ name = None
+ level = None
+ skelecog = None
+ revives = None
+
+ if len(args) > 1:
+ name = args[1]
+ if name == 'x':
+ name = None
+
+ if len(args) > 2:
+ level = int(args[2])
+
+ if len(args) > 3:
+ skelecog = int(args[3])
+
+ if len(args) > 4:
+ revives = int(args[4])
+
+ if not self.air.suitPlanners.has_key(streetId):
+ response = "Street %d is not known." % (streetId)
+
+ else:
+ sp = self.air.suitPlanners[streetId]
+ map = sp.getZoneIdToPointMap()
+ canonicalZoneId = ZoneUtil.getCanonicalZoneId(zoneId)
+ if not map.has_key(canonicalZoneId):
+ response = "Zone %d isn't near a suit point." % (canonicalZoneId)
+ else:
+ points = map[canonicalZoneId][:]
+ suit = sp.createNewSuit([], points,
+ suitName = name,
+ suitLevel = level,
+ skelecog = skelecog,
+ revives = revives)
+ if suit:
+ response = "Here comes %s." % (SuitBattleGlobals.SuitAttributes[suit.dna.name]['name'])
+ else:
+ response = "Could not create suit."
+
+ self.down_setMagicWordResponse(senderId, response)
+
+
+ def doBattle(self, word, av, zoneId, senderId):
+ """Handle the ~battle magic word."""
+
+ # There's no easy way to find the battle that the caller is
+ # near or involved in. We'll have to walk through all the
+ # battles currently in the doId2do map.
+ battle = self.__findInvolvedBattle(av.doId)
+ if battle == None:
+ battle = self.__findBattleInZone(zoneId)
+
+ if battle == None:
+ response = "No battle in zone %s." % (zoneId)
+ else:
+ args = word.split()
+ if len(args) < 2:
+ response = "Battle %s in state %s" % (battle.doId, battle.fsm.getCurrentState().getName())
+
+ elif args[1] == 'abort':
+ battle.abortBattle()
+ response = "Battle %s aborted." % (battle.doId)
+
+ else:
+ response = "Uknown battle command %s" % (args[1])
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def __findInvolvedBattle(self, avId):
+ """Returns the battle, if any, in which the indicated avatar
+ is a participant, or None if the avatar is not involved in any
+ battles."""
+
+ for dobj in self.air.doId2do.values():
+ if (isinstance(dobj, DistributedBattleBaseAI.DistributedBattleBaseAI)):
+ if avId in dobj.toons:
+ return dobj
+
+ def __findBattleInZone(self, zoneId):
+ """Returns the battle, if any, in the indicated zoneId, or
+ None if no battle is occurring in the indicated zone."""
+
+ for dobj in self.air.doId2do.values():
+ if (isinstance(dobj, DistributedBattleBaseAI.DistributedBattleBaseAI)):
+ if dobj.zoneId == zoneId:
+ return dobj
+
+
+
+
+ def doCogInvasion(self, word, av, zoneId, senderId):
+ """Handle the ~invasion magic word."""
+
+ invMgr = self.air.suitInvasionManager
+
+ if invMgr.getInvading():
+ cogType = invMgr.getCogType()
+ numRemaining = invMgr.getNumCogsRemaining()
+ cogName = SuitBattleGlobals.SuitAttributes[cogType[0]]['name']
+ response = ("Invasion already in progress: %s, %s" % (cogName, numRemaining))
+ else:
+ args=word.split()
+ if len(args) < 3 or len(args) > 4:
+ response = "Error: Must specify cogType and numCogs"
+ else:
+ cogType = args[1]
+ numCogs = int(args[2])
+ if len(args) == 4:
+ skeleton = int(args[3])
+ else:
+ skeleton = 0
+ cogNameDict = SuitBattleGlobals.SuitAttributes.get(cogType)
+ if cogNameDict:
+ cogName = cogNameDict['name']
+ if skeleton:
+ cogName = TTLocalizer.Skeleton + " " + cogName
+ if invMgr.startInvasion(cogType, numCogs, skeleton):
+ response = ("Invasion started: %s, %s" % (cogName, numCogs))
+ else:
+ response = ("Invasion failed: %s, %s" % (cogName, numCogs))
+ else:
+ response = ("Unknown cogType: %s" % (cogType))
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def stopCogInvasion(self, word, av, zoneId, senderId):
+ invMgr = self.air.suitInvasionManager
+
+ if invMgr.getInvading():
+ self.air.suitInvasionManager.stopInvasion()
+ response = ("Invasion stopped.")
+ else:
+ response = ("No invasion found.")
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def getCogInvasion(self, word, av, zoneId, senderId):
+ invMgr = self.air.suitInvasionManager
+
+ if invMgr.getInvading():
+ cogType, skeleton = invMgr.getCogType()
+ numRemaining = invMgr.getNumCogsRemaining()
+ cogName = SuitBattleGlobals.SuitAttributes[cogType]['name']
+ if skeleton:
+ cogName = TTLocalizer.Skeleton + " " + cogName
+ response = ("Invasion is in progress: %s, %s remaining" % (cogName, numRemaining))
+ else:
+ response = ("No invasion found.")
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def startAllFireworks(self, word, av, zoneId, senderId):
+ fMgr = self.air.fireworkManager
+ fMgr.stopAllShows()
+ fMgr.startAllShows(None)
+ response = "Shows started in all hoods."
+ self.down_setMagicWordResponse(senderId, response)
+
+ def startFireworks(self, word, av, zoneId, senderId):
+ fMgr = self.air.fireworkManager
+ if fMgr.isShowRunning(zoneId):
+ response = ("Show already running in zone: %s" % (zoneId))
+ else:
+ args=word.split()
+ if len(args) == 2:
+ showType = int(args[1])
+ if fMgr.startShow(zoneId, showType, 1):
+ response = ("Show started, showType: %s" % showType)
+ else:
+ response = ("Show failed, showType: %s" % showType)
+ else:
+ # Default to showType 0
+ response = (TTLocalizer.startFireworksResponse \
+ %( ToontownGlobals.NEWYEARS_FIREWORKS, \
+ PartyGlobals.FireworkShows.Summer, \
+ ToontownGlobals.JULY4_FIREWORKS))
+ self.down_setMagicWordResponse(senderId, response)
+
+ def stopFireworks(self, word, av, zoneId, senderId):
+ if self.air.fireworkManager.stopShow(zoneId):
+ response = ("Show stopped, zoneId: %s" % zoneId)
+ else:
+ response = ("Show stop failed, zoneId: %s" % zoneId)
+ self.down_setMagicWordResponse(senderId, response)
+
+ def stopAllFireworks(self, word, av, zoneId, senderId):
+ numStopped = self.air.fireworkManager.stopAllShows()
+ response = ("Stopped %s firework show(s)" % (numStopped))
+ self.down_setMagicWordResponse(senderId, response)
+
+ def doCogs(self, word, av, zoneId, senderId):
+ """Handle the ~cogs magic word."""
+ streetId = ZoneUtil.getBranchZone(zoneId)
+
+ args=word.split()
+ firstKeyword = 1
+
+ sync = 0
+ fly = 0
+ count = -1
+
+ if len(args) > 1:
+ if args[1] == "all":
+ streetId = "all"
+ firstKeyword = 2
+ else:
+ try:
+ streetId = int(args[1])
+ firstKeyword = 2
+ except:
+ pass
+
+ # Check for extra keywords.
+ for k in range(firstKeyword, len(args)):
+ word = args[k]
+ if word == "sync":
+ sync = 1
+ elif word == "fly":
+ fly = 1
+ elif word == "count=x":
+ count = None
+ elif word[:6] == "count=":
+ count = int(word[6:])
+ else:
+ self.down_setMagicWordResponse(senderId, "invalid keyword %s" % (word))
+ return
+
+ if streetId == "all":
+ numTarget = 0
+ numActual = 0
+ numPerTrack = {}
+ sp = None
+ for sp in self.air.suitPlanners.values():
+ numTarget += sp.calcDesiredNumFlyInSuits() + sp.calcDesiredNumBuildingSuits()
+ numActual += sp.numFlyInSuits + sp.numBuildingSuits
+ sp.countNumSuitsPerTrack(numPerTrack)
+ if sync:
+ sp.resyncSuits()
+ if fly:
+ sp.flySuits()
+ if count != -1:
+ sp.currDesired = count
+
+ if sp == None:
+ response = "No cogs active."
+ else:
+ response = "Overall, %d cogs (%s); target is %d." % (
+ numActual, sp.formatNumSuitsPerTrack(numPerTrack), numTarget)
+
+ elif not self.air.suitPlanners.has_key(streetId):
+ response = "Street %d is not known." % (streetId)
+
+ else:
+ sp = self.air.suitPlanners[streetId]
+
+ numTarget = sp.calcDesiredNumFlyInSuits() + sp.calcDesiredNumBuildingSuits()
+ numActual = sp.numFlyInSuits + sp.numBuildingSuits
+ numPerTrack = {}
+ sp.countNumSuitsPerTrack(numPerTrack)
+
+ if sync:
+ sp.resyncSuits()
+ if fly:
+ sp.flySuits()
+ if count != -1:
+ sp.currDesired = count
+ response = "Street %d has %d cogs (%s); target is %d." % (
+ streetId, numActual, sp.formatNumSuitsPerTrack(numPerTrack),
+ numTarget)
+
+ self.down_setMagicWordResponse(senderId, response)
+
+
+ def doMinigame(self, word, av, zoneId, senderId):
+ """Handle the ~minigame magic word: request a particular minigame."""
+ args = word.split()
+ if len(args) == 1:
+ # No minigame parameter specified: clear the request.
+ mgRequest = MinigameCreatorAI.RequestMinigame.get(av.doId)
+ if mgRequest != None:
+ mgId = mgRequest[0]
+ del MinigameCreatorAI.RequestMinigame[av.doId]
+ response = "Request for minigame %d cleared." % (mgId)
+ else:
+ response = "Usage: ~minigame [ [difficulty] [safezone]]"
+ else:
+ # Try to determine the minigame id, keep flag, and the difficulty
+ # and safezone overrides, if any
+ name = args[1]
+ mgId = None
+ mgKeep = 0
+ mgDiff = None
+ mgSzId = None
+
+ try:
+ mgId = int(name)
+ numMgs = len(ToontownGlobals.MinigameIDs)
+ if mgId < 1 or mgId > numMgs or mgId not in ToontownGlobals.MinigameIDs:
+ response = "minigame ID '%s' is out of range" % mgId
+ mgId = None
+ except:
+ name = string.lower(name)
+ if name[-4:] == "game":
+ name = name[:-4]
+ if name[:11] == "distributed":
+ name = name[11:]
+ mgId = ToontownGlobals.MinigameNames.get(name)
+ if mgId == None:
+ response = "Unknown minigame '%s'." % (name)
+
+ argIndex = 2
+ while argIndex < len(args):
+ arg = args[argIndex]
+ arg = string.lower(arg)
+ argIndex += 1
+
+ # it's either a difficulty (float), 'keep',
+ # or a safezone (string)
+
+ # is it 'keep'?
+ if arg == 'keep':
+ mgKeep = 1
+ continue
+
+ # is it a difficulty?
+ try:
+ mgDiff = float(arg)
+ continue
+ except:
+ pass
+
+ mgSzId = self.Str2szId.get(arg)
+ if mgSzId is not None:
+ continue
+
+ # it's a string, but it's not a safezone.
+ response = ("unknown safezone '%s'; use "
+ "tt, dd, dg, mm, br, dl" % arg)
+ mgId = None
+ break
+
+ if mgId != None:
+ # mdId must be the first element
+ MinigameCreatorAI.RequestMinigame[av.doId] = (
+ mgId, mgKeep, mgDiff, mgSzId)
+ response = "Selected minigame %d" % mgId
+ if mgDiff is not None:
+ response += ", difficulty %s" % mgDiff
+ if mgSzId is not None:
+ response += ", safezone %s" % mgSzId
+ if mgKeep:
+ response += ", keep=true"
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def doTreasures(self, word, av, zoneId, senderId):
+ """Handle the ~treasures magic word: fill up the safezone with
+ treasures."""
+ args = word.split()
+
+ hood = None
+ for h in self.air.hoods:
+ if h.zoneId == zoneId:
+ hood = h
+ break
+
+ if hood == None or hood.treasurePlanner == None:
+ self.down_setMagicWordResponse(senderId, "Not in a safezone.")
+ return
+
+ tp = hood.treasurePlanner
+
+ if len(args) == 1:
+ # No parameter: report the current treasure count.
+ response = "%s treasures." % (tp.numTreasures())
+
+ elif args[1] == "all":
+ # ~treasures all: fill up all available treasures.
+ tp.placeAllTreasures()
+ response = "now %s treasures." % (tp.numTreasures())
+
+ else:
+ response = "Invalid treasures command: %s" % (args[1])
+
+ self.down_setMagicWordResponse(senderId, response)
+
+
+ def doEmotes(self, word, av, zoneId, senderId):
+ """Handle the ~emote magic word: turns on/off the specified emotion"""
+ args = word.split()
+ if len(args) == 1:
+ # No parameter specified: clear the request.
+ response = "No emote specified."
+ elif len(args) == 2:
+ # No parameter specified: clear the request.
+ response = "Need to specify 0 or 1."
+ else:
+ emoteId = int(args[1])
+ on = int(args[2])
+ if emoteId > len(av.emoteAccess) or emoteId < 0:
+ response = "Not a valid emote"
+ elif on not in [0, 1]:
+ response = "Not a valid bit"
+ else:
+ av.setEmoteAccessId(emoteId, on)
+ if on:
+ response = "Emote %d enabled" % emoteId
+ else:
+ response = "Emote %d disabled" % emoteId
+ self.down_setMagicWordResponse(senderId, response)
+
+
+ def doCatalog(self, word, av, zoneId, senderId):
+ """Handle the ~catalog magic word: manage catalogs"""
+ now = time.time()
+ args = word.split()
+
+ # There may be an optional "after" parameter on many of these
+ # commands, which specifies the number of minutes to delay
+ # before doing the action.
+ afterMinutes = 0
+ if "after" in args:
+ a = args.index("after")
+ afterMinutes = int(args[a + 1])
+ del args[a + 1]
+ del args[a]
+
+ if len(args) == 1:
+ # No parameter: report the current catalog.
+ duration = (av.catalogScheduleNextTime * 60) - time.time()
+ response = "Week %d, next catalog in %s." % \
+ (av.catalogScheduleCurrentWeek,
+ PythonUtil.formatElapsedSeconds(duration))
+
+ elif args[1] == "next":
+ # ~catalog next: advance to the next catalog.
+ week = av.catalogScheduleCurrentWeek + 1
+ self.air.catalogManager.forceCatalog(av, week, afterMinutes)
+ response = "Issued catalog for week %s." % (week)
+
+ elif args[1] == "week":
+ # ~catalog week n: force to the catalog of the nth week.
+ # Note: need to have catalog-skip-seeks set to true to jump
+ # more than one week
+ week = int(args[2])
+ if week > 0:
+ self.air.catalogManager.forceCatalog(av, week, afterMinutes)
+ response = "Forced to catalog week %s." % (week)
+ else:
+ response = "Invalid catalog week %s." % (week)
+
+ elif args[1] == "season":
+ # ~catalog season mm/dd: regenerate the monthly catalog
+ # items as if it were the indicated month and day.
+ if len(args) == 3:
+ mmdd = args[2].split('/')
+ mm = int(mmdd[0])
+ dd = int(mmdd[1])
+ else:
+ mm = int(args[2])
+ dd = int(args[3])
+
+ self.air.catalogManager.forceMonthlyCatalog(av, mm, dd)
+ response = "%s items for %d/%0d." % (len(av.monthlyCatalog), mm, dd)
+
+ elif (args[1] == "clear") or (args[1] == "reset"):
+ # ~catalog clear: reset the catalog (and the back catalog)
+ # to its initial state.
+ av.b_setCatalog(CatalogItemList.CatalogItemList(),
+ CatalogItemList.CatalogItemList(),
+ CatalogItemList.CatalogItemList())
+ av.catalogScheduleCurrentWeek = 0
+ av.catalogScheduleNextTime = 0
+ self.air.catalogManager.deliverCatalogFor(av)
+ response = "Catalog reset."
+
+ elif args[1] == "deliver":
+ # ~catalog deliver: force the immediate delivery of all
+ # of the on-order item(s).
+
+ now = (int)(time.time() / 60 + 0.5)
+ deliveryTime = now + afterMinutes
+
+ for item in av.onOrder:
+ item.deliveryDate = deliveryTime
+ av.onOrder.markDirty()
+ av.b_setDeliverySchedule(av.onOrder)
+
+ response = "Delivered %s item(s)." % (len(av.onOrder))
+
+ elif args[1] in ["reload", "dump"]:
+ # These commands are handled by the client; we ignore them.
+ return
+
+ else:
+ response = "Invalid catalog command: %s" % (args[1])
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def doDna(self, word, av, zoneId, senderId):
+ """Handle the ~dna magic word: change your dna"""
+
+ # Strip of the "~dna" part; everything else is parameters to
+ # AvatarDNA.updateToonProperties.
+ parms = string.strip(word[4:])
+
+ # Get a copy of the avatar's current DNA.
+ dna = ToonDNA.ToonDNA(av.dna.makeNetString())
+
+ # Modify it according to the user's parameter selection.
+ eval("dna.updateToonProperties(%s)" % (parms))
+
+ av.b_setDNAString(dna.makeNetString())
+ response = "%s" % (dna.asTuple(),)
+
+ self.down_setMagicWordResponse(senderId, response)
+
+ def getPet(self, av):
+ response = None
+ pet = None
+
+ petId = av.getPetId()
+ if petId == 0:
+ response = "don't have a pet"
+ else:
+ pet = self.air.doId2do.get(petId)
+ if pet is None:
+ response = "pet not active, use ~pet"
+ return pet, response
+
+ def doBossBattle(self, word, av, zoneId, senderId):
+ """Handle the ~bossBattle magic word: manage a final boss
+ battle."""
+
+ args = word.split()
+
+ # Find the particular Boss Cog that's in the same zone with
+ # the avatar.
+ bossCog = None
+ for distObj in self.air.doId2do.values():
+ if isinstance(distObj, DistributedBossCogAI.DistributedBossCogAI):
+ if distObj.zoneId == zoneId:
+ bossCog = distObj
+ break
+
+ if bossCog == None:
+ # The caller isn't in a zone with a Boss Cog; use the
+ # default Boss zone.
+
+ # In this case, we must accept a dept indicator.
+ if len(args) < 2 or args[1] not in SuitDNA.suitDepts:
+ response = "Error: Must specify boss dept: s, m, l, c"
+ self.down_setMagicWordResponse(senderId, response)
+ return
+
+ dept = args[1]
+ del args[1]
+ deptIndex = SuitDNA.suitDepts.index(dept)
+
+ if self.__bossBattleZoneId[deptIndex] == None:
+ # Make up a new zone for the battle.
+ zoneId = self.air.allocateZone()
+ self.__bossBattleZoneId[deptIndex] = zoneId
+
+ bossCog = self.makeBossCog(dept, zoneId)
+ bossCog.b_setState('Frolic')
+ self.__bossCog[deptIndex] = bossCog
+
+ else:
+ bossCog = self.__bossCog[deptIndex]
+
+ response = None
+ if len(args) == 1:
+ # No parameter: send the avatar to the battle zone.
+ self.sendUpdateToAvatarId(av.doId, 'requestTeleport',
+ ["cogHQLoader", "cogHQBossBattle",
+ bossCog.getHoodId(),
+ bossCog.zoneId, 0])
+
+ elif args[1] == "list":
+ # ~bossBattle [smlc] list: list the current boss battles
+ # underway for the indicated type of suit.
+ response = ""
+ for bc in DistributedBossCogAI.AllBossCogs:
+ if bc.dept == bossCog.dept and bc != bossCog:
+ response += ", %s %s %s" % (bc.zoneId, bc.state, len(bc.involvedToons))
+ if response:
+ response = response[2:]
+ else:
+ response = "No boss battles."
+
+ elif args[1] == "spy":
+ # ~bossBattle spy [smlc] : go visit the indicated
+ # boss battle in ghost mode.
+ zoneId = int(args[2])
+ bc = bossCog
+ for bc in DistributedBossCogAI.AllBossCogs:
+ if bc.zoneId == zoneId:
+ break
+ if bc.zoneId == zoneId:
+ self.sendUpdateToAvatarId(av.doId, 'requestTeleport',
+ ["cogHQLoader", "cogHQBossBattle",
+ bc.getHoodId(),
+ bc.zoneId, 0])
+ av.b_setGhostMode(2)
+ else:
+ response = "No boss battle in zone %s" % (zoneId)
+
+
+ elif args[1] == "start":
+ # ~bossBattle start: restart the boss battle.
+ if not bossCog.hasToons():
+ response = "No toons."
+ else:
+ response = "Elevator started."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('WaitForToons')
+
+ elif args[1] == "one":
+ # ~bossBattle one: straight to battle one.
+ if not bossCog.hasToons():
+ response = "No toons."
+ else:
+ response = "Battle one started."
+ bossCog.acceptNewToons()
+ bossCog.makeBattleOneBattles()
+ bossCog.b_setState('BattleOne')
+
+ elif args[1] == "preTwo":
+ # ~bossBattle preTwo: preview to battle two.
+ if not bossCog.hasToons():
+ response = "No toons."
+ elif not hasattr(bossCog, 'enterRollToBattleTwo'):
+ response = "Battle two preview."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('PrepareBattleTwo')
+ else:
+ response = "Battle two preview."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('RollToBattleTwo')
+
+ elif args[1] == "two":
+ # ~bossBattle two: straight to battle two.
+ if not bossCog.hasToons():
+ response = "No toons."
+ else:
+ response = "Battle two started."
+ bossCog.acceptNewToons()
+ if hasattr(bossCog, 'makeBattleTwoBattles'):
+ bossCog.makeBattleTwoBattles()
+ bossCog.b_setState('BattleTwo')
+
+ elif args[1] == "preThree":
+ # ~bossBattle preThree: preview to battle three.
+ if not bossCog.hasToons():
+ response = "No toons."
+ else:
+ response = "Battle three preview."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('PrepareBattleThree')
+
+ elif args[1] == "three":
+ # ~bossBattle three: straight to battle three.
+ if not bossCog.hasToons():
+ response = "No toons."
+ else:
+ response = "Battle three started."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('BattleThree')
+
+ elif args[1] == "preFour":
+ # ~bossBattle preFour: preview to battle four.
+ if not bossCog.hasToons():
+ response = "No toons."
+ else:
+ response = "Battle four preview."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('PrepareBattleFour')
+
+ elif args[1] == "four":
+ # ~bossBattle four: straight to battle four.
+ if not bossCog.hasToons():
+ response = "No toons."
+ else:
+ response = "Battle four started."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('BattleFour')
+
+ elif args[1] == "victory":
+ # ~bossBattle victory: straight to the victory sequence.
+ if not bossCog.hasToons():
+ response = "No toons."
+ else:
+ response = "Victory sequence started."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('Victory')
+
+ elif args[1] == "fsm":
+ # ~bossBattle fsm state: directly to named state.
+ if len(args) <= 2:
+ response = "No state specified."
+ else:
+ response = "Requested state %s." % (args[2])
+ bossCog.b_setState(args[2])
+
+ elif args[1] == "stop":
+ # ~bossBattle start: stop the boss battle.
+ response = "Battle stopped."
+ bossCog.acceptNewToons()
+ bossCog.b_setState('Frolic')
+
+ elif args[1] == "hit":
+ # ~bossBattle hit [damage]: hit the boss cog during the
+ # pie scene. This will make him dizzy first if he is not
+ # already dizzy (as if we successfully lobbed a pie into
+ # the undercarriage) and then applies the indicated damage
+ # (as if we hit his upper body the indicated number of
+ # times).
+
+ if len(args) <= 2:
+ damage = 1
+ else:
+ damage = int(args[2])
+
+ ##
+ ## this is very suspect .. it is reasigning a internal var(msgSender)..
+ ## it is trying to make the message look like it came from a difrent avatarID
+ ## to other areas of the code .. ???
+ self.air.msgSender = av.doId
+ bossCog.magicWordHit(damage, av.doId)
+
+ elif args[1] == "safe":
+ # ~bossBattle safe: Ignore hits to the toon during the pie
+ # scene. This magic word is handled by the client.
+ pass
+
+ elif args[1] == "avatarEnter":
+ # ~bossBattle avatarEnter: Force a call to d_avatarEnter
+ #This magic word is handled by the client.
+ pass
+
+ elif args[1] == "stun":
+ # ~bossBattle stun: stun all of the goons in the CFO
+ # battle. This magic word is handled by the client.
+ pass
+
+ elif args[1] == "destroy":
+ # ~bossBattle destroy: destroy all of the goons in the CFO
+ # battle. This magic word is handled by the client.
+ pass
+
+ elif args[1] == "reset":
+ # ~bossBattle reset: reset all of the goons, cranes, and
+ # safes in the CFO sequence.
+ bossCog.magicWordReset()
+
+ elif args[1] == "goons":
+ # ~bossBattle goons [num]: reset all of the goons and set
+ # the number to the indicated value.
+ if len(args) > 2:
+ bossCog.maxGoons = int(args[2])
+
+ bossCog.magicWordResetGoons()
+ elif args[1] == "toggleMove":
+ # make the ceo toggle doing move attacks
+ if hasattr(bossCog, 'toggleMove'):
+ doingMoveAttack = bossCog.toggleMove()
+ response = "doing move attack = %s" % doingMoveAttack
+ else:
+ response = "toggleMove is only for CEO"
+ else:
+ response = "Invalid bossBattle command: %s" % (args[1])
+
+ if response:
+ self.down_setMagicWordResponse(senderId, response)
+
+ def makeBossCog(self, dept, zoneId):
+ bossCog = None
+
+ if dept == 's':
+ bossCog = DistributedSellbotBossAI.DistributedSellbotBossAI(self.air)
+ bossCog.generateWithRequired(zoneId)
+ bossCog.b_setState('Frolic')
+
+ elif dept == 'm':
+ bossCog = DistributedCashbotBossAI.DistributedCashbotBossAI(self.air)
+ bossCog.generateWithRequired(zoneId)
+
+ elif dept == 'l':
+ bossCog = DistributedLawbotBossAI.DistributedLawbotBossAI(self.air)
+ bossCog.generateWithRequired(zoneId)
+ bossCog.b_setState('Frolic')
+ elif dept == 'c':
+ bossCog = DistributedBossbotBossAI.DistributedBossbotBossAI(self.air)
+ bossCog.generateWithRequired(zoneId)
+ bossCog.b_setState('Frolic')
+
+ else:
+ raise StandardError, 'Not implemented: boss cog %s' % (dept)
+
+ return bossCog
+
+
+ def doGarden(self, word, av, zoneId, senderId):
+ """Handle the ~garden magic worde."""
+
+ args = word.split()
+
+ response = None
+ action = None
+ if len(args) == 1:
+ # No parameter: change it to usage
+ self.down_setMagicWordResponse(
+ senderId,
+ "Usage:\n~garden action \n'~garden help' for more info ")
+ return
+
+ action = args[1]
+ if action == 'help':
+ response = 'start\nclear\nwilt \nunwilt \nplant \nclearCarriedSpecials\nclearCollection\ncompleteCollection\nwater \nshovel <0-3> \nrandomBasket\nnuke\nepoch \nwateringCan <0-3> '
+ elif action == 'start':
+ messenger.send("gardenTest", [senderId])
+ response = "Test Garden Planted"
+ elif action == 'clear':
+ messenger.send("gardenClear", [senderId])
+ response = "Garden Cleared"
+ elif action == 'nuke':
+ #clear the garden and remove the planters
+ #clear the garden started flag
+ messenger.send("gardenNuke", [senderId])
+ av.b_setGardenStarted(False)
+ response = "Garden Nuked"
+ elif action == 'specials':
+ #messenger.send("gardenSpecials", [senderId])
+ receiver = self.air.doId2do.get(senderId)
+ if receiver:
+ receiver.giveMeSpecials()
+ response = "Garden Specials Added"
+ elif action == 'wilt':
+ if len(args) >= 3:
+ messenger.send("wiltGarden", [senderId, int(args[2])] )
+ else:
+ messenger.send("wiltGarden", [senderId])
+ response = "Garden wilted"
+ elif action == 'unwilt':
+ if len(args) >= 3:
+ messenger.send("unwiltGarden", [senderId, int(args[2])] )
+ else:
+ messenger.send("unwiltGarden", [senderId])
+ response = "Garden unwilted"
+ elif action == 'water':
+ waterLevel = 1
+ if len(args) > 2:
+ waterLevel = int(args[2])
+ specificHardPoint = -1
+ if len(args) > 3:
+ specificHardPoint = int(args[3])
+
+ messenger.send("waterGarden", [senderId, waterLevel, specificHardPoint] )
+
+ response = "water level changed to %d" % waterLevel
+ elif action == 'growth':
+ waterLevel = 1
+ if len(args) > 2:
+ waterLevel = int(args[2])
+ specificHardPoint = -1
+ if len(args) > 3:
+ specificHardPoint = int(args[3])
+
+ messenger.send("growthGarden", [senderId, waterLevel, specificHardPoint] )
+
+ response = "growth level changed to %d" % waterLevel
+ elif action == 'plant':
+ type = 0
+ plot = 0
+ water = 0
+ growth = 1
+ variety = 0
+ if len(args) > 2:
+ type = int(args[2])
+ if len(args) > 3:
+ plot = int(args[3])
+ if len(args) > 4:
+ water = int(args[4])
+ if len(args) > 5:
+ growth = int(args[5])
+ if len(args) > 6:
+ variety = int(args[6])
+
+ response = "Planting type=%d plot=%d water=%d growth=%d" % (type,plot,water,growth)
+ messenger.send("gardenPlant", [senderId, type, plot, water, growth, variety])
+ elif action == 'clearCarriedSpecials':
+ av.b_setGardenSpecials( [])
+ response = "Cleared garden specials carried by toon"
+ elif action == 'clearCollection':
+ av.b_setFlowerCollection( [], [])
+ response = "Cleared flower collection."
+ elif action == 'completeCollection':
+ #from toontown.estate import GardenGlobals
+ varietyList = []
+ speciesList = []
+ flowerSpecies = GardenGlobals.getFlowerSpecies()
+ for species in flowerSpecies:
+ numVarieties = len(GardenGlobals.getFlowerVarieties(species))
+ for variety in range(numVarieties):
+ speciesList.append(species)
+ varietyList.append(variety)
+
+ av.b_setFlowerCollection( speciesList, varietyList)
+ av.b_setGardenTrophies([])
+ response = "Complete flower collection."
+ elif action == 'epoch':
+ numEpoch = 1
+ if len(args) > 2:
+ numEpoch = int(args[2])
+ messenger.send("epochGarden", [senderId, numEpoch])
+ response = "%d garden epoch has been run" % numEpoch
+ elif action == 'shovel':
+ if len(args) < 3:
+ response = "specify a shovel (0-3)"
+ else:
+ shovel = 0
+ passedShovel = int(args[2])
+ if passedShovel >= 0 and \
+ passedShovel < GardenGlobals.MAX_SHOVELS:
+ shovel = passedShovel
+ skill =0
+ if len(args) >= 4:
+ passedSkill = int(args[3])
+ skill = min (passedSkill, GardenGlobals.ShovelAttributes[shovel]['skillPts'] - 1)
+ skill = max (skill, 0)
+
+ av.b_setShovel(shovel)
+ av.b_setShovelSkill(skill)
+ response = "Set shovel=%d shovelSkill=%d" % (shovel,skill)
+ elif action == 'wateringCan':
+ if len(args) < 3:
+ response = "specify a watering can (0-3)"
+ else:
+ wateringCan = 0
+ passedWateringCan = int(args[2])
+ if passedWateringCan >= 0 and \
+ passedWateringCan < GardenGlobals.MAX_WATERING_CANS:
+ wateringCan = passedWateringCan
+ skill =0
+ if len(args) >= 4:
+ passedSkill = int(args[3])
+ skill = min (passedSkill, GardenGlobals.WateringCanAttributes[wateringCan]['skillPts'] - 1)
+ skill = max (skill, 0)
+
+ av.b_setWateringCan(wateringCan)
+ av.b_setWateringCanSkill(skill)
+ response = "Set wateringCan=%d wateringCanSkill=%d" % (wateringCan,skill)
+ elif action == 'randomBasket':
+ av.makeRandomFlowerBasket()
+ self.down_setMagicWordResponse(senderId, "Created random flower basket")
+ elif action == "allTrophies":
+ allTrophyList = GardenGlobals.TrophyDict.keys()
+ av.b_setGardenTrophies(allTrophyList)
+ self.down_setMagicWordResponse(senderId, "All garden trophies")
+ else:
+ response = 'Invalid garden command.'
+ if response:
+ self.down_setMagicWordResponse(senderId, response)
+
+ def doGolf(self, word, av, zoneId, senderId):
+ """Handle the ~golf magic words."""
+ args = word.split()
+ response = None
+ action = None
+
+ if len(args) == 1:
+ # No parameter: change it to usage
+ self.down_setMagicWordResponse(
+ senderId,
+ "Usage:\n~golf action \n'~golf help' for more info ")
+ return
+
+ action = args[1]
+ if action == 'help':
+ response = 'endHole\nendcourse\ntest\nclearHistory\nMaxHistory'
+ elif action == 'drive':
+ course = GolfManagerAI.GolfManagerAI().findGolfCourse(senderId)
+ response = "drive failed."
+ if course:
+ result = course.toggleDrivePermission(senderId)
+ if result:
+ response = "Press up, down, left&right simultaneously to drive."
+ else:
+ response = "Toon is no longer driving."
+ elif action == 'endhole' or action == 'endHole':
+ course = GolfManagerAI.GolfManagerAI().findGolfCourse(senderId)
+ if course:
+ holeId, holeDoId = course.abortCurrentHole()
+ response = "Aborting holeId %d, doId=%d" % (holeId, holeDoId)
+ else:
+ response = "Couldn't find golf course"
+ elif action == 'endcourse' or action == 'endCourse':
+ course = GolfManagerAI.GolfManagerAI().findGolfCourse(senderId)
+ if course:
+ course.setCourseAbort()
+ response = "Aborting course %d" % course.doId
+ else:
+ response = "Couldn't find golf course"
+ elif action == 'test':
+ #messenger.send("gardenTest", [senderId])
+ response = "golf test"
+ avList = [senderId];
+ args = word.split()
+ import string
+ for i in range(2, len(args)):
+ avList.append(string.atoi(args[i]))
+ manager = GolfManagerAI.GolfManagerAI()
+ #simbase.golfGoer.generateWithRequired(OTPGlobals.UberZone)
+ courseId = 0
+ zoneId = manager.readyGolfCourse(avList, courseId)
+ for avId in avList:
+ golfer = simbase.air.doId2do.get(avId)
+ if golfer:
+ golfer.sendUpdate("sendToGolfCourse", [zoneId])
+ response = 'sending to golf course %d courseId=%d' % \
+ (zoneId, courseId)
+ elif action == 'clearBest':
+ response = 'clearBest failed'
+ av = simbase.air.doId2do.get(senderId)
+ if av:
+ emptyHoleBest = [0] * 18
+ av.b_setGolfHoleBest(emptyHoleBest)
+ emptyCourseBest = [0] * 3
+ av.b_setGolfCourseBest(emptyCourseBest)
+ response = 'golf best cleared'
+ elif action == 'maxBest':
+ response = 'maxBest failed'
+ av = simbase.air.doId2do.get(senderId)
+ if av:
+ emptyHoleBest = [1] * 18
+ av.b_setGolfHoleBest(emptyHoleBest)
+ emptyCourseBest = [1] * 3
+ av.b_setGolfCourseBest(emptyCourseBest)
+ response = 'golf best maxed'
+ elif action == 'clearHistory':
+ response = 'clearHistory failed'
+ emptyHistory = [0] * 18
+ av = simbase.air.doId2do.get(senderId)
+ if av:
+ av.b_setGolfHistory(emptyHistory)
+ response = 'golf history cleared'
+ elif action == 'maxHistory':
+ response = 'maxHistory failed'
+ maxHistory = [600] * 18
+ av = simbase.air.doId2do.get(senderId)
+ if av:
+ av.b_setGolfHistory(maxHistory)
+ response = 'golf history maxeded'
+ elif action == 'midHistory':
+ # set it up so that we just need one more course complete to get a cup
+ response = 'midHistory failed'
+ midHistory = [0] * 18
+ midHistory[GolfGlobals.CoursesCompleted] = GolfGlobals.TrophyRequirements[GolfGlobals.CoursesCompleted][1] - 1
+ #midHistory = [GolfGlobals.CoursesUnderPar] = GolfGlobals.TrophyRequirements[GolfGlobals.CoursesUnderPar][0]
+ midHistory[GolfGlobals.HoleInOneShots] = GolfGlobals.TrophyRequirements[GolfGlobals.HoleInOneShots][0]
+ midHistory[GolfGlobals.EagleOrBetterShots] = GolfGlobals.TrophyRequirements[GolfGlobals.EagleOrBetterShots][0]
+ midHistory[GolfGlobals.BirdieOrBetterShots] = GolfGlobals.TrophyRequirements[GolfGlobals.BirdieOrBetterShots][0]
+ midHistory[GolfGlobals.ParOrBetterShots] = GolfGlobals.TrophyRequirements[GolfGlobals.ParOrBetterShots][0]
+ midHistory[GolfGlobals.MultiPlayerCoursesCompleted] = GolfGlobals.TrophyRequirements[GolfGlobals.MultiPlayerCoursesCompleted][0]
+ midHistory[GolfGlobals.CourseZeroWins] = GolfGlobals.TrophyRequirements[GolfGlobals.CourseZeroWins][0]
+ midHistory[GolfGlobals.CourseOneWins] = GolfGlobals.TrophyRequirements[GolfGlobals.CourseOneWins][0]
+ midHistory[GolfGlobals.CourseTwoWins] = GolfGlobals.TrophyRequirements[GolfGlobals.CourseTwoWins][0]
+
+ av = simbase.air.doId2do.get(senderId)
+ if av:
+ av.b_setGolfHistory(midHistory)
+ response = 'golf history midded'
+ elif action == 'unlimitedSwing' or action == "us":
+ av.b_setUnlimitedSwing(not av.getUnlimitedSwing())
+ if av.getUnlimitedSwing():
+ response = "Av %s has an unlimited swing" % (av.getDoId())
+ else:
+ response = "Av %s does NOT have unlimited swings" % (av.getDoId())
+ elif action == 'hole':
+ import string
+ manager = GolfManagerAI.GolfManagerAI()
+ if len(args) <= 2:
+ # No minigame parameter specified: clear the request.
+ holeRequest = GolfManagerAI.RequestHole.get(av.doId)
+ if holeRequest != None:
+ holeId = holeRequest[0]
+ del GolfManagerAI.RequestHole[av.doId]
+ response = "Request for hole %d cleared." % (holeId)
+ else:
+ response = "Usage: ~golf hole []"
+ else:
+ holeId = None
+ holeKeep = 0
+ name = args[2]
+ try:
+ holeId = int(name)
+ if holeId not in GolfGlobals.HoleInfo:
+ response = "hole ID '%s' is out of range" % holeId
+ holeId = None
+ except:
+ #name = string.lower(name)
+ #for testHoleId in GolfGlobals.HoleInfo:
+ # holeName = string.lower(GolfGlobals.getHoleName(testHoleId))
+ # if name == holeName:
+ # holeId = testHoleId
+ # break;
+ if holeId == None:
+ response = "Unknown hole '%s'." % (name)
+
+ argIndex = 2
+ while argIndex < len(args):
+ arg = args[argIndex]
+ arg = string.lower(arg)
+ argIndex += 1
+
+ # it's either a difficulty (float), 'keep',
+ # or a safezone (string)
+
+ # is it 'keep'?
+ if arg == 'keep':
+ holeKeep = 1
+ continue
+
+ if holeId != None:
+ # hoId must be the first element
+ GolfManagerAI.RequestHole[av.doId] = (
+ holeId, holeKeep)
+ response = "Selected hole %d as 1st hole" % holeId
+ if holeKeep:
+ response += ", keep=true"
+
+
+ if response:
+ self.down_setMagicWordResponse(senderId, response)
+
+
+ def doMail(self,word, av, zoneId, senderId):
+ """Handle mail magic words."""
+ args = word.split()
+ response = None
+ action = None
+
+ if len(args) == 1:
+ # No parameter: change it to usage
+ self.down_setMagicWordResponse(
+ senderId,
+ "Usage:\n~mail action \n'~mail help' for more info ")
+ return
+
+ action = args[1]
+
+ if action == 'simple':
+ if len(args) < 4:
+ response = "~mail simple 'text'"
+ else:
+ recipient = int(args[2])
+ text = args[3]
+ for i in xrange(4, len(args)):
+ text += ' ' + args[i]
+ self.air.mailManager.sendSimpleMail(
+ senderId, recipient, text)
+
+ if response:
+ self.down_setMagicWordResponse(senderId, response)
+
+
+ def doParty(self,word, av, zoneId, senderId):
+ """Handle mail magic words."""
+ args = word.split()
+ response = None
+ action = None
+
+ if len(args) == 1:
+ # No parameter: change it to usage
+ self.down_setMagicWordResponse(
+ senderId,
+ "Available commands: plan, new, update, checkStart, end, debugGrid")
+ return
+
+ action = args[1]
+
+ if action == 'new':
+ if len(args) < 2:
+ response = "~party new ... "
+ else:
+ invitees = []
+ for i in xrange(2, len(args)):
+ invitees.append( int(args[i]))
+ # start the party 1 minute from now
+ startTime = datetime.datetime.now(self.air.toontownTimeManager.serverTimeZone) + datetime.timedelta(minutes=-1)
+ endTime = startTime + datetime.timedelta(hours=PartyGlobals.DefaultPartyDuration)
+
+ from toontown.parties.PartyEditorGrid import PartyEditorGrid
+
+ # Make the avatar rich.
+ av.b_setMaxBankMoney(5000)
+ av.b_setMoney(av.maxMoney)
+ av.b_setBankMoney(av.maxBankMoney)
+
+ gridEditor = PartyEditorGrid(None)
+
+ # Flip on the Y so it matches the grid in-game.
+ gridEditor.grid.reverse()
+
+ # Given a center coord (x or y) and a size (w or h), returns a list of indices in
+ # in the grid on that axis. (WARNING: Can return invalid indices.)
+ def gridComputeRange(centerGrid, size):
+ result = []
+ if size == 1:
+ result = [centerGrid]
+ else:
+ # Need to round with negative values otherwise for center=0, size=3, the
+ # result will be [1, 0] when we expect [1, 0, -1].
+ # The range without rounding: range(int(1.5), int(-1.5), -1)
+ # The range with rounding: range(int(1.5), int(-2), -1)
+ # Not a problem with center>=2 in this example:
+ # The range without rounding: range(int(3.5), int(0.5), -1)
+ # The range with rounding: range(int(3.5), int(0), -1)
+ result = range(int(centerGrid + size/2.0),
+ int(centerGrid - round(size/2.0)), -1)
+
+ # The result list should be the same size as given.
+ assert len(result) == size, "Bad result range: c=%s s=%s result=%s" % (centerGrid, size, result)
+
+ return result
+
+ # Returns true if the given space is available centered at x,y with dims w,h.
+ def gridIsAvailable(x, y, w, h):
+ for j in gridComputeRange(y, h):
+ if 0 > j or j >= len(gridEditor.grid):
+ return False
+ for i in gridComputeRange(x, w):
+ if 0 > i or i >= len(gridEditor.grid[0]):
+ return False
+ if gridEditor.grid[j][i] == None:
+ return False
+
+ #print "grid available: xy(%s %s) wh(%s %s)" % (x, y, w, h)
+ return True
+
+ # Returns the first x,y (centered) that has space for w,h.
+ def gridGetAvailable(w, h):
+ for y in range(len(gridEditor.grid)):
+ for x in range(len(gridEditor.grid[0])):
+ if gridIsAvailable(x, y, w, h):
+ return x, y
+ return None, None
+
+ # Returns True and an x,y (centered) coord for the given space. Marks that space used.
+ def gridTryPlace(w, h):
+ x, y = gridGetAvailable(w, h)
+ if not x == None:
+ for j in gridComputeRange(y, h):
+ for i in gridComputeRange(x, w):
+ gridEditor.grid[j][i] = None
+ return True, x, y
+ else:
+ return False, None, None
+
+ actualActIdsToAdd = [
+ #PartyGlobals.ActivityIds.PartyJukebox, # mut.ex: PartyJukebox40
+ PartyGlobals.ActivityIds.PartyCannon,
+ #PartyGlobals.ActivityIds.PartyTrampoline,
+ PartyGlobals.ActivityIds.PartyCatch,
+ #PartyGlobals.ActivityIds.PartyDance, # mut.ex: PartyDance20
+ PartyGlobals.ActivityIds.PartyTugOfWar,
+ PartyGlobals.ActivityIds.PartyFireworks,
+ PartyGlobals.ActivityIds.PartyClock,
+ PartyGlobals.ActivityIds.PartyJukebox40,
+ PartyGlobals.ActivityIds.PartyDance20,
+ PartyGlobals.ActivityIds.PartyCog,
+ PartyGlobals.ActivityIds.PartyVictoryTrampoline, # victory party
+ ]
+
+ actualDecorIdsToAdd = [
+ PartyGlobals.DecorationIds.BalloonAnvil,
+ PartyGlobals.DecorationIds.BalloonStage,
+ PartyGlobals.DecorationIds.Bow,
+ PartyGlobals.DecorationIds.Cake,
+ PartyGlobals.DecorationIds.Castle,
+ PartyGlobals.DecorationIds.GiftPile,
+ PartyGlobals.DecorationIds.Horn,
+ PartyGlobals.DecorationIds.MardiGras,
+ PartyGlobals.DecorationIds.NoiseMakers,
+ PartyGlobals.DecorationIds.Pinwheel,
+ PartyGlobals.DecorationIds.GagGlobe,
+ #PartyGlobals.DecorationIds.BannerJellyBean,
+ PartyGlobals.DecorationIds.CakeTower,
+ #PartyGlobals.DecorationIds.HeartTarget, # valentoons
+ #PartyGlobals.DecorationIds.HeartBanner, # valentoons
+ #PartyGlobals.DecorationIds.FlyingHeart, # valentoons
+ PartyGlobals.DecorationIds.Hydra, # 16: victory party
+ PartyGlobals.DecorationIds.BannerVictory, # 17: victory party
+ PartyGlobals.DecorationIds.CannonVictory, # 18: victory party
+ PartyGlobals.DecorationIds.CogStatueVictory, # 19: victory party
+ PartyGlobals.DecorationIds.TubeCogVictory, # 20: victory party
+ PartyGlobals.DecorationIds.cogIceCreamVictory, # 21: victory party
+ ]
+
+ activities = []
+
+ for itemId in actualActIdsToAdd:
+ item = PartyGlobals.ActivityInformationDict[itemId]
+ success, x, y = gridTryPlace(*item['gridsize'])
+ if success:
+ print "~party new ADDED: Activity %s %s at %s, %s" % (itemId, str(item['gridsize']), x, y)
+ # item index, grid x, grid y, heading
+ partyItem = (itemId, x, y, 0)
+ activities.append(partyItem)
+ else:
+ print "~party new SKIPPED: No room for activity %s" % itemId
+
+ decorations = []
+
+ for itemId in actualDecorIdsToAdd:
+ item = PartyGlobals.DecorationInformationDict[itemId]
+ success, x, y = gridTryPlace(*item['gridsize'])
+ if success:
+ print "~party new ADDED: Decoration %s %s at %s, %s" % (itemId, str(item['gridsize']), x, y)
+ # item index, grid x, grid y, heading
+ partyItem = (itemId, x, y, 0)
+ decorations.append(partyItem)
+ else:
+ print "~party new SKIPPED: No room for decoration %s" % itemId
+
+ isPrivate = False
+ inviteTheme = PartyGlobals.InviteTheme.Birthday
+ self.air.partyManager.addPartyRequest(
+ senderId,
+ startTime.strftime("%Y-%m-%d %H:%M:%S"),
+ endTime.strftime("%Y-%m-%d %H:%M:%S"),
+ isPrivate,
+ inviteTheme,
+ activities,
+ decorations,
+ invitees,
+ )
+ # force an immediate check of which parties can start
+ self.air.partyManager.forceCheckStart()
+
+ elif action == 'update':
+ # simulate this avatarLogging in, which forces invites
+ # and party updates from the dbs
+ self.air.partyManager.partyUpdate(senderId)
+
+ elif action == 'checkStart':
+ # force an immediate check of which parties can start
+ self.air.partyManager.forceCheckStart()
+
+ elif action == 'unreleasedServer':
+ newVal = self.air.partyManager.toggleAllowUnreleasedServer()
+ response = "Allow Unreleased Server= %s" % newVal
+
+ elif action == 'canBuy':
+ newVal = self.air.partyManager.toggleCanBuyParties()
+ response = "can buy parties= %s" % newVal
+
+ elif action == 'end':
+ response = self.air.partyManager.magicWordEnd(senderId)
+
+ elif action == 'plan':
+ response = "Going to party grounds to plan"
+
+ # hoodId determines the loading
+ hoodId = ToontownGlobals.PartyHood
+
+ self.sendUpdateToAvatarId(av.doId, 'requestTeleport',
+ ["safeZoneLoader", "party",
+ hoodId, 0, 0])
+
+ if response:
+ self.down_setMagicWordResponse(senderId, response)
diff --git a/toontown/src/ai/TrashcanBuffHolidayAI.py b/toontown/src/ai/TrashcanBuffHolidayAI.py
new file mode 100644
index 0000000..06e0ab6
--- /dev/null
+++ b/toontown/src/ai/TrashcanBuffHolidayAI.py
@@ -0,0 +1,16 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.ai import HolidayBaseAI
+from toontown.ai import PropBuffHolidayAI
+from toontown.ai import DistributedPhaseEventMgrAI
+from toontown.toonbase import ToontownGlobals
+
+class TrashcanBuffHolidayAI(PropBuffHolidayAI.PropBuffHolidayAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'TrashcanBuffHolidayAI')
+
+ PostName = 'TrashcanBuffHoliday'
+
+ def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
+ PropBuffHolidayAI.PropBuffHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)
+
diff --git a/toontown/src/ai/TrashcanZeroHolidayAI.py b/toontown/src/ai/TrashcanZeroHolidayAI.py
new file mode 100644
index 0000000..84402e0
--- /dev/null
+++ b/toontown/src/ai/TrashcanZeroHolidayAI.py
@@ -0,0 +1,47 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.ai import HolidayBaseAI
+from toontown.ai import PhasedHolidayAI
+from toontown.ai import DistributedTrashcanZeroMgrAI
+from toontown.toonbase import ToontownGlobals
+
+class TrashcanZeroHolidayAI(PhasedHolidayAI.PhasedHolidayAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'TrashcanZeroHolidayAI')
+
+ PostName = 'trashcanZeroHoliday'
+
+ def __init__(self, air, holidayId, startAndEndTimes, phaseDates):
+ PhasedHolidayAI.PhasedHolidayAI.__init__(self, air, holidayId, startAndEndTimes, phaseDates)
+
+ def start(self):
+ # instantiate the object
+ PhasedHolidayAI.PhasedHolidayAI.start(self)
+ self.trashcanZeroMgr = DistributedTrashcanZeroMgrAI.DistributedTrashcanZeroMgrAI (
+ self.air, self.startAndEndTimes, self.phaseDates)
+ self.trashcanZeroMgr.generateWithRequired(ToontownGlobals.UberZone)
+ # let the holiday system know we started
+ bboard.post(self.PostName)
+
+ def stop(self):
+ # let the holiday system know we stopped
+ bboard.remove(self.PostName)
+ # remove the object
+ #self.resistanceEmoteMgr.requestDelete()
+ self.trashcanZeroMgr.requestDelete()
+
+ def forcePhase(self, newPhase):
+ """Force our holiday to a certain phase. Returns true if succesful"""
+ result = False
+ try:
+ newPhase = int(newPhase)
+ except:
+ newPhase = 0
+ if newPhase >= self.trashcanZeroMgr.getNumPhases():
+ self.notify.warning("newPhase %d invalid in forcePhase" % newPhase)
+ return
+ self.curPhase = newPhase
+ self.trashcanZeroMgr.forcePhase(newPhase)
+ result = True
+ return result
+
diff --git a/toontown/src/ai/TrickOrTreatMgrAI.py b/toontown/src/ai/TrickOrTreatMgrAI.py
new file mode 100644
index 0000000..8553714
--- /dev/null
+++ b/toontown/src/ai/TrickOrTreatMgrAI.py
@@ -0,0 +1,103 @@
+import ScavengerHuntMgrAI
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals
+from toontown.ai import DistributedTrickOrTreatTargetAI
+from otp.otpbase import OTPGlobals
+
+import time
+
+GOALS = {
+ 0 : 2649, # TTC
+ 1 : 1834, # DD
+ 2 : 4835, # MM
+ 3 : 5620, # DG
+ 4 : 3707, # BR
+ 5 : 9619, # DL
+ }
+
+# This dictionary defines the milestones for this scavenger hunt
+MILESTONES = {
+ 0: ((0, 1, 2, 3, 4, 5), 'All Trick-or-Treat goals found'),
+ }
+
+class TrickOrTreatMgrAI(ScavengerHuntMgrAI.ScavengerHuntMgrAI):
+ """
+ This is the TrickOrTreat manager that extends the scanvenger hunt
+ by providing unique rewards and milestones.
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('TrickOrTreatMgrAI')
+
+ def __init__(self, air, holidayId):
+ ScavengerHuntMgrAI.ScavengerHuntMgrAI.__init__(self, air, holidayId)
+
+ def createListeners(self):
+ """
+ Create the listeners that will look for an event in the relavent zone
+ """
+ for id in self.goals.keys():
+ mgrAI = DistributedTrickOrTreatTargetAI.DistributedTrickOrTreatTargetAI(self.air,
+ self.hunt,
+ id,
+ self,
+ )
+ self.targets[id] = mgrAI
+ self.targets[id].generateWithRequired(self.goals[id])
+
+ @property
+ def goals(self):
+ return GOALS
+
+ @property
+ def milestones(self):
+ return MILESTONES
+
+ def huntCompletedReward(self, avId, goal, firstTime = False):
+ """
+ Reward the Toon for completing the TrickOrTreat with
+ a pumpkin head
+ """
+ if firstTime:
+ self.air.writeServerEvent('pumpkinHeadEarned', avId, 'Trick-or-Treat scavenger hunt complete.')
+
+ av = self.air.doId2do.get(avId)
+ localTime = time.localtime()
+ date = (localTime[0],
+ localTime[1],
+ localTime[2],
+ localTime[6],
+ )
+
+ from toontown.ai import HolidayManagerAI
+ endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(date)
+ endTime += ToontownGlobals.TOT_REWARD_END_OFFSET_AMOUNT
+
+ if not av:
+ self.notify.warning(
+ 'Tried to send milestone feedback to av %s, but they left' % avId)
+ else:
+ av.b_setCheesyEffect(OTPGlobals.CEPumpkin, 0, (time.time()/60)+1)
+ #av.b_setCheesyEffect(OTPGlobals.CEPumpkin, 0, endTime/60)
+
+ def huntGoalAlreadyFound(self, avId):
+ """
+ This goal has already been found
+ """
+ av = self.air.doId2do.get(avId)
+ if not av:
+ self.notify.warning(
+ 'Tried to send goal feedback to av %s, but they left' % avId)
+ else:
+ av.sendUpdate('trickOrTreatTargetMet', [0])
+
+ def huntGoalFound(self, avId, goal):
+ """
+ One of the goals in the milestone were found,
+ so we reward the toon.
+ """
+ av = self.air.doId2do.get(avId)
+ # Do all the updates at once
+ av.addMoney(ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT)
+ self.avatarCompletedGoal(avId, goal)
+ # Start jellybean reward effect
+ av.sendUpdate('trickOrTreatTargetMet', [ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT])
\ No newline at end of file
diff --git a/toontown/src/ai/UtilityAIRepository.py b/toontown/src/ai/UtilityAIRepository.py
new file mode 100644
index 0000000..ab6239f
--- /dev/null
+++ b/toontown/src/ai/UtilityAIRepository.py
@@ -0,0 +1,30 @@
+from direct.directnotify import DirectNotifyGlobal
+from otp.ai import AIDistrict
+
+class UtilityAIRepository(AIDistrict.AIDistrict):
+ """
+ This class serves as the AI repository for a maintenance utility
+ (e.g. RepairAvatars) that wants to get an AI-level connection to
+ the message director without actually creating an AI district.
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ "UtilityAIRepository")
+
+
+ def __init__(self, *args, **kw):
+ AIDistrict.AIDistrict.__init__(self, *args, **kw)
+
+ # The UtilityAIRepository sets this to 0 to indicate we should
+ # not do things like issue new catalogs to toons that we load
+ # in. However, in the normal AI repository, we should do
+ # these things.
+ self.doLiveUpdates = 0
+
+ def requestNewDistrict(self):
+ pass
+
+ def createDistrict(self, districtId, districtName):
+ self.fsm.request('playGame')
+
+ def deleteDistrict(self, districtId):
+ pass
diff --git a/toontown/src/ai/UtilityStart.py b/toontown/src/ai/UtilityStart.py
new file mode 100644
index 0000000..f5147f3
--- /dev/null
+++ b/toontown/src/ai/UtilityStart.py
@@ -0,0 +1,45 @@
+import time
+import os
+import sys
+
+# Initialize ihooks importer On the production servers, we run genPyCode -n
+# meaning no squeeze, so nobody else does this. When we squeeze, the
+# unpacker does this for us and it does not hurt to do in either case.
+import ihooks
+ihooks.install()
+
+print "Initializing..."
+
+from otp.ai.AIBaseGlobal import *
+import UtilityAIRepository
+
+simbase.mdip = simbase.config.GetString("msg-director-ip", "localhost")
+simbase.mdport = simbase.config.GetInt("msg-director-port", 6665)
+simbase.esip = simbase.config.GetString("event-server-ip", "localhost")
+simbase.esport = simbase.config.GetInt("event-server-port", 4343)
+
+districtType = 0
+ssId = simbase.config.GetInt("utility-ssid", 20100000)
+utilityChannel = simbase.config.GetInt("utility-channel", 399900000)
+
+if simbase.config.GetBool("want-dev", 0):
+ # In development, the dcfiles are specified in prc files
+ dcFileNames = None
+else:
+ # In production we have to list them out
+ dcFileNames = ['otp.dc', 'toon.dc']
+
+simbase.air = UtilityAIRepository.UtilityAIRepository(simbase.mdip,
+ simbase.mdport,
+ simbase.esip,
+ simbase.esport,
+ dcFileNames,
+ 1,
+ "Utility",
+ districtType,
+ ssId,
+ utilityChannel,
+ utilityChannel + 1)
+
+# How we let the world know we are not running a service
+simbase.aiService = 0
diff --git a/toontown/src/ai/ValentinesDayMgrAI.py b/toontown/src/ai/ValentinesDayMgrAI.py
new file mode 100644
index 0000000..3eb5262
--- /dev/null
+++ b/toontown/src/ai/ValentinesDayMgrAI.py
@@ -0,0 +1,23 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals, TTLocalizer
+from toontown.ai import HolidayBaseAI
+
+class ValentinesDayMgrAI(HolidayBaseAI.HolidayBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'ValentinesDayMgrAI')
+
+ PostName = 'ValentinesDay'
+ StartStopMsg = 'ValentinesDayStartStop'
+
+ def __init__(self, air, holidayId):
+ HolidayBaseAI.HolidayBaseAI.__init__(self, air, holidayId)
+
+ def start(self):
+ # Let the holiday system know we started
+ bboard.post(ValentinesDayMgrAI.PostName, True)
+
+ def stop(self):
+ # Let the holiday system know we stopped
+ bboard.remove(ValentinesDayMgrAI.PostName)
+
\ No newline at end of file
diff --git a/toontown/src/ai/WelcomeValleyManager.py b/toontown/src/ai/WelcomeValleyManager.py
new file mode 100644
index 0000000..2acbf30
--- /dev/null
+++ b/toontown/src/ai/WelcomeValleyManager.py
@@ -0,0 +1,62 @@
+from pandac.PandaModules import *
+
+from direct.distributed import DistributedObject
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals
+from direct.showbase import PythonUtil
+
+class WelcomeValleyManager(DistributedObject.DistributedObject):
+ """WelcomeValleyManager
+
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory("WelcomeValleyManager")
+
+ neverDisable = 1
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+
+ ### DistributedObject methods ###
+
+ def generate(self):
+ """generate(self)
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ if base.cr.welcomeValleyManager != None:
+ base.cr.welcomeValleyManager.delete()
+ base.cr.welcomeValleyManager = self
+ DistributedObject.DistributedObject.generate(self)
+
+ def disable(self):
+ """disable(self)
+ This method is called when the DistributedObject is removed from
+ active duty and stored in a cache.
+ """
+ self.ignore(ToontownGlobals.SynchronizeHotkey)
+ base.cr.welcomeValleyManager = None
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ """delete(self)
+ This method is called when the DistributedObject is permanently
+ removed from the world and deleted from the cache.
+ """
+ self.ignore(ToontownGlobals.SynchronizeHotkey)
+ base.cr.welcomeValleyManager = None
+ DistributedObject.DistributedObject.delete(self)
+
+# now done locally on the AI
+## def d_clientSetZone(self, zoneId):
+## # Tell the AI which zone we're going to. We don't bother to
+## # do this for every little zone on the street, just major zone
+## # changes (e.g. through the quiet zone).
+## self.sendUpdate("clientSetZone", [zoneId])
+
+ def requestZoneId(self, origZoneId, callback):
+ context = self.getCallbackContext(callback)
+ self.sendUpdate("requestZoneIdMessage", [origZoneId, context])
+
+
+ def requestZoneIdResponse(self, zoneId, context):
+ self.doCallbackContext(context, [zoneId])
diff --git a/toontown/src/ai/WelcomeValleyManagerAI.py b/toontown/src/ai/WelcomeValleyManagerAI.py
new file mode 100644
index 0000000..08b1fa1
--- /dev/null
+++ b/toontown/src/ai/WelcomeValleyManagerAI.py
@@ -0,0 +1,488 @@
+from pandac.PandaModules import *
+
+from direct.distributed import DistributedObjectAI
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals
+from toontown.hood import ZoneUtil
+from toontown.hood import TTHoodDataAI
+from toontown.hood import GSHoodDataAI
+from direct.task import Task
+import random
+
+# These are the population thresholds we use to balance our automatic
+# creation of WelcomeValleys.
+
+# The minimum number of people that should be in the playground before
+# we start to phase out the hood.
+PGminimum = 1
+
+# The "stable" watermark; the first time we reach this number of
+# people in the playground, we consider the hood to be in a stable
+# state.
+PGstable = 15
+
+# The maximum number of people in the playground before we stop adding
+# people to the hood.
+PGmaximum = 20
+
+# How often, in seconds, to report the current WelcomeValley state to
+# the log.
+LogInterval = 300
+
+
+# The basic balancing algorithm for creating and destroying
+# WelcomeValley hoods is as follows.
+
+# There are three classes of hoods: New, Stable, and Removing.
+# At any given time, there may be either zero or one New hoods, any
+# number of Stable hoods, and any number of Removing hoods.
+# Initially there are no hoods.
+
+# When a new avatar arrives, it will be assigned to the New hood if
+# there is one; otherwise, it will be assigned to the Stable hood with
+# the smallest playground population, unless there are no Stable hoods
+# with a population less than PGmaximum (in which case a New hood will
+# be created).
+
+# When a New hood is created, it will continue to be considered New
+# (and thus receive all newly arriving avatars), until its playground
+# population reaches PGstable, at which point the hood is moved into
+# the Stable pool.
+
+# If at any point the playground population of a hood in the Stable
+# pool decreases below PGminimum (and it is not the only hood
+# remaining), it is moved to the Removing pool. A suitable
+# replacement hood is chosen using the algorithm for a new avatar,
+# above, and this new hood is associated with the Removing hood. Any
+# avatar that requests a zone change to or within a Removing hood is
+# instead redirected the hood's replacement. (Note that clients who
+# are teleporting to a friend in a Removing hood do not request this
+# zone change via the AI, and so will not be redirected to a different
+# hood--they will still arrive in the same hood with their friend.)
+# For the purposes of balancing, we consider the replacement hood
+# immediately contains its population plus that of the Removing
+# hood.
+
+# Finally, if the total population of any hood reaches zero, the hood
+# is completely removed.
+
+# There are a few considerations that have led to this algorithm.
+# Firstly, we want to minimize the amount of time a playground is
+# nearly empty; you should always be able to enter the game and see
+# several people in the playground. Thus, we aggressively fill a New
+# hood up quickly, instead of distributing new avatars evenly among
+# all available hoods; and once we decide to remove a hood we
+# aggressively move avatars off it at the first opportunity.
+# Secondly, we would like to keep avatars together whenever possible;
+# thus, when we decide to remove a hood, we choose only one hood to be
+# its replacement, and all avatars are moved from the source hood to
+# the same replacement hood (even if this will result in an overfull
+# replacement hood).
+
+
+class WelcomeValleyManagerAI(DistributedObjectAI.DistributedObjectAI):
+ notify = DirectNotifyGlobal.directNotify.newCategory("WelcomeValleyManagerAI")
+
+ def __init__(self, air):
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+
+ self.welcomeValleyAllocator = UniqueIdAllocator(
+ ToontownGlobals.WelcomeValleyBegin / 2000,
+ ToontownGlobals.WelcomeValleyEnd / 2000 - 1)
+ self.welcomeValleys = {}
+ self.avatarZones = {}
+
+ self.newHood = None
+ self.stableHoods = []
+ self.removingHoods = []
+
+ def generate(self):
+ DistributedObjectAI.DistributedObjectAI.generate(self)
+
+ if simbase.config.GetBool('report-welcome-valleys', 0):
+ self.doReportLater()
+
+ def delete(self):
+ name = self.taskName("WelcomeValleyLog")
+ taskMgr.remove(name)
+ self.ignoreAll()
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+# now done locally on the AI
+## def clientSetZone(self, zoneId):
+## """
+## This is used by the client to inform the AI which zone he is
+## going to. It is mainly used to balance the WelcomeValley zones,
+## so the AI can know how many avatars are in each WelcomeValley.
+## """
+## avId = self.air.getAvatarIdFromSender()
+## lastZoneId = self.avatarSetZone(avId, zoneId)
+
+## # Temporary kludge to ensure ghost mode doesn't remain on
+## # longer than it should--that's probably the result of an
+## # unintended bug. If ghost mode is on, we turn it off
+## # whenever the client switches zones. Note that this is not a
+## # real solution, since a hacked client can simply not send the
+## # clientSetZone messages.
+## avatar = self.air.doId2do.get(avId)
+## if avatar and avatar.ghostMode:
+## self.air.writeServerEvent('suspicious', avId, 'has ghost mode %s transitioning from zone %s to %s' % (avatar.ghostMode, lastZoneId, zoneId))
+## if avatar.ghostMode == 1:
+## avatar.b_setGhostMode(0)
+
+## if avatar and avatar.cogIndex >= 0:
+## if (lastZoneId != 11100 and lastZoneId != 12100 and lastZoneId != 13100 ) or \
+## (zoneId < 61000 and zoneId != 11000 and zoneId != 12000 and zoneId != 13000):
+## self.air.writeServerEvent('suspicious', avId, 'has cogIndex %s transitioning from zone %s to %s' % (avatar.cogIndex, lastZoneId, zoneId))
+## avatar.b_setCogIndex(-1)
+
+ def toonSetZone(self, avId, zoneId):
+ lastZoneId = self.avatarSetZone(avId, zoneId)
+
+ # Temporary kludge to ensure ghost mode doesn't remain on
+ # longer than it should--that's probably the result of an
+ # unintended bug. If ghost mode is on, we turn it off
+ # whenever the client switches zones. Note that this is not a
+ # real solution, since a hacked client can simply not send the
+ # clientSetZone messages.
+ avatar = self.air.doId2do.get(avId)
+ if avatar and avatar.ghostMode:
+ if avatar.ghostMode == 1:
+ avatar.b_setGhostMode(0)
+
+ if avatar and avatar.cogIndex >= 0:
+ if (lastZoneId != 11100 and lastZoneId != 12100 and lastZoneId != 13100 ) or \
+ (zoneId < 61000 and zoneId != 11000 and zoneId != 12000 and zoneId != 13000):
+ avatar.b_setCogIndex(-1)
+
+ def avatarSetZone(self, avId, zoneId):
+ lastZoneId = self.avatarZones.get(avId)
+ if lastZoneId == zoneId:
+ return lastZoneId
+
+ if zoneId == None:
+ self.notify.debug("Avatar %s has left the shard." % (avId))
+ del self.avatarZones[avId]
+ self.ignore(self.air.getAvatarExitEvent(avId))
+ else:
+ self.notify.debug("Avatar %s is now in zone %s." % (avId, zoneId))
+
+ # First, add the avatar into his/her new zone. We must do
+ # this first so we don't risk momentarily bringing the
+ # hood population to 0.
+ hoodId = ZoneUtil.getHoodId(zoneId)
+
+ # if this is a GS hoodId, just grab the TT hood
+ if (hoodId % 2000) < 1000:
+ hood = self.welcomeValleys.get(hoodId)
+ else:
+ hood = self.welcomeValleys.get(hoodId - 1000)
+
+ if hood:
+ hood[0].incrementPopulation(zoneId, 1)
+ if (hood == self.newHood) and hood[0].getPgPopulation() >= PGstable:
+ # This is now a stable hood.
+ self.__newToStable(hood)
+
+ self.avatarZones[avId] = zoneId
+
+ if lastZoneId == None:
+ # This is the first time we have heard from this avatar.
+ self.acceptOnce(self.air.getAvatarExitEvent(avId),
+ self.avatarLogout, extraArgs=[avId])
+ else:
+ # Now, remove the avatar from his/her previous zone.
+ lastHoodId = ZoneUtil.getHoodId(lastZoneId)
+
+ # if this is a GS hoodId, just grab the TT hood
+ if (lastHoodId % 2000) < 1000:
+ hood = self.welcomeValleys.get(lastHoodId)
+ else:
+ hood = self.welcomeValleys.get(lastHoodId - 1000)
+
+ if hood:
+ hood[0].incrementPopulation(lastZoneId, -1)
+ if hood[0].getHoodPopulation() == 0:
+ self.__hoodIsEmpty(hood)
+
+ elif (hood != self.newHood) and not hood[0].hasRedirect() and \
+ hood[0].getPgPopulation() < PGminimum:
+ self.__stableToRemoving(hood)
+
+ return lastZoneId
+
+ def avatarLogout(self, avId):
+ self.avatarSetZone(avId, None)
+
+ def makeNew(self, hoodId):
+ # Makes the indicated hoodId new, if possible. Used for magic
+ # word purposes only. Returns a string describing the action.
+ hood = self.welcomeValleys.get(hoodId)
+ if hood == None:
+ return "Hood %s is unknown." % (hoodId)
+ if hood == self.newHood:
+ return "Hood %s is already new." % (hoodId)
+ if self.newHood != None:
+ self.__newToStable(self.newHood)
+
+ if hood in self.removingHoods:
+ self.removingHoods.remove(hood)
+ hood[0].setRedirect(None)
+ else:
+ self.stableHoods.remove(hood)
+
+ self.newHood = hood
+ return "Hood %s is now New." % (hoodId)
+
+ def makeStable(self, hoodId):
+ # Moves the indicated hoodId to the Stable pool, if possible.
+ # Used for magic word purposes only. Returns a string
+ # describing the action.
+ hood = self.welcomeValleys.get(hoodId)
+ if hood == None:
+ return "Hood %s is unknown." % (hoodId)
+ if hood in self.stableHoods:
+ return "Hood %s is already Stable." % (hoodId)
+
+ if hood == self.newHood:
+ self.__newToStable(hood)
+ else:
+ self.removingHoods.remove(hood)
+ hood[0].setRedirect(None)
+ self.stableHoods.append(hood)
+
+ return "Hood %s is now Stable." % (hoodId)
+
+ def makeRemoving(self, hoodId):
+ # Moves the indicated hoodId to the Removing pool, if possible.
+ # Used for magic word purposes only. Returns a string
+ # describing the action.
+ hood = self.welcomeValleys.get(hoodId)
+ if hood == None:
+ return "Hood %s is unknown." % (hoodId)
+ if hood in self.removingHoods:
+ return "Hood %s is already Removing." % (hoodId)
+
+ if hood == self.newHood:
+ self.__newToStable(hood)
+
+ self.__stableToRemoving(hood)
+
+ return "Hood %s is now Removing." % (hoodId)
+
+ def checkAvatars(self):
+ # Checks that all of the avatars recorded as being logged in
+ # are actually still in the doId2do map, and logs out any that
+ # are not. Returns a list of the removed avId's. This is
+ # normally used for magic word purposes only.
+ removed = []
+ for avId in self.avatarZones.keys():
+ if avId not in self.air.doId2do:
+ # Here's one for removing.
+ removed.append(avId)
+ self.avatarLogout(avId)
+
+ return removed
+
+ def __newToStable(self, hood):
+ # This New hood's population has reached the stable limit;
+ # mark it as a Stable hood.
+ self.notify.info("Hood %s moved to Stable." % (hood[0].zoneId))
+
+ assert(hood == self.newHood)
+ self.newHood = None
+ self.stableHoods.append(hood)
+
+ def __stableToRemoving(self, hood):
+ # This hood's population has dropped too low;
+ # schedule it for removal.
+ self.notify.info("Hood %s moved to Removing." % (hood[0].zoneId))
+
+ assert(hood in self.stableHoods)
+ self.stableHoods.remove(hood)
+ replacementHood = self.chooseWelcomeValley(allowCreateNew = 0)
+ if replacementHood == None:
+ # Hmm, we couldn't find a suitable
+ # replacement, so just keep this one.
+ self.stableHoods.append(hood)
+ else:
+ hood[0].setRedirect(replacementHood)
+ self.removingHoods.append(hood)
+
+ def __hoodIsEmpty(self, hood):
+ self.notify.info("Hood %s is now empty." % (hood[0].zoneId))
+
+ replacementHood = hood[0].replacementHood
+ self.destroyWelcomeValley(hood)
+
+ # Also check the hood this one is redirecting to; we might
+ # have just emptied it too.
+ if replacementHood and replacementHood[0].getHoodPopulation() == 0:
+ self.__hoodIsEmpty(replacementHood)
+
+
+
+ def avatarRequestZone(self, avId, origZoneId):
+ # This services a redirect-zone request for a particular
+ # avatar. The client is requesting to go to the indicated
+ # zoneId, which should be a WelcomeValley zoneId; the AI
+ # should choose which particular WelcomeValley to direct the
+ # client to.
+
+ if not ZoneUtil.isWelcomeValley(origZoneId):
+ # All requests for static zones are returned unchanged.
+ return origZoneId
+
+ origHoodId = ZoneUtil.getHoodId(origZoneId)
+ hood = self.welcomeValleys.get(origHoodId)
+ if not hood:
+ # If we don't know this hood, choose a new one.
+ hood = self.chooseWelcomeValley()
+
+ if not hood:
+ self.notify.warning("Could not create new WelcomeValley hood for avatar %s." % (avId))
+ zoneId = ZoneUtil.getCanonicalZoneId(origZoneId)
+ else:
+ # use TTC hoodId
+ hoodId = hood[0].getRedirect().zoneId
+ zoneId = ZoneUtil.getTrueZoneId(origZoneId, hoodId)
+
+ # Even though the client might choose not to go to the
+ # indicated zoneId for some reason, we will consider the
+ # client as having gone there immediately, for the purposes of
+ # balancing. If the client goes somewhere else instead, it
+ # will tell us that and we can correct this.
+ self.avatarSetZone(avId, zoneId)
+
+ return zoneId
+
+
+ def requestZoneIdMessage(self, origZoneId, context):
+ """
+
+ This message is sent from the client to request a new zoneId
+ for a transition to WelcomeValley.
+
+ """
+ avId = self.air.getAvatarIdFromSender()
+ zoneId = self.avatarRequestZone(avId, origZoneId)
+
+ self.sendUpdateToAvatarId(avId, "requestZoneIdResponse",
+ [zoneId, context])
+
+
+ def chooseWelcomeValley(self, allowCreateNew = 1):
+ # Picks a hood to assign a new avatar to. If allowCreateNew
+ # is 1, a new hood may be created if necessary.
+
+ # First, if we have a New hood, prefer that one.
+ if self.newHood:
+ return self.newHood
+
+ # Next, choose the Stable hood with the smallest playground
+ # population.
+ bestHood = None
+ bestPopulation = None
+ for hood in self.stableHoods:
+ population = hood[0].getPgPopulation()
+ if bestHood == None or population < bestPopulation:
+ bestHood = hood
+ bestPopulation = population
+
+ # Unless there are no hoods with a small-enough population, in
+ # which case we create another New hood.
+ if allowCreateNew and (bestHood == None or bestPopulation >= PGmaximum):
+ self.newHood = self.createWelcomeValley()
+ if self.newHood:
+ self.notify.info("Hood %s is New." % self.newHood[0].zoneId)
+ return self.newHood
+
+ return bestHood
+
+ def createWelcomeValley(self):
+ # Creates new copy of ToontownCentral and Goofy Speedway and returns
+ # thier HoodDataAI. Returns None if no new hoods can be created.
+
+ index = self.welcomeValleyAllocator.allocate()
+ if index == -1:
+ return None
+
+ # TTC
+ ttHoodId = index * 2000
+ ttHood = TTHoodDataAI.TTHoodDataAI(self.air, ttHoodId)
+ self.air.startupHood(ttHood)
+
+ # GS
+ gsHoodId = index * 2000 + 1000
+ gsHood = GSHoodDataAI.GSHoodDataAI(self.air, gsHoodId)
+ self.air.startupHood(gsHood)
+
+ # both hoods are stored in a tuple and referenced by the TTC hoodId
+ self.welcomeValleys[ttHoodId] = (ttHood, gsHood)
+
+ # create a pond bingo manager ai for the new WV
+ if simbase.wantBingo:
+ self.notify.info('creating bingo mgr for Welcome Valley %s' % ttHoodId)
+ self.air.createPondBingoMgrAI(ttHood)
+
+ return (ttHood, gsHood)
+
+ def destroyWelcomeValley(self, hood):
+ hoodId = hood[0].zoneId
+ assert((hoodId % 2000) == 0)
+
+ del self.welcomeValleys[hoodId]
+ self.welcomeValleyAllocator.free(hoodId / 2000)
+ self.air.shutdownHood(hood[0])
+ self.air.shutdownHood(hood[1])
+
+ if self.newHood == hood:
+ self.newHood = None
+ elif hood in self.removingHoods:
+ self.removingHoods.remove(hood)
+ elif hood in self.stableHoods:
+ self.stableHoods.remove(hood)
+
+ def doReportLater(self):
+ name = self.taskName("WelcomeValleyLog")
+ taskMgr.remove(name)
+ taskMgr.doMethodLater(LogInterval, self.doReportTask, name)
+
+ def doReportTask(self, task):
+ self.reportWelcomeValleys()
+ self.doReportLater()
+ return Task.done
+
+ def getAvatarCount(self):
+ # Players
+ # the Welcome Valley hoods.
+ if simbase.fakeDistrictPopulations:
+ return 0
+ answer = 0
+ hoodIds = self.welcomeValleys.keys()
+ for hoodId in hoodIds:
+ hood = self.welcomeValleys[hoodId]
+ answer += hood[0].getHoodPopulation()
+
+ return answer
+
+ def reportWelcomeValleys(self):
+ # Writes a message to the log file showing the current state
+ # of the Welcome Valley hoods.
+
+ self.notify.info("Current Welcome Valleys:")
+ hoodIds = self.welcomeValleys.keys()
+ hoodIds.sort()
+ for hoodId in hoodIds:
+ hood = self.welcomeValleys[hoodId]
+ if hood == self.newHood:
+ flag = "N"
+ elif hood in self.removingHoods:
+ flag = "R"
+ else:
+ flag = " "
+
+ self.notify.info("%s %s %s/%s" % (
+ hood[0].zoneId, flag,
+ hood[0].getPgPopulation(), hood[0].getHoodPopulation()))
+
diff --git a/toontown/src/ai/WinterCarolingMgrAI.py b/toontown/src/ai/WinterCarolingMgrAI.py
new file mode 100644
index 0000000..165d0fc
--- /dev/null
+++ b/toontown/src/ai/WinterCarolingMgrAI.py
@@ -0,0 +1,112 @@
+import ScavengerHuntMgrAI
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals
+from toontown.ai import DistributedWinterCarolingTargetAI
+from otp.otpbase import OTPGlobals
+
+import time
+
+GOALS = {
+ 0 : 2659, # Joy Buzzer to the world, Silly Street, Toontown Central
+ 1 : 1707, # Gifts With A Porpoise, Seaweed Street, Donalds Dock
+ 2 : 5626, # Pine Needle Crafts, Elm Street, Daisy's Garden
+ 3 : 4614, # Shave and Haircut for a song, Alto Avenue, Minnie's Melodyland
+ 4 : 3828, # Snowman's Land, Polar Place, The Brrrgh
+ 5 : 9720, # Talking in Your Sleep Voice Training, Pajama Place, Donald's Dreamland
+ }
+
+# This dictionary defines the milestones for this scavenger hunt
+MILESTONES = {
+ 0: ((0, 1, 2, 3, 4, 5), 'All Winter Caroling goals found'),
+ }
+
+class WinterCarolingMgrAI(ScavengerHuntMgrAI.ScavengerHuntMgrAI):
+ """
+ This is the WinterCaroling manager that extends the scanvenger hunt
+ by providing unique rewards and milestones.
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('WinterCarolingMgrAI')
+
+ def __init__(self, air, holidayId):
+ ScavengerHuntMgrAI.ScavengerHuntMgrAI.__init__(self, air, holidayId)
+
+ def createListeners(self):
+ """
+ Create the listeners that will look for an event in the relavent zone
+ """
+ for id in self.goals.keys():
+ mgrAI = DistributedWinterCarolingTargetAI.DistributedWinterCarolingTargetAI(self.air,
+ self.hunt,
+ id,
+ self,
+ )
+ self.targets[id] = mgrAI
+ self.targets[id].generateWithRequired(self.goals[id])
+
+ @property
+ def goals(self):
+ return GOALS
+
+ @property
+ def milestones(self):
+ return MILESTONES
+
+ def huntCompletedReward(self, avId, goal, firstTime = False):
+ """
+ Reward the Toon for completing the WinterCaroling with
+ a pumpkin head
+ """
+ if firstTime:
+ self.air.writeServerEvent('pumpkinHeadEarned', avId, 'WinterCaroling scavenger hunt complete.')
+
+ av = self.air.doId2do.get(avId)
+ localTime = time.localtime()
+ date = (localTime[0],
+ localTime[1],
+ localTime[2],
+ localTime[6],
+ )
+
+ from toontown.ai import HolidayManagerAI
+ endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(date)
+ startTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getStartTime(date)
+
+ if endTime < startTime:
+ end = time.localtime(endTime)
+ start = time.localtime(startTime)
+
+ newDate = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].adjustDate(date)
+ endTime = HolidayManagerAI.HolidayManagerAI.holidays[self.holidayId].getEndTime(newDate)
+
+ endTime += ToontownGlobals.TOT_REWARD_END_OFFSET_AMOUNT
+
+ if not av:
+ self.notify.warning(
+ 'Tried to send milestone feedback to av %s, but they left' % avId)
+ else:
+ #av.b_setCheesyEffect(OTPGlobals.CESnowMan, 0, (time.time()/60)+1)
+ av.b_setCheesyEffect(OTPGlobals.CESnowMan, 0, endTime/60)
+
+ def huntGoalAlreadyFound(self, avId):
+ """
+ This goal has already been found
+ """
+ av = self.air.doId2do.get(avId)
+ if not av:
+ self.notify.warning(
+ 'Tried to send goal feedback to av %s, but they left' % avId)
+ else:
+ av.sendUpdate('winterCarolingTargetMet', [0])
+
+ def huntGoalFound(self, avId, goal):
+ """
+ One of the goals in the milestone were found,
+ so we reward the toon.
+ """
+ av = self.air.doId2do.get(avId)
+ # Do all the updates at once
+ av.addMoney(ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT)
+ self.avatarCompletedGoal(avId, goal)
+ # Start jellybean reward effect
+ av.sendUpdate('winterCarolingTargetMet', [ToontownGlobals.TOT_REWARD_JELLYBEAN_AMOUNT])
\ No newline at end of file
diff --git a/toontown/src/ai/__init__.py b/toontown/src/ai/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/ai/portuguese/HolidayManagerAI_local.py b/toontown/src/ai/portuguese/HolidayManagerAI_local.py
new file mode 100644
index 0000000..5157b79
--- /dev/null
+++ b/toontown/src/ai/portuguese/HolidayManagerAI_local.py
@@ -0,0 +1,206 @@
+#################################################################
+# File: HolidayManagerAI_local.py
+# Purpose: localized holiday schedule and times
+# $Id$
+#################################################################
+
+#################################################################
+# Direct Specific Modules
+#################################################################
+#from direct.directnotify import DirectNotifyGlobal
+from direct.showbase.PythonUtil import Enum, SingletonError
+#from direct.task import Task
+
+#################################################################
+# Toontown Specific Modules
+#################################################################
+from toontown.ai.HolidayInfoDaily import *
+from toontown.ai.HolidayInfoWeekly import *
+from toontown.ai.HolidayInfoMonthly import *
+from toontown.ai.HolidayInfoYearly import *
+from toontown.effects import FireworkManagerAI
+from toontown.fishing import BingoNightHolidayAI
+from toontown.suit import HolidaySuitInvasionManagerAI
+from toontown.ai import BlackCatHolidayMgrAI
+from toontown.toonbase import ToontownGlobals
+from toontown.racing import RaceManagerAI
+
+#################################################################
+# Global Enumerations and Constants
+#################################################################
+Month = Enum('JANUARY, FEBRUARY, MARCH, APRIL, \
+ MAY, JUNE, JULY, AUGUST, SEPTEMBER, \
+ OCTOBER, NOVEMBER, DECEMBER', 1)
+
+Day = Enum('MONDAY, TUESDAY, WEDNESDAY, THURSDAY, \
+ FRIDAY, SATURDAY, SUNDAY')
+
+Holidays = {
+ ToontownGlobals.NEWYEARS_FIREWORKS : HolidayInfo_Yearly(
+ FireworkManagerAI.FireworkManagerAI,
+ # PST: December 31, 07:00 - December 31, 22:00
+ [ (Month.DECEMBER, 31, 07, 0, 0),
+ # Stop them in the middle of the final hour so we do not interrupt a show in the middle
+ (Month.DECEMBER, 31, 22, 30, 0) ]
+ ),
+
+ ToontownGlobals.BLOODSUCKER_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PST: January 8, 04:00 - January 8, 08:00
+ [ (Month.JANUARY, 8, 4, 0, 0),
+ (Month.JANUARY, 8, 8, 0, 0) ]
+ ),
+
+ ToontownGlobals.MOVER_SHAKER_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # February 15, 05:00 - February 15, 10:00
+ [ (Month.FEBRUARY, 15, 5, 0, 0),
+ (Month.FEBRUARY, 15, 10, 0, 0) ]
+ ),
+
+ ToontownGlobals.HEAD_HUNTER_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PST: March 19, 10:00 - March 19, 13:00
+ [ (Month.MARCH, 19, 10, 0, 0),
+ (Month.MARCH, 19, 13, 0, 0) ]
+ ),
+
+ ToontownGlobals.BLACK_CAT_DAY: HolidayInfo_Yearly(
+ BlackCatHolidayMgrAI.BlackCatHolidayMgrAI,
+ [
+ # PST: February 13, 20:00 - February 14, 20:00
+ (Month.FEBRUARY, 13, 20, 0, 1),
+ (Month.FEBRUARY, 14, 19, 59, 59),
+ # PDT: March 13, 21:00 - March 14, 21:00
+ (Month.MARCH, 13, 21, 0, 1),
+ (Month.MARCH, 14, 20, 59, 59),
+ # PDT: June 13, 21:00 - April 14, 21:00
+ (Month.JUNE, 13, 19, 0, 1),
+ (Month.JUNE, 14, 18, 59, 59),
+ # PDT: August 13, 21:00 - August 14, 21:00
+ (Month.AUGUST, 13, 21, 0, 1),
+ (Month.AUGUST, 14, 20, 59, 59),
+ # PDT: October 13, 21:00 - October 14, 21:00
+ (Month.OCTOBER, 13, 21, 0, 1),
+ (Month.OCTOBER, 14, 20, 59, 59),
+ # PDT: October 30, 20:00 - October 31, 21:00
+ (Month.OCTOBER, 30, 20, 0, 1),
+ (Month.OCTOBER, 31, 20, 59, 59),
+ # PST: December 13, 20:00 - December 14, 20:00
+ (Month.DECEMBER, 13, 20, 0, 1),
+ (Month.DECEMBER, 14, 19, 59, 59),
+ ]
+ ),
+
+ ToontownGlobals.THE_MINGLER_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PDT: April 3, 14:00 - April 3, 19:00
+ [ (Month.APRIL, 3, 14, 0, 0),
+ (Month.APRIL, 3, 19, 0, 0) ]
+ ),
+
+ ToontownGlobals.MONEY_BAGS_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PDT: May 12, 10:00 - May 12, 13:00
+ [ (Month.MAY, 12, 10, 0, 0),
+ (Month.MAY, 12, 13, 0, 0) ]
+ ),
+
+ #ToontownGlobals.MR_HOLLYWOOD_INVASION : HolidayInfo_Yearly(
+ #HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ ## PDT: June 12, 12:00 - June 12, 17:00
+ #[ (Month.JUNE, 12, 12, 0, 0),
+ # (Month.JUNE, 12, 17, 0, 0) ]
+ #),
+
+ ToontownGlobals.VALENTINES_FIREWORKS : HolidayInfo_Yearly(
+ FireworkManagerAI.FireworkManagerAI,
+ # PDT: June 12, 20:00 - June 13, 20:00
+ # Valentine is June 12th in Brazil
+ [ (Month.JUNE, 12, 20, 0, 0),
+ (Month.JUNE, 13, 19, 30, 0) ]
+ ),
+
+ ToontownGlobals.TELEMARKETER_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PDT: July 9, 09:00 - July 9 14:00
+ [ (Month.JULY, 9, 9, 0, 0),
+ (Month.JULY, 9, 14, 0, 0) ]
+ ),
+
+ ToontownGlobals.BOTTOMFEEDER_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PDT: August 24, 06:00 - August 24, 11:00
+ [ (Month.AUGUST, 24, 6, 0, 0),
+ (Month.AUGUST, 24, 11, 0, 0) ]
+ ),
+
+ ToontownGlobals.AMBULANCE_CHASER_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PDT: September 8, 07:00 - September 8, 10:00
+ [ (Month.SEPTEMBER, 8, 7, 0, 0),
+ (Month.SEPTEMBER, 8, 10, 0, 0) ]
+ ),
+
+ ToontownGlobals.THE_BIG_CHEESE_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PDT: October 26, 07:00 - October 26, 12:00
+ [ (Month.OCTOBER, 26, 7, 0, 0),
+ (Month.OCTOBER, 26, 12, 0, 0) ]
+ ),
+
+ ToontownGlobals.NUMBER_CRUNCHER_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PST: November 17, 09:00 - November 17, 14:00
+ [ (Month.NOVEMBER, 17, 9, 0, 0),
+ (Month.NOVEMBER, 17, 14, 0, 0) ]
+ ),
+
+ ToontownGlobals.YESMAN_INVASION : HolidayInfo_Yearly(
+ HolidaySuitInvasionManagerAI.HolidaySuitInvasionManagerAI,
+ # PST: December 20, 08:00 - November 17, 13:00
+ [ (Month.DECEMBER, 20, 8, 0, 0),
+ (Month.DECEMBER, 20, 13, 0, 0) ]
+ ),
+
+ ToontownGlobals.WINTER_DECORATIONS : HolidayInfo_Yearly(
+ None, # No class defined, we just want the news manager to be called
+ # PST: December 6, 12:00 - December 25, 09:00
+ [ (Month.DECEMBER, 6, 12, 0, 0),
+ (Month.DECEMBER, 25, 9, 0, 0) ]
+ ),
+
+ ToontownGlobals.FISH_BINGO_NIGHT: HolidayInfo_Weekly(
+ BingoNightHolidayAI.BingoNightHolidayAI,
+ # Fish Bingo Night - runs twice a week
+ [
+ # Time1: 11am PST to 3pm PST on Wednesdays
+ (Day.WEDNESDAY, 11, 0, 0),
+ (Day.WEDNESDAY, 15, 0, 0),
+ # Time2: 11am PST to 3pm PST on Saturdays
+ (Day.SATURDAY, 11, 0, 0),
+ (Day.SATURDAY, 15, 0, 0) ]
+ ),
+
+ ToontownGlobals.KART_RECORD_DAILY_RESET: HolidayInfo_Daily(
+ RaceManagerAI.KartRecordDailyResetter,
+ [(0, 24, 1),
+ (0, 24, 30),
+ ]
+ ),
+ ToontownGlobals.KART_RECORD_WEEKLY_RESET: HolidayInfo_Weekly(
+ RaceManagerAI.KartRecordWeeklyResetter,
+ [(Day.MONDAY, 0, 25, 1),
+ (Day.MONDAY, 0, 25, 30),
+ ]
+ ),
+ ToontownGlobals.CIRCUIT_RACING: HolidayInfo_Weekly(
+ RaceManagerAI.CircuitRaceHolidayMgr,
+ [(Day.SUNDAY, 0, 0, 1),
+ (Day.SUNDAY, 23, 59, 59),
+ (Day.MONDAY, 0, 0, 1),
+ (Day.MONDAY, 23, 59, 59),
+ ]
+ )
+
+}
diff --git a/toontown/src/ai/testAI.py b/toontown/src/ai/testAI.py
new file mode 100644
index 0000000..51ec953
--- /dev/null
+++ b/toontown/src/ai/testAI.py
@@ -0,0 +1,22 @@
+
+# The AI side
+from AIStart import *
+start()
+import DistributedTestAI
+dt = DistributedTestAI.DistributedTestAI(simbase.air)
+dt.setA(5)
+dt.setB("hello")
+dt.generateWithRequired(101)
+
+# The Client side
+import TestClientRepository
+import os
+# Create a test repository
+basePath = os.path.expandvars('$TOONTOWN') or './toontown'
+cr = TestClientRepository.TestClientRepository(basePath+"/src/configfiles/toon.dc")
+# Connect the test repository
+cr.connect("localhost", 6667)
+# Set the Shard
+cr.sendSetShardMsg(2000000)
+# Switch to zone 101
+cr.sendSetZoneMsg(101)
diff --git a/toontown/src/battle/.cvsignore b/toontown/src/battle/.cvsignore
new file mode 100644
index 0000000..985f113
--- /dev/null
+++ b/toontown/src/battle/.cvsignore
@@ -0,0 +1,4 @@
+.cvsignore
+Makefile
+pp.dep
+*.pyc
diff --git a/toontown/src/battle/BattleBase.py b/toontown/src/battle/BattleBase.py
new file mode 100644
index 0000000..4534121
--- /dev/null
+++ b/toontown/src/battle/BattleBase.py
@@ -0,0 +1,404 @@
+from pandac.PandaModules import *
+from toontown.toonbase.ToontownBattleGlobals import *
+from direct.task.Timer import *
+import math
+
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toon import NPCToons
+from toontown.toonbase import TTLocalizer
+
+# locations of the various types of data within the toonAttacks list
+# used when calculating attack damage, accuracy bonus, and damage bonus
+#
+TOON_ID_COL = 0
+TOON_TRACK_COL = 1
+TOON_LVL_COL = 2
+TOON_TGT_COL = 3
+TOON_HP_COL = 4
+TOON_ACCBONUS_COL = 5
+TOON_HPBONUS_COL = 6
+TOON_KBBONUS_COL = 7
+SUIT_DIED_COL = 8
+SUIT_REVIVE_COL = 9
+
+# locations of the various types of data within the suitAttacks list
+# used when calculating toon attack type, target, and attack damage
+#
+SUIT_ID_COL = 0
+SUIT_ATK_COL = 1
+SUIT_TGT_COL = 2
+SUIT_HP_COL = 3
+TOON_DIED_COL = 4
+SUIT_BEFORE_TOONS_COL = 5
+SUIT_TAUNT_COL = 6
+
+# Toon actions and attacks
+#
+NO_ID = -1
+NO_ATTACK = -1
+UN_ATTACK = -2
+PASS_ATTACK = -3 # used so we can display pass indicator
+NO_TRAP = -1
+LURE_SUCCEEDED = -1
+PASS = 98
+SOS = 99
+NPCSOS = 97
+PETSOS = 96
+FIRE = 100
+# Defined in ToontownBattleGlobals.py
+HEAL = HEAL_TRACK
+TRAP = TRAP_TRACK
+LURE = LURE_TRACK
+SOUND = SOUND_TRACK
+THROW = THROW_TRACK
+SQUIRT = SQUIRT_TRACK
+DROP = DROP_TRACK
+# For reference, in ToontownBattleGlobals
+# NPC_RESTOCK_GAGS = 7
+# NPC_TOONS_HIT = 8
+# NPC_COGS_MISS = 9
+
+# Attack times
+#
+TOON_ATTACK_TIME = 12.0
+SUIT_ATTACK_TIME = 12.0
+
+TOON_TRAP_DELAY = 0.8
+
+TOON_SOUND_DELAY = 1.0
+
+TOON_THROW_DELAY = 0.5
+TOON_THROW_SUIT_DELAY = 1.0
+
+TOON_SQUIRT_DELAY = 0.5
+TOON_SQUIRT_SUIT_DELAY = 1.0
+
+TOON_DROP_DELAY = 0.8
+TOON_DROP_SUIT_DELAY = 1.0
+
+
+TOON_RUN_T = 3.3
+TIMEOUT_PER_USER = 5
+
+TOON_FIRE_DELAY = 0.5
+TOON_FIRE_SUIT_DELAY = 1.0
+
+
+# Reward times
+#
+REWARD_TIMEOUT = 120
+FLOOR_REWARD_TIMEOUT = 4
+BUILDING_REWARD_TIMEOUT = 300
+
+try:
+# debugBattles = base.config.GetBool('debug-battles', 0)
+ CLIENT_INPUT_TIMEOUT = base.config.GetFloat('battle-input-timeout', TTLocalizer.BBbattleInputTimeout)
+except:
+# debugBattles = simbase.config.GetBool('debug-battles', 0)
+ CLIENT_INPUT_TIMEOUT = simbase.config.GetFloat('battle-input-timeout', TTLocalizer.BBbattleInputTimeout)
+
+def levelAffectsGroup(track, level):
+ #return (level % 2)
+ return attackAffectsGroup(track, level) #UBER
+
+def attackAffectsGroup(track, level, type=None):
+ #if (track == HEAL and (level % 2)):
+ # return 1
+ #elif (track == LURE and (level % 2)):
+ # return 1
+ #elif (track == SOUND):
+ # return 1
+ #elif (track == NPCSOS or type == NPCSOS or track == PETSOS or type == PETSOS):
+ # return 1
+ #else:
+ # return 0
+ if (track == NPCSOS or type == NPCSOS or track == PETSOS or type == PETSOS):
+ return 1
+ elif (track >= 0) and (track <= DROP_TRACK):
+ return AvPropTargetCat[AvPropTarget[track]][level]
+ else:
+ return 0
+
+
+def getToonAttack(id, track=NO_ATTACK, level=-1, target=-1):
+ """ getToonAttack(id, track, level, target)
+ """
+ return [id, track, level, target, [], 0, 0, [], 0, 0]
+
+def getDefaultSuitAttacks():
+ """ getDefaultSuitAttacks()
+ """
+ suitAttacks = [[NO_ID, NO_ATTACK, -1, [], 0, 0, 0],
+ [NO_ID, NO_ATTACK, -1, [], 0, 0, 0],
+ [NO_ID, NO_ATTACK, -1, [], 0, 0, 0],
+ [NO_ID, NO_ATTACK, -1, [], 0, 0, 0]]
+ return suitAttacks
+
+def getDefaultSuitAttack():
+ """ getDefaultSuitAttack()
+ """
+ return [NO_ID, NO_ATTACK, -1, [], 0, 0, 0]
+
+def findToonAttack(toons, attacks, track):
+ """ findToonAttack(toons, attacks, track)
+ Return all attacks of the specified track sorted by increasing level
+ """
+ foundAttacks = []
+ for t in toons:
+ if (attacks.has_key(t)):
+ attack = attacks[t]
+ local_track = attack[TOON_TRACK_COL]
+ # If it's an NPC, convert to the appropriate track
+ if (track != NPCSOS and attack[TOON_TRACK_COL] == NPCSOS):
+ local_track = NPCToons.getNPCTrack(attack[TOON_TGT_COL])
+ if (local_track == track):
+ if local_track == FIRE:
+ canFire = 1
+ for attackCheck in foundAttacks:
+ if attackCheck[TOON_TGT_COL] == attack[TOON_TGT_COL]:
+ canFire = 0
+ else:
+ pass
+ if canFire:
+ assert(t == attack[TOON_ID_COL])
+ foundAttacks.append(attack)
+
+ else:
+ assert(t == attack[TOON_ID_COL])
+ foundAttacks.append(attack)
+ def compFunc(a, b):
+ if (a[TOON_LVL_COL] > b[TOON_LVL_COL]):
+ return 1
+ elif (a[TOON_LVL_COL] < b[TOON_LVL_COL]):
+ return -1
+ return 0
+ foundAttacks.sort(compFunc)
+ return foundAttacks
+
+# A little pad time added to server time calculations, to allow for
+# slow or out-of-sync clients. In general, the AI server will give
+# each client the expected time to complete its movie, plus
+# SERVER_BUFFER_TIME, and then will ask all the clients to move on
+# with or without the slow one(s).
+SERVER_BUFFER_TIME = 2.0
+
+#CLIENT_INPUT_TIMEOUT = TTLocalizer.BBbattleInputTimeout
+SERVER_INPUT_TIMEOUT = CLIENT_INPUT_TIMEOUT + SERVER_BUFFER_TIME
+
+# The maximum time we expect a suit to take walk to its position in
+# battle.
+MAX_JOIN_T = TTLocalizer.BBbattleInputTimeout
+
+# The length of time for a faceoff taunt.
+FACEOFF_TAUNT_T = 3.5
+
+# length of time we look at the interactive prop helping toons
+FACEOFF_LOOK_AT_PROP_T = 6
+
+# The amount of time it takes to open up the elevator doors and walk
+# out.
+ELEVATOR_T = 4.0
+
+BATTLE_SMALL_VALUE = 0.0000001
+
+# This is the furthest we expect to have to walk from the face-off to
+# get the battle. If we are further away than this, we suspect we are
+# victims of clock skew.
+MAX_EXPECTED_DISTANCE_FROM_BATTLE = 50.0
+
+class BattleBase:
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('BattleBase')
+
+ # This defines the points where the suits will stand in battle.
+ # For each number of suits in the battle (1, 2, 3, or 4), the
+ # corresponding element of suitPoints is a list of n (pos, heading)
+ # pairs for each of the n suits to stand.
+
+ suitPoints = (
+ ((Point3(0, 5, 0), 179),
+ ),
+ ((Point3(2, 5.3, 0), 170),
+ (Point3(-2, 5.3, 0), 180),
+ ),
+ ((Point3(4, 5.2, 0), 170),
+ (Point3(0, 6, 0), 179),
+ (Point3(-4, 5.2, 0), 190),
+ ),
+ ((Point3(6, 4.4, 0), 160),
+ (Point3(2, 6.3, 0), 170),
+ (Point3(-2, 6.3, 0), 190),
+ (Point3(-6, 4.4, 0), 200),
+ ))
+
+ # And this defines the single set of points for suits who are
+ # "pending": they have joined the battle, but are waiting for the
+ # next round to begin before they take their place.
+ suitPendingPoints = (
+ (Point3(-4, 8.2, 0), 190),
+ (Point3(0, 9, 0), 179),
+ (Point3(4, 8.2, 0), 170),
+ (Point3(8, 3.2, 0), 160),
+ )
+
+ # This is similar to the above, but for toons instead of suits.
+ toonPoints = (
+ ((Point3(0, -6, 0), 0),
+ ),
+ ((Point3(1.5, -6.5, 0), 5),
+ (Point3(-1.5, -6.5, 0), -5),
+ ),
+ ((Point3(3, -6.75, 0), 5),
+ (Point3(0, -7, 0), 0),
+ (Point3(-3, -6.75, 0), -5),
+ ),
+ ((Point3(4.5, -7, 0), 10),
+ (Point3(1.5, -7.5, 0), 5),
+ (Point3(-1.5, -7.5, 0), -5),
+ (Point3(-4.5, -7, 0), -10),
+ ))
+
+ toonPendingPoints = (
+ (Point3(-3, -8, 0), -5),
+ (Point3(0, -9, 0), 0),
+ (Point3(3, -8, 0), 5),
+ (Point3(5.5, -5.5, 0), 20),
+ )
+
+ # These define the points on the perimeter of the battle circle
+ # for suits and toons who are "joining"; this allows the avatar to
+ # walk a circle around the battle to get to its pending point,
+ # defined above.
+ posA = Point3(0, 10, 0)
+ posB = Point3(-7.071, 7.071, 0)
+ posC = Point3(-10, 0, 0)
+ posD = Point3(-7.071, -7.071, 0)
+ posE = Point3(0, -10, 0)
+ posF = Point3(7.071, -7.071, 0)
+ posG = Point3(10, 0, 0)
+ posH = Point3(7.071, 7.071, 0)
+ allPoints = (posA, posB, posC, posD, posE, posF, posG, posH)
+ toonCwise = [posA, posB, posC, posD, posE]
+ toonCCwise = [posH, posG, posF, posE]
+ suitCwise = [posE, posF, posG, posH, posA]
+ suitCCwise = [posD, posC, posB, posA]
+
+ suitSpeed = 4.8
+ toonSpeed = 8.0
+
+ def __init__(self):
+ """ __init__()
+ """
+ self.pos = Point3(0, 0, 0)
+ self.initialSuitPos = Point3(0, 1, 0)
+ self.timer = Timer()
+ self.resetLists()
+
+ def resetLists(self):
+ """ resetLists()
+ """
+ self.suits = []
+ self.pendingSuits = []
+ self.joiningSuits = []
+ self.activeSuits = []
+ self.luredSuits = []
+ self.suitGone = 0
+
+ self.toons = []
+ self.joiningToons = []
+ self.pendingToons = []
+ self.activeToons = []
+ self.runningToons = []
+ self.toonGone = 0
+
+ # keep track of toons who helped, so we know which toons just passed all the time
+ self.helpfulToons = []
+
+
+ def calcFaceoffTime(self, centerpos, suitpos):
+ """ calcFaceoffTime(centerpos, suitpos)
+ """
+ facing = Vec3(centerpos - suitpos)
+ facing.normalize()
+ suitdest = Point3(centerpos - Point3(facing * 6.0))
+ dist = Vec3(suitdest - suitpos).length()
+ return (dist / BattleBase.suitSpeed)
+
+ def calcSuitMoveTime(self, pos0, pos1):
+ """ calcSuitMoveTime(pos0, pos1)
+ """
+ dist = Vec3(pos0 - pos1).length()
+ return (dist / BattleBase.suitSpeed)
+
+ def calcToonMoveTime(self, pos0, pos1):
+ """ calcToonMoveTime(pos0, pos1)
+ """
+ dist = Vec3(pos0 - pos1).length()
+ return (dist / BattleBase.toonSpeed)
+
+ def buildJoinPointList(self, avPos, destPos, toon=0):
+ """ buildJoinPointList(avPos, destPos, toon)
+
+ This function is called when suits or toons ask to join the
+ battle and need to figure out how to walk to their selected
+ pending point (destPos). It builds a list of points the
+ avatar should walk through in order to get there. If the list
+ is empty, the avatar will walk straight there.
+ """
+ # In the default case, avatars walk around the perimeter of
+ # the battle cell to get to their target point. Figure out
+ # the shortest path around the circle.
+
+ # First, find the closest battle join point
+ minDist = 999999.0
+ nearestP = None
+ for p in BattleBase.allPoints:
+ dist = Vec3(avPos - p).length()
+ if (dist < minDist):
+ nearestP = p
+ minDist = dist
+ assert(nearestP != None)
+ self.notify.debug('buildJoinPointList() - avp: %s nearp: %s' % \
+ (avPos, nearestP))
+
+ # See if destPos is the closest point
+ dist = Vec3(avPos - destPos).length()
+ if (dist < minDist):
+ self.notify.debug('buildJoinPointList() - destPos is nearest')
+ return []
+
+ if (toon == 1):
+ if (nearestP == BattleBase.posE):
+ self.notify.debug('buildJoinPointList() - posE')
+ plist = [BattleBase.posE]
+ elif (BattleBase.toonCwise.count(nearestP) == 1):
+ self.notify.debug('buildJoinPointList() - clockwise')
+ index = BattleBase.toonCwise.index(nearestP)
+ plist = BattleBase.toonCwise[index+1:]
+ else:
+ self.notify.debug('buildJoinPointList() - counter-clockwise')
+ assert(BattleBase.toonCCwise.count(nearestP) == 1)
+ index = BattleBase.toonCCwise.index(nearestP)
+ plist = BattleBase.toonCCwise[index+1:]
+ else:
+ if (nearestP == BattleBase.posA):
+ self.notify.debug('buildJoinPointList() - posA')
+ plist = [BattleBase.posA]
+ elif (BattleBase.suitCwise.count(nearestP) == 1):
+ self.notify.debug('buildJoinPointList() - clockwise')
+ index = BattleBase.suitCwise.index(nearestP)
+ plist = BattleBase.suitCwise[index+1:]
+ else:
+ self.notify.debug('buildJoinPointList() - counter-clockwise')
+ assert(BattleBase.suitCCwise.count(nearestP) == 1)
+ index = BattleBase.suitCCwise.index(nearestP)
+ plist = BattleBase.suitCCwise[index+1:]
+
+ self.notify.debug('buildJoinPointList() - plist: %s' % plist)
+ return plist
+
+ def addHelpfulToon(self, toonId):
+ """Add toonId to our helpful toons, make sure it's in the list at most once."""
+ if toonId not in self.helpfulToons:
+ self.helpfulToons.append(toonId)
+
diff --git a/toontown/src/battle/BattleCalculatorAI.py b/toontown/src/battle/BattleCalculatorAI.py
new file mode 100644
index 0000000..0e236c1
--- /dev/null
+++ b/toontown/src/battle/BattleCalculatorAI.py
@@ -0,0 +1,3205 @@
+from BattleBase import *
+from DistributedBattleAI import *
+from toontown.toonbase.ToontownBattleGlobals import *
+
+import random
+from toontown.suit import DistributedSuitBaseAI
+import SuitBattleGlobals
+import BattleExperienceAI
+from toontown.toon import NPCToons
+from toontown.pets import PetTricks, DistributedPetProxyAI
+from direct.showbase.PythonUtil import lerp
+
+
+class BattleCalculatorAI:
+ """
+ An object that each battle
+ creates in order to perform all of the combat calculations, such
+ as hits/misses, damage amounts, bonuses, etc
+
+ Attributes:
+ Derived plus...
+ battle: reference to the battle that owns this object
+ SuitAttackers: a map of suit id's and each toon that did damage
+ to that suit and how much damage was done, this is
+ used to let the suit 'intelligently' pick which
+ toon it is going to attack next, generally favoring
+ the toon that did the most damage to it
+ currentlyLuredSuits: list of currently lured suits, used for
+ calculating knockback bonuses and drop
+ hits/misses during a single round (once a suit
+ takes damage, it is no longer lured)
+ kbBonuses: a list of accumulated damage done to a lured suit by
+ knockback bonus qualified attacks and the track of each
+ attack the damage was done by, when that track of
+ attacks is done a single, total knockback bonus is
+ applied to the suit
+ hpBonuses: same as kbBonuses but used to record knock-back bonuses
+ toonAtkOrder: list of toon id's that indicate the order that the
+ toon attacks for a single round will play out
+ toonHPAdjusts: a list of toon HP adjustments used for accurate
+ calculations of each toon's health, useful for
+ deciding if a toon has just died and cannot
+ perform its chosen attack as well as letting the
+ suits know if a toon dies so they dont beat on a dead
+ toon
+ toonSkillPtsGained: a dictionary of lists of experience gained
+ in each track, indexed by toonId
+ """
+
+ # a map of the number of previous hits to
+ # an accuracy bonus for the next attack
+ AccuracyBonuses = [0, 20, 40, 60]
+
+ # a map of the number of previous hits to
+ # a damage bonus for the next attack
+ DamageBonuses = [0, 20, 20, 20]
+
+ # how much of an accuracy bonus a toon gets for each level of
+ # an attack track
+ AttackExpPerTrack = [0, 10, 20, 30, 40, 50, 60] #HARDCODE UBER
+
+ # number of max rounds for targets to be lured for each different
+ # level of lure
+ NumRoundsLured = [2, 2, 3, 3, 4, 4, 15]
+
+ # a trap level value placed in self.traps if more than one trap is
+ # placed on a single suit within the same round, this value should not
+ # be -1 (the value of NO_TRAP) or a valid level of 0 or greater
+ TRAP_CONFLICT = -2
+
+ # whether or not to apply healt adjustments to suits and toons when
+ # damages are calculated from attacks
+ APPLY_HEALTH_ADJUSTMENTS = 1
+
+ # make attacks on toons always miss
+ TOONS_TAKE_NO_DAMAGE = 0
+
+ # whether or not to reduce the reported attack damages to the
+ # target's min and max hp
+ CAP_HEALS = 1
+
+ # whether or not to clear the SuitAttackers map each round, this
+ # way suits don't remember which toon attacked him/her for more than
+ # a single round
+ CLEAR_SUIT_ATTACKERS = 1
+
+ # a temporary flag until a more correct version of the movie is in place
+ # which will unlure suits as soon as they are knocked back, rather than
+ # waiting until the suits does his/her attack
+ SUITS_UNLURED_IMMEDIATELY = 1
+
+ # whether or not the battle calculator should clear out trap toon attacks
+ # if there is already a trap on the target
+ CLEAR_MULTIPLE_TRAPS = 0
+
+ # a flag placed in the KBBONUS_COL for a toon attack (right now only
+ # for sounds and drops) that hits when the target is lured, this is used
+ # so the movie can know to do something special for the target, such as
+ # not play a sidestep animation for a drop and play a special animation
+ # for the target when hit by a sound
+ KBBONUS_LURED_FLAG = 0
+
+ # another flag for the movies that is placed into the KBBONUS_COL for
+ # lure attacks to indicate each suit that is successfully lured
+ KBBONUS_TGT_LURED = 1
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('BattleCalculatorAI')
+
+ toonsAlwaysHit = simbase.config.GetBool('toons-always-hit', 0)
+ toonsAlwaysMiss = simbase.config.GetBool('toons-always-miss', 0)
+ toonsAlways5050 = simbase.config.GetBool('toons-always-5050', 0)
+ suitsAlwaysHit = simbase.config.GetBool('suits-always-hit', 0)
+ suitsAlwaysMiss = simbase.config.GetBool('suits-always-miss', 0)
+ immortalSuits = simbase.config.GetBool('immortal-suits', 0)
+
+ propAndOrganicBonusStack = simbase.config.GetBool('prop-and-organic-bonus-stack', 0)
+
+ def __init__(self, battle, tutorialFlag=0):
+ self.battle = battle
+
+ # a map of maps, containing suit id's and each toon that did
+ # damage to that suit and how much damage was done, this is
+ # used to let the suit 'intelligently' pick which
+ # toon it is going to attack next, generally favoring
+ # the toon that did the most damage to it
+ self.SuitAttackers = {}
+
+ # list of currently lured suits, used for calculating knockback bonuses
+ # and drop hits/misses during a single round (lured suits are no longer
+ # lured after the round is over)
+ self.currentlyLuredSuits = {}
+# self.suitsUnluredThisRound = []
+
+ # a list created each round and used to keep track of which lure
+ # applies to which target when there are multiple lures in a single
+ # round
+ self.successfulLures = {}
+
+ # list indicating toon attack order
+ self.toonAtkOrder = []
+
+ # a list of toon HP adjustments used for sub-round calculations
+ # of which suit might actually kill a specific toon, useful for
+ # deciding if a toon has just died and cannot perform its chosen
+ # attack
+ self.toonHPAdjusts = {}
+
+ # a dict of lists of the number of skill points gained in each
+ # attack skill for each toon in this battle, at the beginning of
+ # a battle these are zeroed, as the battle progresses, each toon
+ # will gain skill points, when a toon leaves the battle the skill
+ # points will have to be applied and the list in this structure
+ # zeroed out
+ self.toonSkillPtsGained = {}
+
+ # map of suitId's to trap lvl that is associated with that suit,
+ # used to let the battle calculator remember between and during
+ # rounds which suits have traps, this list contains the same values
+ # as the DistributedSuitAI.battleTrap flag but is independent of it
+ # since we cannot rely on that value being set when we expect it to
+ self.traps = {}
+ self.npcTraps = {}
+
+ self.suitAtkStats = {}
+
+ # list used for calculating toon hpBonuses each round
+ self.__clearBonuses(hp=1)
+ self.__clearBonuses(hp=0)
+ self.delayedUnlures = []
+
+ # This is a factor that's applied to all experience points
+ # awarded in this battle. It's always 1 for a street battle,
+ # but a building battle will set this higher based on the
+ # current floor number within the building.
+ self.__skillCreditMultiplier = 1
+
+ self.tutorialFlag = tutorialFlag
+
+ self.trainTrapTriggered = False
+
+ def setSkillCreditMultiplier(self, mult):
+ self.__skillCreditMultiplier = mult
+
+ def getSkillCreditMultiplier(self):
+ return self.__skillCreditMultiplier
+
+ def cleanup(self):
+ self.battle = None
+
+ def __calcToonAtkHit(self, attackIndex, atkTargets):
+ """
+ attackIndex, toonAttacks entry indicating the attack
+ to use
+ Returns: 1 if attack hit, 0 if not, the second number is the
+ resulting accuracy of the attack not taking into
+ account any accuracy bonus
+
+ calculate whether or not a specific toon attack hits
+ or misses all of its targets (assumes that either
+ all targets are hit or all are missed)
+ """
+ if len(atkTargets) == 0:
+ return 0, 0
+
+ # If this is a tutorial, the toon always hits. I'm returning
+ # a 95% accuracy just as a dummy number, because I don't think
+ # it gets used. Just in case it does, 95% is a safe value. Greater
+ # than that may cause something to crash somewhere.
+ if self.tutorialFlag:
+ return 1, 95
+
+ if self.toonsAlways5050:
+ roll = random.randint(0, 99)
+ if roll < 50:
+ return 1, 95
+ else:
+ return 0, 0
+
+ if self.toonsAlwaysHit:
+ return 1, 95
+ elif self.toonsAlwaysMiss:
+ return 0, 0
+
+ debug = self.notify.getDebug()
+ attack = self.battle.toonAttacks[attackIndex]
+
+ atkTrack, atkLevel = self.__getActualTrackLevel(attack)
+
+ # According to Justin's comment it might be risky to return 100%
+ # accuracy for any attack other than trap
+ # If the track comes back as NPCSOS, it's not a normal toon attack,
+ # so it really doesn't matter what we return anyway
+ if (atkTrack == NPCSOS):
+ return (1, 95)
+
+ if (atkTrack == FIRE):
+ return (1, 95)
+
+ # certain attack tracks (trap) always hit
+ if (atkTrack == TRAP):
+ if debug:
+ self.notify.debug("Attack is a trap, so it hits regardless")
+ attack[TOON_ACCBONUS_COL] = 0
+ return (1, 100)
+ elif (atkTrack == DROP and attack[TOON_TRACK_COL] == NPCSOS):
+ # NPC drop can hit multiple targets
+ unluredSuits = 0
+ for tgt in atkTargets:
+ if (not self.__suitIsLured(tgt.getDoId())):
+ unluredSuits = 1
+ if (unluredSuits == 0):
+ attack[TOON_ACCBONUS_COL] = 1
+ return (0, 0)
+ elif (atkTrack == DROP):
+ # there can be only one target for a drop, and if the target is
+ # lured, the drop misses
+ # uber drop has multiple targets
+ # RAU TODO, make uber drop be able hit only some of the suits and miss lured suits
+ allLured = True
+ for i in range(len(atkTargets)):
+ if (self.__suitIsLured(atkTargets[i].getDoId())):
+ assert(self.notify.debug("Drop on lured suit " +
+ str(atkTargets[i].getDoId()) + " missed"))
+ else:
+ allLured = False
+
+ if allLured:
+ attack[TOON_ACCBONUS_COL] = 1
+ return (0, 0)
+ elif (atkTrack == PETSOS):
+ return self.__calculatePetTrickSuccess(attack)
+
+ # in the case of suit targets, go through each target and pick out
+ # defense of the highest level suit
+ tgtDef = 0
+ numLured = 0
+ if (atkTrack != HEAL):
+ for currTarget in atkTargets:
+ thisSuitDef = self.__targetDefense(currTarget, atkTrack)
+ if debug:
+ self.notify.debug("Examining suit def for toon attack: " +
+ str(thisSuitDef))
+ tgtDef = min(thisSuitDef, tgtDef)
+ if (self.__suitIsLured(currTarget.getDoId())):
+ numLured += 1
+
+ # for combos (multiple toon attacks of the same track with the same
+ # target), use the track exp acc bonus of the highest level toon
+ # attacking in this combo
+ trackExp = self.__toonTrackExp(attack[TOON_ID_COL], atkTrack)
+ for currOtherAtk in self.toonAtkOrder:
+ if currOtherAtk != attack[TOON_ID_COL]:
+ nextAttack = self.battle.toonAttacks[currOtherAtk]
+ nextAtkTrack = self.__getActualTrack(nextAttack)
+ if atkTrack == nextAtkTrack and \
+ attack[TOON_TGT_COL] == nextAttack[TOON_TGT_COL]:
+ currTrackExp = self.__toonTrackExp(
+ nextAttack[TOON_ID_COL], atkTrack)
+ if debug:
+ self.notify.debug("Examining toon track exp bonus: " +
+ str(currTrackExp))
+ trackExp = max(currTrackExp, trackExp)
+
+ if debug:
+ if atkTrack == HEAL:
+ self.notify.debug("Toon attack is a heal, no target def used")
+ else:
+ self.notify.debug("Suit defense used for toon attack: " +
+ str(tgtDef))
+ self.notify.debug("Toon track exp bonus used for toon attack: " +
+ str(trackExp))
+
+ # now determine the accuracy of the attack the toon is using
+ #
+ # NPCs always hit
+ if (attack[TOON_TRACK_COL] == NPCSOS):
+ randChoice = 0
+ else:
+ randChoice = random.randint(0, 99)
+ propAcc = AvPropAccuracy[atkTrack][atkLevel]
+
+ # use an adjusted LURE accuracy level if they have a fruiting gag-tree
+ if atkTrack == LURE:
+ treebonus = self.__toonCheckGagBonus(attack[TOON_ID_COL], atkTrack, atkLevel)
+ propBonus = self.__checkPropBonus(atkTrack)
+ if self.propAndOrganicBonusStack:
+ propAcc = 0
+ if treebonus :
+ self.notify.debug( "using organic bonus lure accuracy")
+ propAcc+= AvLureBonusAccuracy[atkLevel]
+ if propBonus:
+ self.notify.debug( "using prop bonus lure accuracy")
+ propAcc+= AvLureBonusAccuracy[atkLevel]
+ else:
+ if treebonus or propBonus:
+ self.notify.debug( "using oragnic OR prop bonus lure accuracy")
+ propAcc = AvLureBonusAccuracy[atkLevel]
+
+ attackAcc = propAcc + trackExp + tgtDef
+
+ # see if the previous attack was the same track as the current
+ # track, if so the hit or miss status of this attack is the same
+ # as the that of the prevous attack
+ currAtk = self.toonAtkOrder.index(attackIndex)
+ # Heals shouldn't be affected by the previous attack
+ if (currAtk > 0 and atkTrack != HEAL):
+ prevAtkId = self.toonAtkOrder[currAtk - 1]
+ prevAttack = self.battle.toonAttacks[prevAtkId]
+ prevAtkTrack = self.__getActualTrack(prevAttack)
+
+ # if the track of this attack is the same as the previous attack
+ # and the target(s) of this and the previous attacks are the same
+ # and the previous attack was side-stepped, then this attack
+ # is also side-stepped since attacks of the same track occurr at
+ # once NOTE: the target column for the attack is the id of the
+ # target, if the attack is a group attack, this id is 0 since
+ # there is no single target, in either case the real id or id of
+ # 0 must be the same for this and the previous attack
+ # we need to special handle a lure attack, since lures can be
+ # either group or single targets and when one hits in a single
+ # round all others during that same round also hit
+ lure = atkTrack == LURE and \
+ ((not attackAffectsGroup(atkTrack, atkLevel,
+ attack[TOON_TRACK_COL]) and \
+ self.successfulLures.has_key(attack[TOON_TGT_COL])) or \
+ attackAffectsGroup(atkTrack, atkLevel, attack[TOON_TRACK_COL]))
+ if atkTrack == prevAtkTrack and \
+ (attack[TOON_TGT_COL] == prevAttack[TOON_TGT_COL] or \
+ lure):
+
+ if prevAttack[TOON_ACCBONUS_COL] == 1:
+ if debug:
+ self.notify.debug("DODGE: Toon attack track dodged")
+ elif prevAttack[TOON_ACCBONUS_COL] == 0:
+ if debug:
+ self.notify.debug("HIT: Toon attack track hit")
+ else:
+ assert 0, "Unknown value for sidestep flag"
+ attack[TOON_ACCBONUS_COL] = prevAttack[TOON_ACCBONUS_COL]
+ return (not attack[TOON_ACCBONUS_COL],
+# (not attack[TOON_ACCBONUS_COL]) * 100)
+ attackAcc)
+
+ atkAccResult = attackAcc
+ if debug:
+ self.notify.debug("setting atkAccResult to %d" % atkAccResult)
+ acc = attackAcc + self.__calcToonAccBonus(attackIndex)
+
+ if (atkTrack != LURE and atkTrack != HEAL):
+ # non-lure attacks are affected by how many targets are lured,
+ # the more targets that are lured, the better chance the attack
+ # has to hit, if all targets are lured, the attack will hit
+ if atkTrack != DROP:
+ if numLured == len(atkTargets):
+ # all suits are lured so the attack hits regardless
+ if debug:
+ self.notify.debug("all targets are lured, attack hits")
+ attack[TOON_ACCBONUS_COL] = 0
+ return (1, 100)
+ else:
+ # at least one target is not lured, but lets reduce the
+ # defense of all the targets based on how many suits are lured
+ luredRatio = float(numLured) / float(len(atkTargets))
+ accAdjust = 100 * luredRatio
+ if accAdjust > 0 and debug:
+ self.notify.debug(str(numLured) + " out of " +
+ str(len(atkTargets)) +
+ " targets are lured, so adding " +
+ str(accAdjust) +
+ " to attack accuracy")
+ acc += accAdjust
+ else:
+ #lets reverse the logic if it's a drop
+ if numLured == len(atkTargets):
+ # all suits are lured so the attack misses
+ if debug:
+ self.notify.debug("all targets are lured, attack misses")
+ attack[TOON_ACCBONUS_COL] = 0
+ return (0, 0)
+ else:
+ #RAU this feels too overpowered
+ pass
+ # at least one target is not lured, but lets reduce the
+ # defense of all the targets based on how many suits are lured
+ #luredRatio = float(numLured) / float(len(atkTargets))
+ #luredRatio = 1.0 - luredRatio
+ #accAdjust = 100 * luredRatio
+ #if accAdjust > 0 and debug:
+ # self.notify.debug(str(numLured) + " out of " +
+ # str(len(atkTargets)) +
+ # " targets are lured, so adding " +
+ # str(accAdjust) +
+ # " to attack accuracy")
+ #acc += accAdjust
+
+
+
+
+
+ # NOTE: We are imposing an upper limit on accuracy, so there is
+ # always some chance of missing (for toons anyway)
+ if acc > MaxToonAcc:
+ acc = MaxToonAcc
+
+ if randChoice < acc:
+ if debug:
+ self.notify.debug("HIT: Toon attack rolled" +
+ str(randChoice) +
+ "to hit with an accuracy of" + str(acc))
+ attack[TOON_ACCBONUS_COL] = 0
+ else:
+ if debug:
+ self.notify.debug("MISS: Toon attack rolled" +
+ str(randChoice) +
+ "to hit with an accuracy of" + str(acc))
+ attack[TOON_ACCBONUS_COL] = 1
+ return (not attack[TOON_ACCBONUS_COL], atkAccResult)
+
+
+ def __toonTrackExp(self, toonId, track):
+ """
+ toonId, toon to get the track exp value for
+
+ calculate a track exp value used for the specified
+ toon's attack accuracy calculations
+ """
+ # CCC look at next attacks that are the same track, take the max
+ # toon track exp and use that for this attack
+ toon = self.battle.getToon(toonId)
+ if (toon != None):
+ toonExpLvl = toon.experience.getExpLevel(track)
+ exp = self.AttackExpPerTrack[toonExpLvl]
+ if track == HEAL:
+ exp = exp * 0.5
+ self.notify.debug("Toon track exp: " + str(toonExpLvl) +
+ " and resulting acc bonus: " + str(exp))
+ return exp
+ else:
+ return 0
+
+ def __toonCheckGagBonus(self, toonId, track, level):
+ toon = self.battle.getToon(toonId)
+ if (toon != None):
+ return toon.checkGagBonus(track, level)
+ else:
+ return False
+
+ def __checkPropBonus(self, track):
+ """Return true if this battle has a prop buffing it."""
+ result = False
+ if self.battle.getInteractivePropTrackBonus() == track:
+ result = True
+ return result
+
+ def __targetDefense(self, suit, atkTrack):
+ """
+ Parameters: suit, the suit to get the defense of
+ atkTrack, attack track type being used on the suit
+
+ calculate a suit's defense value given its level
+ """
+ if atkTrack == HEAL:
+ return 0
+ suitDef = SuitBattleGlobals.SuitAttributes[suit.dna.name]\
+ ['def'][suit.getLevel()]
+ return -suitDef
+
+ def __createToonTargetList(self, attackIndex):
+ """
+ attackIndex, index into the toonAttacks list which
+ indicates which attack we are using
+
+ create a list of targets for the specified toon
+ attack, the target type of the attack determines
+ which participants in the battle are targets
+ """
+
+ attack = self.battle.toonAttacks[attackIndex]
+ atkTrack, atkLevel = self.__getActualTrackLevel(attack)
+ targetList = []
+ if (atkTrack == NPCSOS):
+ # Must be a non-toon NPC attack of some kind
+ return targetList
+ if (not attackAffectsGroup(atkTrack, atkLevel, attack[TOON_TRACK_COL])):
+ # add only the specified target to the target list
+ if (atkTrack == HEAL):
+ # If the "attack" is a heal, then the target is a
+ # toon, and for toons we only keep track of the doId,
+ # so just add that to the target list.
+ target = attack[TOON_TGT_COL]
+ else:
+ # Otherwise, the target must be a suit.
+ target = self.battle.findSuit(attack[TOON_TGT_COL])
+
+ if target != None:
+ targetList.append(target)
+ else:
+ # all targets currently in battle
+ #
+ if (atkTrack == HEAL or atkTrack == PETSOS):
+ if (attack[TOON_TRACK_COL] == NPCSOS or atkTrack == PETSOS):
+ targetList = self.battle.activeToons
+ else:
+ # copy over the toon ids for all toons except for the
+ # one doing the heal
+ for currToon in self.battle.activeToons:
+ if attack[TOON_ID_COL] != currToon:
+ targetList.append(currToon)
+ else:
+ targetList = self.battle.activeSuits
+ return targetList
+
+ def __prevAtkTrack(self, attackerId, toon=1):
+ """
+ toon, 1 if the attacker is a toon, 0 otherwise
+
+ get the track type of the previous attack, wrt to
+ the attackerId given, and in the order that the movie
+ will play out the attacks
+ """
+ if toon:
+ prevAtkIdx = self.toonAtkOrder.index(attackerId) - 1
+ if prevAtkIdx >= 0:
+ prevAttackerId = self.toonAtkOrder[prevAtkIdx]
+ attack = self.battle.toonAttacks[prevAttackerId]
+ return self.__getActualTrack(attack)
+ else:
+ return NO_ATTACK
+ else:
+ assert 0, "__prevAtkTrack: not defined for suits!"
+
+ def getSuitTrapType(self, suitId):
+ """
+ Parameters: suitId, the suit to get the trap type from
+ Returns: the type of trap currently on the suit, NO_TRAP if
+ no trap exists on this suit
+
+ the type of trap the specified suit has on it
+ """
+ if (self.traps.has_key(suitId)):
+ if (self.traps[suitId][0] == self.TRAP_CONFLICT):
+ return NO_TRAP
+ else:
+ return self.traps[suitId][0]
+ else:
+ return NO_TRAP
+
+ def __suitTrapDamage(self, suitId):
+ """
+ Returns the damage that will be done to the suit when its trap
+ is sprung.
+ """
+ if (self.traps.has_key(suitId)):
+ return self.traps[suitId][2]
+ else:
+ return 0
+
+ def addTrainTrapForJoiningSuit(self, suitId):
+ """
+ a joining suit needs to be marked with the train trap
+ """
+ self.notify.debug('addTrainTrapForJoiningSuit suit=%d self.traps=%s' % (suitId,self.traps))
+ trapInfoToUse = None
+ for trapInfo in self.traps.values():
+ if trapInfo[0] == UBER_GAG_LEVEL_INDEX:
+ trapInfoToUse = trapInfo
+ break
+
+ if trapInfoToUse:
+ self.traps[suitId] = trapInfoToUse
+ else:
+ self.notify.warning('huh we did not find a train trap?')
+
+
+
+ def __addSuitGroupTrap(self, suitId, trapLvl, attackerId, allSuits, npcDamage = 0):
+ """
+ suitId, the suit to place the trap on
+ trapLvl, the type of trap to place
+
+ place a trap in front of a specific suit
+ if we're place a group trap, and we detect a conflict, we need to
+ mark all the other suits as trap conflicts
+ """
+ #import pdb; pdb.set_trace()
+ if (npcDamage == 0):
+ if (self.traps.has_key(suitId)):
+ # a trap level of TRAP_CONFLICT indicates that this suit has
+ # had more than one trap placed on it this round so any new
+ # traps placed on this suit should not stay
+ if (self.traps[suitId][0] == self.TRAP_CONFLICT):
+ pass
+ else:
+ # this is the second trap placed on this suit this round
+ # so both the previous trap and this trap are gone, indicate
+ # this case by setting trap level to 'TRAP_CONFLICT'
+ self.traps[suitId][0] = self.TRAP_CONFLICT
+
+ #mark all the suits as trap conflict
+ for suit in allSuits:
+ id = suit.doId
+ if (self.traps.has_key(id)):
+ self.traps[id][0] = self.TRAP_CONFLICT
+ else:
+ self.traps[id] = [self.TRAP_CONFLICT, 0, 0]
+
+ else:
+ toon = self.battle.getToon(attackerId)
+ organicBonus = toon.checkGagBonus(TRAP, trapLvl)
+ propBonus = self.__checkPropBonus(TRAP)
+ damage = getAvPropDamage(TRAP, trapLvl,
+ toon.experience.getExp(TRAP), organicBonus,
+ propBonus, self.propAndOrganicBonusStack)
+ if self.itemIsCredit(TRAP, trapLvl):
+ self.traps[suitId] = [trapLvl, attackerId, damage]
+ else:
+ # If we don't deserve credit for the high-level trap
+ # attack, don't bother to record the creator.
+ self.traps[suitId] = [trapLvl, 0, damage]
+
+ #what do we do in a multi suit battle, if we lure
+ #one suit the do the train trap? Currently we just unlure
+ self.notify.debug('calling __addLuredSuitsDelayed')
+ self.__addLuredSuitsDelayed(attackerId, targetId=-1, ignoreDamageCheck = True)
+ else:
+ # NPC traps defer to any pre-set traps, but they can take
+ # the spot of two traps that collided
+ if (self.traps.has_key(suitId)):
+ if (self.traps[suitId][0] == self.TRAP_CONFLICT):
+ self.traps[suitId] = [trapLvl, 0, npcDamage]
+ elif (not self.__suitIsLured(suitId)):
+ self.traps[suitId] = [trapLvl, 0, npcDamage]
+
+
+ def __addSuitTrap(self, suitId, trapLvl, attackerId, npcDamage = 0):
+ """
+ suitId, the suit to place the trap on
+ trapLvl, the type of trap to place
+
+ place a trap in front of a specific suit
+ """
+ if (npcDamage == 0):
+ if (self.traps.has_key(suitId)):
+ # a trap level of TRAP_CONFLICT indicates that this suit has
+ # had more than one trap placed on it this round so any new
+ # traps placed on this suit should not stay
+ if (self.traps[suitId][0] == self.TRAP_CONFLICT):
+ pass
+ else:
+ # this is the second trap placed on this suit this round
+ # so both the previous trap and this trap are gone, indicate
+ # this case by setting trap level to 'TRAP_CONFLICT'
+ self.traps[suitId][0] = self.TRAP_CONFLICT
+ else:
+ toon = self.battle.getToon(attackerId)
+ organicBonus = toon.checkGagBonus(TRAP, trapLvl)
+ propBonus = self.__checkPropBonus(TRAP)
+ damage = getAvPropDamage(TRAP, trapLvl,
+ toon.experience.getExp(TRAP), organicBonus,
+ propBonus, self.propAndOrganicBonusStack)
+ if self.itemIsCredit(TRAP, trapLvl):
+ self.traps[suitId] = [trapLvl, attackerId, damage]
+ else:
+ # If we don't deserve credit for the high-level trap
+ # attack, don't bother to record the creator.
+ self.traps[suitId] = [trapLvl, 0, damage]
+ else:
+ # NPC traps defer to any pre-set traps, but they can take
+ # the spot of two traps that collided
+ if (self.traps.has_key(suitId)):
+ if (self.traps[suitId][0] == self.TRAP_CONFLICT):
+ self.traps[suitId] = [trapLvl, 0, npcDamage]
+ elif (not self.__suitIsLured(suitId)):
+ self.traps[suitId] = [trapLvl, 0, npcDamage]
+
+ def __removeSuitTrap(self, suitId):
+ """
+ suitId, the doId of the suit to remove the trap from
+
+ remove any trap from the specified suit
+ """
+ if self.traps.has_key(suitId):
+ del self.traps[suitId]
+
+ def __clearTrapCreator(self, creatorId, suitId = None):
+ """
+ creatorId, the doId of the toon who placed the trap
+ suitId, the particular suit's trap to remove (or None)
+
+ remove the specified creator id from any trap on
+ the specified target (or any target if suitId is
+ None). this should be done when the creator should
+ no longer get exp credit for the trap
+ """
+ if suitId == None:
+ for currTrap in self.traps.keys():
+ if creatorId == self.traps[currTrap][1]:
+ self.traps[currTrap][1] = 0
+ else:
+ if self.traps.has_key(suitId):
+ assert(self.traps[suitId][1] == 0 or self.traps[suitId][1] == creatorId)
+ self.traps[suitId][1] = 0
+
+ def __trapCreator(self, suitId):
+ """
+ suitId, the doId of the suit to remove the trap from
+
+ If a suit has a trap associated with it, get the
+ id of who created the trap, for the purposes of
+ awarding experience points after the trap is
+ sprung. If the creator is no longer in the
+ battle, or if the creator is still in the
+ battle but used an overly-powerful trap item, 0
+ is returned.
+ """
+ if self.traps.has_key(suitId):
+ return self.traps[suitId][1]
+ else:
+ return 0
+
+ def __initTraps(self):
+ """
+ Make sure every round to go through the traps list
+ and any that exist and have values of 'TRAP_CONFLICT'
+ should be deleted since this is a new round and
+ 'trap conflicts' dont persist between rounds
+ """
+ self.trainTrapTriggered = False
+ keysList = self.traps.keys()
+ for currTrap in keysList:
+ if (self.traps[currTrap][0] == self.TRAP_CONFLICT):
+ del self.traps[currTrap]
+
+ def __calcToonAtkHp(self, toonId):
+ """
+ attackIndex, index into the list of toonAttacks
+ that indicates which attack we are to be calculating
+ the damage for
+
+ Calculate the amount of damage the specified attack
+ will do
+ """
+ # generate a list of possible targets based on the attack and
+ # determine the damage done to each
+ assert(self.battle.toonAttacks.has_key(toonId))
+ attack = self.battle.toonAttacks[toonId]
+ targetList = self.__createToonTargetList(toonId)
+
+ #import pdb; pdb.set_trace()
+
+ # make sure the attack has hit all targets before even
+ # trying to continue
+ atkHit, atkAcc = self.__calcToonAtkHit(toonId, targetList)
+
+ atkTrack, atkLevel, atkHp = self.__getActualTrackLevelHp(attack)
+
+ if (not atkHit and atkTrack != HEAL):
+ return
+
+ # for each target calculate the damage done, it is possible
+ # to be different amounts for the different targets
+ # validTargetAvail tabulates if there is at least one valid
+ # target for this attack (valid depends on the attack, but it
+ # involves the target's lure status as well as if the target
+ # is dead or not)
+ validTargetAvail = 0
+ lureDidDamage = 0
+ currLureId = -1
+ for currTarget in range(len(targetList)):
+ # first get the attack's base damage
+ # check to see if this is a 'lure' attack, if it is, the
+ # lure itself doesn't damage the target, but rather any
+ # trap that might exist in front of the target
+ attackLevel = -1
+ attackTrack = None
+ attackDamage = 0
+ toonTarget = 0
+ targetLured = 0
+ if (atkTrack == HEAL or atkTrack == PETSOS):
+ # the targets are toons, so we need to handle getting info
+ # about the target differently than we do a suit
+ targetId = targetList[currTarget]
+ toonTarget = 1
+ else:
+ targetId = targetList[currTarget].getDoId()
+ if (atkTrack == LURE):
+ # check to see if the target of this lure has a trap set
+ # on him/her, if no then go ahead and lure the suit normally,
+ # if yes, then the trap will damage the target and the target
+ # will effectively not end up lured (because the trap itself
+ # unlures the target)
+ if (self.getSuitTrapType(targetId) == NO_TRAP):
+ if self.notify.getDebug():
+ self.notify.debug("Suit lured, but no trap exists")
+
+ if self.SUITS_UNLURED_IMMEDIATELY:
+ # make sure to add the suit to the lured list,
+ # if suits are unlured immediately then we only need
+ # to add suits which are not lured onto a trap to
+ # the currently lured list (if the suit was lured this
+ # round by some other attack, then still add the suit
+ # to the lured list because we want to update the lured
+ # suit info with this new lure, but if the suit was
+ # lured on a previous round and is still lured, then
+ # this lure does not affect the suit)
+ if not self.__suitIsLured(targetId, prevRound=1):
+ if not self.__combatantDead(targetId,
+ toon=toonTarget):
+ validTargetAvail = 1
+ rounds = self.NumRoundsLured[atkLevel]
+ wakeupChance = 100 - (atkAcc * 2)
+ npcLurer = (attack[TOON_TRACK_COL] == NPCSOS)
+ currLureId = self.__addLuredSuitInfo(
+ targetId, -1, rounds, wakeupChance, toonId,
+ atkLevel, lureId=currLureId, npc = npcLurer)
+ if self.notify.getDebug():
+ self.notify.debug(
+ "Suit lured for " +
+ str(rounds)+
+ " rounds max with " +
+ str(wakeupChance)+
+ "% chance to wake up each round")
+ targetLured = 1
+ else:
+ # get the damage for the trap that was triggered
+ # which is obtained from asking the target itself
+ # since we only get here if there is a trap in front
+ # of the target, we know that the target is not lured
+ attackTrack = TRAP
+ #attackLevel = targetList[currTarget].battleTrap
+ if (self.traps.has_key(targetId)):
+ trapInfo = self.traps[targetId]
+ attackLevel = trapInfo[0]
+ else:
+ attackLevel = NO_TRAP
+ attackDamage = self.__suitTrapDamage(targetId)
+
+ # determine who placed the trap and give them exp
+ trapCreatorId = self.__trapCreator(targetId)
+ if trapCreatorId > 0:
+ self.notify.debug("Giving trap EXP to toon " +
+ str(trapCreatorId))
+ self.__addAttackExp(attack, track=TRAP,
+ level=attackLevel,
+ attackerId=trapCreatorId)
+
+ # Remove the owner record now, so we don't
+ # accidentally give the trap placer multiple
+ # experience points if multiple players sprung the
+ # trap.
+ self.__clearTrapCreator(trapCreatorId, targetId)
+
+ lureDidDamage = 1
+ if self.notify.getDebug():
+ self.notify.debug(
+ "Suit lured right onto a trap! (" +
+ str(AvProps[attackTrack][attackLevel]) +
+ "," + str(attackLevel) + ")")
+ if not self.__combatantDead(targetId, toon=toonTarget):
+ validTargetAvail = 1
+ targetLured = 1
+ if not self.SUITS_UNLURED_IMMEDIATELY:
+ # make sure to add the suit to the lured list,
+ # if suits are not unlured immediately, then even a suit
+ # which is lured onto a trap will still be lured until
+ # all toon attacks are finished
+ if not self.__suitIsLured(targetId, prevRound=1):
+ if not self.__combatantDead(targetId,
+ toon=toonTarget):
+ validTargetAvail = 1
+ rounds = self.NumRoundsLured[atkLevel]
+ wakeupChance = 100 - (atkAcc * 2)
+ npcLurer = (attack[TOON_TRACK_COL] == NPCSOS)
+ currLureId = self.__addLuredSuitInfo(
+ targetId, -1, rounds, wakeupChance, toonId,
+ atkLevel, lureId=currLureId, npc = npcLurer)
+ if self.notify.getDebug():
+ self.notify.debug(
+ "Suit lured for " +
+ str(rounds)+
+ " rounds max with " +
+ str(wakeupChance)+
+ "% chance to wake up each round")
+ targetLured = 1
+
+ # be sure to unlure this guy after calculating toon attacks
+ # if he/she was lured onto a trap, attackLevel should be
+ # the level of the trap that the suit was lured onto
+ if attackLevel != -1:
+ self.__addLuredSuitsDelayed(toonId, targetId)
+
+ # now check to see if this target is lured by this lure
+ # based on any previous lures that happened this round,
+ # higher level lures over-ride lower level ones because
+ # they cause the suit to be lured for a longer period of
+ # time
+ if targetLured and \
+ (not self.successfulLures.has_key(targetId) or \
+ (self.successfulLures.has_key(targetId) and \
+ self.successfulLures[targetId][1] < \
+ atkLevel)):
+ self.notify.debug("Adding target " + str(targetId) +
+ " to successfulLures list")
+ self.successfulLures[targetId] = [
+ toonId,
+ atkLevel,
+ atkAcc,
+ -1]
+ else:
+ if (atkTrack == TRAP):
+ #import pdb; pdb.set_trace()
+ npcDamage = 0
+ if (attack[TOON_TRACK_COL] == NPCSOS):
+ npcDamage = atkHp
+ if self.CLEAR_MULTIPLE_TRAPS:
+ # if this attack is a trap and the target already has
+ # a trap on it, clear the attack and move to the next
+ # attack. NOTE: this assumes that a trap will only
+ # affect a single target
+ if self.getSuitTrapType(targetId) != NO_TRAP:
+ self.__clearAttack(toonId)
+ return
+
+ # be sure to set our own internal trap indicator for
+ # this suit so no more traps can be placed on this target
+ if atkLevel == UBER_GAG_LEVEL_INDEX:
+ #the SOS trap will also affect a group, but we want the train trap to behave differently
+ self.__addSuitGroupTrap(targetId, atkLevel, toonId, targetList, npcDamage)
+
+ if self.__suitIsLured(targetId):
+ # if this is a train trap on a lured suit,
+ # be sure to place a indicator in the
+ # KBBONUS_COL so the battle movie knows
+ # that this is a train trap on a lured suit
+ # and it should do something different than other
+ # attack types on lured suits
+ self.notify.debug("Train Trap on lured suit %d, \n indicating with KBBONUS_COL flag" % targetId)
+ tgtPos = self.battle.activeSuits.index(
+ targetList[currTarget])
+ attack[TOON_KBBONUS_COL][tgtPos] = \
+ self.KBBONUS_LURED_FLAG
+ else:
+ self.__addSuitTrap(targetId, atkLevel, toonId, npcDamage)
+ elif self.__suitIsLured(targetId) and \
+ atkTrack == SOUND:
+ # if this is a sound on a lured suit,
+ # be sure to place a indicator in the
+ # KBBONUS_COL so the battle movie knows
+ # that this is a drop or sound on a lured suit
+ # and it should do something different than other
+ # attack types on lured suits
+ self.notify.debug("Sound on lured suit, " +
+ "indicating with KBBONUS_COL flag")
+ tgtPos = self.battle.activeSuits.index(
+ targetList[currTarget])
+ attack[TOON_KBBONUS_COL][tgtPos] = \
+ self.KBBONUS_LURED_FLAG
+
+ attackLevel = atkLevel
+ attackTrack = atkTrack
+ toon = self.battle.getToon(toonId)
+ assert toon
+ if ((attack[TOON_TRACK_COL] == NPCSOS and lureDidDamage != 1) or
+ attack[TOON_TRACK_COL] == PETSOS):
+ # If lureDidDamage == 1, attackDamage has already been
+ # filled in
+ attackDamage = atkHp
+ else:
+ if (atkTrack == FIRE):
+ suit = self.battle.findSuit(targetId)
+ if suit:
+ costToFire = 1#suit.getActualLevel()
+ abilityToFire = toon.getPinkSlips()
+ numLeft = abilityToFire - costToFire
+ if numLeft < 0:
+ numLeft = 0
+ toon.b_setPinkSlips(numLeft)
+ if costToFire > abilityToFire:
+ commentStr = "Toon attempting to fire a %s cost cog with %s pinkslips" % (costToFire, abilityToFire)
+ simbase.air.writeServerEvent('suspicious', toonId, commentStr)
+ dislId = toon.DISLid
+ simbase.air.banManager.ban(toonId, dislId, commentStr)
+ print("Not enough PinkSlips to fire cog - print a warning here")
+ else:
+ suit.skeleRevives = 0
+ attackDamage = suit.getHP()
+
+ else:
+ attackDamage = 0
+ bonus = 0
+ else:
+ organicBonus = toon.checkGagBonus(attackTrack, attackLevel)
+ propBonus = self.__checkPropBonus(attackTrack)
+ attackDamage = getAvPropDamage(attackTrack, attackLevel,
+ toon.experience.getExp(attackTrack), organicBonus,
+ propBonus, self.propAndOrganicBonusStack)
+
+
+ if not self.__combatantDead(targetId, toon=toonTarget):
+ #RAU a drop on a lured suit is not a valid target
+ if self.__suitIsLured(targetId) and \
+ atkTrack == DROP:
+ self.notify.debug('not setting validTargetAvail, since drop on a lured suit')
+ else:
+ validTargetAvail = 1
+
+ # check to see if any damage is done at all by this attack,
+ # just because it hit does not mean it necessarily did damage
+ if (attackLevel == -1) and not (atkTrack == FIRE):
+ # if suit is lured, and no trap exists, no damage
+ # is taken but the lure works, so set damage to -1
+ # to indicate the that the lure was successful and
+ # we get proper visual indication
+ result = LURE_SUCCEEDED
+ else:
+ # only specify a damage for a non-trap attack, the
+ # damage field for traps is used elsewhere and we should
+ # not change it
+ if (atkTrack != TRAP):
+ result = attackDamage
+ if (atkTrack == HEAL):
+ if not self.__attackHasHit(attack, suit=0):
+ # heals that indicate a 'miss' only do 1/5 the
+ # normal heal amount
+ result = result * 0.2
+ if self.notify.getDebug():
+ self.notify.debug("toon does " +
+ str(result) +
+ " healing to toon(s)")
+ else:
+ if self.__suitIsLured(targetId) and \
+ atkTrack == DROP:
+ result = 0
+ self.notify.debug('setting damage to 0, since drop on a lured suit')
+
+ if self.notify.getDebug():
+ self.notify.debug("toon does " +
+ str(result) +
+ " damage to suit")
+ else:
+ result = 0
+
+ #if (self.dbg_ToonSuperDamage):
+ # result = 200
+
+ if (result != 0 or atkTrack == PETSOS):
+ # be sure to set the amount of damage done by this attack
+ # in the appropriate slot in the toonAttacks list
+ # corresponding to this target
+ targets = self.__getToonTargets(attack)
+ if not targetList[currTarget] in targets:
+ if self.notify.getDebug():
+ self.notify.debug("Target of toon is not accessible!")
+ continue
+ targetIndex = targets.index(targetList[currTarget])
+ if (atkTrack == HEAL):
+ # we need to divide up the heal amounts among all
+ # targets, and heals alway hit the target
+ result = result / len(targetList)
+ if self.notify.getDebug():
+ self.notify.debug("Splitting heal among " +
+ str(len(targetList)) +
+ " targets")
+ assert(targetIndex < len(attack[TOON_HP_COL]))
+
+ # if this target is already in the successfulLures list,
+ # then be sure to put in the damage done to this target
+ # by this lure since this might not be the actual lure
+ # that ends up causing damage to the suit
+ if (self.successfulLures.has_key(targetId) and
+ atkTrack == LURE):
+ self.notify.debug("Updating lure damage to " +
+ str(result))
+ self.successfulLures[targetId][3] = result
+ else:
+ attack[TOON_HP_COL][targetIndex] = result
+
+ # make sure to give exp for any lures associated with
+ # this target if it was lured when it took damage
+ if (result > 0 and atkTrack != HEAL and atkTrack != DROP
+ and atkTrack != PETSOS):
+ attackTrack = LURE
+ lureInfos = self.__getLuredExpInfo(targetId)
+
+ # determine who lured the target and give them exp
+ # then make sure to remove the lured info for this
+ # target
+ for currInfo in lureInfos:
+ if currInfo[3]:
+ self.notify.debug("Giving lure EXP to toon " +
+ str(currInfo[0]))
+ self.__addAttackExp(attack, track=attackTrack,
+ level=currInfo[1],
+ attackerId=currInfo[0])
+
+ # now that this lurer has gotten exp for luring this
+ # target that took damage while lured, remove any
+ # references that this target might have to this
+ # lurer to make sure the lurer only gets exp once per
+ # lure, comprende?
+ self.__clearLurer(currInfo[0],
+ lureId=currInfo[2])
+
+ if lureDidDamage:
+ # since the lure resulted in damage, give exp to the toon
+ if self.itemIsCredit(atkTrack, atkLevel):
+ self.notify.debug("Giving lure EXP to toon " + str(toonId))
+ self.__addAttackExp(attack)
+
+
+
+ # if it has been discovered that all targets are dead or lured and
+ # this is a lure attack or this attack track is different from the
+ # previous attack and all targets are dead, then we want to clear
+ # out this attack
+ if (not validTargetAvail and
+ self.__prevAtkTrack(toonId) != atkTrack):
+ self.__clearAttack(toonId)
+
+ def __getToonTargets(self, attack):
+ """
+ attack, the attack to get the targets for
+
+ Get a list of available targets for a specific
+ attack
+ """
+ track = self.__getActualTrack(attack)
+ if (track == HEAL or track == PETSOS):
+ # heals from a toon only apply to other toons
+ return self.battle.activeToons
+ else:
+ # all other attack types apply to suits
+ return self.battle.activeSuits
+
+ def __attackHasHit(self, attack, suit=0):
+ """
+ attack, the attack to check
+ Returns: 1 if the attack successfully hit
+ 0 if the attack has not hit anything
+
+ Check the given attack to see if it has hit anything
+ """
+ # return whether or not the target(s) dodged the attack
+ if (suit == 1):
+ for dmg in attack[SUIT_HP_COL]:
+ if (dmg > 0):
+ return 1
+ return 0
+ else:
+ track = self.__getActualTrack(attack)
+ return not attack[TOON_ACCBONUS_COL] and \
+ track != NO_ATTACK
+
+ def __attackDamage(self, attack, suit=0):
+ """
+ attack, the attack to find the damage for
+ suit, 1 if the attack is a suit attack, 0 otherwise
+ Returns: damage done to one of the targets, 0 if no damage
+ was done
+
+ Ask how much damage an attack did to any given target
+ """
+ if suit:
+ for dmg in attack[SUIT_HP_COL]:
+ if (dmg > 0):
+ return dmg
+ return 0
+ else:
+ for dmg in attack[TOON_HP_COL]:
+ if (dmg > 0):
+ return dmg
+ return 0
+
+ def __attackDamageForTgt(self, attack, tgtPos, suit=0):
+ """
+ attack, the attack to find the damage for
+ suit, 1 if the attack is a suit attack, 0 otherwise
+ Returns: damage done to one of the targets, 0 if no damage
+ was done
+
+ Ask how much damage an attack did to a specific
+ target
+ """
+ if suit:
+ return attack[SUIT_HP_COL][tgtPos]
+ else:
+ return attack[TOON_HP_COL][tgtPos]
+
+ def __calcToonAccBonus(self, attackKey):
+ """
+ attackIndex, the attack to calculate the accuracy
+ bonus for
+
+ Calculate the accuracy bonus for a specific attack
+ """
+ # look at previous attacks performed this round to determine
+ # accuracy bonus
+ numPrevHits = 0
+ attackIdx = self.toonAtkOrder.index(attackKey)
+ for currPrevAtk in range(attackIdx - 1, -1, -1):
+ attack = self.battle.toonAttacks[attackKey]
+ atkTrack, atkLevel = self.__getActualTrackLevel(attack)
+ prevAttackKey = self.toonAtkOrder[currPrevAtk]
+ prevAttack = self.battle.toonAttacks[prevAttackKey]
+ prvAtkTrack, prvAtkLevel = self.__getActualTrackLevel(prevAttack)
+
+ # only if the previous attack hit, the current
+ # attack is not the same track as the prevous attack, and
+ # the target of the attacks are the same (or the previous
+ # or current attack are a group attack), then there is a
+ # possibility of getting an accuracy bonus for this attack
+ if self.__attackHasHit(prevAttack) and \
+ (attackAffectsGroup(prvAtkTrack, prvAtkLevel, prevAttack[TOON_TRACK_COL]) or \
+ attackAffectsGroup(atkTrack, atkLevel, attack[TOON_TRACK_COL]) or \
+ attack[TOON_TGT_COL] == prevAttack[TOON_TGT_COL]) and \
+ atkTrack != prvAtkTrack:
+ numPrevHits += 1
+
+ if numPrevHits > 0 and self.notify.getDebug():
+ self.notify.debug("ACC BONUS: toon attack received accuracy " +
+ "bonus of " +
+ str(self.AccuracyBonuses[numPrevHits]) +
+ " from previous attack by (" +
+ str(attack[TOON_ID_COL]) +
+ ") which hit")
+ return self.AccuracyBonuses[numPrevHits]
+
+
+ def __applyToonAttackDamages(self, toonId, hpbonus=0, kbbonus=0):
+ """
+ attackIndex, the attack to apply damages from
+ Returns: total damage done to everything
+
+ Apply any damages for the specified toon attack by
+ modifying any suit hit-points, also be sure to flag
+ if any suit died as a result of this damage
+ """
+ # now be sure to adjust the damage to the suit, but only
+ # if the track of the attack is not 0, meaning it is not
+ # a heal, if it's a heal, then the damage is applied as
+ # a plus to the target's health and we don't handle adjusting
+ # toon health here (additionally attack 1 is a trap attacks,
+ # doesn't cause damage directly but only in conjunction with a
+ # lure attack)
+ totalDamages = 0
+ if not self.APPLY_HEALTH_ADJUSTMENTS:
+ return totalDamages
+ assert(self.battle.toonAttacks.has_key(toonId))
+ attack = self.battle.toonAttacks[toonId]
+ track = self.__getActualTrack(attack)
+ if (track != NO_ATTACK and track != SOS and
+ track != TRAP and track != NPCSOS):
+ # first create a list of targets based on group or
+ # single target designation for this particular attack
+ targets = self.__getToonTargets(attack)
+ for position in range(len(targets)):
+ if hpbonus:
+ # handle applying the hp-bonus if this target
+ # was actually hit by this attack
+ if targets[position] in \
+ self.__createToonTargetList(toonId):
+ damageDone = attack[TOON_HPBONUS_COL]
+ else:
+ damageDone = 0
+ elif kbbonus:
+ # handle applying the hp-bonus if this target
+ # was actually hit by this attack
+ if targets[position] in \
+ self.__createToonTargetList(toonId):
+ damageDone = attack[TOON_KBBONUS_COL][position]
+ else:
+ damageDone = 0
+ else:
+ assert(position < len(attack[TOON_HP_COL]))
+ damageDone = attack[TOON_HP_COL][position]
+ if damageDone <= 0 or self.immortalSuits:
+ # suit at this position was not hit
+ continue
+ if (track == HEAL or track == PETSOS):
+ # target of toon attack was another toon, we
+ # don't want to apply any damage yet
+ currTarget = targets[position]
+ assert(self.toonHPAdjusts.has_key(currTarget))
+ if self.CAP_HEALS:
+ # make sure to bound the toon's health to its
+ # max health
+ toonHp = self.__getToonHp(currTarget)
+ toonMaxHp = self.__getToonMaxHp(currTarget)
+ if toonHp + damageDone > toonMaxHp:
+ damageDone = toonMaxHp - toonHp
+ attack[TOON_HP_COL][position] = damageDone
+ self.toonHPAdjusts[currTarget] += damageDone
+ totalDamages = totalDamages + damageDone
+ continue
+
+ # we should only get here if the target is a suit and
+ # at least 1hp of damage was done
+ currTarget = targets[position]
+ assert isinstance(currTarget,
+ DistributedSuitBaseAI.DistributedSuitBaseAI), \
+ targets
+ currTarget.setHP(currTarget.getHP() - damageDone)
+ targetId = currTarget.getDoId()
+ if self.notify.getDebug():
+ if hpbonus:
+ self.notify.debug(str(targetId) +
+ ": suit takes " +
+ str(damageDone) +
+ " damage from HP-Bonus")
+ elif kbbonus:
+ self.notify.debug(str(targetId) +
+ ": suit takes " +
+ str(damageDone) +
+ " damage from KB-Bonus")
+ else:
+ self.notify.debug(str(targetId) + ": suit takes " +
+ str(damageDone) + " damage")
+ totalDamages = totalDamages + damageDone
+
+ # if the suit died from this or a previous
+ # attack, make sure to set the 'died' field for
+ # the target to 1, indicating to the higher-ups
+ # that this suit has died
+ if currTarget.getHP() <= 0:
+ if currTarget.getSkeleRevives() >= 1:
+ currTarget.useSkeleRevive()
+ attack[SUIT_REVIVE_COL] = \
+ attack[SUIT_REVIVE_COL] | (1 << position)
+ else:
+ self.suitLeftBattle(targetId)
+ attack[SUIT_DIED_COL] = \
+ attack[SUIT_DIED_COL] | (1 << position)
+ if self.notify.getDebug():
+ self.notify.debug("Suit" + str(targetId) +
+ "bravely expired in combat")
+
+ return totalDamages
+
+ def __combatantDead(self, avId, toon):
+ """
+ attackIdx, index into the appropriate attacks list
+ indicating which attack we are examining
+ toon, whether this attacker is a toon or a suit
+ Returns: 1 if the attacker is dead, 0 otherwise
+
+ Check to see if the attacker for the specified
+ attack is still alive, useful to check to see if
+ the attack should be calculated
+ """
+ if toon:
+ if self.__getToonHp(avId) <= 0:
+ return 1
+ else:
+ # the suit's hp is adjusted after each attack, so the current
+ # value should be correctly adjusted for attack ordering
+ suit = self.battle.findSuit(avId)
+ assert(suit != None)
+ if (suit.getHP() <= 0):
+ return 1
+ return 0
+
+ def __combatantJustRevived(self, avId):
+ suit = self.battle.findSuit(avId)
+ assert(suit != None)
+ if suit.reviveCheckAndClear():
+ return 1
+ else:
+ return 0
+
+ def __addAttackExp(self, attack, track=-1, level=-1, attackerId=-1):
+ """
+ attack, the toon attack to record exp for
+ track, optional track to add exp to
+ lvl, optional lvl to add exp to
+ toonId, optional toon to add exp to
+
+ Record any exp gained for a toon for the specified
+ attack, the optional parameters, if all are provided
+ will override the attack parameter and add exp to
+ the specified toon for the specified attack track
+ and level
+ """
+ trk = -1
+ lvl = -1
+ id = -1
+ if track != -1 and level != -1 and attackerId != -1:
+ trk = track
+ lvl = level
+ id = attackerId
+ elif self.__attackHasHit(attack):
+ if self.notify.getDebug():
+ self.notify.debug("Attack " + repr(attack) + " has hit")
+ # now be sure to update the skill points gained for the
+ # attacking toon, but only if the attack actually 'hit'
+ # something, meaning it was a 'successful' attack
+ trk = attack[TOON_TRACK_COL]
+ lvl = attack[TOON_LVL_COL]
+ id = attack[TOON_ID_COL]
+
+ if trk != -1 and trk != NPCSOS and trk != PETSOS and \
+ lvl != -1 and id != -1:
+ expList = self.toonSkillPtsGained.get(id, None)
+ if expList == None:
+ expList = [0, 0, 0, 0, 0, 0, 0]
+ self.toonSkillPtsGained[id] = expList
+
+ expList[trk] = min(ExperienceCap,
+ expList[trk] + (lvl + 1) * self.__skillCreditMultiplier)
+
+ def __clearTgtDied(self, tgt, lastAtk, currAtk):
+ """
+ tgt, the target (in this case a suit)
+ lastAtk, an attack that might have killed the 'tgt'
+ currAtk, an attack that should flag 'tgt' killed if
+ necessary
+
+ Check to see if the suit died flag for 'lastAtk'
+ should be cleared. This can happen if 'lastAtk'
+ killed the suit, but 'currAtk', which happened right
+ after 'lastAtk', is of the same track and thus we
+ want to play both 'lastAtk' and 'currAtk' for visual
+ and gameplay appeal, even though 'currAtk' is not
+ actually necessary to kill the suit. In this case
+ we only want to flag that the suit died on 'currAtk'
+ so the movie can play the attack sequence properly.
+ """
+ position = self.battle.activeSuits.index(tgt)
+ currAtkTrack = self.__getActualTrack(currAtk)
+ lastAtkTrack = self.__getActualTrack(lastAtk)
+ if currAtkTrack == lastAtkTrack and \
+ lastAtk[SUIT_DIED_COL] & (1 << position) and \
+ self.__attackHasHit(currAtk, suit=0):
+ if self.notify.getDebug():
+ self.notify.debug("Clearing suit died for " +
+ str(tgt.getDoId()) +
+ " at position " + str(position) +
+ " from toon attack " +
+ str(lastAtk[TOON_ID_COL]) +
+ " and setting it for " +
+ str(currAtk[TOON_ID_COL]))
+
+ # clear the flag from the previous attack
+ lastAtk[SUIT_DIED_COL] = \
+ lastAtk[SUIT_DIED_COL] ^ (1 << position)
+
+ # make sure since the last attack
+ # indicated that it killed the
+ # suit and we are now clearing out
+ # that indication, that this
+ # attack, which is of the same
+ # track, does flag the suit as
+ # dead
+ assert tgt.getHP() <= 0
+ self.suitLeftBattle(tgt.getDoId())
+ currAtk[SUIT_DIED_COL] = \
+ currAtk[SUIT_DIED_COL] | (1 << position)
+
+ def __addDmgToBonuses(self, dmg, attackIndex, hp=1):
+ """
+ dmg, how much damage to add to the bonuses list
+ attackIndex, the attack that did specified damage
+ hp, 1 if this is for the hpBonuses list, 0 for the
+ kbBonuses list
+
+ Add a given damage value done by a specified attack
+ to a specified bonus list, this should be called
+ as soon as a suit is damaged by any attack
+ """
+ toonId = self.toonAtkOrder[attackIndex]
+ attack = self.battle.toonAttacks[toonId]
+ atkTrack = self.__getActualTrack(attack)
+ if (atkTrack == HEAL or atkTrack == PETSOS):
+ return
+
+ # for each target of this attack, keep track of any bonus from this
+ # attack
+ tgts = self.__createToonTargetList(toonId)
+ for currTgt in tgts:
+ tgtPos = self.battle.suits.index(currTgt)
+ attackerId = self.toonAtkOrder[attackIndex]
+ attack = self.battle.toonAttacks[attackerId]
+ track = self.__getActualTrack(attack)
+ if hp:
+ if self.hpBonuses[tgtPos].has_key(track):
+ self.hpBonuses[tgtPos][track].append([attackIndex, dmg])
+ else:
+ self.hpBonuses[tgtPos][track] = [[attackIndex, dmg]]
+ else:
+ # only add a kbBonus value if the suit is currently lured
+ if self.__suitIsLured(currTgt.getDoId()):
+ if self.kbBonuses[tgtPos].has_key(track):
+ self.kbBonuses[tgtPos][track].append([attackIndex, dmg])
+ else:
+ self.kbBonuses[tgtPos][track] = [[attackIndex, dmg]]
+
+ def __clearBonuses(self, hp=1):
+ """
+ hp, 1 if the hpBonuses list should be cleared, 0 if
+ the kbBonuses list should be cleared
+
+ Clear a specific bonuses list, usually done before
+ calculating a new round
+ """
+ if hp:
+ self.hpBonuses = [{}, {}, {}, {}]
+ else:
+ self.kbBonuses = [{}, {}, {}, {}]
+
+ def __bonusExists(self, tgtSuit, hp=1):
+ """
+ tgtSuit, the suit to check to see if some sort of
+ damage bonus will be applied to him/her
+ hp, 1 to check for a hp-bonus, 0 to check for a
+ knock-back bonus
+ Returns: 1 if a bonus does exist, 0 otherwise
+
+ Check to see if a certain bonus type exists for a
+ given target suit
+ """
+ tgtPos = self.activeSuits.index(tgtSuit)
+ if hp:
+ bonusLen = len(self.hpBonuses[tgtPos])
+ else:
+ bonusLen = len(self.kbBonuses[tgtPos])
+ if bonusLen > 0:
+ return 1
+ return 0
+
+ def __processBonuses(self, hp=1):
+ """
+ Process all current hpBonus information, also
+ clearing out the hpBonus information so a new track
+ can be processed
+ """
+ # Process all hpBonus information, making sure to update the
+ # appropriate attacks with the calculated hpBonus for grouped
+ # attacks of the same track that did damage. NOTE: this code
+ # is fairly basic because it assumes that all attacks in a single
+ # track type of attacks are either group or single targets. If in
+ # the future attacks in a track that does damage (not including the
+ # heal track) varies in group or single targets, this function will
+ # not work the way it should. This is because the hpBonuses list is
+ # ordered by target and so an attack that receives an hp-bonus on
+ # say the first target will apply that hp-bonus to all of its
+ # targets depending on if it is a group or single type attack. And
+ # since any other attack that might also receive an hp-bonus in
+ # combination with this attack must be the same track, it must
+ # therefore also be of the same target type. This prevents say
+ # target 1 from receiving a damage bonus of 10 from attack 1 and
+ # target 2 from receiving a damage bonus of 5 from attack 1 (all
+ # targets receiving a damage bonus from attack 1 would all be 10)
+ # Additionally there is only one spot for a hp-bonus for each toon
+ # attack (so if in the future group/single target attacks are mixed
+ # within a single track that does damage then this code will have to
+ # be improved to handle this case and the self.battle.toonAttacks
+ # list will have to have 4 entries for hp-bonuses for each toon
+ # attack)
+ if hp:
+ bonusList = self.hpBonuses
+ self.notify.debug("Processing hpBonuses: " +
+ repr(self.hpBonuses))
+ else:
+ bonusList = self.kbBonuses
+ self.notify.debug("Processing kbBonuses: " +
+ repr(self.kbBonuses))
+ tgtPos = 0
+ for currTgt in bonusList:
+ for currAtkType in currTgt.keys():
+ # for an hpBonus we need at least 2 damages from a single
+ # attack type, for kbBonuses we only need at least 1 damage
+ # value
+ if len(currTgt[currAtkType]) > 1 or \
+ ((not hp) and len(currTgt[currAtkType]) > 0):
+ totalDmgs = 0
+ for currDmg in currTgt[currAtkType]:
+ totalDmgs += currDmg[1]
+ numDmgs = len(currTgt[currAtkType])
+ attackIdx = currTgt[currAtkType][numDmgs-1][0]
+ attackerId = self.toonAtkOrder[attackIdx]
+ attack = self.battle.toonAttacks[attackerId]
+ if hp:
+ attack[TOON_HPBONUS_COL] = math.ceil(
+ totalDmgs * \
+ (self.DamageBonuses[numDmgs - 1] * 0.01))
+ if self.notify.getDebug():
+ self.notify.debug(
+ "Applying hp bonus to track " +
+ str(attack[TOON_TRACK_COL]) +
+ " of " +
+ str(attack[TOON_HPBONUS_COL]))
+ else:
+ if (len(attack[TOON_KBBONUS_COL]) > tgtPos):
+ attack[TOON_KBBONUS_COL][tgtPos] = \
+ totalDmgs * 0.5
+ if self.notify.getDebug():
+ self.notify.debug(
+ "Applying kb bonus to track " +
+ str(attack[TOON_TRACK_COL]) +
+ " of " +
+ str(attack[TOON_KBBONUS_COL][tgtPos]) +
+ " to target " +
+ str(tgtPos))
+ else:
+ self.notify.warning("invalid tgtPos for knock back bonus: %d" % tgtPos)
+ # move on to the next suit in line
+ tgtPos += 1
+
+ if hp:
+ self.__clearBonuses()
+ else:
+ self.__clearBonuses(hp=0)
+
+
+ def __handleBonus(self, attackIdx, hp=1):
+ """
+ attackIdx, index into self.toonAtkOrder indicating
+ which toon attack to calculate the HPBonus for
+
+ Handle any Bonus that might apply for a specific
+ toon attack (possible bonuses are hp and knockback)
+ """
+ attackerId = self.toonAtkOrder[attackIdx]
+ attack = self.battle.toonAttacks[attackerId]
+ atkDmg = self.__attackDamage(attack, suit=0)
+ atkTrack = self.__getActualTrack(attack)
+ if atkDmg > 0:
+ if hp:
+ # add the attack damage to the hpBonuses list
+ # lures don't get hpBonuses
+ if (atkTrack != LURE):
+ self.notify.debug("Adding dmg of " + str(atkDmg) +
+ " to hpBonuses list")
+ self.__addDmgToBonuses(atkDmg, attackIdx)
+ else:
+ # make sure this attack can get a knockback bonus, if
+ # so add the damage done by the attack to the kbBonuses
+ # list
+ if self.__knockBackAtk(attackerId, toon=1):
+ self.notify.debug("Adding dmg of " + str(atkDmg) +
+ " to kbBonuses list")
+ self.__addDmgToBonuses(atkDmg, attackIdx, hp=0)
+
+
+ def __clearAttack(self, attackIdx, toon=1):
+ """
+ // Parameters: attackIdx, the attack to clear out
+ // toon, 1 if this is a toon attack, 0 for a suit attack
+ // Function: clear out a specific attack, usually because it has
+ // been determined that all of its targets are dead
+ """
+ if toon:
+ if self.notify.getDebug():
+ self.notify.debug(
+ "clearing out toon attack for toon " +
+ str(attackIdx) +
+ "...")
+ attack = self.battle.toonAttacks[attackIdx]
+ self.battle.toonAttacks[attackIdx] = \
+ getToonAttack(attackIdx)
+
+ # make sure to create all the necessary hp columns
+ # for this toon attack
+ longest = max(len(self.battle.activeToons),
+ len(self.battle.activeSuits))
+ taList = self.battle.toonAttacks
+ for j in range(longest):
+ taList[attackIdx][TOON_HP_COL].append(-1)
+ taList[attackIdx][TOON_KBBONUS_COL].append(-1)
+
+ if self.notify.getDebug():
+ self.notify.debug("toon attack is now " + \
+ repr(self.battle.toonAttacks[attackIdx]))
+ else:
+ self.notify.warning("__clearAttack not implemented for suits!")
+
+ def __rememberToonAttack(self, suitId, toonId, damage):
+ """
+ suitId, the suit that the toon attacked
+ toonId, the toon that attacked
+ damage, how much damage the toon did to the suit
+
+ Allow the suit to remember the toon that attacked
+ it and it will attack back if it thinks the toon is
+ worthy of his/her time
+ """
+ if not self.SuitAttackers.has_key(suitId):
+ self.SuitAttackers[suitId] = { toonId: damage }
+ elif not self.SuitAttackers[suitId].has_key(toonId):
+ self.SuitAttackers[suitId][toonId] = damage
+ elif self.SuitAttackers[suitId][toonId] <= damage:
+ # this toon has done more damage than was done on a previous
+ # round, so replace any lesser recorded damage for this toon
+ self.SuitAttackers[suitId] = [toonId, damage]
+
+ def __postProcessToonAttacks(self):
+ """
+ After all battle calculations have been performed
+ for all toon attacks, make another pass through the
+ attacks to clear out attacks that target dead suits
+ and add up experience points gained by toons this
+ round, also apply damages/heals done by toon attacks
+ """
+ # Now go through each toon attack in order and clear out any
+ # that has all dead targets. For any attack that is still left,
+ # apply its damages to all of its targets
+ self.notify.debug("__postProcessToonAttacks()")
+ lastTrack = -1
+ lastAttacks = []
+ self.__clearBonuses()
+ for currToonAttack in self.toonAtkOrder:
+ if currToonAttack != -1:
+ # if this attack targets suits, get all of its targets
+ # and make sure at least one of them is still alive,
+ # otherwise clear out the attack, also be sure to handle
+ # any hp bonuses (excluding hp bonuses for heals)
+ attack = self.battle.toonAttacks[currToonAttack]
+ atkTrack, atkLevel = self.__getActualTrackLevel(attack)
+ if (atkTrack != HEAL and atkTrack != SOS and
+ atkTrack != NO_ATTACK and atkTrack != NPCSOS and
+ atkTrack != PETSOS):
+
+ targets = self.__createToonTargetList(currToonAttack)
+
+ allTargetsDead = 1
+ for currTgt in targets:
+ # be sure to let the suit remember which toon
+ # attacked it so the suit can attack back
+ # if the track of this attack is the same as that of
+ # the previous attack
+ damageDone = self.__attackDamage(attack, suit=0)
+ if damageDone > 0:
+ self.__rememberToonAttack(
+ currTgt.getDoId(),
+ attack[TOON_ID_COL],
+ damageDone)
+
+ # Make sure traps are placed on the suits
+ if (atkTrack == TRAP):
+ if (self.traps.has_key(currTgt.doId)):
+ trapInfo = self.traps[currTgt.doId]
+ currTgt.battleTrap = trapInfo[0]
+
+ targetDead = 0
+ if currTgt.getHP() > 0:
+ allTargetsDead = 0
+ else:
+ targetDead = 1
+ if (atkTrack != LURE):
+ # check every previous attack that is of
+ # the same track as the current attack.
+ # If they both kill the same suit, then
+ # clear out the suit died flag for the
+ # prevous attack. This does not apply to
+ # LURE attacks since lure's are not combined
+ # during movie playback and thus the first lure
+ # did kill the suit and the second lure plays
+ # after the suit has already died.
+ for currLastAtk in lastAttacks:
+ self.__clearTgtDied(currTgt, currLastAtk,
+ attack)
+
+ # process the successful lures list to see if this
+ # guy was lured and if so which attack successfully
+ # lured him/her/it
+ tgtId = currTgt.getDoId()
+ if (self.successfulLures.has_key(tgtId) and
+ atkTrack == LURE):
+ lureInfo = self.successfulLures[tgtId]
+ self.notify.debug("applying lure data: " +
+ repr(lureInfo))
+ toonId = lureInfo[0]
+ lureAtk = self.battle.toonAttacks[toonId]
+ tgtPos = self.battle.activeSuits.index(currTgt)
+
+ #check if a train trap will triger, if so remember it
+ if (self.traps.has_key(currTgt.doId)):
+ trapInfo = self.traps[currTgt.doId]
+ if trapInfo[0] == UBER_GAG_LEVEL_INDEX:
+ self.notify.debug('train trap triggered for %d' % currTgt.doId)
+ self.trainTrapTriggered = True
+
+ # make sure to remove any trap that might be
+ # associated with this target since he/she/it
+ # is now lured and therefore should have
+ # triggered the trap
+ self.__removeSuitTrap(tgtId)
+
+ # be sure to flag that this target has been
+ # lured by this attack, and also indicate any
+ # damage that the lure might have resulted in
+ lureAtk[TOON_KBBONUS_COL][tgtPos] = \
+ self.KBBONUS_TGT_LURED
+ lureAtk[TOON_HP_COL][tgtPos] = lureInfo[3]
+
+ elif (self.__suitIsLured(tgtId) and
+ atkTrack == DROP):
+ # if this is a drop on a lured suit,
+ # be sure to place a indicator in the
+ # KBBONUS_COL so the battle movie knows
+ # that this is a drop or sound on a lured suit
+ # and it should do something different than other
+ # attack types on lured suits
+ self.notify.debug("Drop on lured suit, " +
+ "indicating with KBBONUS_COL " +
+ "flag")
+ tgtPos = self.battle.activeSuits.index(currTgt)
+ attack[TOON_KBBONUS_COL][tgtPos] = \
+ self.KBBONUS_LURED_FLAG
+
+ # if this individual target is dead, then make sure to
+ # clear out any damage and kbBonus info for it
+ if (targetDead and atkTrack != lastTrack):
+ tgtPos = self.battle.activeSuits.index(currTgt)
+ attack[TOON_HP_COL][tgtPos] = 0
+ attack[TOON_KBBONUS_COL][tgtPos] = -1
+
+ # Clear out this toon attack if all of its targets are
+ # dead and it is not part of a combo attack (one of a
+ # group of attacks from the same track).
+ if (allTargetsDead and atkTrack != lastTrack):
+ if self.notify.getDebug():
+ self.notify.debug("all targets of toon attack " +
+ str(currToonAttack) +
+ " are dead")
+ self.__clearAttack(currToonAttack, toon=1)
+ attack = self.battle.toonAttacks[currToonAttack]
+ atkTrack, atkLevel = self.__getActualTrackLevel(attack)
+
+ # now apply any relevant damages to the target suit(s)
+ # (or health to toons)
+ damagesDone = self.__applyToonAttackDamages(currToonAttack)
+ self.__applyToonAttackDamages(currToonAttack, hpbonus=1)
+
+ # apply knockback bonuses for this attack only if it is not
+ # a lure, since lures dont get knockback bonuses anyways and
+ # we use the knockback bonuses values for something other
+ # than knockback bonuses when the attack is a lure (same goes
+ # for drops and sounds)
+ if (atkTrack != LURE and atkTrack != DROP and
+ atkTrack != SOUND):
+ self.__applyToonAttackDamages(currToonAttack, kbbonus=1)
+
+ # remember the attack so the future attacks this round
+ # that are of the same track type can look at them and
+ # set the target died flag appropriately
+ if (lastTrack != atkTrack):
+ lastAttacks = []
+ lastTrack = atkTrack
+ lastAttacks.append(attack)
+
+ # record any exp received for this attack
+ if (self.itemIsCredit(atkTrack, atkLevel)):
+ if (atkTrack == TRAP or atkTrack == LURE):
+ # Don't add trap exp here; that is handled as
+ # soon as the trap is triggered, and don't add
+ # lure exp here; that is only given if the
+ # lured targets are damaged while lured.
+ pass
+
+ elif (atkTrack == HEAL):
+ # For heal attacks, we only give credit if any
+ # heal points were actually awarded. You don't
+ # get credit for healing someone who didn't need
+ # healing!
+ if damagesDone != 0:
+ self.__addAttackExp(attack)
+
+ else:
+ self.__addAttackExp(attack)
+
+ if self.trainTrapTriggered:
+ #since a train trap can be triggered from a single lure, make sure the
+ #trap is removed for the other suits
+ for suit in self.battle.activeSuits:
+ suitId = suit.doId
+ self.__removeSuitTrap(suitId)
+ suit.battleTrap = NO_TRAP
+ self.notify.debug('train trap triggered, removing trap from %d' % suitId)
+
+ # if debugging, print out all of the final toon attacks after all
+ # adjustments have been performed on them
+ if self.notify.getDebug():
+ for currToonAttack in self.toonAtkOrder:
+ attack = self.battle.toonAttacks[currToonAttack]
+ self.notify.debug("Final Toon attack: " + str(attack))
+
+
+ def __allTargetsDead(self, attackIdx, toon=1):
+ """
+ attackIdx, the attack to examine
+ toon, 1 if this is a toon attack, 0 if it is suit
+ Returns: 1 if all targets are dead, 0 otherwise
+
+ Check to see if all targets of a specific attack
+ are currently dead
+ """
+ allTargetsDead = 1
+ if toon:
+ # now that we have added some more damage done to any
+ # targets, make sure to clear out
+ targets = self.__createToonTargetList(attackIdx)
+ for currTgt in targets:
+ if currTgt.getHp() > 0:
+ allTargetsDead = 0
+ break
+ else:
+ self.notify.warning("__allTargetsDead: suit ver. not implemented!")
+ return allTargetsDead
+
+
+ def __clearLuredSuitsByAttack(self, toonId, kbBonusReq=0, targetId=-1):
+ """
+ toonId, the attack to wake suits up from
+ kbBonusReq, 1 if a knockback bonus on the suit is
+ required for the suit to become unlured, 0 if no
+ knockback bonus is needed
+
+ Wake up all suits which have been hit by the
+ specified toon attack
+ """
+ if self.notify.getDebug():
+ self.notify.debug("__clearLuredSuitsByAttack")
+ if targetId != -1 and \
+ self.__suitIsLured(t.getDoId()):
+ self.__removeLured(t.getDoId())
+ else:
+ tgtList = self.__createToonTargetList(toonId)
+ for t in tgtList:
+ # for all targets of this attack, if any have received a
+ # knockback bonus and are currently lured, wake them up
+ if self.__suitIsLured(t.getDoId()) and \
+ (not kbBonusReq or self.__bonusExists(t, hp=0)):
+ self.__removeLured(t.getDoId())
+ if self.notify.getDebug():
+ self.notify.debug("Suit %d stepping from lured spot" %
+ t.getDoId())
+ else:
+ self.notify.debug("Suit " + str(t.getDoId()) +
+ " not found in currently lured suits")
+
+ def __clearLuredSuitsDelayed(self):
+ """
+ toonId, the attack to wake suits up from
+ kbBonusReq, 1 if a knockback bonus on the suit is
+ required for the suit to become unlured, 0 if no
+ knockback bonus is needed
+
+ Wke up all suits which have been hit by the
+ specified toon attack
+ """
+ if self.notify.getDebug():
+ self.notify.debug("__clearLuredSuitsDelayed")
+ for t in self.delayedUnlures:
+ # for all targets of this attack, if any have received a
+ # knockback bonus and are currently lured, wake them up
+ if self.__suitIsLured(t):
+# self.suitsUnluredThisRound.append(t)
+ self.__removeLured(t)
+ if self.notify.getDebug():
+ self.notify.debug("Suit %d stepping back from lured spot" %
+ t)
+ else:
+ self.notify.debug("Suit " + str(t) +
+ " not found in currently lured suits")
+ self.delayedUnlures = []
+
+
+ def __addLuredSuitsDelayed(self, toonId, targetId=-1, ignoreDamageCheck = False):
+ """
+ toonId, the attack to wake suits up from
+ kbBonusReq, 1 if a knockback bonus on the suit is
+ required for the suit to become unlured, 0 if no
+ knockback bonus is needed
+
+ Add any suits damaged by the specified toon attack
+ to the delayed-unlure list to make sure that they
+ will be unlured as soon as possible (usually when
+ the last toon attack is processed or when a new track
+ of toon attacks is processed, whichever comes first)
+ """
+ if self.notify.getDebug():
+ self.notify.debug("__addLuredSuitsDelayed")
+ if targetId != -1:
+ # add only the specified target to the unlure list
+ self.delayedUnlures.append(targetId)
+ else:
+ tgtList = self.__createToonTargetList(toonId)
+ for t in tgtList:
+ # for all targets of this attack, if any are lured then
+ # plan to unlure them at a later time
+ if self.__suitIsLured(t.getDoId()) and \
+ not t.getDoId() in self.delayedUnlures and \
+ (self.__attackDamageForTgt(self.battle.toonAttacks[toonId],
+ self.battle.activeSuits.index(t),
+ suit=0) > 0 or \
+ ignoreDamageCheck):
+ self.delayedUnlures.append(t.getDoId())
+
+
+ def __calculateToonAttacks(self):
+ """
+ For each toon attack in the toonAttacks list,
+ calculate and fill in appropriate damage,
+ accuracyBonus, and HPBonus values
+ """
+ # a list of suits, and any knockback bonuses that are applied
+ # to them, each sublist corresponds to each entry in battle.suits
+ # the first value in the each sublist is the total knockback
+ # bonus damage done to that suit, while the second value in each
+ # sublist is the toon attack track that has gotten the corresponging
+ # knockback bonus damage
+ self.notify.debug("__calculateToonAttacks()")
+ self.__clearBonuses(hp=0)
+ currTrack = None
+
+ self.notify.debug("Traps: " + str(self.traps))
+
+ # Determine the maximum level attack item we'll get credit
+ # for, based on the suits in the battle. This is the same as
+ # the highest level suit we're currently facing.
+ maxSuitLevel = 0
+ for cog in self.battle.activeSuits:
+ maxSuitLevel = max(maxSuitLevel, cog.getActualLevel())
+ self.creditLevel = maxSuitLevel
+
+
+ for toonId in self.toonAtkOrder:
+ # do some checks to make sure this toon actually has an attack
+ # and that this toon is not already dead
+ assert(toonId != -1)
+ if self.__combatantDead(toonId, toon=1):
+ if self.notify.getDebug():
+ self.notify.debug("Toon %d is dead and can't attack" %
+ toonId)
+ continue
+
+ assert(self.battle.toonAttacks.has_key(toonId))
+ attack = self.battle.toonAttacks[toonId]
+ atkTrack = self.__getActualTrack(attack)
+
+
+
+ if (atkTrack != NO_ATTACK and atkTrack != SOS and
+ atkTrack != NPCSOS):
+
+ if self.notify.getDebug():
+ self.notify.debug("Calculating attack for toon: %d" %
+ toonId)
+
+ # if this a new track of attacks, then any suit that is in
+ # the 'unlure' list (list of suits that should be unlured
+ # as soon as it is reasonable) should now be unlured
+ # for now, only do this if suits are unlured immediately,
+ # otherwise wait to unlure the suit until all toon attacks
+ # have been calculated
+ if self.SUITS_UNLURED_IMMEDIATELY:
+ if currTrack and atkTrack != currTrack:
+ self.__clearLuredSuitsDelayed()
+ currTrack = atkTrack
+
+ # now calculate how much damage the attack has done, not yet
+ # accounting for bonuses
+ self.__calcToonAtkHp(toonId)
+
+ # now handle the knockback and hp bonuses
+ attackIdx = self.toonAtkOrder.index(toonId)
+ self.__handleBonus(attackIdx, hp=0)
+ self.__handleBonus(attackIdx, hp=1)
+
+ # if this is the last toon attack this round, is an attack that
+ # can knock a target back, and it has hit its target, be sure
+ # to wakeup any suits knocked back by this attack, but if this
+ # is not the last attack but still knocks back its target,
+ # place the suit in the 'delayedUnlures' list to make sure it
+ # is unlured when we get to a new track of attacks
+ lastAttack = self.toonAtkOrder.index(toonId) >= \
+ len(self.toonAtkOrder) - 1
+ unlureAttack = self.__attackHasHit(attack, suit=0) and \
+ self.__unlureAtk(toonId, toon=1)
+ if unlureAttack:
+ if lastAttack:
+ self.__clearLuredSuitsByAttack(toonId)
+ else:
+ self.__addLuredSuitsDelayed(toonId)
+
+ # if this is the last toon attack this round, be sure to clean
+ # up any delayed unlured suits that might still be around,
+ # possible unlured from this last attack or some other previous
+ # attack that is of the same track as this one
+ if lastAttack:
+ self.__clearLuredSuitsDelayed()
+
+ # process all hpBonuses and kbBonuses and apply the appropriate
+ # bonus values to the appropriate locations in the toonAttacks
+ # then make a second pass on all toon attacks to clear out attacks
+ # that no longer have valid targets
+ self.__processBonuses(hp=0)
+ self.__processBonuses(hp=1)
+ self.__postProcessToonAttacks()
+
+ def __knockBackAtk(self, attackIndex, toon=1):
+ """
+ attackIndex, the attack to check
+ toon, whether or not the attack is a toon attack
+ Returns: 1 if the attack is a knockback attack, 0 otherwise
+
+ Determine if a specific attack is one that is
+ eligible for a knockback bonus
+ """
+ if toon and \
+ (self.battle.toonAttacks[attackIndex][TOON_TRACK_COL] == THROW or \
+ self.battle.toonAttacks[attackIndex][TOON_TRACK_COL] == SQUIRT):
+ if self.notify.getDebug():
+ self.notify.debug("attack is a knockback")
+ return 1
+ return 0
+
+ def __unlureAtk(self, attackIndex, toon=1):
+ """
+ attackIndex, the attack to check
+ toon, whether or not the attack is a toon attack
+ Returns: 1 if the attack is an unlure attack, 0 otherwise
+
+ Determine if a specific attack is one that is
+ eligible for unluring a target
+ """
+ attack = self.battle.toonAttacks[attackIndex]
+ track = self.__getActualTrack(attack)
+ if (toon and (track == THROW or track == SQUIRT or track == SOUND)):
+ if self.notify.getDebug():
+ self.notify.debug("attack is an unlure")
+ return 1
+ return 0
+
+ def __calcSuitAtkType(self, attackIndex):
+ """
+ attackIndex, row in suitAttacks that contains the
+ attack type and the target of the attack
+
+ Determine the best attack type for a suit
+ """
+ theSuit = self.battle.activeSuits[attackIndex]
+ attacks = SuitBattleGlobals.SuitAttributes[theSuit.dna.name]['attacks']
+ atk = SuitBattleGlobals.pickSuitAttack(attacks, theSuit.getLevel())
+ return atk
+
+ def __calcSuitTarget(self, attackIndex):
+ """
+ attackIndex, row in suitAttacks that contains the
+ attack type and the target of the attack
+ Returns: an index into the battle.toons list indicating the
+ target for the specified suit attack
+
+ Determine the best target for a suit attack
+ """
+ attack = self.battle.suitAttacks[attackIndex]
+ suitId = attack[SUIT_ID_COL]
+ if self.SuitAttackers.has_key(suitId) and \
+ random.randint(0, 99) < 75:
+ # first calculate the total damage done to this suit by all
+ # recorded attackers, this is so we can create a frequency
+ # list of damage percentages that we can randomly pick from
+ totalDamage = 0
+ for currToon in self.SuitAttackers[suitId].keys():
+ totalDamage += self.SuitAttackers[suitId][currToon]
+
+ # create a list of damage percentages and pick one of the
+ # weighted values, this tells us which toon attacker that
+ # the suit should attack
+ dmgs = []
+ for currToon in self.SuitAttackers[suitId].keys():
+ dmgs.append((self.SuitAttackers[suitId][currToon] /
+ totalDamage) * 100)
+ dmgIdx = SuitBattleGlobals.pickFromFreqList(dmgs)
+ if (dmgIdx == None):
+ toonId = self.__pickRandomToon(suitId)
+ else:
+ toonId = self.SuitAttackers[suitId].keys()[dmgIdx]
+ if (toonId == -1 or toonId not in self.battle.activeToons):
+ return -1
+ self.notify.debug("Suit attacking back at toon " + str(toonId))
+ return self.battle.activeToons.index(toonId)
+ else:
+ #return random.randint(0, len(self.battle.activeToons) - 1)
+ # make sure we only randomly choose from the active toons
+ # that are still alive at this point in the round
+ return self.__pickRandomToon(suitId)
+
+ def __pickRandomToon(self, suitId):
+ liveToons = []
+ for currToon in self.battle.activeToons:
+ if not self.__combatantDead(currToon, toon=1):
+ liveToons.append(self.battle.activeToons.index(currToon))
+ if len(liveToons) == 0:
+ # no toons left alive to attack
+ self.notify.debug("No tgts avail. for suit " + str(suitId))
+ return -1
+ chosen = random.choice(liveToons)
+ self.notify.debug("Suit randomly attacking toon " +
+ str(self.battle.activeToons[chosen]))
+ return chosen
+
+ def __suitAtkHit(self, attackIndex):
+ """
+ Calculate whether or not a specific suit attack hit
+ """
+ if self.suitsAlwaysHit:
+ return 1
+ elif self.suitsAlwaysMiss:
+ return 0
+ theSuit = self.battle.activeSuits[attackIndex]
+ atkType = self.battle.suitAttacks[attackIndex][SUIT_ATK_COL]
+
+ atkInfo = SuitBattleGlobals.getSuitAttack(theSuit.dna.name,
+ theSuit.getLevel(),
+ atkType)
+ atkAcc = atkInfo['acc']
+ suitAcc = SuitBattleGlobals.SuitAttributes[theSuit.dna.name]\
+ ['acc'][theSuit.getLevel()]
+
+ # Jesse changed this because he doesn't think we need suit
+ # accuracies.
+ #acc = (atkAcc + suitAcc) / 2
+ acc = atkAcc
+ randChoice = random.randint(0, 99)
+ if self.notify.getDebug():
+ self.notify.debug("Suit attack rolled " + str(randChoice) +
+ " to hit with an accuracy of " + str(acc) +
+ " (attackAcc: " + str(atkAcc) +
+ " suitAcc: " + str(suitAcc) + ")")
+ if randChoice < acc:
+ return 1
+ return 0
+
+ def __suitAtkAffectsGroup(self, attack):
+ atkType = attack[SUIT_ATK_COL]
+
+ # determine if the attack targets a group or an individual
+ theSuit = self.battle.findSuit(attack[SUIT_ID_COL])
+ assert theSuit != None
+ atkInfo = SuitBattleGlobals.getSuitAttack(theSuit.dna.name,
+ theSuit.getLevel(),
+ atkType)
+ return atkInfo['group'] != SuitBattleGlobals.ATK_TGT_SINGLE
+
+ def __createSuitTargetList(self, attackIndex):
+ """
+ attackIndex, index into the suitAttacks list which
+ indicates which attack we are using
+
+ Create a list of targets for the specified suit
+ attack, the target type of the attack determines
+ which participants in the battle are targets
+ """
+ attack = self.battle.suitAttacks[attackIndex]
+ targetList = []
+ if attack[SUIT_ATK_COL] == NO_ATTACK:
+ self.notify.debug("No attack, no targets")
+ return targetList
+ debug = self.notify.getDebug()
+
+ if not self.__suitAtkAffectsGroup(attack):
+ # add only the specified target to the target list
+ targetList.append(self.battle.activeToons[attack[SUIT_TGT_COL]])
+ if debug:
+ self.notify.debug("Suit attack is single target")
+ else:
+ # all toons currently in battle
+ if debug:
+ self.notify.debug("Suit attack is group target")
+ for currToon in self.battle.activeToons:
+ if debug:
+ self.notify.debug("Suit attack will target toon" +
+ str(currToon))
+ targetList.append(currToon)
+ return targetList
+
+ def __calcSuitAtkHp(self, attackIndex):
+ """
+ attackIndex, row in suitAttacks that contains the
+ attack type and the target of the attack
+
+ Calculate how much damage a specific suit attack
+ does to a toon
+ """
+ targetList = self.__createSuitTargetList(attackIndex)
+ attack = self.battle.suitAttacks[attackIndex]
+
+ for currTarget in range(len(targetList)):
+ toonId = targetList[currTarget]
+ toon = self.battle.getToon(toonId)
+
+ result = 0
+ if toon and toon.immortalMode:
+ # At least 1 hp, otherwise the battle counts it as a miss.
+ result = 1
+ elif self.TOONS_TAKE_NO_DAMAGE:
+ result = 0
+ elif self.__suitAtkHit(attackIndex):
+ # suits don't get any accuracy or damage bonuses
+ atkType = attack[SUIT_ATK_COL]
+
+ theSuit = self.battle.findSuit(attack[SUIT_ID_COL])
+ atkInfo = SuitBattleGlobals.getSuitAttack(
+ theSuit.dna.name,
+ theSuit.getLevel(),
+ atkType)
+ result = atkInfo['hp']
+
+ # be sure to set the amount of damage done by this attack
+ # in the appropriate slot in the toonAttacks list
+ # corresponding to this target
+ targetIndex = self.battle.activeToons.index(toonId)
+ attack[SUIT_HP_COL][targetIndex] = result
+
+ def __getToonHp(self, toonDoId):
+ """
+ toonDoId, the unique id of the toon
+ Returns: the health of the specified toon, as it is
+ currently known on the server
+
+ Get the health of a toon given its doId
+ """
+ handle = self.battle.getToon(toonDoId)
+ if handle != None and self.toonHPAdjusts.has_key(toonDoId):
+ return handle.hp + self.toonHPAdjusts[toonDoId]
+ else:
+ return 0
+
+ def __getToonMaxHp(self, toonDoId):
+ """
+ toonDoId, the unique id of the toon
+ Returns: the health of the specified toon, as it is
+ currently known on the server
+
+ Get the health of a toon given its doId
+ """
+ handle = self.battle.getToon(toonDoId)
+ if (handle != None):
+ assert(self.toonHPAdjusts.has_key(toonDoId))
+ return handle.maxHp
+ else:
+ return 0
+
+ def __applySuitAttackDamages(self, attackIndex):
+ """
+ attackIndex, the suit attack to apply damages for
+
+ Apply damages created by the specified suit attack
+ """
+ # we dont actually adjust the toon health here, we just need to
+ # indicate if the toon has died and print something out for each
+ # toon hit
+ attack = self.battle.suitAttacks[attackIndex]
+ if self.APPLY_HEALTH_ADJUSTMENTS:
+ for t in self.battle.activeToons:
+ # first see if the toon was damaged by this attack
+ position = self.battle.activeToons.index(t)
+ assert(position < len(attack[SUIT_HP_COL]))
+ if attack[SUIT_HP_COL][position] <= 0:
+ # toon at this position was not hit by this attack
+ continue
+
+ # now see if the toon had died, if so, remove the toon from
+ # the battle and mark the toon as being killed by this attack
+ # also, whether or not the toon is dead, adjust the hp adjust
+ # value for the toon in our toonHPAdjusts list which is used
+ # to keep toon health values as up to date as possible so we
+ # know exactly when the toon died within the sequence of
+ # attacks
+ assert(position < len(attack[SUIT_HP_COL]))
+ toonHp = self.__getToonHp(t)
+ assert(self.toonHPAdjusts.has_key(t))
+ if toonHp - attack[SUIT_HP_COL][position] <= 0:
+ if self.notify.getDebug():
+ self.notify.debug("Toon %d has died, removing" % t)
+ self.toonLeftBattle(t)
+ attack[TOON_DIED_COL] = \
+ attack[TOON_DIED_COL] | (1 << position)
+ if self.notify.getDebug():
+ self.notify.debug("Toon " + str(t) + " takes " +
+ str(attack[SUIT_HP_COL][position]) +
+ " damage")
+ self.toonHPAdjusts[t] -= attack[SUIT_HP_COL][position]
+ self.notify.debug("Toon " + str(t) + " now has " +
+ str(self.__getToonHp(t)) + " health")
+
+ def __suitCanAttack(self, suitId):
+ """
+ suitId, the suit to check
+ Returns: 1 if the suit can attack, 0 otherwise
+
+ Check to see if a specific suit is able to attack.
+ various factors determine this, such as if the suit
+ is lured and if the suit is dead
+ """
+ if self.__combatantDead(suitId, toon=0) or \
+ self.__suitIsLured(suitId) or self.__combatantJustRevived(suitId):
+ return 0
+ return 1
+
+ def __updateSuitAtkStat(self, toonId):
+ if self.suitAtkStats.has_key(toonId):
+ self.suitAtkStats[toonId] += 1
+ else:
+ self.suitAtkStats[toonId] = 1
+
+ def __printSuitAtkStats(self):
+ self.notify.debug("Suit Atk Stats:")
+ for currTgt in self.suitAtkStats.keys():
+ if not currTgt in self.battle.activeToons:
+ continue
+ tgtPos = self.battle.activeToons.index(currTgt)
+ self.notify.debug(" toon " + str(currTgt) +
+ " at position " + str(tgtPos) +
+ " was attacked " +
+ str(self.suitAtkStats[currTgt]) +
+ " times")
+ self.notify.debug("\n")
+
+ def __calculateSuitAttacks(self):
+ """
+ For each suit attack on the suitAttacks list,
+ calculate and fill in appropriate attack type,
+ target, and damage information
+ """
+ for i in range(len(self.battle.suitAttacks)):
+ if i < len(self.battle.activeSuits):
+
+ # check to make sure that the suit is not dead before
+ # worrying about its attack, but before doing that make
+ # sure the current suit attack refers to an existing suit
+ # by setting the doId of the attack to the suit that is
+ # performing the attack
+ suitId = self.battle.activeSuits[i].doId
+ self.battle.suitAttacks[i][SUIT_ID_COL] = suitId
+ if not self.__suitCanAttack(suitId):
+ if self.notify.getDebug():
+ self.notify.debug("Suit %d can't attack" % suitId)
+ continue
+
+ # make sure this suit is not in either the pending
+ # or to pending lists
+ if self.battle.pendingSuits.count(
+ self.battle.activeSuits[i])>0 or \
+ self.battle.joiningSuits.count(
+ self.battle.activeSuits[i])>0:
+ continue
+
+ attack = self.battle.suitAttacks[i]
+ # only perform attack calculations if this suit exists in
+ # the list of suits currently existing in the battle
+ # order of calculation matters, since some calculations depend
+ # on previously calculated values, such as an attack's damage
+ # depending on attack type, we also need to handle damage
+ # to multiple targets for group attacks
+ attack[SUIT_ID_COL] = self.battle.activeSuits[i].doId
+ attack[SUIT_ATK_COL] = self.__calcSuitAtkType(i)
+ attack[SUIT_TGT_COL] = self.__calcSuitTarget(i)
+ if attack[SUIT_TGT_COL] == -1:
+ # no available target, clear out the suit attack
+ self.battle.suitAttacks[i] = getDefaultSuitAttack()
+ attack = self.battle.suitAttacks[i]
+ self.notify.debug("clearing suit attack, no avail targets")
+ self.__calcSuitAtkHp(i)
+
+ # for debugging, keep track of how many times each toon
+ # was attacked
+ if attack[SUIT_ATK_COL] != NO_ATTACK:
+ if self.__suitAtkAffectsGroup(attack):
+ for currTgt in self.battle.activeToons:
+ self.__updateSuitAtkStat(currTgt)
+ else:
+ tgtId = self.battle.activeToons[attack[SUIT_TGT_COL]]
+ self.__updateSuitAtkStat(tgtId)
+
+ # if this attack targets suits, get all of its targets
+ # and make sure at least one of them is still alive,
+ # otherwise clear out the attack
+ targets = self.__createSuitTargetList(i)
+ allTargetsDead = 1
+ for currTgt in targets:
+ if self.__getToonHp(currTgt) > 0:
+ allTargetsDead = 0
+ break
+ if allTargetsDead:
+ # clear out attack, all targets for it are already dead
+ self.battle.suitAttacks[i] = \
+ getDefaultSuitAttack()
+ if self.notify.getDebug():
+ self.notify.debug("clearing suit attack, targets dead")
+ self.notify.debug("suit attack is now " + \
+ repr(self.battle.suitAttacks[i]))
+ self.notify.debug("all attacks: " +
+ repr(self.battle.suitAttacks))
+ attack = self.battle.suitAttacks[i]
+
+ # now apply any relevant damages to the target toon(s)
+ if self.__attackHasHit(attack, suit=1):
+ self.__applySuitAttackDamages(i)
+
+ if self.notify.getDebug():
+ self.notify.debug("Suit attack: " +
+ str(self.battle.suitAttacks[i]))
+
+ # determine if this suit will attack after the toon attacks
+ # or after. For now, all toons attack before any suits
+ attack[SUIT_BEFORE_TOONS_COL] = 0
+
+ def __updateLureTimeouts(self):
+ """
+ Check all lured suits and see if the lure has expired
+ also randomly un-lure suits
+ """
+ # check for lured suits and determine which should no longer be
+ # lured
+ if self.notify.getDebug():
+ self.notify.debug("__updateLureTimeouts()")
+ self.notify.debug("Lured suits: " + str(self.currentlyLuredSuits))
+ noLongerLured = []
+
+ # check each lured suit, add to the lured suit's count of how many
+ # rounds it has been lured, then check to see if the suit has now been
+ # lured for the maximum rounds allowed for the particular lure used
+ # on the suit, if not, randomly decide if the suit should 'magically
+ # wake up' from the lure based on the fail chance of the particular
+ # lure originally used on the suit
+ for currLuredSuit in self.currentlyLuredSuits.keys():
+ self.__incLuredCurrRound(currLuredSuit)
+ if self.__luredMaxRoundsReached(currLuredSuit) or \
+ self.__luredWakeupTime(currLuredSuit):
+ noLongerLured.append(currLuredSuit)
+
+ # now we can safely clear out the no-longer-lured suits
+ for currLuredSuit in noLongerLured:
+# self.suitsUnluredThisRound.append(currLuredSuit)
+ self.__removeLured(currLuredSuit)
+ if self.notify.getDebug():
+ self.notify.debug("Lured suits: " + str(self.currentlyLuredSuits))
+
+
+ def __initRound(self):
+ """
+ Called when a new round starts, sets up initial
+ values used to calculate various bonuses
+ """
+ # only allow suits to remember who attacked them for a single round
+ if self.CLEAR_SUIT_ATTACKERS:
+ self.SuitAttackers = {}
+
+ # fill in self.toonAtkOrder at the beginning of each round, this
+ # list contains the order of toon attacks, each element
+ # is an index into the self.battle.toonAttacks list
+ self.toonAtkOrder = []
+ # Fill in PETSOS attacks first
+ attacks = findToonAttack(self.battle.activeToons,
+ self.battle.toonAttacks,
+ PETSOS)
+ for atk in attacks:
+ self.toonAtkOrder.append(atk[TOON_ID_COL])
+
+
+ #Do the cog firing
+ attacks = findToonAttack(self.battle.activeToons,
+ self.battle.toonAttacks,
+ FIRE)
+ for atk in attacks:
+ self.toonAtkOrder.append(atk[TOON_ID_COL])
+
+
+ for track in range(HEAL, DROP + 1):
+ # find all attacks for each possible type of attack
+ attacks = findToonAttack(self.battle.activeToons,
+ self.battle.toonAttacks,
+ track)
+ # Make sure that any non-NPC traps occur before the first
+ # NPC trap (if any)
+ if (track == TRAP):
+ sortedTraps = []
+ for atk in attacks:
+ if (atk[TOON_TRACK_COL] == TRAP):
+ sortedTraps.append(atk)
+ for atk in attacks:
+ if (atk[TOON_TRACK_COL] == NPCSOS):
+ sortedTraps.append(atk)
+ assert(len(attacks) == len(sortedTraps))
+ attacks = sortedTraps
+
+ for atk in attacks:
+ # indicate where this toon fits in in the order of things, and
+ # be sure to move to the next attack
+ self.toonAtkOrder.append(atk[TOON_ID_COL])
+
+ # Handle special NPC attacks
+ specials = findToonAttack(self.battle.activeToons,
+ self.battle.toonAttacks, NPCSOS)
+
+ toonsHit = 0
+ cogsMiss = 0
+ for special in specials:
+ npc_track = NPCToons.getNPCTrack(special[TOON_TGT_COL])
+ if (npc_track == NPC_TOONS_HIT):
+ BattleCalculatorAI.toonsAlwaysHit = 1
+ toonsHit = 1
+ elif (npc_track == NPC_COGS_MISS):
+ BattleCalculatorAI.suitsAlwaysMiss = 1
+ cogsMiss = 1
+
+ if self.notify.getDebug():
+ self.notify.debug("Toon attack order: " +
+ str(self.toonAtkOrder))
+ self.notify.debug("Active toons: " +
+ str(self.battle.activeToons))
+ self.notify.debug("Toon attacks: " +
+ str(self.battle.toonAttacks))
+ self.notify.debug("Active suits: " +
+ str(self.battle.activeSuits))
+ self.notify.debug("Suit attacks: " +
+ str(self.battle.suitAttacks))
+
+ # all toons should now have their HP set properly locally, so
+ # clear out any HP adjustments we may have, this list contains
+ # a list of indexes into battle.toons
+ self.toonHPAdjusts = {}
+ for t in self.battle.activeToons:
+ self.toonHPAdjusts[t] = 0
+
+ # list used for calculating HPBonuses that toons may receive by
+ # coordinating attacks
+ self.__clearBonuses()
+
+ # remove any toons that are no longer in the battle, this can happen
+ # if a toon has recently left unexpectedly
+ self.__updateActiveToons()
+ self.delayedUnlures = []
+
+ # initialize the traps list each round, which consists of removing
+ # trap conflicts that might have occurred last round
+ self.__initTraps()
+
+ # clear out the successful lures map which is used to figure out
+ # for each target which lures apply and which don't
+ self.successfulLures = {}
+
+ # clear the list that we use to keep track of suits unlured during
+ # a single round of attacks
+# self.suitsUnluredThisRound = []
+
+ return toonsHit, cogsMiss
+
+ def calculateRound(self):
+ """
+ Calculate a single round of toon and suit attacks
+ for a single battle
+ """
+ # Make sure toonAttacks have the right number of TOON_HP_COL
+ # initial values and TOON_KBBONUS_COL initial values
+ # Also make sure suitAttacks have the right number of SUIT_HP_COL
+ # initial values
+ longest = max(len(self.battle.activeToons),
+ len(self.battle.activeSuits))
+ for t in self.battle.activeToons:
+ assert(self.battle.toonAttacks.has_key(t))
+ for j in range(longest):
+ self.battle.toonAttacks[t][TOON_HP_COL].append(-1)
+ self.battle.toonAttacks[t][TOON_KBBONUS_COL].append(-1)
+ for i in range(4):
+ for j in range(len(self.battle.activeToons)):
+ self.battle.suitAttacks[i][SUIT_HP_COL].append(-1)
+
+ # set-up some initial values that help calculate various bonuses
+ # that can occurr in a round
+ toonsHit, cogsMiss = self.__initRound()
+
+ # broadcast the active suit HP's to everyone in the battle
+ for suit in self.battle.activeSuits:
+ if suit.isGenerated():
+ suit.b_setHP(suit.getHP())
+
+ # test to see if the suits have been deleted and do nothing if that is the case
+ for suit in self.battle.activeSuits:
+ if not hasattr(suit, "dna"):
+ self.notify.warning("a removed suit is in this battle!")
+ return None
+
+
+
+ #self.__calculateFiredCogs(self)
+
+ # perform number calculations, also apply damages/heals
+ self.__calculateToonAttacks()
+
+ # update lure timeout values after toon attacks and before suit
+ # attacks, so suits can wake up from being lured before their chance
+ # to attack but the toons still have a chance to hit a lured suit
+ # this round
+ self.__updateLureTimeouts()
+
+ self.__calculateSuitAttacks()
+
+ # Restore globals
+ if (toonsHit == 1):
+ BattleCalculatorAI.toonsAlwaysHit = 0
+ if (cogsMiss == 1):
+ BattleCalculatorAI.suitsAlwaysMiss = 0
+
+ if self.notify.getDebug():
+ self.notify.debug("Toon skills gained after this round: " + \
+ repr(self.toonSkillPtsGained))
+ self.__printSuitAtkStats()
+
+ return None
+
+ def __calculateFiredCogs():
+ import pdb; pdb.set_trace()
+
+ def toonLeftBattle(self, toonId):
+ """
+ toonId, the id of the toon that has left the battle
+
+ Notify the battle calculator when a toon leaves
+ this battle
+ """
+ if self.notify.getDebug():
+ self.notify.debug('toonLeftBattle()' + str(toonId))
+ if self.toonSkillPtsGained.has_key(toonId):
+ del self.toonSkillPtsGained[toonId]
+ if self.suitAtkStats.has_key(toonId):
+ del self.suitAtkStats[toonId]
+
+ if not self.CLEAR_SUIT_ATTACKERS:
+ # clear out the SuitAttackers map (which allows suits to remember
+ # which toons attacked him/her last round) of the specified toonId
+ oldSuitIds = []
+ for s in self.SuitAttackers.keys():
+ if self.SuitAttackers[s].has_key(toonId):
+ del self.SuitAttackers[s][toonId]
+ if len(self.SuitAttackers[s]) == 0:
+ oldSuitIds.append(s)
+
+ # remove any suit ids that refer to an empty toonId/damage map
+ for oldSuitId in oldSuitIds:
+ del self.SuitAttackers[oldSuitId]
+
+ # clear out any reference that a trap might have for this toon,
+ # this means that any trap this toon created that still exists
+ # and gets triggered will no longer give trap exp to this toon
+ self.__clearTrapCreator(toonId)
+
+ # remove any references of this toon from the currentlyLuredSuits
+ # list, which remembers which toons lured which suits (used for
+ # giving the toon exp when the lured suit takes damage), when a
+ # toon leaves the battle, any lure credit for the toon is removed
+ self.__clearLurer(toonId)
+
+ def suitLeftBattle(self, suitId):
+ """
+ suitId, the id of the suit that has left the battle
+
+ Notify the battle calculator when a suit leaves
+ this battle (most likely by death)
+ """
+ if self.notify.getDebug():
+ self.notify.debug('suitLeftBattle(): ' + str(suitId))
+
+ # remove the suit from the currently lured suits list
+ self.__removeLured(suitId)
+
+ # also make sure to remove the suit from the 'which toon attacked
+ # me last round' list
+ if self.SuitAttackers.has_key(suitId):
+ del self.SuitAttackers[suitId]
+
+ # remove the suit from the traps list which helps us keep track of
+ # which suits have traps on them
+ self.__removeSuitTrap(suitId)
+
+ def __updateActiveToons(self):
+ """
+ Every round, update lists that remember toon id's
+ from previous rounds, because a toon might have
+ unexpectedly left the battle
+ """
+ if self.notify.getDebug():
+ self.notify.debug('updateActiveToons()')
+ # don't remove exp for toons that are not in this battle; some
+ # parts of the game (factories, VP battles) use a single dict
+ # to accumulate toon exp over multiple battles; toons that are in
+ # the dict may be currently in a different battle or not in a
+ # battle at all, but that's no reason to remove them from the dict.
+ # exp entries are removed when a toon leaves this battle before it
+ # finishes (when they get sad, run away, or disconnect) --
+ # see DistributedBattleBaseAI.__removeToon
+ #for toonId in self.toonSkillPtsGained.keys():
+ # if not toonId in self.battle.activeToons:
+ # self.notify.debug("Exp for toon " + str(toonId) +
+ # " has been cleared")
+ # del self.toonSkillPtsGained[toonId]
+
+ if not self.CLEAR_SUIT_ATTACKERS:
+ # clear out the SuitAttackers map (which allows suits to remember
+ # which toons attacked him/her last round) of all toons that are
+ # not found in the battle.activeToons list
+ oldSuitIds = []
+ for s in self.SuitAttackers.keys():
+ for t in self.SuitAttackers[s].keys():
+ if not t in self.battle.activeToons:
+ del self.SuitAttackers[s][t]
+ if len(self.SuitAttackers[s]) == 0:
+ oldSuitIds.append(s)
+
+ # remove any suit ids that refer to an empty toonId/damage map
+ for oldSuitId in oldSuitIds:
+ del self.SuitAttackers[oldSuitId]
+
+ # clear out any traps creator id's that refer to a toon not found
+ # in the active toons list
+ for trap in self.traps.keys():
+ if not self.traps[trap][1] in self.battle.activeToons:
+ self.notify.debug("Trap for toon " +
+ str(self.traps[trap][1]) +
+ " will no longer give exp")
+ self.traps[trap][1] = 0
+
+ def getSkillGained(self, toonId, track):
+ return BattleExperienceAI.getSkillGained(self.toonSkillPtsGained, toonId, track)
+
+ def getLuredSuits(self):
+ """
+ Get the doId's of all suits that are currently lured
+ """
+ luredSuits = self.currentlyLuredSuits.keys()
+ # make sure that any suits that were still lured at the begining
+ # of this round are still in the lured suits list that we return
+ # to the battle
+# for currLured in self.suitsUnluredThisRound:
+# if not currLured in luredSuits:
+# luredSuits.append(currLured)
+ self.notify.debug("Lured suits reported to battle: " +
+ repr(luredSuits))
+ return luredSuits
+
+ def __suitIsLured(self, suitId, prevRound=0):
+ """
+ suitId, doId of the suit to checkout
+ prevRound, only return true if the suit has been
+ lured in a previous round
+ Returns: 1 if the suit is lured, 0 otherwise
+
+ Check to see if a suit is currently lured
+ """
+ inList = self.currentlyLuredSuits.has_key(suitId)
+ if prevRound:
+ # only return true if the suit has been lured for at least
+ # one entire round
+ return inList and self.currentlyLuredSuits[suitId][0] != -1
+ return inList
+
+ def __findAvailLureId(self, lurerId):
+ """
+ lurerId, the doId of the lurer
+ Returns: the lowest available unique lure id wrt to the
+ specified lurer
+ Find the lowest available lure id associated with
+ a specific lurer, this way suits lured by different
+ lure attacks from the same lurer can be distinguished
+ from each other and exp can be given properly
+ """
+ luredSuits = self.currentlyLuredSuits.keys()
+ lureIds = []
+ for currLured in luredSuits:
+ lurerInfo = self.currentlyLuredSuits[currLured][3]
+ lurers = lurerInfo.keys()
+ for currLurer in lurers:
+ # if we have found a match for the specified lurer, add the
+ # lureId to a list of lureId's for this toon
+ currId = lurerInfo[currLurer][1]
+ if currLurer == lurerId and not currId in lureIds:
+ lureIds.append(currId)
+ lureIds.sort()
+ currId = 1
+ for currLureId in lureIds:
+ if currLureId != currId:
+ return currId
+ currId += 1
+ return currId
+
+ def __addLuredSuitInfo(self, suitId, currRounds, maxRounds, wakeChance,
+ lurer, lureLvl, lureId=-1, npc=0):
+ """
+ suitId, doId of the suit to be added
+ currRounds, current number of rounds that the suit
+ has been lured for (usually this should
+ be -1 when a suit is first lured)
+ maxRounds, maximum number of battle rounds that this
+ suit will be lured
+ wakeChance, percent chance of the suit becoming
+ unlured each battle round
+ lurer, doId of the toon that lured this suit
+ lureLvl, the level of the lure used
+ lureId, if provided, use this value to identify
+ this specific lure, if not provided find
+ the lowest available lure id unique to the
+ lurer
+ Returns: the id used for the lure info, this can be used to
+ pass back to this function when we are adding
+ multiple lured suit infos for the same lure
+
+ Add a suit to the lured suits list (updates any
+ currently existing data if the suit is already lured,
+ and adds a reference to the lurer so exp can be given
+ if the suit takes damage while lured)
+ """
+ if lureId == -1:
+ availLureId = self.__findAvailLureId(lurer)
+ else:
+ availLureId = lureId
+ # Nobody gets experience for NPC lures
+ if (npc == 1):
+ credit = 0
+ else:
+ credit = self.itemIsCredit(LURE, lureLvl)
+
+ if self.currentlyLuredSuits.has_key(suitId):
+ # This suit was already lured; we're just luring it more.
+
+ lureInfo = self.currentlyLuredSuits[suitId]
+ if not lureInfo[3].has_key(lurer):
+ # This toon hasn't lured this suit yet this time around.
+ lureInfo[1] += maxRounds
+ if wakeChance < lureInfo[2]:
+ lureInfo[2] = wakeChance
+ lureInfo[3][lurer] = [lureLvl, availLureId, credit]
+
+ else:
+ # This suit hadn't been lured yet.
+ lurerInfo = { lurer: [lureLvl, availLureId, credit] }
+ self.currentlyLuredSuits[suitId] = \
+ [currRounds, maxRounds, wakeChance,
+ lurerInfo]
+ self.notify.debug("__addLuredSuitInfo: currLuredSuits -> %s" %
+ repr(self.currentlyLuredSuits))
+ return availLureId
+
+ def __getLurers(self, suitId):
+ """
+ suitId, the id of the lured suit
+ Returns: a list of doId's of the lurers, empyt list if there
+ are none
+
+ Get the doId of the toon that lured the specified
+ suit
+ """
+ if self.__suitIsLured(suitId):
+ return self.currentlyLuredSuits[suitId][3].keys()
+ return []
+
+ def __getLuredExpInfo(self, suitId):
+ """
+ suitId, the doId of the target to get lured info for
+ Returns: a list of lists, each containing the lurer's doId,
+ the level of the lure used, and the unique id
+ distinguishing this particular lure from other
+ possible lures from the same lurer, followed by
+ a boolean flag indicating whether the lurer deserves
+ experience credit:
+ [[, , , ], [...], ...]
+
+ Get lurer info for a specific target, this way when
+ this lured target takes damage we can get the
+ appropriate amount of exp to anyone that helped lure
+ this target
+ """
+ returnInfo = []
+ lurers = self.__getLurers(suitId)
+ if len(lurers) == 0:
+ return returnInfo
+ lurerInfo = self.currentlyLuredSuits[suitId][3]
+ for currLurer in lurers:
+ returnInfo.append([currLurer,
+ lurerInfo[currLurer][0],
+ lurerInfo[currLurer][1],
+ lurerInfo[currLurer][2],
+ ])
+ return returnInfo
+
+ def __clearLurer(self, lurerId, lureId=-1):
+ """
+ lurerId, the doId of the toon to clear from the list
+ lureId, the unique id of the lure that lured this
+ target, this is useful to only clear
+ references of a specific lurer AND a specifc
+ lure (since a single lurer may have done
+ more than a single lure on any of the
+ currently lured targets)
+
+ Remove any references to the specified toon from the
+ lured suits list
+ """
+ luredSuits = self.currentlyLuredSuits.keys()
+ for currLured in luredSuits:
+ lurerInfo = self.currentlyLuredSuits[currLured][3]
+ lurers = lurerInfo.keys()
+ for currLurer in lurers:
+ if currLurer == lurerId and \
+ (lureId == -1 or lureId == lurerInfo[currLurer][1]):
+ del lurerInfo[currLurer]
+
+ def __setLuredMaxRounds(self, suitId, rounds):
+ """
+ suitId, the suit to modify the lure info for
+ rounds, the new maximum number of battle rounds
+
+ Change the maximum number of battle rounds that a
+ specific suit will stay lured
+ """
+ if self.__suitIsLured(suitId):
+ self.currentlyLuredSuits[suitId][1] = rounds
+
+ def __setLuredWakeChance(self, suitId, chance):
+ """
+ suitId, the suit to modify the lure info for
+ chance, the new wakeup chance
+
+ Change the percentage chance that a specific suit
+ will wakeup from being lured each battle round
+ """
+ if self.__suitIsLured(suitId):
+ self.currentlyLuredSuits[suitId][2] = chance
+
+ def __incLuredCurrRound(self, suitId):
+ """
+ suitId, the suit to modify the lure info for
+
+ Increment the number of battle rounds that a specific
+ suit has been lured for
+ """
+ if self.__suitIsLured(suitId):
+ self.currentlyLuredSuits[suitId][0] += 1
+
+ def __removeLured(self, suitId):
+ """
+ suitId, the suit to be unlured
+
+ Unlure a specific suit
+ """
+ if self.__suitIsLured(suitId):
+ del self.currentlyLuredSuits[suitId]
+
+ def __luredMaxRoundsReached(self, suitId):
+ """
+ suitId, the suit to check lure info for
+ Returns: 1 if the suit has reached max rounds, 0 otherwise
+
+ Check to see if a specific suit has reached the max
+ number of rounds that it can be lured for
+ """
+ return self.__suitIsLured(suitId) and \
+ self.currentlyLuredSuits[suitId][0] >= \
+ self.currentlyLuredSuits[suitId][1]
+
+ def __luredWakeupTime(self, suitId):
+ """
+ suitId, the suit to check on
+ Returns: 1 if the suit has awakened, 0 otherwise
+
+ Check to see if a specific suit randomly wakes up
+ from being lured (should only be called once each
+ battle round since a random check is made each call)
+ """
+ return self.__suitIsLured(suitId) and \
+ self.currentlyLuredSuits[suitId][0] > 0 and \
+ random.randint(0, 99) < \
+ self.currentlyLuredSuits[suitId][2]
+
+ def itemIsCredit(self, track, level):
+ """
+ This returns true if a toon will gain credit for using this
+ item in this particular battle, false otherwise. This is
+ based on the credit level computed at the beginning of
+ __calculateToonAttacks.
+ """
+ if (track == PETSOS):
+ return 0
+ return level < self.creditLevel
+
+ def __getActualTrack(self, toonAttack):
+ # NPC friends share the same attack columns, so we often need to
+ # convert from an NPCSOS track to whatever track the NPC uses
+ if (toonAttack[TOON_TRACK_COL] == NPCSOS):
+ track = NPCToons.getNPCTrack(toonAttack[TOON_TGT_COL])
+ if (track != None):
+ return track
+ else:
+ self.notify.warning("No NPC with id: %d" % toonAttack[TOON_TGT_COL])
+ return toonAttack[TOON_TRACK_COL]
+
+ def __getActualTrackLevel(self, toonAttack):
+ # NPC friends share the same attack columns, so we often need to
+ # convert from an NPCSOS track to whatever track the NPC uses
+ if (toonAttack[TOON_TRACK_COL] == NPCSOS):
+ track, level, hp = NPCToons.getNPCTrackLevelHp(toonAttack[TOON_TGT_COL])
+ if (track != None):
+ return track, level
+ else:
+ self.notify.warning("No NPC with id: %d" % toonAttack[TOON_TGT_COL])
+ return toonAttack[TOON_TRACK_COL], toonAttack[TOON_LVL_COL]
+
+ def __getActualTrackLevelHp(self, toonAttack):
+ # NPC friends share the same attack columns, so we often need to
+ # convert from an NPCSOS track to whatever track the NPC uses
+ if (toonAttack[TOON_TRACK_COL] == NPCSOS):
+ track, level, hp = NPCToons.getNPCTrackLevelHp(toonAttack[TOON_TGT_COL])
+ if (track != None):
+ return track, level, hp
+ else:
+ self.notify.warning("No NPC with id: %d" % toonAttack[TOON_TGT_COL])
+ elif (toonAttack[TOON_TRACK_COL] == PETSOS):
+ trick = toonAttack[TOON_LVL_COL]
+ petProxyId = toonAttack[TOON_TGT_COL]
+ trickId = toonAttack[TOON_LVL_COL]
+ assert(trickId < len(PetTricks.TrickHeals))
+ healRange = PetTricks.TrickHeals[trickId]
+ hp = 0
+ if simbase.air.doId2do.has_key(petProxyId):
+ petProxy = simbase.air.doId2do[petProxyId]
+ if trickId < len(petProxy.trickAptitudes):
+ aptitude = petProxy.trickAptitudes[trickId]
+ hp = int(lerp(healRange[0], healRange[1], aptitude))
+ else:
+ self.notify.warning("pet proxy: %d not in doId2do!" % petProxyId)
+ return toonAttack[TOON_TRACK_COL], toonAttack[TOON_LVL_COL], hp
+ return toonAttack[TOON_TRACK_COL], toonAttack[TOON_LVL_COL], 0
+
+ def __calculatePetTrickSuccess(self, toonAttack):
+ petProxyId = toonAttack[TOON_TGT_COL]
+ if not simbase.air.doId2do.has_key(petProxyId):
+ self.notify.warning("pet proxy %d not in doId2do!" % petProxyId)
+ toonAttack[TOON_ACCBONUS_COL] = 1
+ return (0, 0)
+ petProxy = simbase.air.doId2do[petProxyId]
+ trickId = toonAttack[TOON_LVL_COL]
+ toonAttack[TOON_ACCBONUS_COL] = petProxy.attemptBattleTrick(trickId)
+ if toonAttack[TOON_ACCBONUS_COL] == 1:
+ return (0, 0)
+ else:
+ return (1, 100)
+
+# history
+#
+# 21Mar01 jlbutler created
+# 26Mar01 jlbutler added bonus calculations to suit and toon attacks
+# 15Jun01 jlbutler added 'getAvPropDamage' to calculate the proper damage
+# for a given prop for a specific toon
+# 25Jul01 jnschell moved 'getAvPropDamage' to ToontownBattleGlobals.py
+# 27Jul01 jlbutler fixed a few bugs with trap and lure and combinations
+# with other toon attacks
+# 27Aug01 jlbutler Added code to remember statistics about suit attacks
+# during the course of the entire battle. Also fixed
+# a problem where suits would attack dead toons because
+# they would look randomly for a target in
+# self.battle.activeToons but this list is not updated
+# immediately as far as toon deaths go, so instead suits
+# create a temporary list of active toons that are still
+# alive when the suit gets around to attacking.
diff --git a/toontown/src/battle/BattleExperience.py b/toontown/src/battle/BattleExperience.py
new file mode 100644
index 0000000..ca9e63c
--- /dev/null
+++ b/toontown/src/battle/BattleExperience.py
@@ -0,0 +1,31 @@
+from toontown.toonbase import ToontownBattleGlobals
+
+# This file contains a collection of functions to manage battle
+# experience and generation of reward movies on the client side.
+# These functions used to be methods on DistributedBattleBase and
+# Movie, but they have been pulled out here to collect them together
+# and generalize them for final battles, which might have as many as 8
+# Toons.
+
+
+def genRewardDicts(entries):
+ toonRewardDicts = []
+ for toonId, origExp, earnedExp, origQuests, items, missedItems, origMerits, merits, parts in entries:
+ if (toonId != -1):
+ dict = {}
+ toon = base.cr.doId2do.get(toonId)
+ if (toon == None):
+ continue
+ dict['toon'] = toon
+ dict['origExp'] = origExp
+ dict['earnedExp'] = earnedExp
+ dict['origQuests'] = origQuests
+ dict['items'] = items
+ dict['missedItems'] = missedItems
+ dict['origMerits'] = origMerits
+ dict['merits'] = merits
+ dict['parts'] = parts
+ toonRewardDicts.append(dict)
+ #import pdb; pdb.set_trace()
+
+ return toonRewardDicts
diff --git a/toontown/src/battle/BattleExperienceAI.py b/toontown/src/battle/BattleExperienceAI.py
new file mode 100644
index 0000000..aaeac7c
--- /dev/null
+++ b/toontown/src/battle/BattleExperienceAI.py
@@ -0,0 +1,246 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownBattleGlobals
+from toontown.suit import SuitDNA
+
+# This file contains a collection of functions to manage battle
+# experience and generation of reward movies on the AI side. These
+# functions used to be methods on DistributedBattleBaseAI,
+# BattleCalculatorAI, and Movie, but they have been pulled out here to
+# collect them together and generalize them for final battles, which
+# might have as many as 8 Toons.
+
+BattleExperienceAINotify = DirectNotifyGlobal.directNotify.newCategory('BattleExprienceAI')
+
+def getSkillGained(toonSkillPtsGained, toonId, track):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: get the skill points obtained so far in this battle
+ // for a toon position and a specific attack track
+ // Parameters: toonSlot, which toon to get the skill points for
+ // track, the attack track to get the skill pts for
+ // Changes:
+ // Returns: skill points received so far in this battle
+ ////////////////////////////////////////////////////////////////////
+ """
+ exp = 0
+ expList = toonSkillPtsGained.get(toonId, None)
+ if expList != None:
+ exp = expList[track]
+ return int(exp + 0.5)
+
+def getBattleExperience(numToons, activeToons, toonExp,
+ toonSkillPtsGained, toonOrigQuests, toonItems,
+ toonOrigMerits, toonMerits,
+ toonParts, suitsKilled, helpfulToonsList = None):
+ # First, build a list of active toons for the quest manager
+ if helpfulToonsList == None:
+ BattleExperienceAINotify.warning("=============\nERROR ERROR helpfulToons=None in assignRewards , tell Red")
+ if __debug__:
+ import pdb; pdb.set_trace()
+
+ p = []
+ for k in range(numToons):
+ toon = None
+ if (k < len(activeToons)):
+ toonId = activeToons[k]
+ toon = simbase.air.doId2do.get(toonId)
+
+ if (toon == None):
+ p.append(-1)
+ p.append([0, 0, 0, 0, 0, 0, 0])
+ p.append([0, 0, 0, 0, 0, 0, 0])
+ p.append([]) # orig quests
+ p.append([]) # items
+ p.append([]) # missed items
+ p.append([0, 0, 0, 0]) # orig merits
+ p.append([0, 0, 0, 0]) # merits
+ p.append([0, 0, 0, 0]) # parts
+ else:
+ assert(toon.doId == toonId)
+ p.append(toonId)
+ origExp = toonExp[toonId]
+ earnedExp = []
+ for i in range(len(ToontownBattleGlobals.Tracks)):
+ earnedExp.append(getSkillGained(toonSkillPtsGained, toonId, i))
+ p.append(origExp)
+ p.append(earnedExp)
+ origQuests = toonOrigQuests.get(toonId, [])
+ p.append(origQuests)
+ items = toonItems.get(toonId, ([], []))
+ p.append(items[0])
+ p.append(items[1])
+ origMerits = toonOrigMerits.get(toonId, [])
+ p.append(origMerits)
+ merits = toonMerits.get(toonId, [0, 0, 0, 0])
+ p.append(merits)
+ parts = toonParts.get(toonId, [0, 0, 0, 0])
+ p.append(parts)
+
+
+ # Now make a list of the suits that were killed so we can update
+ # quest progress during the movie
+ deathList = []
+ # create a lookup table of the indices of the active toons
+ toonIndices = {}
+ for i in range(len(activeToons)):
+ # map toonId -> toon's battle index
+ toonIndices[activeToons[i]] = i
+ for deathRecord in suitsKilled:
+ # Record the suit index and the suit level
+ level = deathRecord['level']
+ type = deathRecord['type']
+ if deathRecord['isVP'] or deathRecord['isCFO']:
+ # VPs/CFOs are not 'cogs' per se
+ # they have no level or type; they only have a track
+ # pass dept index as type. This, combined with isVP/isCFO flag,
+ # uniquely identifies VPs/CFOs.
+ level = 0
+ typeNum = SuitDNA.suitDepts.index(deathRecord['track'])
+ else:
+ # Convert the type (name string) into an int to pass on the network
+ typeNum = SuitDNA.suitHeadTypes.index(type)
+
+ # create a bitmask that represents which toons were involved
+ involvedToonIds = deathRecord['activeToons']
+ toonBits = 0
+ for toonId in involvedToonIds:
+ if toonId in toonIndices:
+ toonBits |= (1 << toonIndices[toonId])
+
+ # create a flags byte w/ extra info
+ flags = 0
+ if deathRecord['isSkelecog']:
+ flags |= ToontownBattleGlobals.DLF_SKELECOG
+ if deathRecord['isForeman']:
+ flags |= ToontownBattleGlobals.DLF_FOREMAN
+ if deathRecord['isVP']:
+ flags |= ToontownBattleGlobals.DLF_VP
+ if deathRecord['isCFO']:
+ flags |= ToontownBattleGlobals.DLF_CFO
+ if deathRecord['isSupervisor']:
+ flags |= ToontownBattleGlobals.DLF_SUPERVISOR
+ if deathRecord['isVirtual']:
+ flags |= ToontownBattleGlobals.DLF_VIRTUAL
+ if ("hasRevies" in deathRecord) and (deathRecord['hasRevives']):
+ flags |= ToontownBattleGlobals.DLF_REVIVES
+ deathList.extend([typeNum, level, toonBits, flags])
+ # Put the deathList in the master array
+ p.append(deathList)
+
+
+ #add the bitfields of the toons ubergags
+ #print("activeToons %s" % (activeToons))
+ uberStats = getToonUberStatus(activeToons, numToons)
+ #uberStats = [77,42]
+ #print(uberStats)
+ p.append(uberStats)
+ #import pdb; pdb.set_trace()
+ #p.append([77,42])
+
+ if helpfulToonsList == None:
+ helpfulToonsList = []
+ p.append(helpfulToonsList)
+
+ return p
+
+def getToonUberStatus(toons, numToons):
+ #UBERCHANGE
+ #print("getToonUberStatus")
+
+ fieldList = []
+ uberIndex = ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1
+ for toonId in toons:
+ toonList = []
+ toon = simbase.air.doId2do.get(toonId)
+ if toon == None:
+ #toonList = [0,0,0,0,0,0,0]
+ fieldList.append(-1)
+ else:
+ for trackIndex in range(ToontownBattleGlobals.MAX_TRACK_INDEX + 1):
+ toonList.append(toon.inventory.numItem(trackIndex, uberIndex))
+ fieldList.append(ToontownBattleGlobals.encodeUber(toonList))
+ lenDif = numToons - len(toons)
+ if lenDif > 0:
+ for index in range(lenDif):
+ fieldList.append(-1)
+ #print(fieldList)
+ return fieldList
+
+
+def assignRewards(activeToons, toonSkillPtsGained, suitsKilled, zoneId, helpfulToons=None):
+ # First, build a list of active toons for the quest manager
+ if helpfulToons == None:
+ BattleExperienceAINotify.warning("=============\nERROR ERROR helpfulToons=None in assignRewards , tell Red")
+ if __debug__:
+ import pdb; pdb.set_trace()
+
+ activeToonList = []
+ for t in activeToons:
+ toon = simbase.air.doId2do.get(t)
+ if (toon != None):
+ activeToonList.append(toon)
+
+ # Now walk through the list and add the gained experience to each
+ # toon.
+ for toon in activeToonList:
+ for i in range(len(ToontownBattleGlobals.Tracks)):
+
+ uberIndex = ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1
+ exp = getSkillGained(toonSkillPtsGained, toon.doId, i)
+ needed = ToontownBattleGlobals.Levels[i][ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1] + ToontownBattleGlobals.UberSkill
+ #print("Track %s Needed %s Current %s" % (ToontownBattleGlobals.Tracks[i], needed, exp + toon.experience.getExp(i)))
+ assert(exp >= 0)
+ hasUber = 0
+ totalExp = exp + toon.experience.getExp(i)
+ if (toon.inventory.numItem(i, uberIndex) > 0):
+ hasUber = 1
+ if (totalExp >= (needed)) or (totalExp >= ToontownBattleGlobals.MaxSkill):
+ #the toon has exceeded the uberGag tredmill threshold
+ #and needs to be awarded the USE of an ubergag
+ #then the toon should have their exp level reduced to the amount needed to have the uber gag
+ #print("uber threshold met")
+ if toon.inventory.totalProps < toon.getMaxCarry() and not hasUber:
+ #make sure the toon has room for the uber gag
+ #print("adding uber gag")
+ uberLevel = ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1
+ #need to hang this and assign it after the toons play their movies
+ #taskMgr.doMethodLater(3.0, toon.inventory.addItem, 'ToT-phrase-reset', extraArgs=[i, uberLevel])
+ toon.inventory.addItem(i, uberLevel)
+ toon.experience.setExp(i, ToontownBattleGlobals.Levels[i][ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1])
+ else:
+ toon.experience.setExp(i, ToontownBattleGlobals.MaxSkill)
+ pass
+ #print("full not adding ubergag")
+ elif (exp > 0):
+ #print("regular exp")
+ newGagList = toon.experience.getNewGagIndexList(i, exp)
+ toon.experience.addExp(i, amount=exp)
+ toon.inventory.addItemWithList(i, newGagList)
+
+
+ toon.b_setExperience(toon.experience.makeNetString())
+ toon.d_setInventory(toon.inventory.makeNetString())
+
+ # The AI starts each toon dancing. Each client will
+ # eventually be responsible for stopping the dance,
+ # after the client has viewed the entire reward movie.
+ # This means that all toons will start dancing at the
+ # same time, but they may not all end at the same time.
+ toon.b_setAnimState('victory', 1)
+
+ # Tell the quest manager about the cogs this toon killed
+ # so it can update the quest progress
+ if simbase.air.config.GetBool('battle-passing-no-credit', True):
+ # toons who just pass all the time will not get the quest credit
+ if helpfulToons and (toon.doId in helpfulToons):
+ simbase.air.questManager.toonKilledCogs(toon, suitsKilled, zoneId, activeToonList)
+ # Tell the cog page manager about the cogs this toon killed
+ simbase.air.cogPageManager.toonKilledCogs(toon, suitsKilled, zoneId)
+ else:
+ BattleExperienceAINotify.debug('toon=%d unhelpful not getting killed cog quest credit' % toon.doId)
+ else:
+ simbase.air.questManager.toonKilledCogs(toon, suitsKilled, zoneId, activeToonList)
+ # Tell the cog page manager about the cogs this toon killed
+ simbase.air.cogPageManager.toonKilledCogs(toon, suitsKilled, zoneId)
+
+
diff --git a/toontown/src/battle/BattleManagerAI.py b/toontown/src/battle/BattleManagerAI.py
new file mode 100644
index 0000000..2fb7a8c
--- /dev/null
+++ b/toontown/src/battle/BattleManagerAI.py
@@ -0,0 +1,76 @@
+import DistributedBattleAI
+from direct.directnotify import DirectNotifyGlobal
+
+class BattleManagerAI:
+
+ """ This class used to assume that there was one battle cell per zone.
+ It now supports any number of battle cells per zone, and requires that
+ you pass in 'battle cell IDs' to identify battle cells. There is nothing
+ magic about battle cell IDs, they just have to be unique and map
+ one-to-one with the battle cells. For systems that happen to have one
+ battle cell per zone, like the streets, it should be perfectly fine to
+ use zoneIds as battle cell IDs. """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('BattleManagerAI')
+
+ def __init__(self, air):
+ """__init__(air)
+ """
+ self.air = air
+ self.cellId2battle = {}
+ self.battleConstructor = DistributedBattleAI.DistributedBattleAI
+
+ def cellHasBattle(self, cellId):
+ """ cellHasBattle(cellId)
+ """
+ return (self.cellId2battle.has_key(cellId))
+
+ def getBattle(self, cellId):
+ if self.cellId2battle.has_key(cellId):
+ return self.cellId2battle[cellId]
+ return None
+
+ def newBattle(self, cellId, zoneId, pos, suit, toonId,
+ finishCallback=None, maxSuits=4, interactivePropTrackBonus = -1):
+ """ newBattle(zoneId, pos, suit, toonId, finishCallback, maxSuits)
+ """
+ if self.cellId2battle.has_key(cellId):
+ # if a battle already exists here, don't create a new one,
+ # but instead make the suit join the battle or fly away.
+
+ # This is an extremely rare case; normally a suit won't
+ # try to start a battle where a battle is already taking
+ # place.
+ self.notify.info("A battle is already present in the suit's zone!")
+ if not self.requestBattleAddSuit(cellId, suit):
+ # No room for the suit, fly him away.
+ suit.flyAwayNow()
+
+ battle = self.cellId2battle[cellId]
+ battle.signupToon(toonId, pos[0], pos[1], pos[2])
+
+ else:
+ # Generate a new battle. This is the normal case.
+ battle = self.battleConstructor(
+ self.air, self,
+ pos, suit, toonId, zoneId, finishCallback, maxSuits, interactivePropTrackBonus = interactivePropTrackBonus)
+ battle.generateWithRequired(zoneId)
+ battle.battleCellId = cellId
+ self.cellId2battle[cellId] = battle
+
+ return battle
+
+ def requestBattleAddSuit(self, cellId, suit):
+ """ requestBattleAddSuit(zoneId, suit)
+ """
+ assert(self.cellId2battle.has_key(cellId))
+ return (self.cellId2battle[cellId].suitRequestJoin(suit))
+
+ def destroy(self, battle):
+ """ destroy(battle)
+ """
+ cellId = battle.battleCellId
+ self.notify.debug('BattleManager - destroying battle %d' % cellId)
+ assert(self.cellId2battle.has_key(cellId))
+ del self.cellId2battle[cellId]
+ battle.requestDelete()
diff --git a/toontown/src/battle/BattleParticles.py b/toontown/src/battle/BattleParticles.py
new file mode 100644
index 0000000..3356feb
--- /dev/null
+++ b/toontown/src/battle/BattleParticles.py
@@ -0,0 +1,245 @@
+from direct.particles.ParticleEffect import *
+
+import os
+from direct.directnotify import DirectNotifyGlobal
+from direct.showbase import AppRunnerGlobal
+
+notify = DirectNotifyGlobal.directNotify.newCategory('BattleParticles')
+
+# This is a list of the effects used in the tutorial
+# They are in phase_3.5, not phase_5
+TutorialParticleEffects = (
+ "gearExplosionBig.ptf",
+ "gearExplosionSmall.ptf",
+ "gearExplosion.ptf",
+ )
+
+ParticleNames = (
+ "audit-div",
+ "audit-five",
+ "audit-four",
+ "audit-minus",
+ "audit-mult",
+ "audit-one",
+ "audit-plus",
+ "audit-six",
+ "audit-three",
+ "audit-two",
+ "blah",
+ "brainstorm-box",
+ "brainstorm-env",
+ "brainstorm-track",
+ "buzzwords-crash",
+ "buzzwords-inc",
+ "buzzwords-main",
+ "buzzwords-over",
+ "buzzwords-syn",
+ "confetti",
+ "doubletalk-double",
+ "doubletalk-dup",
+ "doubletalk-good",
+ "filibuster-cut",
+ "filibuster-fiscal",
+ "filibuster-impeach",
+ "filibuster-inc",
+ "jargon-brow",
+ "jargon-deep",
+ "jargon-hoop",
+ "jargon-ipo",
+ "legalese-hc",
+ "legalese-qpq",
+ "legalese-vd",
+ "mumbojumbo-boiler",
+ "mumbojumbo-creative",
+ "mumbojumbo-deben",
+ "mumbojumbo-high",
+ "mumbojumbo-iron",
+ "poundsign",
+ "schmooze-genius",
+ "schmooze-instant",
+ "schmooze-master",
+ "schmooze-viz",
+ # the old one - still used
+ "roll-o-dex",
+ "rollodex-card",
+ "dagger",
+ "fire",
+ "snow-particle",
+ "raindrop",
+ "gear",
+ "checkmark",
+ "dollar-sign",
+ "spark",
+ )
+
+particleModel = None
+
+# Where should we look to find a particle file?
+particleSearchPath = None
+
+def loadParticles():
+ global particleModel
+ if (particleModel == None):
+ particleModel = loader.loadModel("phase_3.5/models/props/suit-particles")
+
+def unloadParticles():
+ global particleModel
+ if (particleModel != None):
+ particleModel.removeNode()
+ del(particleModel)
+ particleModel = None
+
+def getParticle(name):
+ global particleModel
+ if (name in ParticleNames):
+ particle = particleModel.find("**/" + str(name))
+ return particle
+ else:
+ notify.warning('getParticle() - no name: %s' % name)
+ return None
+
+def loadParticleFile(name):
+ global particleSearchPath
+ if particleSearchPath == None:
+ particleSearchPath = DSearchPath()
+ if AppRunnerGlobal.appRunner:
+ # In the web-publish runtime, it will always be here:
+ particleSearchPath.appendDirectory(Filename.expandFrom('$TT_3_5_ROOT/phase_3.5/etc'))
+ else:
+ # In other environments, including the dev environment, look here:
+ basePath = os.path.expandvars('$TOONTOWN') or './toontown'
+ particleSearchPath.appendDirectory(Filename.fromOsSpecific(basePath+'/src/battle'))
+ particleSearchPath.appendDirectory(Filename.fromOsSpecific(basePath+'/src/safezone'))
+ particleSearchPath.appendDirectory(Filename('phase_3.5/etc'))
+ particleSearchPath.appendDirectory(Filename('phase_4/etc'))
+ particleSearchPath.appendDirectory(Filename('phase_5/etc'))
+ particleSearchPath.appendDirectory(Filename('phase_8/etc'))
+ particleSearchPath.appendDirectory(Filename('phase_9/etc'))
+ particleSearchPath.appendDirectory(Filename('.'))
+ pfile = Filename(name)
+ found = vfs.resolveFilename(pfile, particleSearchPath)
+
+ if not found:
+ notify.warning('loadParticleFile() - no path: %s' % name)
+ return
+ notify.debug('Loading particle file: %s' % pfile)
+ effect = ParticleEffect()
+ # print "particle filename = ", pfile.getFullpath()
+ effect.loadConfig(pfile)
+ return effect
+
+def createParticleEffect(name=None, file=None, numParticles=None, color=None):
+ # If don't provide name, grabbing the particle effect straight from the file name given
+ if not name:
+ assert (file != None)
+ fileName = file + '.ptf'
+ return loadParticleFile(fileName)
+
+ if (name == 'GearExplosion'):
+ return __makeGearExplosion(numParticles)
+ elif (name == 'BigGearExplosion'):
+ return __makeGearExplosion(numParticles, 'Big')
+ elif (name == 'WideGearExplosion'):
+ return __makeGearExplosion(numParticles, 'Wide')
+ elif (name == 'BrainStorm'):
+ return loadParticleFile('brainStorm.ptf')
+ elif (name == 'BuzzWord'):
+ return loadParticleFile('buzzWord.ptf')
+ elif (name == 'Calculate'):
+ return loadParticleFile('calculate.ptf')
+ elif (name == 'Confetti'):
+ return loadParticleFile('confetti.ptf')
+ elif (name == 'DemotionFreeze'):
+ return loadParticleFile('demotionFreeze.ptf')
+ elif (name == 'DemotionSpray'):
+ return loadParticleFile('demotionSpray.ptf')
+ elif (name == 'DoubleTalkLeft'):
+ return loadParticleFile('doubleTalkLeft.ptf')
+ elif (name == 'DoubleTalkRight'):
+ return loadParticleFile('doubleTalkRight.ptf')
+ elif (name == 'FingerWag'):
+ return loadParticleFile('fingerwag.ptf')
+ elif (name == 'FiredFlame'):
+ return loadParticleFile('firedFlame.ptf')
+ elif (name == 'FreezeAssets'):
+ return loadParticleFile('freezeAssets.ptf')
+ elif (name == 'GlowerPower'):
+ return loadParticleFile('glowerPowerKnives.ptf')
+ elif (name == 'HotAir'):
+ return loadParticleFile('hotAirSpray.ptf')
+ elif (name == 'PoundKey'):
+ return loadParticleFile('poundkey.ptf')
+ elif (name == 'ShiftSpray'):
+ return loadParticleFile('shiftSpray.ptf')
+ elif (name == 'ShiftLift'):
+ return __makeShiftLift()
+ elif (name == 'Shred'):
+ return loadParticleFile('shred.ptf')
+ elif (name == 'Smile'):
+ return loadParticleFile('smile.ptf')
+ elif (name == 'SpriteFiredFlecks'):
+ return loadParticleFile('spriteFiredFlecks.ptf')
+ elif (name == 'Synergy'):
+ return loadParticleFile('synergy.ptf')
+ elif (name == 'Waterfall'):
+ return loadParticleFile('waterfall.ptf')
+ elif (name == 'PoundKey'):
+ return loadParticleFile('poundkey.ptf')
+ elif (name == 'RubOut'):
+ return __makeRubOut(color)
+ elif (name == 'SplashLines'):
+ return loadParticleFile('splashlines.ptf')
+ elif (name == 'Withdrawal'):
+ return loadParticleFile('withdrawal.ptf')
+ else:
+ notify.warning('createParticleEffect() - no name: %s' % name)
+ return None
+
+def setEffectTexture(effect, name, color=None):
+ assert(effect != None)
+ particles = effect.getParticlesNamed('particles-1')
+ np = getParticle(name)
+ assert(not np.isEmpty())
+ if color:
+ particles.renderer.setColor(color)
+ particles.renderer.setFromNode(np)
+
+def __makeGearExplosion(numParticles=None, style = 'Normal'):
+ if style == 'Normal':
+ effect = loadParticleFile('gearExplosion.ptf')
+ elif style == 'Big':
+ effect = loadParticleFile('gearExplosionBig.ptf')
+ elif style == 'Wide':
+ effect = loadParticleFile('gearExplosionWide.ptf')
+ if numParticles:
+ particles = effect.getParticlesNamed('particles-1')
+ particles.setPoolSize(numParticles)
+ return effect
+
+def __makeRubOut(color=None):
+ effect = loadParticleFile('demotionUnFreeze.ptf')
+ loadParticles()
+ setEffectTexture(effect, 'snow-particle')
+ particles = effect.getParticlesNamed('particles-1')
+ particles.renderer.setInitialXScale(0.03)
+ particles.renderer.setFinalXScale(0.000)
+ particles.renderer.setInitialYScale(0.02)
+ particles.renderer.setFinalYScale(0.000)
+ if color:
+ particles.renderer.setColor(color)
+ else:
+ particles.renderer.setColor(Vec4(0.54, 0.92, 0.32, 0.7))
+ return effect
+
+def __makeShiftLift():
+ effect = loadParticleFile('pixieDrop.ptf')
+ particles = effect.getParticlesNamed('particles-1')
+ particles.renderer.setCenterColor(Vec4(1, 1, 0, .9))
+ particles.renderer.setEdgeColor(Vec4(1, 1, 0, .6))
+ particles.emitter.setRadius(0.01)
+ effect.setHpr(0, 180, 0)
+ effect.setPos(0, 0, 0)
+ return effect
+
+
+
diff --git a/toontown/src/battle/BattlePlace.py b/toontown/src/battle/BattlePlace.py
new file mode 100644
index 0000000..a971968
--- /dev/null
+++ b/toontown/src/battle/BattlePlace.py
@@ -0,0 +1,150 @@
+from pandac.PandaModules import *
+from toontown.toon import Toon
+from toontown.hood import Place
+
+class BattlePlace(Place.Place):
+ def __init__(self, loader, doneEvent):
+ assert(self.notify.debug("__init__()"))
+ Place.Place.__init__(self, loader, doneEvent)
+
+ def load(self):
+ Place.Place.load(self)
+ # load Toon battle anims and props
+ Toon.loadBattleAnims()
+
+ def setState(self, state, battleEvent=None):
+ assert(self.notify.debug("setState(state="+str(state)
+ +", battleEvent="+str(battleEvent)+")"))
+ if (battleEvent):
+ if not self.fsm.request(state, [battleEvent]):
+ self.notify.warning("fsm.request('%s') returned 0 (zone id %s, avatar pos %s)."
+ % (state, self.zoneId, base.localAvatar.getPos(render)))
+ else:
+ if not self.fsm.request(state):
+ self.notify.warning("fsm.request('%s') returned 0 (zone id %s, avatar pos %s)."
+ % (state, self.zoneId, base.localAvatar.getPos(render)))
+
+ def enterWalk(self, flag=0):
+ assert(self.notify.debug("enterWalk()"))
+ Place.Place.enterWalk(self, flag)
+ # handlers
+ self.accept("enterBattle", self.handleBattleEntry)
+
+ def exitWalk(self):
+ assert(self.notify.debug("exitWalk()"))
+ Place.Place.exitWalk(self)
+ self.ignore("enterBattle")
+
+ def enterWaitForBattle(self):
+ assert(self.notify.debug("enterWaitForBattle()"))
+ base.localAvatar.b_setAnimState('neutral', 1)
+
+ def exitWaitForBattle(self):
+ assert(self.notify.debug("exitWaitForBattle()"))
+
+ def enterBattle(self, event):
+ assert(self.notify.debug("enterBattle()"))
+ self.loader.music.stop()
+ base.playMusic(self.loader.battleMusic, looping=1, volume=0.9)
+ self.enterTownBattle(event)
+
+ # Make sure the toon's anim state gets reset
+ base.localAvatar.b_setAnimState('off', 1)
+ # A query for a teleport location might come along while we're
+ # in battle, which is acceptable. We should handle it, so
+ # friends can teleport to us to help us out.
+ # We can't put this handler in the TownBattle object, because
+ # it doesn't know what zone or hood we're in.
+ self.accept("teleportQuery", self.handleTeleportQuery)
+ base.localAvatar.setTeleportAvailable(1)
+ # Disable leave to pay / set parent password
+ base.localAvatar.cantLeaveGame = 1
+
+ def enterTownBattle(self, event):
+ self.loader.townBattle.enter(event, self.fsm.getStateNamed("battle"))
+
+ def exitBattle(self):
+ assert(self.notify.debug("exitBattle()"))
+ self.loader.townBattle.exit()
+ self.loader.battleMusic.stop()
+ base.playMusic(self.loader.music, looping=1, volume=0.8)
+ base.localAvatar.cantLeaveGame = 0
+ base.localAvatar.setTeleportAvailable(0)
+ self.ignore("teleportQuery")
+
+ def handleBattleEntry(self):
+ assert(self.notify.debug("handleBattleEntry()"))
+ self.fsm.request("battle")
+
+ def enterFallDown(self, extraArgs=[]):
+ assert(self.notify.debug("enterFallDown()"))
+ # exitWalk hides the laffmeter, so start it here
+ base.localAvatar.laffMeter.start()
+ # Play the 'slip backwards' animation
+ base.localAvatar.b_setAnimState('FallDown',
+ callback=self.handleFallDownDone,
+ extraArgs=extraArgs)
+
+ def handleFallDownDone(self):
+ # put place back in walk state after squish is done
+ base.cr.playGame.getPlace().setState("walk")
+
+ def exitFallDown(self):
+ assert(self.notify.debug("exitFallDown"))
+ base.localAvatar.laffMeter.stop()
+
+ def enterSquished(self):
+ assert(self.notify.debug("enterSquished()"))
+ # exitWalk hides the laffmeter, so start it here
+ base.localAvatar.laffMeter.start()
+ # Play the 'squish' animation
+ base.localAvatar.b_setAnimState('Squish')
+ # Put toon back in walk state after a couple seconds
+ taskMgr.doMethodLater(2.0,
+ self.handleSquishDone,
+ base.localAvatar.uniqueName("finishSquishTask"))
+
+ def handleSquishDone(self, extraArgs=[]):
+ # put place back in walk state after squish is done
+ base.cr.playGame.getPlace().setState("walk")
+ #self.fsm.request("walk")
+
+ def exitSquished(self):
+ assert(self.notify.debug("exitSquished()"))
+ taskMgr.remove(base.localAvatar.uniqueName("finishSquishTask"))
+ base.localAvatar.laffMeter.stop()
+
+ def enterZone(self, newZone):
+ """
+ Puts the toon in the indicated zone. newZone may either be a
+ CollisionEntry object as determined by a floor polygon, or an
+ integer zone id. It may also be None, to indicate no zone.
+ """
+ assert(self.notify.debug("enterZone(newZone=%s)"%(newZone,)))
+ if isinstance(newZone, CollisionEntry):
+ # Get the name of the collide node
+ try:
+ newZoneId = int(newZone.getIntoNode().getName())
+ except:
+ self.notify.warning("Invalid floor collision node in street: %s" % (newZone.getIntoNode().getName()))
+ return
+ else:
+ newZoneId = newZone
+
+ self.doEnterZone(newZoneId)
+
+ def doEnterZone(self, newZoneId):
+ """
+ Puts the Toon in the indicated zone, which is an integer
+ number. This is overridden in Street.py to implement
+ zone-based visibility of geometry.
+ """
+ if newZoneId != self.zoneId:
+ # Tell the server that we changed zones
+ if newZoneId != None:
+ base.cr.sendSetZoneMsg(newZoneId)
+ self.notify.debug("Entering Zone %d" % (newZoneId))
+
+ # The new zone is now old
+ self.zoneId = newZoneId
+ assert(self.notify.debug(" newZoneId="+str(newZoneId)))
diff --git a/toontown/src/battle/BattleProps.py b/toontown/src/battle/BattleProps.py
new file mode 100644
index 0000000..d73ed37
--- /dev/null
+++ b/toontown/src/battle/BattleProps.py
@@ -0,0 +1,448 @@
+from pandac.PandaModules import *
+from direct.actor import Actor
+from direct.directnotify import DirectNotifyGlobal
+from otp.otpbase import OTPGlobals
+import random
+
+Props = (
+ #
+ # Toon props
+ #
+ # fanfare partyball
+ (5, 'partyBall', 'partyBall'),
+ # heal
+ (5, 'feather', 'feather-mod', 'feather-chan'), # tickle
+ (5, 'lips', 'lips'), # smooch
+ (5, 'lipstick', 'lipstick'), # smooch
+ (5, 'hat', 'hat'), # happy-dance
+ (5, 'cane', 'cane'), # happy-dance
+ (5, 'cubes', 'cubes-mod', 'cubes-chan'), # juggle
+ (5, 'ladder', 'ladder2'),
+ # lure
+ (4, 'fishing-pole', 'fishing-pole-mod', 'fishing-pole-chan'),
+ (5, '1dollar', '1dollar-bill-mod', '1dollar-bill-chan'),
+ (5, 'big-magnet', 'magnet'),
+ (5, 'hypno-goggles', 'hypnotize-mod', 'hypnotize-chan'),
+ (5, 'slideshow', 'av_screen'),
+ # trap
+ (5, 'banana', 'banana-peel-mod', 'banana-peel-chan'),
+ (5, 'rake', 'rake-mod', 'rake-chan'),
+ (5, 'marbles', 'marbles-mod', 'marbles-chan'),
+ (5, 'tnt', 'tnt-mod', 'tnt-chan'),
+ (5, 'trapdoor', 'trapdoor'),
+ (5, 'quicksand', 'quicksand'),
+ (5, 'traintrack', 'traintrack2'),
+ (5, 'train', 'train'),
+ # sound
+ (5, 'megaphone', 'megaphone'),
+ (5, 'aoogah', 'aoogah'),
+ (5, 'bikehorn', 'bikehorn'),
+ (5, 'bugle', 'bugle'),
+ (5, 'elephant', 'elephant'),
+ (5, 'fog_horn', 'fog_horn'),
+ (5, 'whistle', 'whistle'),
+ (5, 'singing', 'singing'),
+ # throw
+ (3.5, 'creampie', 'tart'),
+ (5, 'fruitpie-slice', 'fruit-pie-slice'),
+ (5, 'creampie-slice', 'cream-pie-slice'),
+ (5, 'birthday-cake', 'birthday-cake-mod', 'birthday-cake-chan'),
+ (5, 'wedding-cake', 'wedding_cake'),
+ # squirt
+ # The squirting flower is used in the tutorial
+ (3.5, 'squirting-flower', 'squirting-flower'), # flower
+ (5, 'glass', 'glass-mod', 'glass-chan'), # water glass
+ (4, 'water-gun', 'water-gun'), # water pistol
+ (3.5, 'bottle', 'bottle'), # seltzer
+ (5, 'firehose', 'firehose-mod', 'firehose-chan'), # firehose
+ (5, 'hydrant', 'battle_hydrant'), # fire hydrant
+ (4, 'stormcloud', 'stormcloud-mod', 'stormcloud-chan'),
+ (5, 'geyser', 'geyser'),
+ # drop
+ (3.5, 'button', 'button'),
+ (5, 'flowerpot', 'flowerpot-mod', 'flowerpot-chan'),
+ (5, 'sandbag', 'sandbag-mod', 'sandbag-chan'),
+ (4, 'anvil', 'anvil-mod', 'anvil-chan'),
+ (5, 'weight', 'weight-mod', 'weight-chan'),
+ (5, 'safe', 'safe-mod', 'safe-chan'),
+ (5, 'piano', 'piano-mod', 'piano-chan'),
+ #
+ # Suit props
+ #
+ (5, 'rake-react', 'rake-step-mod', 'rake-step-chan'),
+ (5, 'pad', 'pad'),
+ (4, 'propeller', 'propeller-mod', 'propeller-chan'),
+ (5, 'calculator', 'calculator-mod', 'calculator-chan'),
+ (5, 'rollodex', 'roll-o-dex'),
+ (5, 'rubber-stamp', 'rubber-stamp'),
+ (5, 'rubber-stamp-pad', 'rubber-stamp-pad-mod', 'rubber-stamp-pad-chan'),
+ (5, 'smile', 'smile-mod', 'smile-chan'),
+ (5, 'golf-club', 'golf-club'),
+ (5, 'golf-ball', 'golf-ball'),
+ (5, 'redtape', 'redtape'),
+ # the tube is for wrapping around the toon
+ (5, 'redtape-tube', 'redtape-tube'),
+ (5, 'bounced-check', 'bounced-check'),
+ (5, 'calculator', 'calculator-mod', 'calculator-chan'),
+ (3.5, 'clip-on-tie', 'clip-on-tie-mod', 'clip-on-tie-chan'),
+ (5, 'pen', 'pen'),
+ (5, 'pencil', 'pencil'),
+ (3.5, 'phone', 'phone'),
+ (3.5, 'receiver', 'receiver'),
+ (5, 'sharpener', 'sharpener'),
+ (3.5, 'shredder', 'shredder'),
+ # shredder paper also used for eviction notice and restraining order
+ (3.5, 'shredder-paper', 'shredder-paper-mod', 'shredder-paper-chan'),
+ (5, 'watercooler', 'watercooler'),
+ (5, 'dagger', 'dagger'),
+ (5, 'card', 'card'),
+ (5, 'baseball', 'baseball'),
+ (5, 'bird', 'bird'),
+ (5, 'can', 'can'),
+ (5, 'cigar', 'cigar'),
+ (5, 'evil-eye', 'evil-eye'),
+ (5, 'gavel', 'gavel'), # needs anim!
+ (5, 'half-windsor', 'half-windsor'),
+ (5, 'lawbook', 'lawbook'),
+ (5, 'newspaper', 'newspaper'),
+ (5, 'pink-slip', 'pink-slip'),
+ (5, 'teeth', 'teeth-mod', 'teeth-chan'), # wind-up
+ (5, 'power-tie', 'power-tie'), # there is a more polygonal version of this
+ # if we deem it necesary.
+ #
+ # Battle effects
+ #
+ (3.5, 'spray', 'spray'),
+ (3.5, 'splash', 'splash'),
+ (3.5, 'splat', 'splat-mod', 'splat-chan'),
+ (3.5, 'stun', 'stun-mod', 'stun-chan'),
+ (3.5, 'glow', 'glow'),
+ (3.5, 'suit_explosion', 'suit_explosion-mod', 'suit_explosion-chan'),
+ (3.5, 'suit_explosion_dust', 'dust_cloud'),
+
+ #
+ # water effects
+ #
+ (4, 'ripples', 'ripples'),
+ (4, 'wake', 'wake'),
+ (4, 'splashdown', 'SZ_splashdown-mod', 'SZ_splashdown-chan'),
+ )
+
+# splat dict: pie-name: (scale, color)
+CreampieColor = VBase4(250./255., 241./255., 24./255., 1.)
+FruitpieColor = VBase4(55./255., 40./255., 148./255., 1.)
+BirthdayCakeColor = VBase4(253./255., 119./255., 220./255., 1.)
+Splats = {
+ 'tart': (0.3, FruitpieColor),
+ 'fruitpie-slice': (0.5, FruitpieColor),
+ 'creampie-slice': (0.5, CreampieColor),
+ 'fruitpie': (0.7, FruitpieColor),
+ 'creampie': (0.7, CreampieColor),
+ 'birthday-cake': (0.9, BirthdayCakeColor),
+ }
+Variants = ('tart', 'fruitpie', 'splat-tart', 'dust', 'kapow', 'double-windsor',
+ 'splat-fruitpie-slice', 'splat-creampie-slice', 'splat-fruitpie',
+ 'splat-creampie', 'splat-birthday-cake', 'splash-from-splat',
+ 'clip-on-tie', 'lips', 'small-magnet', '5dollar', '10dollar',
+ 'suit_explosion', 'quicksand', 'trapdoor', 'geyser', 'ship', 'trolley','traintrack')
+
+class PropPool:
+ """
+ The PropPool loads props and their animations if they have them.
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('PropPool')
+
+ def __init__(self):
+ self.props = {}
+ self.propCache = []
+ self.propStrings = {}
+ self.propTypes = {}
+ self.maxPoolSize = base.config.GetInt("prop-pool-size", 8)
+
+ # load ref's to the props enumerated above
+ for p in Props:
+ phase = p[0]
+ propName = p[1]
+ modelName = p[2]
+ # See if the prop is animated
+ if (len(p) == 4):
+ animName = p[3]
+ propPath = self.getPath(phase, modelName)
+ animPath = self.getPath(phase, animName)
+ self.propTypes[propName] = 'actor'
+ self.propStrings[propName] = (propPath, animPath)
+ else:
+ propPath = self.getPath(phase, modelName)
+ self.propTypes[propName] = 'model'
+ self.propStrings[propName] = (propPath,)
+
+ # load the ref's for variant props
+ propName = 'tart'
+ self.propStrings[propName] = (self.getPath(3.5,'tart'),)
+ self.propTypes[propName] = 'model'
+
+ propName = 'fruitpie'
+ self.propStrings[propName] = (self.getPath(3.5, 'tart'),)
+ self.propTypes[propName] = 'model'
+
+ propName = 'double-windsor'
+ self.propStrings[propName] = (self.getPath(5, 'half-windsor'),)
+ self.propTypes[propName] = 'model'
+
+ splatAnimFileName = self.getPath(3.5, 'splat-chan')
+ for splat in Splats.keys():
+ propName = 'splat-' + splat
+ self.propStrings[propName] = (self.getPath(3.5, 'splat-mod'), splatAnimFileName)
+ self.propTypes[propName] = 'actor'
+
+ propName = 'splash-from-splat'
+ self.propStrings[propName] = (self.getPath(3.5, 'splat-mod'), splatAnimFileName)
+ self.propTypes[propName] = 'actor'
+
+ propName = 'small-magnet'
+ self.propStrings[propName] = (self.getPath(5, 'magnet'),)
+ self.propTypes[propName] = 'model'
+
+ propName = '5dollar'
+ self.propStrings[propName] = (self.getPath(5, '1dollar-bill-mod'),
+ self.getPath(5, '1dollar-bill-chan'))
+ self.propTypes[propName] = 'actor'
+
+ propName = '10dollar'
+ self.propStrings[propName] = (self.getPath(5, '1dollar-bill-mod'),
+ self.getPath(5, '1dollar-bill-chan'))
+ self.propTypes[propName] = 'actor'
+
+ propName = 'dust'
+ self.propStrings[propName] = (self.getPath(5, 'dust-mod'),
+ self.getPath(5, 'dust-chan'))
+ self.propTypes[propName] = 'actor'
+
+ propName = 'kapow'
+ self.propStrings[propName] = (self.getPath(5, 'kapow-mod'),
+ self.getPath(5, 'kapow-chan'))
+ self.propTypes[propName] = 'actor'
+
+ propName = 'ship'
+ # TODO: move to phase 5!
+ self.propStrings[propName] = ("phase_5/models/props/ship.bam",)
+ self.propTypes[propName] = 'model'
+
+ propName = 'trolley'
+ self.propStrings[propName] = ("phase_4/models/modules/trolley_station_TT",)
+ self.propTypes[propName] = 'model'
+
+ def getPath(self, phase, model):
+ return ("phase_%s/models/props/%s" % (phase, model))
+
+ def makeVariant(self, name):
+ """makeVariant(self, string)
+ """
+ # scale the original pie down
+ if (name == 'tart'):
+ self.props[name].setScale(0.5)
+
+ # scale the original pie down
+ elif (name == 'fruitpie'):
+ self.props[name].setScale(0.75)
+
+ # scale the half-windsor up
+ elif (name == 'double-windsor'):
+ self.props[name].setScale(1.5)
+
+ # munge the various pie splats
+ elif (name[:6] == 'splat-'):
+ prop = self.props[name]
+ scale = prop.getScale() * Splats[name[6:]][0]
+ prop.setScale(scale)
+ prop.setColor(Splats[name[6:]][1])
+
+ # hack up a splash for the squirt attacks
+ elif (name == 'splash-from-splat'):
+ self.props[name].setColor(0.75, 0.75, 1.0, 1.0)
+
+ # yer tie's crooked
+ elif (name == 'clip-on-tie'):
+ tie = self.props[name]
+ tie.getChild(0).setHpr(23.86, -16.03, 9.18)
+
+ # make the small magnet smaller
+ elif (name == 'small-magnet'):
+ self.props[name].setScale(0.5)
+
+ # fixing scale point on shredder-paper
+ # NOTE: I don't think this is getting run;
+ # 'shredder-paper' is not in the list of variants
+ elif (name == 'shredder-paper'):
+ paper = self.props[name]
+ paper.setPosHpr(2.22, -0.95, 1.16, -48.61, 26.57, -111.51)
+ paper.flattenMedium()
+
+ # place lips at the origin
+ elif (name == 'lips'):
+ lips = self.props[name]
+ lips.setPos(0, 0, -3.04)
+ lips.flattenMedium()
+
+ # set the 5 dollar bill texture
+ elif (name == '5dollar'):
+ tex = loader.loadTexture('phase_5/maps/dollar_5.jpg')
+ tex.setMinfilter(Texture.FTLinearMipmapLinear)
+ tex.setMagfilter(Texture.FTLinear)
+ self.props[name].setTexture(tex, 1)
+
+ # set the 10 dollar bill texture
+ elif (name == '10dollar'):
+ tex = loader.loadTexture('phase_5/maps/dollar_10.jpg')
+ tex.setMinfilter(Texture.FTLinearMipmapLinear)
+ tex.setMagfilter(Texture.FTLinear)
+ self.props[name].setTexture(tex, 1)
+
+ # set the draw order on the dust clouds (cloud1 is front)
+ elif (name == 'dust'):
+ bin = 110
+ for cloudNum in range(1, 12):
+ cloudName = '**/cloud' + str(cloudNum)
+ cloud = self.props[name].find(cloudName)
+ cloud.setBin('fixed', bin)
+ bin -= 10
+
+ # set the draw order on the kapow
+ elif (name == 'kapow'):
+ l = self.props[name].find('**/letters')
+ l.setBin('fixed', 20)
+ e = self.props[name].find('**/explosion')
+ e.setBin('fixed', 10)
+
+ # pick random suit explosion text
+ elif (name == 'suit_explosion'):
+ joints = ["**/joint_scale_POW", "**/joint_scale_BLAM", "**/joint_scale_BOOM",]
+ # pick two joints to hide at random
+ joint = random.choice(joints)
+ self.props[name].find(joint).hide()
+ joints.remove(joint)
+ joint = random.choice(joints)
+ self.props[name].find(joint).hide()
+
+ # Put these things in the shadow bin to avoid flickering.
+ # They are drawn behind the actual drop shadows (which are at
+ # sort 0).
+ elif (name == 'quicksand' or name == 'trapdoor'):
+ p = self.props[name]
+ p.setBin('shadow', -5)
+ p.setDepthWrite(0)
+ p.getChild(0).setPos(0, 0, OTPGlobals.FloorOffset)
+
+ elif (name == 'traintrack' or name == 'traintrack2'):
+ #hide the tunnels, and set the bin the same way we did trap door
+ prop = self.props[name]
+ prop.find('**/tunnel3').hide()
+ prop.find('**/tunnel2').hide()
+ #import pdb; pdb.set_trace()
+ pass
+ #prop.setBin('shadow', -5)
+ #prop.setDepthWrite(0)
+ prop.find('**/tracksA').setPos(0, 0, OTPGlobals.FloorOffset)
+
+
+ # make the geyser tflip animate
+ elif (name == 'geyser'):
+ p = self.props[name]
+ s = SequenceNode("geyser")
+ p.findAllMatches("**/Splash*").reparentTo(NodePath(s))
+ s.loop(0)
+ s.setFrameRate(12)
+ p.attachNewNode(s)
+
+ # load the boat from donald's dock
+ elif (name == 'ship'):
+ self.props[name] = self.props[name].find('**/ship_gag')
+
+ # load the trolley from the TT station
+ elif (name == 'trolley'):
+ self.props[name] = self.props[name].find('**/trolley_car')
+
+ def unloadProps(self):
+ """ unloadProps()
+ """
+ for p in self.props.values():
+ # make sure it's loaded before we remove it
+ if (type(p) != type(())):
+ self.__delProp(p)
+ self.props = {}
+ self.propCache = []
+
+ def getProp(self, name):
+ """ getProp(name)
+ """
+ assert(self.propStrings.has_key(name))
+ return self.__getPropCopy(name)
+
+ def __getPropCopy(self, name):
+ assert(self.propStrings.has_key(name))
+ assert(self.propTypes.has_key(name))
+ if (self.propTypes[name] == 'actor'):
+ # make sure the props is loaded
+ if not self.props.has_key(name):
+ prop = Actor.Actor()
+ prop.loadModel(self.propStrings[name][0])
+ animDict = {}
+ animDict[name] = self.propStrings[name][1]
+ prop.loadAnims(animDict)
+ prop.setName(name)
+ self.storeProp(name, prop)
+ # modify the geometry if necessary
+ if (name in Variants):
+ self.makeVariant(name)
+ return Actor.Actor(other=self.props[name])
+ else:
+ # make sure the props is loaded
+ if not self.props.has_key(name):
+ prop = loader.loadModel(self.propStrings[name][0])
+ prop.setName(name)
+ self.storeProp(name, prop)
+ # modify the geometry if necessary
+ if (name in Variants):
+ self.makeVariant(name)
+ # this must be a copyTo(), since the props may get
+ # mangled and mutilated in order to get them
+ # oriented the right way, etc.
+ return self.props[name].copyTo(hidden)
+
+ def storeProp(self, name, prop):
+ """storeProp(self, string, nodePath)
+ Determine how to store the prop in the prop cache.
+ """
+ self.props[name] = prop
+ self.propCache.append(prop)
+ if (len(self.props) > self.maxPoolSize):
+ # remove the oldest prop
+ oldest = self.propCache.pop(0)
+ # remove from dictionary
+ del(self.props[oldest.getName()])
+ # cleanup the prop
+ self.__delProp(oldest)
+
+ self.notify.debug("props = %s" % self.props)
+ self.notify.debug("propCache = %s" % self.propCache)
+
+ def getPropType(self, name):
+ assert(self.propTypes.has_key(name))
+ return self.propTypes[name]
+
+ def __delProp(self, prop):
+ """__delProp(self, prop)
+ This is a convenience function for deleting prop INSTANCES.
+ It does NOT affect the prop dict or cache! Suckah!
+ """
+ if (prop == None):
+ self.notify.warning('tried to delete null prop!')
+ return
+ if (isinstance(prop, Actor.Actor)):
+ prop.cleanup()
+ else:
+ prop.removeNode()
+
+
+globalPropPool = PropPool()
diff --git a/toontown/src/battle/BattleSounds.py b/toontown/src/battle/BattleSounds.py
new file mode 100644
index 0000000..e78849c
--- /dev/null
+++ b/toontown/src/battle/BattleSounds.py
@@ -0,0 +1,83 @@
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+from direct.showbase import AppRunnerGlobal
+import os
+
+## BattleSounds exists as a separate audiomanager from the default one in Showbase.py
+## so we can completely flush battle sounds when we go to areas that battles
+## cannot occur. (see globalBattleSoundCache.clear() in TownLoader.py)
+##
+## might be simpler to use 1 audio manager so you have only 1 set if audio settings, but
+## you could add tagged sounds so all the 'battle'-tagged sounds could be flushed from the
+## cache at once. cache size might need to be increased in battle areas though
+
+class BattleSounds:
+ notify = DirectNotifyGlobal.directNotify.newCategory('BattleSounds')
+
+ def __init__(self):
+ assert(self.notify.debug("__init__()"))
+ self.mgr = AudioManager.createAudioManager()
+ self.isValid=0
+ if self.mgr != None and self.mgr.isValid():
+ self.isValid=1
+ limit = base.config.GetInt('battle-sound-cache-size', 15)
+ self.mgr.setCacheLimit(limit)
+
+ # make sure user sound settings are applied to this snd manager
+ base.addSfxManager(self.mgr)
+
+ self.setupSearchPath()
+
+ def setupSearchPath(self):
+ """ Sets self.sfxSearchPath with the appropriate search path
+ to find battle sound effects. """
+
+ self.sfxSearchPath = DSearchPath()
+ if AppRunnerGlobal.appRunner:
+ # In the web-publish runtime, look here:
+ self.sfxSearchPath.appendDirectory(Filename.expandFrom('$TT_3_ROOT/phase_3/audio/sfx'))
+ self.sfxSearchPath.appendDirectory(Filename.expandFrom('$TT_3_5_ROOT/phase_3.5/audio/sfx'))
+ self.sfxSearchPath.appendDirectory(Filename.expandFrom('$TT_4_ROOT/phase_4/audio/sfx'))
+ self.sfxSearchPath.appendDirectory(Filename.expandFrom('$TT_5_ROOT/phase_5/audio/sfx'))
+ else:
+ # In other environments, including the dev environment, look here:
+ self.sfxSearchPath.appendDirectory(Filename('phase_3/audio/sfx'))
+ self.sfxSearchPath.appendDirectory(Filename('phase_3.5/audio/sfx'))
+ self.sfxSearchPath.appendDirectory(Filename('phase_4/audio/sfx'))
+ self.sfxSearchPath.appendDirectory(Filename('phase_5/audio/sfx'))
+ self.sfxSearchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('$TTMODELS/built/phase_3/audio/sfx')))
+ self.sfxSearchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('$TTMODELS/built/phase_3.5/audio/sfx')))
+ self.sfxSearchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('$TTMODELS/built/phase_4/audio/sfx')))
+ self.sfxSearchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('$TTMODELS/built/phase_5/audio/sfx')))
+
+
+ def clear(self):
+ assert(self.notify.debug("clear()"))
+ if self.isValid:
+ self.mgr.clearCache()
+
+ def getSound(self, name):
+ assert(self.notify.debug("getSound(name=%s)"%(name)))
+ if self.isValid:
+ filename = Filename(name)
+ found = vfs.resolveFilename(filename, self.sfxSearchPath)
+
+ if not found:
+ # If it wasn't found, try once more to reset the
+ # search path. Maybe the first time we set it, we
+ # didn't have all of the phases loaded yet.
+ self.setupSearchPath()
+ found = vfs.resolveFilename(filename, self.sfxSearchPath)
+
+ if not found:
+ # If it's still not found, something's wrong.
+ self.notify.warning('%s not found on:' % name)
+ print self.sfxSearchPath
+
+ else:
+ return self.mgr.getSound(filename.getFullpath())
+
+ return self.mgr.getNullSound()
+
+globalBattleSoundCache = BattleSounds()
+
diff --git a/toontown/src/battle/DistributedBattle.py b/toontown/src/battle/DistributedBattle.py
new file mode 100644
index 0000000..8c0a7fb
--- /dev/null
+++ b/toontown/src/battle/DistributedBattle.py
@@ -0,0 +1,354 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import ToontownBattleGlobals
+import DistributedBattleBase
+from direct.directnotify import DirectNotifyGlobal
+import MovieUtil
+from toontown.suit import Suit
+from direct.actor import Actor
+from toontown.toon import TTEmote
+from otp.avatar import Emote
+import SuitBattleGlobals
+from toontown.distributed import DelayDelete
+import random
+
+class DistributedBattle(DistributedBattleBase.DistributedBattleBase):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattle')
+
+ camFOFov = ToontownBattleGlobals.BattleCamFaceOffFov
+ camFOPos = ToontownBattleGlobals.BattleCamFaceOffPos
+ # this event comes from PlayGame.setPlace()
+ PlayGameSetPlaceEvent = "playGameSetPlace"
+
+ def __init__(self, cr):
+ townBattle = cr.playGame.hood.loader.townBattle
+ DistributedBattleBase.DistributedBattleBase.__init__(self, cr,
+ townBattle)
+ self.setupCollisions(self.uniqueBattleName('battle-collide'))
+
+ def generate(self):
+ DistributedBattleBase.DistributedBattleBase.generate(self)
+ #dbgBattleMarkers = loader.loadModel("dbgBattleMarkers.egg")
+ #dbgBattleMarkers.reparentTo(self)
+
+ def announceGenerate(self):
+ DistributedBattleBase.DistributedBattleBase.generate(self)
+
+
+
+ def disable(self):
+ DistributedBattleBase.DistributedBattleBase.disable(self)
+ self.ignore(self.PlayGameSetPlaceEvent)
+
+ def delete(self):
+ DistributedBattleBase.DistributedBattleBase.delete(self)
+ self.ignore(self.PlayGameSetPlaceEvent)
+ self.removeCollisionData()
+
+ ##### Messages From The Server #####
+
+ def setInteractivePropTrackBonus(self, trackBonus):
+ DistributedBattleBase.DistributedBattleBase.setInteractivePropTrackBonus(self, trackBonus)
+ if self.interactivePropTrackBonus >= 0:
+ if base.cr.playGame.hood:
+ self.calcInteractiveProp()
+ else:
+ self.acceptOnce(self.PlayGameSetPlaceEvent , self.calcInteractiveProp)
+
+ def calcInteractiveProp(self):
+ """We didn't have loader the first time through, get the interactiveProp now."""
+ if base.cr.playGame.hood:
+ loader = base.cr.playGame.hood.loader
+ if hasattr(loader,"getInteractiveProp"):
+ self.interactiveProp = loader.getInteractiveProp(self.zoneId)
+ self.notify.debug("self.interactiveProp = %s" % self.interactiveProp)
+ else:
+ self.notify.warning("no loader.getInteractiveProp self.interactiveProp is None")
+ else:
+ self.notify.warning("no hood self.interactiveProp is None")
+
+
+ def setMembers(self, suits, suitsJoining, suitsPending, suitsActive,
+ suitsLured, suitTraps,
+ toons, toonsJoining, toonsPending, toonsActive,
+ toonsRunning, timestamp):
+ if self.battleCleanedUp():
+ return
+
+ oldtoons = DistributedBattleBase.DistributedBattleBase.setMembers(self,
+ suits, suitsJoining, suitsPending, suitsActive, suitsLured,
+ suitTraps,
+ toons, toonsJoining, toonsPending, toonsActive, toonsRunning,
+ timestamp)
+
+ # If the battle is full, we need to make the collision sphere
+ # tangible so other toons can't walk through the battle
+ if (len(self.toons) == 4 and len(oldtoons) < 4):
+ self.notify.debug('setMembers() - battle is now full of toons')
+ self.closeBattleCollision()
+ elif (len(self.toons) < 4 and len(oldtoons) == 4):
+ self.openBattleCollision()
+
+ # Each state will have an enter function, an exit function,
+ # and a datagram handler, which will be set during each enter function.
+
+ # Specific State functions
+
+ ##### Off state #####
+
+ ##### FaceOff state #####
+
+ def __faceOff(self, ts, name, callback):
+ if (len(self.suits) == 0):
+ self.notify.warning('__faceOff(): no suits.')
+ return
+ if (len(self.toons) == 0):
+ self.notify.warning('__faceOff(): no toons.')
+ return
+
+ # Pick only the first suit for the faceoff, if there happen to
+ # be more than one.
+ suit = self.suits[0]
+ point = self.suitPoints[0][0]
+ suitPos = point[0]
+ suitHpr = VBase3(point[1], 0.0, 0.0)
+
+ # And ditto for the first toon.
+ toon = self.toons[0]
+ point = self.toonPoints[0][0]
+ toonPos = point[0]
+ toonHpr = VBase3(point[1], 0.0, 0.0)
+
+ p = toon.getPos(self)
+ toon.setPos(self, p[0], p[1], 0.0)
+ toon.setShadowHeight(0)
+
+ suit.setState('Battle')
+
+ suitTrack = Sequence()
+ toonTrack = Sequence()
+
+ # Make suit and toon face each other (and exchange taunts)
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ suitTrack.append(Func(suit.headsUp, toon))
+ taunt = SuitBattleGlobals.getFaceoffTaunt(suit.getStyleName(),
+ suit.doId)
+ suitTrack.append(Func(suit.setChatAbsolute, taunt, CFSpeech | CFTimeout))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.headsUp, suit))
+
+ suitHeight = suit.getHeight()
+ suitOffsetPnt = Point3(0, 0, suitHeight)
+
+ # Determine the battle positions based on initial angle
+ # between the suit and the battle center (we want the suit to walk
+ # as short a distance as possible)
+ faceoffTime = self.calcFaceoffTime(self.getPos(), self.initialSuitPos)
+ #assert(faceoffTime > BATTLE_SMALL_VALUE)
+ # make sure the faceoff time is non-zero
+ faceoffTime = max(faceoffTime, BATTLE_SMALL_VALUE)
+ delay = FACEOFF_TAUNT_T
+
+ if (self.hasLocalToon()):
+ # empirical hack to pick a mid-height view, left in to sortof match the old view
+ MidTauntCamHeight = suitHeight*0.66
+ MidTauntCamHeightLim = suitHeight-1.8
+ if (MidTauntCamHeight < MidTauntCamHeightLim):
+ MidTauntCamHeight = MidTauntCamHeightLim
+
+ TauntCamY = 16
+ TauntCamX = random.choice((-5, 5))
+ TauntCamHeight = random.choice((MidTauntCamHeight, 1, 11))
+
+ camTrack = Sequence()
+ camTrack.append(Func(camera.wrtReparentTo, suit))
+ camTrack.append(Func(base.camLens.setFov, self.camFOFov))
+ camTrack.append(Func(camera.setPos, TauntCamX, TauntCamY, TauntCamHeight))
+ camTrack.append(Func(camera.lookAt, suit, suitOffsetPnt))
+ camTrack.append(Wait(delay))
+ camTrack.append(Func(base.camLens.setFov, self.camFov))
+ camTrack.append(Func(camera.wrtReparentTo, self))
+ camTrack.append(Func(camera.setPos, self.camFOPos))
+ camTrack.append(Func(camera.lookAt, suit.getPos(self)))
+ camTrack.append(Wait(faceoffTime))
+ if self.interactiveProp:
+ camTrack.append(Func(camera.lookAt, self.interactiveProp.node.getPos(self)))
+ camTrack.append(Wait(FACEOFF_LOOK_AT_PROP_T))
+
+ suitTrack.append(Wait(delay))
+ toonTrack.append(Wait(delay))
+
+ # Make suit and toon face their destination spots in the battle
+ suitTrack.append(Func(suit.headsUp, self, suitPos))
+ suitTrack.append(Func(suit.clearChat))
+ toonTrack.append(Func(toon.headsUp, self, toonPos))
+
+ # Make suit and toon walk to their battle spots
+ suitTrack.append(Func(suit.loop, 'walk'))
+ suitTrack.append(LerpPosInterval(suit, faceoffTime, suitPos,
+ other=self))
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ suitTrack.append(Func(suit.setHpr, self, suitHpr))
+
+ toonTrack.append(Func(toon.loop, 'run'))
+ toonTrack.append(LerpPosInterval(toon, faceoffTime, toonPos,
+ other=self))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.setHpr, self, toonHpr))
+
+ if base.localAvatar == toon:
+ soundTrack = Sequence(
+ Wait(delay),
+ SoundInterval(base.localAvatar.soundRun, loop = 1,
+ duration=faceoffTime, node=base.localAvatar),
+ )
+ else:
+ soundTrack = Wait(delay + faceoffTime)
+ mtrack = Parallel(suitTrack, toonTrack, soundTrack)
+
+ if (self.hasLocalToon()):
+ # No arrows - they just get in the way
+ NametagGlobals.setMasterArrowsOn(0)
+ mtrack = Parallel(mtrack, camTrack)
+
+ done = Func(callback)
+ track = Sequence(mtrack, done, name = name)
+ track.delayDeletes = [
+ DelayDelete.DelayDelete(toon, '__faceOff'),
+ DelayDelete.DelayDelete(suit, '__faceOff')
+ ]
+ track.start(ts)
+ self.storeInterval(track, name)
+
+ def enterFaceOff(self, ts):
+ self.notify.debug('enterFaceOff()')
+ self.delayDeleteMembers()
+ if (len(self.toons) > 0 and
+ base.localAvatar == self.toons[0]):
+ Emote.globalEmote.disableAll(self.toons[0], "dbattle, enterFaceOff")
+ self.__faceOff(ts, self.faceOffName, self.__handleFaceOffDone)
+ if self.interactiveProp:
+ self.interactiveProp.gotoFaceoff()
+
+
+ def __handleFaceOffDone(self):
+ self.notify.debug('FaceOff done')
+ # Only the toon that initiated the battle needs to reply
+ if (len(self.toons) > 0 and
+ base.localAvatar == self.toons[0]):
+ self.d_faceOffDone(base.localAvatar.doId)
+
+ def exitFaceOff(self):
+ self.notify.debug('exitFaceOff()')
+ if (len(self.toons) > 0 and base.localAvatar == self.toons[0]):
+ Emote.globalEmote.releaseAll(self.toons[0], "dbattle exitFaceOff")
+ self.finishInterval(self.faceOffName)
+ # remove the delayDelete, so an exited toon doesn't hang around
+ self.clearInterval(self.faceOffName)
+ self._removeMembersKeep()
+
+ ##### WaitForInput state #####
+
+ ##### PlayMovie state #####
+
+ ##### Reward state #####
+
+ def enterReward(self, ts):
+ self.notify.debug('enterReward()')
+ self.disableCollision()
+ self.delayDeleteMembers()
+ Emote.globalEmote.disableAll(base.localAvatar, "dbattle, enterReward")
+
+ if (self.hasLocalToon()):
+ NametagGlobals.setMasterArrowsOn(0)
+ if (self.localToonActive() == 0):
+ self.removeInactiveLocalToon(base.localAvatar)
+
+ # Some of the toons may finish the movie before we do; be
+ # prepared to show them moving around when they do.
+ for toon in self.toons:
+ toon.startSmooth()
+
+ self.accept('resumeAfterReward', self.handleResumeAfterReward)
+ if self.interactiveProp:
+ self.interactiveProp.gotoVictory()
+ self.playReward(ts)
+
+ # This function gets overridden by DistributedBattleTutorial.py
+ def playReward(self, ts):
+ self.movie.playReward(ts, self.uniqueName('reward'),
+ self.handleRewardDone)
+
+ def handleRewardDone(self):
+ self.notify.debug('Reward done')
+ if (self.hasLocalToon()):
+ self.d_rewardDone(base.localAvatar.doId)
+
+ self.movie.resetReward()
+
+ # Now request our local battle object enter the Resume state,
+ # which frees us from the battle. The distributed object may
+ # not enter the Resume state yet (it has to wait until all the
+ # toons involved have reported back up), but there's no reason
+ # we have to wait around for that.
+
+ # We have to send a message, instead of directly asking the
+ # ClassicFSM to switch states, since we might call this method when
+ # we finish the track in exitReward().
+ messenger.send('resumeAfterReward')
+
+ def handleResumeAfterReward(self):
+ self.fsm.request("Resume")
+
+ def exitReward(self):
+ self.notify.debug('exitReward()')
+ self.ignore('resumeAfterReward')
+ # In case we're observing and the server cuts us off
+ # this guarantees all final animations get started and things
+ # get cleaned up
+ self.movie.resetReward(finish=1)
+ self._removeMembersKeep()
+ NametagGlobals.setMasterArrowsOn(1)
+ Emote.globalEmote.releaseAll(base.localAvatar, "dbattle, exitReward")
+
+ ##### Resume state #####
+
+ def enterResume(self, ts=0):
+ self.notify.debug('enterResume()')
+ if (self.hasLocalToon()):
+ self.removeLocalToon()
+ if self.interactiveProp:
+ self.interactiveProp.requestIdleOrSad()
+
+
+ def exitResume(self):
+ pass
+
+ #########################
+ ##### LocalToon ClassicFSM #####
+ #########################
+
+ ##### HasLocalToon state #####
+
+ ##### NoLocalToon state #####
+
+ def enterNoLocalToon(self):
+ self.notify.debug('enterNoLocalToon()')
+ # Enable battle collision sphere
+ self.enableCollision()
+
+ def exitNoLocalToon(self):
+ # Disable battle collision sphere
+ self.disableCollision()
+
+ ##### WaitForServer state #####
+
+ def enterWaitForServer(self):
+ self.notify.debug('enterWaitForServer()')
+
+ def exitWaitForServer(self):
+ pass
diff --git a/toontown/src/battle/DistributedBattleAI.py b/toontown/src/battle/DistributedBattleAI.py
new file mode 100644
index 0000000..c3b3420
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleAI.py
@@ -0,0 +1,202 @@
+from otp.ai.AIBase import *
+from BattleBase import *
+from BattleCalculatorAI import *
+from toontown.toonbase.ToontownBattleGlobals import *
+from SuitBattleGlobals import *
+
+import DistributedBattleBaseAI
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+import random
+
+# attack properties table
+class DistributedBattleAI(DistributedBattleBaseAI.DistributedBattleBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleAI')
+
+ def __init__(self, air, battleMgr, pos, suit, toonId, zoneId,
+ finishCallback=None, maxSuits=4, tutorialFlag=0,
+ levelFlag=0, interactivePropTrackBonus = -1):
+ """__init__(air, battleMgr, pos, suit, toonId, zoneId,
+ finishCallback, maxSuits)
+ """
+ DistributedBattleBaseAI.DistributedBattleBaseAI.__init__(
+ self, air,
+ zoneId, finishCallback, maxSuits=maxSuits,
+ tutorialFlag=tutorialFlag,
+ interactivePropTrackBonus = interactivePropTrackBonus)
+
+ self.battleMgr = battleMgr
+ self.pos = pos
+
+ # Store the initial position of the suit when contact occurred
+ self.initialSuitPos = suit.getConfrontPosHpr()[0]
+ # CCC for now use the suit position as the toon position as they
+ # should be fairly close anyways
+ self.initialToonPos = suit.getConfrontPosHpr()[0]
+ self.addSuit(suit)
+ # For levels, we need to initialize exp lists before we add the
+ # toon
+ self.avId = toonId
+
+ if (levelFlag == 0):
+ self.addToon(toonId)
+
+ self.faceOffToon = toonId
+ self.fsm.request('FaceOff')
+
+ def generate(self):
+ DistributedBattleBaseAI.DistributedBattleBaseAI.generate(self)
+ toon = simbase.air.doId2do.get(self.avId)
+ if toon:
+ if hasattr(self, "doId"):
+ toon.b_setBattleId(self.doId)
+ else:
+ toon.b_setBattleId(-1)
+ self.avId = None
+
+ def faceOffDone(self):
+ """ faceOffDone(toonId)
+ """
+ assert(self.notify.debug("faceOffDone()"))
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreFaceOffDone == 1):
+ self.notify.debug('faceOffDone() - ignoring toon: %d' % toonId)
+ return
+ elif (self.fsm.getCurrentState().getName() != 'FaceOff'):
+ self.notify.warning('faceOffDone() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('faceOffDone() - toon: %d not in toon list' % \
+ toonId)
+ return
+ self.notify.debug('toon: %d done facing off' % toonId)
+ self.handleFaceOffDone()
+
+ # Each state will have an enter function, an exit function,
+ # and a datagram handler, which will be set during each enter function.
+
+ # Specific State functions
+
+ ##### Off state #####
+
+ ##### FaceOff state #####
+
+ # original suit and toon face off and then walk to positions,
+ # other toons or suits walk directly to wait positions
+
+ def enterFaceOff(self):
+ self.notify.debug('enterFaceOff()')
+ self.joinableFsm.request('Joinable')
+ self.runableFsm.request('Unrunable')
+ # From here on out DistributedBattleAI only controls DistributedSuitAI
+ # DistributedBattle controls DistributedSuit
+ self.suits[0].releaseControl()
+ timeForFaceoff = self.calcFaceoffTime(self.pos,
+ self.initialSuitPos) + FACEOFF_TAUNT_T + \
+ SERVER_BUFFER_TIME
+ if self.interactivePropTrackBonus >= 0:
+ timeForFaceoff += FACEOFF_LOOK_AT_PROP_T
+ self.timer.startCallback(timeForFaceoff,
+ self.__serverFaceOffDone)
+ return None
+
+ def __serverFaceOffDone(self):
+ self.notify.debug('faceoff timed out on server')
+ self.ignoreFaceOffDone = 1
+ self.handleFaceOffDone()
+
+ def exitFaceOff(self):
+ self.timer.stop()
+ return None
+
+ def handleFaceOffDone(self):
+ assert(self.notify.debug("DistributedBattleAI.handleFaceOffDone()"))
+ self.timer.stop()
+ assert(len(self.activeSuits) == 0)
+ self.activeSuits.append(self.suits[0])
+ assert(len(self.activeToons) == 0)
+ # The face off toon might have disconnected, so we need to check
+ # that self.toons[0] is the face off toon.
+ if (len(self.toons) == 0):
+ assert(self.notify.debug('handleFaceOffDone() - no toons!'))
+ self.b_setState('Resume')
+ elif (self.faceOffToon == self.toons[0]):
+ self.activeToons.append(self.toons[0])
+ # Clear out the toon's earned experience so far.
+ self.sendEarnedExperience(self.toons[0])
+ self.d_setMembers()
+ self.b_setState('WaitForInput')
+
+ ##### WaitForJoin state #####
+
+ ##### WaitForInput state #####
+
+ ##### PlayMovie state #####
+
+ def localMovieDone(self, needUpdate, deadToons, deadSuits, lastActiveSuitDied):
+ if (len(self.toons) == 0):
+ # Toons lost - tell all the suits to start walking again
+ self.d_setMembers()
+ self.b_setState('Resume')
+ elif (len(self.suits) == 0):
+ # Toons won - award experience, etc.
+ assert(needUpdate == 1)
+ for toonId in self.activeToons:
+ toon = self.getToon(toonId)
+ if toon:
+ self.toonItems[toonId] = self.air.questManager.recoverItems(toon, self.suitsKilled, self.zoneId)
+ if toonId in self.helpfulToons:
+ self.toonMerits[toonId] = self.air.promotionMgr.recoverMerits(toon, self.suitsKilled, self.zoneId)
+ else:
+ self.notify.debug("toon %d not helpful, skipping merits" % toonId)
+
+ self.d_setMembers()
+ self.d_setBattleExperience()
+ self.b_setState('Reward')
+ else:
+ # Continue with the battle
+ if (needUpdate == 1):
+ self.d_setMembers()
+ if ((len(deadSuits) > 0 and lastActiveSuitDied == 0) or
+ (len(deadToons) > 0)):
+ self.needAdjust = 1
+ # Wait for input will call __requestAdjust()
+ self.setState('WaitForJoin')
+
+ ##### Reward state #####
+
+ # Toons won - distribute rewards
+
+ def enterReward(self):
+ self.notify.debug('enterReward()')
+ self.joinableFsm.request('Unjoinable')
+ self.runableFsm.request('Unrunable')
+ self.resetResponses()
+ self.assignRewards()
+ self.startRewardTimer()
+
+ def startRewardTimer(self):
+ # Set an upper timeout for the reward movie. If no toons
+ # report back by this time, call it done anyway.
+ self.timer.startCallback(REWARD_TIMEOUT, self.serverRewardDone)
+
+ def exitReward(self):
+ return None
+
+
+ ##### Resume state #####
+
+ # Any remaining suits resume their assignment or fly away
+
+ def enterResume(self):
+ self.notify.debug('enterResume()')
+ self.joinableFsm.request('Unjoinable')
+ self.runableFsm.request('Unrunable')
+ DistributedBattleBaseAI.DistributedBattleBaseAI.enterResume(self)
+
+ if self.finishCallback:
+ self.finishCallback(self.zoneId)
+
+ self.battleMgr.destroy(self)
diff --git a/toontown/src/battle/DistributedBattleBase.py b/toontown/src/battle/DistributedBattleBase.py
new file mode 100644
index 0000000..ea0a32f
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleBase.py
@@ -0,0 +1,1990 @@
+from pandac.PandaModules import *
+from toontown.toonbase.ToonBaseGlobal import *
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from direct.distributed.ClockDelta import *
+
+from toontown.toonbase import ToontownBattleGlobals
+from direct.distributed import DistributedNode
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task.Task import Task
+from direct.directnotify import DirectNotifyGlobal
+import Movie
+import MovieUtil
+from toontown.suit import Suit
+from direct.actor import Actor
+import BattleProps
+from direct.particles import ParticleEffect
+import BattleParticles
+from toontown.hood import ZoneUtil
+from toontown.distributed import DelayDelete
+from toontown.toon import TTEmote
+from otp.avatar import Emote
+
+class DistributedBattleBase(DistributedNode.DistributedNode, BattleBase):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleBase')
+
+ id = 0
+
+ camPos = ToontownBattleGlobals.BattleCamDefaultPos
+ camHpr = ToontownBattleGlobals.BattleCamDefaultHpr
+ camFov = ToontownBattleGlobals.BattleCamDefaultFov
+ camMenuFov = ToontownBattleGlobals.BattleCamMenuFov
+ camJoinPos = ToontownBattleGlobals.BattleCamJoinPos
+ camJoinHpr = ToontownBattleGlobals.BattleCamJoinHpr
+
+ def __init__(self, cr, townBattle):
+ """__init__(cr)
+ """
+ DistributedNode.DistributedNode.__init__(self, cr)
+
+ # DistributedNode inherits from NodePath, but doesn't call
+ # NodePath's constructor to avoid multiple calls to it that
+ # result from multiple inheritance along the Actor tree
+ NodePath.__init__(self)
+ self.assign(render.attachNewNode(
+ self.uniqueBattleName('distributed-battle')))
+
+ BattleBase.__init__(self)
+
+ self.bossBattle = 0
+ self.townBattle = townBattle
+ self.__battleCleanedUp = 0
+
+ self.activeIntervals = {}
+ self.localToonJustJoined = 0
+ self.choseAttackAlready = 0
+ self.toons = []
+ self.exitedToons = []
+ self.suitTraps = ''
+ self.membersKeep = None
+
+ # Create unique event/task names
+ self.faceOffName = self.uniqueBattleName('faceoff')
+ self.localToonBattleEvent = self.uniqueBattleName(
+ 'localtoon-battle-event')
+ self.adjustName = self.uniqueBattleName('adjust')
+ self.timerCountdownTaskName = self.uniqueBattleName('timer-countdown')
+
+ self.movie = Movie.Movie(self)
+ self.timer = Timer()
+
+ self.needAdjustTownBattle = 0
+
+ self.streetBattle = 1
+ self.levelBattle = 0
+
+ self.localToonFsm = ClassicFSM.ClassicFSM('LocalToon',
+ [State.State('HasLocalToon',
+ self.enterHasLocalToon,
+ self.exitHasLocalToon,
+ ['NoLocalToon',
+ 'WaitForServer']),
+ State.State('NoLocalToon',
+ self.enterNoLocalToon,
+ self.exitNoLocalToon,
+ ['HasLocalToon',
+ 'WaitForServer']),
+ State.State('WaitForServer',
+ self.enterWaitForServer,
+ self.exitWaitForServer,
+ ['HasLocalToon',
+ 'NoLocalToon'])],
+ # Initial state
+ 'WaitForServer',
+ # Final state
+ 'WaitForServer',
+ )
+ self.localToonFsm.enterInitialState()
+
+ self.fsm = ClassicFSM.ClassicFSM('DistributedBattle',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['FaceOff',
+ 'WaitForInput',
+ 'WaitForJoin',
+ 'MakeMovie',
+ 'PlayMovie',
+ 'Reward',
+ 'Resume']),
+ State.State('FaceOff',
+ self.enterFaceOff,
+ self.exitFaceOff,
+ ['WaitForInput']),
+ State.State('WaitForJoin',
+ self.enterWaitForJoin,
+ self.exitWaitForJoin,
+ ['WaitForInput',
+ 'Resume']),
+ State.State('WaitForInput',
+ self.enterWaitForInput,
+ self.exitWaitForInput,
+ ['WaitForInput',
+ 'PlayMovie',
+ 'Resume']),
+ State.State('MakeMovie',
+ self.enterMakeMovie,
+ self.exitMakeMovie,
+ ['PlayMovie',
+ 'Resume']),
+ State.State('PlayMovie',
+ self.enterPlayMovie,
+ self.exitPlayMovie,
+ ['WaitForInput',
+ 'WaitForJoin',
+ 'Reward',
+ 'Resume']),
+ State.State('Reward',
+ self.enterReward,
+ self.exitReward,
+ ['Resume']),
+ State.State('Resume',
+ self.enterResume,
+ self.exitResume,
+ [])],
+ # Initial state
+ 'Off',
+ # Final state
+ 'Off',
+ )
+ self.fsm.enterInitialState()
+
+ self.adjustFsm = ClassicFSM.ClassicFSM('Adjust',
+ [State.State('Adjusting',
+ self.enterAdjusting,
+ self.exitAdjusting,
+ ['NotAdjusting']),
+ State.State('NotAdjusting',
+ self.enterNotAdjusting,
+ self.exitNotAdjusting,
+ ['Adjusting'])],
+ # Initial state
+ 'NotAdjusting',
+ # Final state
+ 'NotAdjusting',
+ )
+ self.adjustFsm.enterInitialState()
+ self.interactiveProp = None
+
+ def uniqueBattleName(self, name):
+ DistributedBattleBase.id += 1
+ return (name + '-%d' % DistributedBattleBase.id)
+
+ def generate(self):
+ """ generate()
+ """
+ self.notify.debug("generate(%s)" % (self.doId))
+ DistributedNode.DistributedNode.generate(self)
+ self.__battleCleanedUp = 0
+ self.reparentTo(render)
+
+ def storeInterval(self, interval, name):
+ if name in self.activeIntervals:
+ ival = self.activeIntervals[name]
+ # if the interval has a delayDelete, finish it now and destroy the delayDeletes
+ # otherwise, preserve the original behavior of letting the old interval continue
+ # to run if it's running
+ if (hasattr(ival, 'delayDelete') or hasattr(ival, 'delayDeletes')):
+ self.clearInterval(name, finish=1)
+ self.activeIntervals[name] = interval
+
+ def __cleanupIntervals(self):
+ for interval in self.activeIntervals.values():
+ interval.finish()
+ DelayDelete.cleanupDelayDeletes(interval)
+ self.activeIntervals = {}
+
+ def clearInterval(self, name, finish=0):
+ """ Clean up the specified Interval
+ """
+ if (self.activeIntervals.has_key(name)):
+ ival = self.activeIntervals[name]
+ if finish:
+ ival.finish()
+ else:
+ ival.pause()
+ if self.activeIntervals.has_key(name):
+ DelayDelete.cleanupDelayDeletes(ival)
+ # cleanupDelayDeletes might cause the involved avatar to be deleted,
+ # which would clear the interval out of self.activeIntervals. Check
+ # again to see if it's still in the dict
+ if self.activeIntervals.has_key(name):
+ del self.activeIntervals[name]
+ else:
+ self.notify.debug('interval: %s already cleared' % name)
+
+ def finishInterval(self, name):
+ """ Force the specified Interval to jump to the end
+ """
+ if (self.activeIntervals.has_key(name)):
+ interval = self.activeIntervals[name]
+ interval.finish()
+
+ def disable(self):
+ """ disable()
+ """
+ self.notify.debug("disable(%s)" % (self.doId))
+ self.cleanupBattle()
+ DistributedNode.DistributedNode.disable(self)
+
+ def battleCleanedUp(self):
+ return self.__battleCleanedUp
+
+ def cleanupBattle(self):
+ # Undo all the battle stuff as if it has been disabled. This
+ # can be called on the client when we need the battle to get
+ # out of the way now, without waiting for a disable message
+ # from the AI.
+
+ if self.__battleCleanedUp:
+ return
+ self.notify.debug("cleanupBattle(%s)" % (self.doId))
+
+ self.__battleCleanedUp = 1
+
+ self.__cleanupIntervals()
+ self.fsm.requestFinalState()
+
+ # RequestFinalState(), above, may set the camera back to
+ # camFov, the default fov for battles, but removeLocalToon,
+ # below, should restore it to the DefaultCameraFov for
+ # non-battle gameplay.
+ if (self.hasLocalToon()):
+ self.removeLocalToon()
+ # Just for good measure. In some cases, the above might
+ # not restore the fov properly.
+ base.camLens.setFov(ToontownGlobals.DefaultCameraFov)
+ self.localToonFsm.request('WaitForServer')
+
+ self.ignoreAll()
+
+ for suit in self.suits:
+ if (suit.battleTrap != NO_TRAP):
+ self.notify.debug('250 calling self.removeTrap, suit=%d' % suit.doId)
+ self.removeTrap(suit)
+ suit.battleTrap = NO_TRAP
+ suit.battleTrapProp = None
+ self.notify.debug('253 suit.battleTrapProp = None')
+ suit.battleTrapIsFresh = 0
+
+ self.suits = []
+ self.pendingSuits = []
+ self.joiningSuits = []
+ self.activeSuits = []
+ self.suitTraps = ''
+
+ self.toons = []
+ self.joiningToons = []
+ self.pendingToons = []
+ self.activeToons = []
+ self.runningToons = []
+
+ self.__stopTimer()
+
+ # It's important to clean up the intervals before we reset
+ # membersKeep, since some of the intervals might reference
+ # objects protected by membersKeep.
+ self.__cleanupIntervals()
+ self._removeMembersKeep()
+
+ def delete(self):
+ """ delete()
+ """
+ self.notify.debug("delete(%s)" % (self.doId))
+ self.__cleanupIntervals()
+ self._removeMembersKeep()
+ # Eliminate a circular reference
+ self.movie.battle = None
+ del self.townBattle
+ self.removeNode()
+ self.fsm = None
+ self.localToonFsm = None
+ self.adjustFsm = None
+ self.__stopTimer()
+ self.timer = None
+ DistributedNode.DistributedNode.delete(self)
+
+ def loadTrap(self, suit, trapid):
+ #import pdb; pdb.set_trace()
+ self.notify.debug('loadTrap() trap: %d suit: %d' % (trapid, suit.doId))
+ assert(suit.battleTrapProp == None)
+ assert(trapid < len(AvProps[TRAP]))
+ trapName = AvProps[TRAP][trapid]
+ trap = BattleProps.globalPropPool.getProp(trapName)
+ assert(trap != None)
+ suit.battleTrap = trapid
+ suit.battleTrapIsFresh = 0
+ suit.battleTrapProp = trap
+ self.notify.debug('suit.battleTrapProp = trap %s' % trap)
+ if trap.getName() == 'traintrack':
+ #the train track's parent is battle
+ assert self.notify.debug('Not doing wrtReparentTo(suit) for traintrack')
+ pass
+ else:
+ trap.wrtReparentTo(suit)
+ distance = MovieUtil.SUIT_TRAP_DISTANCE
+ if (trapName == 'rake'):
+ distance = MovieUtil.SUIT_TRAP_RAKE_DISTANCE
+ distance += MovieUtil.getSuitRakeOffset(suit)
+ trap.setH(180)
+ trap.setScale(0.7)
+ elif (trapName == 'trapdoor' or
+ trapName == 'quicksand'):
+ trap.setScale(1.7)
+ elif (trapName == 'marbles'):
+ distance = MovieUtil.SUIT_TRAP_MARBLES_DISTANCE
+ trap.setH(94)
+ elif (trapName == 'tnt'):
+ trap.setP(90)
+ # Start particle effect if there isn't one already
+ tip = trap.find("**/joint_attachEmitter")
+ sparks = BattleParticles.createParticleEffect(file='tnt')
+ trap.sparksEffect = sparks
+ sparks.start(tip)
+ trap.setPos(0, distance, 0)
+ if (isinstance(trap, Actor.Actor)):
+ frame = trap.getNumFrames(trapName) - 1
+ trap.pose(trapName, frame)
+
+ def removeTrap(self, suit, removeTrainTrack = False):
+ self.notify.debug('removeTrap() from suit: %d, removeTrainTrack=%s' % (suit.doId, removeTrainTrack))
+ #import pdb; pdb.set_trace()
+ if (suit.battleTrapProp == None):
+ self.notify.debug('suit.battleTrapProp == None, suit.battleTrap=%s setting to NO_TRAP, returning' % suit.battleTrap)
+ suit.battleTrap = NO_TRAP
+ return
+ if suit.battleTrap == UBER_GAG_LEVEL_INDEX:
+ if removeTrainTrack:
+ self.notify.debug('doing removeProp on traintrack')
+ MovieUtil.removeProp(suit.battleTrapProp)
+
+ #make sure we inform the other suits
+ for otherSuit in self.suits:
+ if not otherSuit == suit:
+ otherSuit.battleTrapProp = None
+ self.notify.debug('351 otherSuit=%d otherSuit.battleTrapProp = None' % otherSuit.doId)
+ otherSuit.battleTrap = NO_TRAP
+ otherSuit.battleTrapIsFresh = 0
+ else:
+ self.notify.debug('deliberately not doing removeProp on traintrack')
+ else:
+ self.notify.debug('suit.battleTrap != UBER_GAG_LEVEL_INDEX')
+ MovieUtil.removeProp(suit.battleTrapProp)
+ suit.battleTrapProp = None
+ self.notify.debug('360 suit.battleTrapProp = None')
+ suit.battleTrap = NO_TRAP
+ suit.battleTrapIsFresh = 0
+
+ ##### Convenience Functions #####
+
+ def pause(self):
+ self.timer.stop()
+
+ def unpause(self):
+ self.timer.resume()
+
+ def findSuit(self, id):
+ """ findSuit(id)
+ """
+ for s in self.suits:
+ if (s.doId == id):
+ return s
+ return None
+
+ def findToon(self, id):
+ """ findToon(id)
+ """
+ toon = self.getToon(id)
+ if (toon == None):
+ return None
+ for t in self.toons:
+ if (t == toon):
+ return t
+ return None
+
+ def isSuitLured(self, suit):
+ if (self.luredSuits.count(suit) != 0):
+ return 1
+ return 0
+
+ def unlureSuit(self, suit):
+ self.notify.debug('movie unluring suit %s' % (suit.doId))
+ if (self.luredSuits.count(suit) != 0):
+ self.luredSuits.remove(suit)
+ self.needAdjustTownBattle = 1
+ return None
+
+ def lureSuit(self, suit):
+ self.notify.debug('movie luring suit %s' % (suit.doId))
+ if (self.luredSuits.count(suit) == 0):
+ self.luredSuits.append(suit)
+ self.needAdjustTownBattle = 1
+ return None
+
+ def getActorPosHpr(self, actor, actorList=[]):
+ if (isinstance(actor, Suit.Suit)):
+ if (actorList == []):
+ actorList = self.activeSuits
+ if (actorList.count(actor) != 0):
+ numSuits = len(actorList) - 1
+ index = actorList.index(actor)
+ point = self.suitPoints[numSuits][index]
+ return Point3(point[0]), VBase3(point[1], 0.0, 0.0)
+ else:
+ self.notify.warning('getActorPosHpr() - suit not active')
+ else:
+ if (actorList == []):
+ actorList = self.activeToons
+ if (actorList.count(actor) != 0):
+ numToons = len(actorList) - 1
+ index = actorList.index(actor)
+ point = self.toonPoints[numToons][index]
+ return Point3(point[0]), VBase3(point[1], 0.0, 0.0)
+ else:
+ self.notify.warning('getActorPosHpr() - toon not active')
+
+ ##### Messages From The Server #####
+
+ # These are dummy setters for non-level battles. See toon.dc for
+ # more info.
+ def setLevelDoId(self, levelDoId):
+ pass
+ def setBattleCellId(self, battleCellId):
+ pass
+
+ def setInteractivePropTrackBonus(self, trackBonus):
+ """Greater than or equal to zero if this battle has a prop giving a bonus."""
+ assert self.notify.debugStateCall(self)
+ self.interactivePropTrackBonus = trackBonus
+
+ def getInteractivePropTrackBonus(self):
+ """Greater than or equal to zero if this battle has a prop giving a bonus."""
+ assert self.notify.debugStateCall(self)
+ return self.interactivePropTrackBonus
+
+ def setPosition(self, x, y, z):
+ """setPosition(x, y, z)
+ """
+ self.notify.debug('setPosition() - %d %d %d' % (x, y, z))
+ pos = Point3(x, y, z)
+ self.setPos(pos)
+
+ def setInitialSuitPos(self, x, y, z):
+ """ setInitialSuitPos(x, y, z)
+ """
+ self.initialSuitPos = Point3(x, y, z)
+ # The initial suit position determines the orientation of the battle
+ self.headsUp(self.initialSuitPos)
+
+ assert(self.notify.debug("setInitialSuitPos: suit pos=%s, battle heading=%s" % (self.initialSuitPos, self.getH())))
+
+ def setZoneId(self, zoneId):
+ self.zoneId = zoneId
+
+ def setBossBattle(self, value):
+ self.bossBattle = value
+
+ def setState(self, state, timestamp):
+ """ setState(state, timestamp)
+ """
+ if self.__battleCleanedUp:
+ return
+
+ self.notify.debug('setState(%s)' % state)
+ self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+
+ def setMembers(self, suits, suitsJoining, suitsPending, suitsActive,
+ suitsLured, suitTraps,
+ toons, toonsJoining, toonsPending, toonsActive,
+ toonsRunning, timestamp):
+ if self.__battleCleanedUp:
+ return
+
+ self.notify.debug('setMembers() - suits: %s suitsJoining: %s suitsPending: %s suitsActive: %s suitsLured: %s suitTraps: %s toons: %s toonsJoining: %s toonsPending: %s toonsActive: %s toonsRunning: %s' % (suits, suitsJoining, suitsPending, suitsActive, suitsLured, suitTraps, toons, toonsJoining, toonsPending, toonsActive, toonsRunning))
+
+ ts = globalClockDelta.localElapsedTime(timestamp)
+
+ ##### Update Suits #####
+
+ oldsuits = self.suits
+ self.suits = []
+ suitGone = 0
+ for s in suits:
+ if (self.cr.doId2do.has_key(s)):
+ suit = self.cr.doId2do[s]
+ suit.setState('Battle')
+ self.suits.append(suit)
+ suit.interactivePropTrackBonus = self.interactivePropTrackBonus
+ try:
+ suit.battleTrap
+ except:
+ suit.battleTrap = NO_TRAP
+ suit.battleTrapProp = None
+ self.notify.debug('496 suit.battleTrapProp = None')
+ suit.battleTrapIsFresh = 0
+ else:
+ self.notify.warning('setMembers() - no suit in repository: %d' \
+ % s)
+ # We need a placeholder for the missing suit, so
+ # the index numbers in the code below will line up.
+ self.suits.append(None)
+ suitGone = 1
+
+ # Clear out any suits that have died
+ numSuitsThatDied = 0
+ for s in oldsuits:
+ if (self.suits.count(s) == 0):
+ self.__removeSuit(s)
+ numSuitsThatDied += 1
+ self.notify.debug('suit %d dies, numSuitsThatDied=%d' % (s.doId, numSuitsThatDied))
+
+ if numSuitsThatDied == 4:
+ trainTrap = self.find('**/traintrack')
+ if not trainTrap.isEmpty():
+ self.notify.debug('removing old train trap when 4 suits died')
+ trainTrap.removeNode()
+
+ # See if any new suits have joined
+ for s in suitsJoining:
+ suit = self.suits[int(s)]
+ if (suit != None and self.joiningSuits.count(suit) == 0):
+ self.makeSuitJoin(suit, ts)
+
+ # See if any suits need to move from joining to pending
+ for s in suitsPending:
+ suit = self.suits[int(s)]
+ if (suit != None and self.pendingSuits.count(suit) == 0):
+ self.__makeSuitPending(suit)
+
+ # See if any suits need to move from pending to active
+ activeSuits = []
+ for s in suitsActive:
+ suit = self.suits[int(s)]
+ if (suit != None and self.activeSuits.count(suit) == 0):
+ activeSuits.append(suit)
+
+ # See if any suits are lured forward
+ oldLuredSuits = self.luredSuits
+ self.luredSuits = []
+ for s in suitsLured:
+ suit = self.suits[int(s)]
+ if suit != None:
+ self.luredSuits.append(suit)
+ if (oldLuredSuits.count(suit) == 0):
+ self.needAdjustTownBattle = 1
+ if (self.needAdjustTownBattle == 0):
+ for s in oldLuredSuits:
+ if (self.luredSuits.count(s) == 0):
+ self.needAdjustTownBattle = 1
+
+ # See if any suits have traps in front of them
+ index = 0
+ oldSuitTraps = self.suitTraps
+ self.suitTraps = suitTraps
+ assert(len(suitTraps) == len(self.suits))
+ for s in suitTraps:
+ trapid = int(s)
+ if (trapid == 9):
+ trapid = -1
+ suit = self.suits[index]
+ index += 1
+ if suit != None:
+ if ((trapid == NO_TRAP or
+ trapid != suit.battleTrap) and
+ suit.battleTrapProp != None):
+ self.notify.debug('569 calling self.removeTrap, suit=%d' % suit.doId)
+ self.removeTrap(suit)
+ if (trapid != NO_TRAP and
+ suit.battleTrapProp == None):
+ # If movie is playing, don't load the trap because it
+ # might have been triggered by a lure that same round
+ if (self.fsm.getCurrentState().getName() != 'PlayMovie'):
+ self.loadTrap(suit, trapid)
+
+ # If an old trap is gone or a new trap has appeared, adjust
+ if (len(oldSuitTraps) != len(self.suitTraps)):
+ self.needAdjustTownBattle = 1
+ else:
+ for i in range(len(oldSuitTraps)):
+ if (((oldSuitTraps[i] == '9') and (self.suitTraps[i] != '9')) or
+ ((oldSuitTraps[i] != '9') and (self.suitTraps[i] == '9'))):
+ self.needAdjustTownBattle = 1
+ break
+
+ if suitGone:
+ # Now clean up the holes in the suit list.
+ validSuits = []
+ for s in self.suits:
+ if s != None:
+ validSuits.append(s)
+ self.suits = validSuits
+ self.needAdjustTownBattle = 1
+
+ ##### Update Toons #####
+
+ oldtoons = self.toons
+ self.toons = []
+ toonGone = 0
+ for t in toons:
+ toon = self.getToon(t)
+ if (toon == None):
+ self.notify.warning('setMembers() - toon not in cr!')
+ self.toons.append(None)
+ toonGone = 1
+ continue
+ self.toons.append(toon)
+ if (oldtoons.count(toon) == 0):
+ self.notify.debug('setMembers() - add toon: %d' % toon.doId)
+ self.__listenForUnexpectedExit(toon)
+ toon.stopLookAround()
+ toon.stopSmooth()
+
+ # Clear out any toons that have run away or died
+ for t in oldtoons:
+ if (self.toons.count(t) == 0):
+ if (self.__removeToon(t) == 1):
+ # Return value == 1 indicates local toon teleported away
+ self.notify.debug('setMembers() - local toon left battle')
+ return []
+
+ # See if any new toons have joined
+ for t in toonsJoining:
+ if int(t) < len(self.toons):
+ toon = self.toons[int(t)]
+ if (toon != None and self.joiningToons.count(toon) == 0):
+ self.__makeToonJoin(toon, toonsPending, ts)
+ else:
+ self.notify.warning('setMembers toonsJoining t=%s not in self.toons %s' % (t, self.toons))
+
+ # See if any toons need to move from joining to pending
+ for t in toonsPending:
+ if int(t) < len(self.toons):
+ toon = self.toons[int(t)]
+ if (toon != None and self.pendingToons.count(toon) == 0):
+ self.__makeToonPending(toon, ts)
+ else:
+ self.notify.warning('setMembers toonsPending t=%s not in self.toons %s' % (t, self.toons))
+
+ # See if any toons are running
+ for t in toonsRunning:
+ toon = self.toons[int(t)]
+ if (toon != None and self.runningToons.count(toon) == 0):
+ self.__makeToonRun(toon, ts)
+
+ # See if any toons need to move from pending to active
+ activeToons = []
+ for t in toonsActive:
+ toon = self.toons[int(t)]
+ if (toon != None and self.activeToons.count(toon) == 0):
+ activeToons.append(toon)
+ if (len(activeSuits) > 0 or len(activeToons) > 0):
+ self.__makeAvsActive(activeSuits, activeToons)
+
+ # Remove any empty toons from the toon list
+ if (toonGone == 1):
+ validToons = []
+ for toon in self.toons:
+ if (toon != None):
+ validToons.append(toon)
+ self.toons = validToons
+
+ assert(len(suitsJoining) == len(self.joiningSuits))
+ assert(len(suitsPending) == len(self.pendingSuits))
+ assert(len(suitsActive) == len(self.activeSuits))
+ #assert(len(toonsJoining) == len(self.joiningToons))
+ #assert(len(toonsPending) == len(self.pendingToons))
+ #assert(len(toonsActive) == len(self.activeToons))
+ #assert(len(toonsRunning) == len(self.runningToons))
+
+ # Update the town battle
+ if (len(self.activeToons) > 0):
+ self.__requestAdjustTownBattle()
+
+ # Set the correct local toon state
+ currStateName = self.localToonFsm.getCurrentState().getName()
+ if (self.toons.count(base.localAvatar)):
+ if (oldtoons.count(base.localAvatar) == 0):
+ self.notify.debug('setMembers() - local toon just joined')
+ assert(currStateName != 'HasLocalToon')
+ if (self.streetBattle == 1):
+ # Make sure the toon is in the same zone as the battle
+ base.cr.playGame.getPlace().enterZone(self.zoneId)
+ self.localToonJustJoined = 1
+ # Make sure we're in 'HasLocalToon'
+ if (currStateName != 'HasLocalToon'):
+ self.localToonFsm.request('HasLocalToon')
+ else:
+ if (oldtoons.count(base.localAvatar)):
+ # Make sure the collision sphere goes away so the toon can run
+ self.notify.debug('setMembers() - local toon just ran')
+ assert(currStateName != 'NoLocalToon')
+ # if this is a level battle, be sure to unlock the visibility
+ if self.levelBattle:
+ self.unlockLevelViz()
+ # Make sure we're in 'NoLocalToon'
+ if (currStateName != 'NoLocalToon'):
+ self.localToonFsm.request('NoLocalToon')
+
+ return oldtoons
+
+ def adjust(self, timestamp):
+ """ adjust(timestamp)
+ """
+ if self.__battleCleanedUp:
+ return
+
+ self.notify.debug('adjust(%f) from server' % \
+ globalClockDelta.localElapsedTime(timestamp))
+ self.adjustFsm.request('Adjusting',
+ [globalClockDelta.localElapsedTime(timestamp)])
+
+ def setMovie(self, active, toons, suits,
+ id0, tr0, le0, tg0, hp0, ac0, hpb0, kbb0, died0, revive0,
+ id1, tr1, le1, tg1, hp1, ac1, hpb1, kbb1, died1, revive1,
+ id2, tr2, le2, tg2, hp2, ac2, hpb2, kbb2, died2, revive2,
+ id3, tr3, le3, tg3, hp3, ac3, hpb3, kbb3, died3, revive3,
+ sid0, at0, stg0, dm0, sd0, sb0, st0,
+ sid1, at1, stg1, dm1, sd1, sb1, st1,
+ sid2, at2, stg2, dm2, sd2, sb2, st2,
+ sid3, at3, stg3, dm3, sd3, sb3, st3):
+ """ setMovie()
+ """
+ if self.__battleCleanedUp:
+ return
+
+ self.notify.debug('setMovie()')
+ if (int(active) == 1):
+ self.notify.debug('setMovie() - movie is active')
+ self.movie.genAttackDicts(toons, suits,
+ id0, tr0, le0, tg0, hp0, ac0, hpb0, kbb0, died0, revive0,
+ id1, tr1, le1, tg1, hp1, ac1, hpb1, kbb1, died1, revive1,
+ id2, tr2, le2, tg2, hp2, ac2, hpb2, kbb2, died2, revive2,
+ id3, tr3, le3, tg3, hp3, ac3, hpb3, kbb3, died3, revive3,
+ sid0, at0, stg0, dm0, sd0, sb0, st0,
+ sid1, at1, stg1, dm1, sd1, sb1, st1,
+ sid2, at2, stg2, dm2, sd2, sb2, st2,
+ sid3, at3, stg3, dm3, sd3, sb3, st3)
+
+ def setChosenToonAttacks(self, ids, tracks, levels, targets):
+ """ setChosenToonAttacks(ids, tracks, levels, targets)
+ """
+ if self.__battleCleanedUp:
+ return
+
+ self.notify.debug('setChosenToonAttacks() - (%s), (%s), (%s), (%s)' % \
+ (ids, tracks, levels, targets))
+ toonIndices = []
+ targetIndices = []
+ unAttack = 0
+ localToonInList = 0
+ for i in range(len(ids)):
+ track = tracks[i]
+ level = levels[i]
+ toon = self.findToon(ids[i])
+ if (toon == None or self.activeToons.count(toon) == 0):
+ self.notify.warning('setChosenToonAttacks() - toon gone or not in battle: %d!' % ids[i])
+ toonIndices.append(-1)
+ tracks.append(-1)
+ levels.append(-1)
+ targetIndices.append(-1)
+ continue
+
+ if (toon == base.localAvatar):
+ localToonInList = 1
+ toonIndices.append(self.activeToons.index(toon))
+ if (track == SOS):
+ # For SOS the target is a toonId of a friend
+ targetIndex = -1
+ elif (track == NPCSOS):
+ # For NPCSOS the target is a toonId of a friend
+ targetIndex = -1
+ elif (track == PETSOS):
+ # For PETSOS the target is the doId of a pet
+ targetIndex = -1
+ elif (track == PASS):
+ targetIndex = -1
+ tracks[i] = PASS_ATTACK
+ elif (attackAffectsGroup(track, level)):
+ # We don't specify targets for group attacks
+ targetIndex = -1
+ elif (track == HEAL):
+ target = self.findToon(targets[i])
+ if (target != None and self.activeToons.count(target) != 0):
+ targetIndex = self.activeToons.index(target)
+ else:
+ targetIndex = -1
+ elif (track == UN_ATTACK):
+ targetIndex = -1
+ tracks[i] = NO_ATTACK
+ if (toon == base.localAvatar):
+ unAttack = 1
+ self.choseAttackAlready = 0
+ elif (track == NO_ATTACK):
+ targetIndex = -1
+ else:
+ target = self.findSuit(targets[i])
+ if (target != None and self.activeSuits.count(target) != 0):
+ targetIndex = self.activeSuits.index(target)
+ else:
+ targetIndex = -1
+ targetIndices.append(targetIndex)
+ for i in range(4 - len(ids)):
+ toonIndices.append(-1)
+ tracks.append(-1)
+ levels.append(-1)
+ targetIndices.append(-1)
+ self.townBattleAttacks = (toonIndices, tracks, levels, targetIndices)
+
+ # Update the gui if local toon is in the battle
+ if (self.localToonActive() and localToonInList == 1):
+ assert(self.notify.debug('update gui for localtoon'))
+ if (unAttack == 1 and
+ self.fsm.getCurrentState().getName() == 'WaitForInput'):
+ # Send back to main attack panel if attack was zeroed out
+ if (self.townBattle.fsm.getCurrentState().getName() !=
+ 'Attack'):
+ self.townBattle.setState('Attack')
+ self.townBattle.updateChosenAttacks(self.townBattleAttacks[0],
+ self.townBattleAttacks[1],
+ self.townBattleAttacks[2],
+ self.townBattleAttacks[3])
+
+ def setBattleExperience(self,
+ id0, origExp0, earnedExp0, origQuests0, items0, missedItems0, origMerits0, merits0, parts0,
+ id1, origExp1, earnedExp1, origQuests1, items1, missedItems1, origMerits1, merits1, parts1,
+ id2, origExp2, earnedExp2, origQuests2, items2, missedItems2, origMerits2, merits2, parts2,
+ id3, origExp3, earnedExp3, origQuests3, items3, missedItems3, origMerits3, merits3, parts3,
+ deathList, uberList, helpfulToonsList):
+ #import pdb; pdb.set_trace()
+ if self.__battleCleanedUp:
+ return
+
+ self.movie.genRewardDicts(id0, origExp0, earnedExp0, origQuests0, items0, missedItems0, origMerits0, merits0, parts0,
+ id1, origExp1, earnedExp1, origQuests1, items1, missedItems1, origMerits1, merits1, parts1,
+ id2, origExp2, earnedExp2, origQuests2, items2, missedItems2, origMerits2, merits2, parts2,
+ id3, origExp3, earnedExp3, origQuests3, items3, missedItems3, origMerits3, merits3, parts3,
+ deathList, uberList, helpfulToonsList)
+
+ ##### Functions used by setMembers() #####
+
+ def __listenForUnexpectedExit(self, toon):
+ self.accept(toon.uniqueName('disable'),
+ self.__handleUnexpectedExit, extraArgs=[toon])
+
+ self.accept(toon.uniqueName('died'),
+ self.__handleDied, extraArgs=[toon])
+
+ def __handleUnexpectedExit(self, toon):
+ self.notify.warning('handleUnexpectedExit() - toon: %d' % toon.doId)
+ # Make sure anything lerping this toon is stopped appropriately
+ self.__removeToon(toon, unexpected=1)
+
+ def __handleDied(self, toon):
+ self.notify.warning('handleDied() - toon: %d' % toon.doId)
+ if toon == base.localAvatar:
+ self.d_toonDied(toon.doId)
+ self.cleanupBattle()
+
+ def delayDeleteMembers(self):
+
+ # Prevent the accidental deletion of any toons or suits by
+ # storing a list of DelayDelete objects, one for each member.
+ # The members may be allowed to be deleted later by clearing
+ # the self.membersKeep member.
+
+ membersKeep = []
+ for t in self.toons:
+ membersKeep.append(DelayDelete.DelayDelete(t, 'delayDeleteMembers'))
+ for s in self.suits:
+ membersKeep.append(DelayDelete.DelayDelete(s, 'delayDeleteMembers'))
+ self._removeMembersKeep()
+ self.membersKeep = membersKeep
+
+ def _removeMembersKeep(self):
+ if self.membersKeep:
+ for delayDelete in self.membersKeep:
+ delayDelete.destroy()
+ self.membersKeep = None
+
+ def __removeSuit(self, suit):
+ self.notify.debug('__removeSuit(%d)' % suit.doId)
+ if (self.suits.count(suit) != 0):
+ self.suits.remove(suit)
+ if (self.joiningSuits.count(suit) != 0):
+ self.joiningSuits.remove(suit)
+ if (self.pendingSuits.count(suit) != 0):
+ self.pendingSuits.remove(suit)
+ if (self.activeSuits.count(suit) != 0):
+ self.activeSuits.remove(suit)
+ self.suitGone = 1
+ if (suit.battleTrap != NO_TRAP):
+ self.notify.debug('882 calling self.removeTrap, suit=%d' % suit.doId)
+ self.removeTrap(suit)
+ suit.battleTrap = NO_TRAP
+ suit.battleTrapProp = None
+ self.notify.debug('883 suit.battleTrapProp = None')
+ suit.battleTrapIsFresh = 0
+
+ def __removeToon(self, toon, unexpected=0):
+ self.notify.debug('__removeToon(%d)' % toon.doId)
+ self.exitedToons.append(toon)
+ if (self.toons.count(toon) != 0):
+ self.toons.remove(toon)
+ if (self.joiningToons.count(toon) != 0):
+ self.clearInterval(
+ self.taskName('to-pending-toon-%d' % toon.doId))
+ # double-check to be safe
+ if toon in self.joiningToons:
+ self.joiningToons.remove(toon)
+ if (self.pendingToons.count(toon) != 0):
+ self.pendingToons.remove(toon)
+ if (self.activeToons.count(toon) != 0):
+ self.activeToons.remove(toon)
+ if (self.runningToons.count(toon) != 0):
+ self.clearInterval(self.taskName('running-%d' % toon.doId),
+ finish=1)
+ # double-check to be safe
+ if toon in self.runningToons:
+ self.runningToons.remove(toon)
+ # Turn off handleUnexpectedExit()
+ self.ignore(toon.uniqueName('disable'))
+ self.ignore(toon.uniqueName('died'))
+ self.toonGone = 1
+ if (toon == base.localAvatar):
+ self.removeLocalToon()
+ self.__teleportToSafeZone(toon)
+ return 1
+ return 0
+
+ def removeLocalToon(self):
+ assert(self.notify.debug('removeLocalToon()'))
+ if base.cr.playGame.getPlace() != None:
+ base.cr.playGame.getPlace().setState('walk')
+ base.localAvatar.earnedExperience = None
+ self.localToonFsm.request('NoLocalToon')
+
+ def removeInactiveLocalToon(self, toon):
+ self.notify.debug('removeInactiveLocalToon(%d)' % toon.doId)
+ self.exitedToons.append(toon)
+ if (self.toons.count(toon) != 0):
+ self.toons.remove(toon)
+ if (self.joiningToons.count(toon) != 0):
+ self.clearInterval(self.taskName('to-pending-toon-%d' % \
+ toon.doId), finish=1)
+ # double-check to be safe
+ if toon in self.joiningToons:
+ self.joiningToons.remove(toon)
+ if (self.pendingToons.count(toon) != 0):
+ self.pendingToons.remove(toon)
+ self.ignore(toon.uniqueName('disable'))
+ self.ignore(toon.uniqueName('died'))
+ base.cr.playGame.getPlace().setState('walk')
+ self.localToonFsm.request('WaitForServer')
+
+ def __createJoinInterval(self, av, destPos, destHpr, name, ts, callback,
+ toon=0):
+ joinTrack = Sequence()
+ joinTrack.append(Func(Emote.globalEmote.disableAll, av,"dbattlebase, createJoinInterval"))
+ avPos = av.getPos(self)
+
+ # Pop the avatar to the same height as the battle.
+ avPos = Point3(avPos[0], avPos[1], 0.0)
+ av.setShadowHeight(0)
+
+ plist = self.buildJoinPointList(avPos, destPos, toon)
+ if (len(plist) == 0):
+ # destPos is the closest point - just go straight there
+ joinTrack.append(Func(av.headsUp, self, destPos))
+ if (toon == 0):
+ timeToDest = self.calcSuitMoveTime(avPos, destPos)
+ joinTrack.append(Func(av.loop, 'walk'))
+ else:
+ timeToDest = self.calcToonMoveTime(avPos, destPos)
+ joinTrack.append(Func(av.loop, 'run'))
+
+ if timeToDest > BATTLE_SMALL_VALUE:
+ joinTrack.append(LerpPosInterval(av, timeToDest, destPos,
+ other=self))
+ totalTime = timeToDest
+ else:
+ totalTime = 0
+
+ else:
+
+ # Calculate the time required
+ timeToPerimeter = 0
+ if (toon == 0):
+ timeToPerimeter = self.calcSuitMoveTime(plist[0], avPos)
+ timePerSegment = 10.0 / BattleBase.suitSpeed
+ timeToDest = self.calcSuitMoveTime(BattleBase.posA, destPos)
+ else:
+ timeToPerimeter = self.calcToonMoveTime(plist[0], avPos)
+ timePerSegment = 10.0 / BattleBase.toonSpeed
+ timeToDest = self.calcToonMoveTime(BattleBase.posE, destPos)
+ totalTime = timeToPerimeter + ((len(plist) - 1) * timePerSegment) \
+ + timeToDest
+ assert(timeToPerimeter > BATTLE_SMALL_VALUE)
+ assert(timePerSegment > BATTLE_SMALL_VALUE)
+ assert(timeToDest > BATTLE_SMALL_VALUE)
+ assert(totalTime > BATTLE_SMALL_VALUE)
+ if (totalTime > MAX_JOIN_T):
+ self.notify.warning('__createJoinInterval() - time: %f' % \
+ totalTime)
+
+ # Create a track to move to destPos
+ joinTrack.append(Func(av.headsUp, self, plist[0]))
+ if (toon == 0):
+ joinTrack.append(Func(av.loop, 'walk'))
+ else:
+ joinTrack.append(Func(av.loop, 'run'))
+ joinTrack.append(LerpPosInterval(av, timeToPerimeter, plist[0],
+ other=self))
+ for p in plist[1:]:
+ joinTrack.append(Func(av.headsUp, self, p))
+ joinTrack.append(LerpPosInterval(av, timePerSegment, p,
+ other=self))
+ joinTrack.append(Func(av.headsUp, self, destPos))
+ joinTrack.append(LerpPosInterval(av, timeToDest, destPos,
+ other=self))
+
+ joinTrack.append(Func(av.loop, 'neutral'))
+ joinTrack.append(Func(av.headsUp, self, Point3(0, 0, 0)))
+ tval = totalTime - ts
+ if (tval < 0):
+ tval = totalTime
+ joinTrack.append(Func(Emote.globalEmote.releaseAll, av,"dbattlebase, createJoinInterval"))
+ joinTrack.append(Func(callback, av, tval))
+
+ # Position the camera if local toon is the one joining the battle
+ if (av == base.localAvatar):
+ camTrack = Sequence()
+ def setCamFov(fov):
+ base.camLens.setFov(fov)
+ camTrack.append(Func(setCamFov, self.camFov))
+ camTrack.append(Func(camera.wrtReparentTo, self))
+ camTrack.append(Func(camera.setPos, self.camJoinPos))
+ camTrack.append(Func(camera.setHpr, self.camJoinHpr))
+ return Parallel(joinTrack, camTrack, name=name)
+ else:
+ return Sequence(joinTrack, name=name)
+
+ def makeSuitJoin(self, suit, ts):
+ """ makeSuitJoin(suit, ts)
+ """
+ self.notify.debug('makeSuitJoin(%d)' % suit.doId)
+ # This method is overridden in DistributedBattleFinal.py.
+
+ # A "joining" suit has decided to join the battle, and is in
+ # the process of walking to his waiting place outside the
+ # battle. When he gets there, he gets moved to the pending
+ # list by the AI.
+
+ # Pick an open pending spot
+ # Building battles can have 3 pendingSuits
+ assert(len(self.pendingSuits) <= 3)
+ assert(self.joiningSuits.count(suit) == 0)
+ spotIndex = len(self.pendingSuits) + len(self.joiningSuits)
+ assert(spotIndex <= 4)
+ self.joiningSuits.append(suit)
+ suit.setState('Battle')
+ openSpot = self.suitPendingPoints[spotIndex]
+ pos = openSpot[0]
+ hpr = VBase3(openSpot[1], 0.0, 0.0)
+ trackName = self.taskName('to-pending-suit-%d' % suit.doId)
+ track = self.__createJoinInterval(suit, pos, hpr, trackName,
+ ts, self.__handleSuitJoinDone)
+ track.start(ts)
+
+ # We can't use the membersKeep object here, because we could
+ # get this message in any state.
+ track.delayDelete = DelayDelete.DelayDelete(suit, 'makeSuitJoin')
+
+ self.storeInterval(track, trackName)
+
+ if ToontownBattleGlobals.SkipMovie:
+ track.finish()
+
+ def __handleSuitJoinDone(self, suit, ts):
+ self.notify.debug('suit: %d is now pending' % suit.doId)
+ assert(self.joiningSuits.count(suit) != 0)
+ if (self.hasLocalToon()):
+ self.d_joinDone(base.localAvatar.doId, suit.doId)
+
+ def __makeSuitPending(self, suit):
+ self.notify.debug('__makeSuitPending(%d)' % suit.doId)
+ self.clearInterval(self.taskName('to-pending-suit-%d' % suit.doId),
+ finish=1)
+
+ # A "pending" suit has walked to its correct place outside the
+ # battle, and is standing there patiently waiting for the
+ # round to finish.
+
+ # We might have recently joined, so joiningSuits will be empty
+ if (self.joiningSuits.count(suit)):
+ self.joiningSuits.remove(suit)
+ assert(self.pendingSuits.count(suit) == 0)
+ self.pendingSuits.append(suit)
+
+ def __teleportToSafeZone(self, toon):
+ self.notify.debug('teleportToSafeZone(%d)' % toon.doId)
+ # If the toon has been to the nearest safezone before, go there
+ # Otherwise teleport to the last safezone the toon visited
+ hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
+
+ if hoodId in base.localAvatar.hoodsVisited:
+ target_sz = ZoneUtil.getSafeZoneId(self.zoneId)
+ else:
+ target_sz = ZoneUtil.getSafeZoneId(base.localAvatar.defaultZone)
+
+ base.cr.playGame.getPlace().fsm.request('teleportOut', [{
+ "loader": ZoneUtil.getLoaderName(target_sz),
+ "where": ZoneUtil.getWhereName(target_sz, 1),
+ 'how': 'teleportIn',
+ 'hoodId': target_sz,
+ 'zoneId': target_sz,
+ 'shardId': None,
+ 'avId': -1,
+ 'battle': 1,
+ }])
+
+ def __makeToonJoin(self, toon, pendingToons, ts):
+ """ __makeToonJoin(toon, ts)
+ """
+ self.notify.debug('__makeToonJoin(%d)' % toon.doId)
+ # Move toon into the wait position
+ # Add toon to pending list
+ # Pick an open pending spot
+ assert(len(pendingToons) < 3)
+ assert(self.joiningToons.count(toon) == 0)
+ spotIndex = len(pendingToons) + len(self.joiningToons)
+ self.joiningToons.append(toon)
+ openSpot = self.toonPendingPoints[spotIndex]
+ pos = openSpot[0]
+ hpr = VBase3(openSpot[1], 0.0, 0.0)
+ trackName = self.taskName('to-pending-toon-%d' % toon.doId)
+ track = self.__createJoinInterval(toon, pos, hpr, trackName,
+ ts, self.__handleToonJoinDone, toon=1)
+ if (toon != base.localAvatar):
+ # Ensure the anim state for the toon is off so it doesn't
+ # get set to off mid-interval
+ toon.animFSM.request('off')
+ track.start(ts)
+
+ # We can't use the membersKeep object here, because we could
+ # get this message in any state.
+ track.delayDelete = DelayDelete.DelayDelete(toon, '__makeToonJoin')
+
+ self.storeInterval(track, trackName)
+
+ def __handleToonJoinDone(self, toon, ts):
+ self.notify.debug('__handleToonJoinDone() - pending: %d' % toon.doId)
+ assert(self.joiningToons.count(toon) != 0)
+ if (self.hasLocalToon()):
+ self.d_joinDone(base.localAvatar.doId, toon.doId)
+
+ def __makeToonPending(self, toon, ts):
+ self.notify.debug('__makeToonPending(%d)' % toon.doId)
+ self.clearInterval(self.taskName('to-pending-toon-%d' % toon.doId),
+ finish=1)
+
+ # We might have recently joined, so joiningToons would be empty
+ if (self.joiningToons.count(toon)):
+ self.joiningToons.remove(toon)
+ assert(self.pendingToons.count(toon) == 0)
+ spotIndex = len(self.pendingToons)
+ self.pendingToons.append(toon)
+
+ openSpot = self.toonPendingPoints[spotIndex]
+ pos = openSpot[0]
+ hpr = VBase3(openSpot[1], 0.0, 0.0)
+ toon.loop('neutral')
+ toon.setPosHpr(self, pos, hpr)
+
+ # Start playing the cinematic version of the movie
+ if (base.localAvatar == toon):
+ currStateName = self.fsm.getCurrentState().getName()
+
+ def __makeAvsActive(self, suits, toons):
+ self.notify.debug('__makeAvsActive()')
+ # If we're currently adjusting, stop adjusting and pop them to the
+ # correct positions
+ # In case we're currently adjusting (the server is ahead of us)
+ self.__stopAdjusting()
+
+ for s in suits:
+ if (self.joiningSuits.count(s)):
+ self.notify.warning('suit: %d was in joining list!' % s.doId)
+ self.joiningSuits.remove(s)
+ if (self.pendingSuits.count(s)):
+ self.pendingSuits.remove(s)
+ assert(self.activeSuits.count(s) == 0)
+ self.notify.debug('__makeAvsActive() - suit: %d' % s.doId)
+ self.activeSuits.append(s)
+ if (len(self.activeSuits) >= 1):
+ for suit in self.activeSuits:
+ suitPos, suitHpr = self.getActorPosHpr(suit)
+ if (self.isSuitLured(suit) == 0):
+ suit.setPosHpr(self, suitPos, suitHpr)
+ else:
+ spos = Point3(suitPos[0],
+ suitPos[1] - MovieUtil.SUIT_LURE_DISTANCE,
+ suitPos[2])
+ suit.setPosHpr(self, spos, suitHpr)
+ suit.loop('neutral')
+
+ for toon in toons:
+ if (self.joiningToons.count(toon)):
+ self.notify.warning('toon: %d was in joining list!' % toon.doId)
+ self.joiningToons.remove(toon)
+ if (self.pendingToons.count(toon)):
+ self.pendingToons.remove(toon)
+ self.notify.debug('__makeAvsActive() - toon: %d' % toon.doId)
+ if (self.activeToons.count(toon) == 0):
+ self.activeToons.append(toon)
+ else:
+ self.notify.warning('makeAvsActive() - toon: %d is active!' % \
+ toon.doId)
+ if (len(self.activeToons) >= 1):
+ for toon in self.activeToons:
+ toonPos, toonHpr = self.getActorPosHpr(toon)
+ toon.setPosHpr(self, toonPos, toonHpr)
+ toon.loop('neutral')
+
+ # Local toon might just have become active, so we need to pop up GUIs
+ if (self.fsm.getCurrentState().getName() == 'WaitForInput' and
+ self.localToonActive() and self.localToonJustJoined == 1):
+ self.notify.debug('makeAvsActive() - local toon just joined')
+ self.__enterLocalToonWaitForInput()
+ self.localToonJustJoined = 0
+ self.startTimer()
+
+ def __makeToonRun(self, toon, ts):
+ """ __makeToonRun(toon, ts)
+ """
+ self.notify.debug('__makeToonRun(%d)' % toon.doId)
+ if (self.activeToons.count(toon)):
+ self.activeToons.remove(toon)
+ assert(self.runningToons.count(toon) == 0)
+ self.runningToons.append(toon)
+ # We need to trigger adjust
+ self.toonGone = 1
+ self.__stopTimer()
+ if (self.localToonRunning()):
+ self.townBattle.setState('Off')
+ runMTrack = MovieUtil.getToonTeleportOutInterval(toon)
+ runName = self.taskName('running-%d' % toon.doId)
+ self.notify.debug('duration: %f' % runMTrack.getDuration())
+ runMTrack.start(ts)
+
+ # We can't use the membersKeep object here, because we could
+ # get this message in any state.
+ runMTrack.delayDelete = DelayDelete.DelayDelete(toon, '__makeToonRun')
+
+ self.storeInterval(runMTrack, runName)
+
+ def getToon(self, toonId):
+ if (self.cr.doId2do.has_key(toonId)):
+ return self.cr.doId2do[toonId]
+ else:
+ self.notify.warning('getToon() - toon: %d not in repository!' \
+ % toonId)
+ return None
+
+ ##### Messages To The Server #####
+
+ def d_toonRequestJoin(self, toonId, pos):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:toonRequestJoin()')
+ self.sendUpdate('toonRequestJoin', [pos[0], pos[1], pos[2]])
+
+ def d_toonRequestRun(self, toonId):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:toonRequestRun()')
+ self.sendUpdate('toonRequestRun', [])
+
+ def d_toonDied(self, toonId):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:toonDied()')
+ self.sendUpdate('toonDied', [])
+
+ def d_faceOffDone(self, toonId):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:faceOffDone()')
+ self.sendUpdate('faceOffDone', [])
+
+ def d_adjustDone(self, toonId):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:adjustDone()')
+ self.sendUpdate('adjustDone', [])
+
+ def d_timeout(self, toonId):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:timeout()')
+ self.sendUpdate('timeout', [])
+
+ def d_movieDone(self, toonId):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:movieDone()')
+ self.sendUpdate('movieDone', [])
+
+ def d_rewardDone(self, toonId):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:rewardDone()')
+ self.sendUpdate('rewardDone', [])
+
+ def d_joinDone(self, toonId, avId):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:joinDone(%d)' % (avId))
+ self.sendUpdate('joinDone', [avId])
+
+ def d_requestAttack(self, toonId, track, level, av):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:requestAttack(%d, %d, %d)' % \
+ (track, level, av))
+ self.sendUpdate('requestAttack', [track, level, av])
+
+ def d_requestPetProxy(self, toonId, av):
+ assert(toonId == base.localAvatar.doId)
+ self.notify.debug('network:requestPetProxy(%s)' % av)
+ self.sendUpdate('requestPetProxy', [av])
+
+ # Each state will have an enter function, an exit function,
+ # and a datagram handler, which will be set during each enter function.
+
+ # Specific State functions
+
+ ##### Off state #####
+
+ def enterOff(self, ts=0):
+ self.localToonFsm.requestFinalState()
+ return None
+
+ def exitOff(self):
+ return None
+
+ ##### FaceOff state #####
+
+ def enterFaceOff(self, ts=0):
+
+ return None
+
+ def exitFaceOff(self):
+ return None
+
+ ##### WaitForJoin state #####
+
+ def enterWaitForJoin(self, ts=0):
+ self.notify.debug('enterWaitForJoin()')
+ return None
+
+ def exitWaitForJoin(self):
+ return None
+
+ ##### WaitForInput state #####
+
+ def __enterLocalToonWaitForInput(self):
+ self.notify.debug('enterLocalToonWaitForInput()')
+ # Move the camera into position
+ # (assumes the camera is a child of the battle)
+ camera.setPosHpr(self.camPos, self.camHpr)
+ base.camLens.setFov(self.camMenuFov)
+ # No arrows - they just get in the way
+ NametagGlobals.setMasterArrowsOn(0)
+ # Put local toon into 'Attack' state
+ self.townBattle.setState('Attack')
+ # Begin listening for 'Attack' responses
+ self.accept(self.localToonBattleEvent,
+ self.__handleLocalToonBattleEvent)
+
+ def startTimer(self, ts=0):
+ self.notify.debug('startTimer()')
+ if (ts >= CLIENT_INPUT_TIMEOUT):
+ self.notify.warning('startTimer() - ts: %f timeout: %f' % \
+ (ts, CLIENT_INPUT_TIMEOUT))
+ self.__timedOut()
+ return
+ # Start the timer
+ self.timer.startCallback(CLIENT_INPUT_TIMEOUT - ts,
+ self.__timedOut)
+ # Start a task that sends out the timer values five times a second
+ # I know it is a one second timer, but by supersampling, it appears
+ # to be smooth.
+ timeTask = Task.loop(Task(self.__countdown), Task.pause(0.2))
+ taskMgr.add(timeTask, self.timerCountdownTaskName)
+
+ def __stopTimer(self):
+ self.notify.debug('__stopTimer()')
+ self.timer.stop()
+ taskMgr.remove(self.timerCountdownTaskName)
+
+ def __countdown(self, task):
+ if hasattr(self.townBattle, 'timer'):
+ self.townBattle.updateTimer(int(self.timer.getT()))
+ else:
+ self.notify.warning('__countdown has tried to update a timer that has been deleted. Stopping timer')
+ self.__stopTimer()
+
+ return Task.done
+
+ def enterWaitForInput(self, ts=0):
+ self.notify.debug('enterWaitForInput()')
+ if self.interactiveProp:
+ self.interactiveProp.gotoBattleCheer()
+ self.choseAttackAlready = 0
+ if (self.localToonActive()):
+ self.__enterLocalToonWaitForInput()
+ self.startTimer(ts)
+ if (self.needAdjustTownBattle == 1):
+ self.__adjustTownBattle()
+ return None
+
+ def exitWaitForInput(self):
+ self.notify.debug('exitWaitForInput()')
+ if (self.localToonActive()):
+ self.townBattle.setState('Off')
+ # Back to the standard battle fov
+ base.camLens.setFov(self.camFov)
+ self.ignore(self.localToonBattleEvent)
+ self.__stopTimer()
+ return None
+
+ def __handleLocalToonBattleEvent(self, response):
+ assert(response.has_key('mode'))
+ mode = response['mode']
+ noAttack = 0
+ if (mode == 'Attack'):
+ self.notify.debug('got an attack')
+ track = response['track']
+ level = response['level']
+ target = response['target']
+ targetId = target
+ if (track == HEAL and not levelAffectsGroup(HEAL, level)):
+ if (target >= 0 and target < len(self.activeToons)):
+ targetId = self.activeToons[target].doId
+ else:
+ self.notify.warning('invalid toon target: %d' % target)
+ track = -1
+ level = -1
+ targetId = -1
+ elif (track == HEAL and len(self.activeToons) == 1):
+ self.notify.warning('invalid group target for heal')
+ track = -1
+ level = -1
+ elif (not attackAffectsGroup(track, level)):
+ # Maybe we haven't chosen a target yet, but we still
+ # want to notify the other members of the battle which
+ # track we're choosing.
+ if target >= 0 and target < len(self.activeSuits):
+ targetId = self.activeSuits[target].doId
+ else:
+ target = -1
+ if (len(self.luredSuits) > 0):
+ if (track == TRAP or
+ (track == LURE and (not levelAffectsGroup(LURE, level)))):
+ if target != -1:
+ suit = self.findSuit(targetId)
+ if (self.luredSuits.count(suit) != 0):
+ self.notify.warning('Suit: %d was lured!' % targetId)
+ track = -1
+ level = -1
+ targetId = -1
+ elif (track == LURE):
+ if (levelAffectsGroup(LURE, level) and
+ (len(self.activeSuits) == len(self.luredSuits))):
+ self.notify.warning('All suits are lured!')
+ track = -1
+ level = -1
+ targetId = -1
+ if (track == TRAP):
+ if target != -1:
+ if attackAffectsGroup(track,level):
+ #so this is the uber trap
+ pass
+ else:
+ suit = self.findSuit(targetId)
+ if (suit.battleTrap != NO_TRAP):
+ self.notify.warning('Suit: %d was already trapped!' % \
+ targetId)
+ track = -1
+ level = -1
+ targetId = -1
+
+ #import pdb; pdb.set_trace()
+ self.d_requestAttack(base.localAvatar.doId, track, level,
+ targetId)
+ elif (mode == 'Run'):
+ self.notify.debug('got a run')
+ self.d_toonRequestRun(base.localAvatar.doId)
+ elif (mode == 'SOS'):
+ targetId = response['id']
+ self.notify.debug('got an SOS for friend: %d' % targetId)
+ self.d_requestAttack(base.localAvatar.doId, SOS, -1, targetId)
+ elif (mode == 'NPCSOS'):
+ targetId = response['id']
+ self.notify.debug('got an NPCSOS for friend: %d' % targetId)
+ self.d_requestAttack(base.localAvatar.doId, NPCSOS, -1, targetId)
+ elif (mode == 'PETSOS'):
+ targetId = response['id']
+ trickId = response['trickId']
+ self.notify.debug('got an PETSOS for pet: %d' % targetId)
+ self.d_requestAttack(base.localAvatar.doId, PETSOS, trickId, targetId)
+ elif (mode == 'PETSOSINFO'):
+ petProxyId = response['id']
+ self.notify.debug('got a PETSOSINFO for pet: %d' % petProxyId)
+ # Check to see if the proxy object has already been generated
+ if base.cr.doId2do.has_key(petProxyId):
+ self.notify.debug("pet: %d was already in the repository" % petProxyId)
+ # Throw the event that pet info is available
+ proxyGenerateMessage = "petProxy-%d-generated" % petProxyId
+ messenger.send(proxyGenerateMessage)
+ else:
+ self.d_requestPetProxy(base.localAvatar.doId, petProxyId)
+ noAttack = 1
+ elif (mode == 'Pass'):
+ targetId = response['id']
+ self.notify.debug('got a Pass')
+ self.d_requestAttack(base.localAvatar.doId, PASS, -1, -1)
+
+ elif (mode == 'UnAttack'):
+ assert(self.notify.debug('got an un-attack'))
+ self.d_requestAttack(base.localAvatar.doId, UN_ATTACK, -1, -1)
+ noAttack = 1
+ elif (mode == 'Fire'):
+ target = response['target']
+ targetId = self.activeSuits[target].doId
+ #targetId = target
+ self.d_requestAttack(base.localAvatar.doId, FIRE, -1,
+ targetId)
+ else:
+ self.notify.warning('unknown battle response')
+ return
+ if (noAttack == 1):
+ self.choseAttackAlready = 0
+ else:
+ self.choseAttackAlready = 1
+
+ def __timedOut(self):
+ if (self.choseAttackAlready == 1):
+ return
+ self.notify.debug('WaitForInput timed out')
+ if (self.localToonActive()):
+ self.notify.debug('battle timed out')
+ self.d_timeout(base.localAvatar.doId)
+ assert(self.fsm.getCurrentState().getName() == 'WaitForInput')
+
+ ##### MakeMovie state #####
+
+ def enterMakeMovie(self, ts=0):
+ self.notify.debug('enterMakeMovie()')
+ return None
+
+ def exitMakeMovie(self):
+ return None
+
+ ##### PlayMovie state #####
+
+ def enterPlayMovie(self, ts):
+ #import pdb; pdb.set_trace()
+ self.notify.debug('enterPlayMovie()')
+ self.delayDeleteMembers()
+ if (self.hasLocalToon()):
+ NametagGlobals.setMasterArrowsOn(0)
+ if ToontownBattleGlobals.SkipMovie:
+ self.movie.play(ts, self.__handleMovieDone)
+ self.movie.finish()
+ #self.__handleMovieDone()
+ else:
+ self.movie.play(ts, self.__handleMovieDone)
+ return None
+
+ def __handleMovieDone(self):
+ self.notify.debug('__handleMovieDone()')
+ if (self.hasLocalToon()):
+ self.d_movieDone(base.localAvatar.doId)
+ self.movie.reset()
+
+ def exitPlayMovie(self):
+ self.notify.debug('exitPlayMovie()')
+ # In case we're observing and the server cuts us off
+ # this guarantees all final animations get started and things
+ # get cleaned up
+ self.movie.reset(finish=1)
+ self._removeMembersKeep()
+ # Now it is time to clear out townBattleAttacks.
+ # I wish there was a better place to do this.
+ self.townBattleAttacks = ([-1, -1, -1, -1], # BattleIndices
+ [-1, -1, -1, -1], # tracks
+ [-1, -1, -1, -1], # levels
+ [0, 0, 0, 0] # targets
+ )
+ # Let's try no arrows during battle
+ # NametagGlobals.setMasterArrowsOn(1)
+ return None
+
+ ##### Reward state #####
+
+ ##### Resume state #####
+
+ #########################
+ ##### LocalToon ClassicFSM #####
+ #########################
+
+ def hasLocalToon(self):
+ """ hasLocalToon()
+ """
+ return (self.toons.count(base.localAvatar) > 0)
+
+ def localToonPendingOrActive(self):
+ return ((self.pendingToons.count(base.localAvatar) > 0) or
+ (self.activeToons.count(base.localAvatar) > 0))
+
+ def localToonActive(self):
+ return (self.activeToons.count(base.localAvatar) > 0)
+
+ def localToonActiveOrRunning(self):
+ return ((self.activeToons.count(base.localAvatar) > 0) or
+ (self.runningToons.count(base.localAvatar) > 0))
+
+ def localToonRunning(self):
+ return (self.runningToons.count(base.localAvatar) > 0)
+
+ ##### HasLocalToon state #####
+
+ def enterHasLocalToon(self):
+ self.notify.debug('enterHasLocalToon()')
+ # Put local toon into battle mode
+ if base.cr.playGame.getPlace() != None:
+ base.cr.playGame.getPlace().setState('battle', self.localToonBattleEvent)
+ if localAvatar and hasattr(localAvatar, 'inventory') and localAvatar.inventory:
+ localAvatar.inventory.setInteractivePropTrackBonus(
+ self.interactivePropTrackBonus)
+ # Establish battle camera parameters
+ camera.wrtReparentTo(self)
+ base.camLens.setFov(self.camFov)
+ return None
+
+ def exitHasLocalToon(self):
+ # Ignore any responses from the battle interface
+ self.ignore(self.localToonBattleEvent)
+ self.__stopTimer()
+
+ # restore the inventory to not having a prop bonus
+ if localAvatar and hasattr(localAvatar, 'inventory') and localAvatar.inventory:
+ localAvatar.inventory.setInteractivePropTrackBonus(-1)
+
+ # Restore the camera parameters
+ stateName = None
+ place = base.cr.playGame.getPlace()
+ if place:
+ stateName = place.fsm.getCurrentState().getName()
+ if stateName == 'died':
+ # If we died in the middle of the round, stop the battle
+ # animation and put the camera somewhere good for watching
+ # myself go sad.
+ self.movie.reset()
+ camera.reparentTo(render)
+ camera.setPosHpr(localAvatar,
+ 5.2, 5.45, localAvatar.getHeight() * 0.66,
+ 131.5, 3.6, 0)
+
+ else:
+ # Otherwise, reparent the camera back to the toon where it
+ # belongs (most of the time).
+ camera.wrtReparentTo(base.localAvatar)
+ messenger.send('localToonLeftBattle')
+
+ base.camLens.setFov(ToontownGlobals.DefaultCameraFov)
+ return None
+
+ ##### NoLocalToon state #####
+
+ def enterNoLocalToon(self):
+ self.notify.debug('enterNoLocalToon()')
+ return None
+
+ def exitNoLocalToon(self):
+ return None
+
+ ##### WaitForServer state #####
+
+ def enterWaitForServer(self):
+ self.notify.debug('enterWaitForServer()')
+ return None
+
+ def exitWaitForServer(self):
+ return None
+
+ ######################
+ ##### Adjust ClassicFSM #####
+ ######################
+
+ def createAdjustInterval(self, av, destPos, destHpr, toon=0, run=0):
+ # Just do everything at suit speed since toons are walking
+ if (run == 1):
+ assert(toon == 1)
+ adjustTime = self.calcToonMoveTime(destPos, av.getPos(self))
+ else:
+ adjustTime = self.calcSuitMoveTime(destPos, av.getPos(self))
+ #assert(adjustTime > BATTLE_SMALL_VALUE)
+ self.notify.debug('creating adjust interval for: %d' % av.doId)
+ adjustTrack = Sequence()
+ if (run == 1):
+ adjustTrack.append(Func(av.loop, 'run'))
+ else:
+ adjustTrack.append(Func(av.loop, 'walk'))
+ adjustTrack.append(Func(av.headsUp, self, destPos))
+ adjustTrack.append(LerpPosInterval(av, adjustTime, destPos, other=self))
+ adjustTrack.append(Func(av.setHpr, self, destHpr))
+ adjustTrack.append(Func(av.loop, 'neutral'))
+ return adjustTrack
+
+ def __adjust(self, ts, callback):
+ self.notify.debug('__adjust(%f)' % ts)
+ adjustTrack = Parallel()
+ # See if we need to adjust suits
+ if ((len(self.pendingSuits) > 0) or self.suitGone == 1):
+ self.suitGone = 0
+ numSuits = (len(self.pendingSuits) + len(self.activeSuits)) - 1
+ index = 0
+ for suit in self.activeSuits:
+ # See if already in the right position
+ # We might move sideways if someone died next to us
+ point = self.suitPoints[numSuits][index]
+ pos = suit.getPos(self)
+ destPos = point[0]
+ if (self.isSuitLured(suit) == 1):
+ destPos = Point3(destPos[0],
+ destPos[1] - MovieUtil.SUIT_LURE_DISTANCE,
+ destPos[2])
+ if (pos != destPos):
+ destHpr = VBase3(point[1], 0.0, 0.0)
+ adjustTrack.append(
+ self.createAdjustInterval(suit, destPos, destHpr))
+ index += 1
+
+ for suit in self.pendingSuits:
+ point = self.suitPoints[numSuits][index]
+ destPos = point[0]
+ destHpr = VBase3(point[1], 0.0, 0.0)
+ adjustTrack.append(
+ self.createAdjustInterval(suit, destPos, destHpr))
+ index += 1
+
+ # See if we need to adjust toons
+ if ((len(self.pendingToons) > 0) or self.toonGone == 1):
+ self.toonGone = 0
+ numToons = (len(self.pendingToons) + len(self.activeToons)) - 1
+ index = 0
+ for toon in self.activeToons:
+ # See if already in the right position
+ # We might move sideways if someone died next to us
+ point = self.toonPoints[numToons][index]
+ pos = toon.getPos(self)
+ destPos = point[0]
+ if (pos != destPos):
+ destHpr = VBase3(point[1], 0.0, 0.0)
+ adjustTrack.append(
+ self.createAdjustInterval(toon, destPos, destHpr))
+
+ index += 1
+ for toon in self.pendingToons:
+ point = self.toonPoints[numToons][index]
+ destPos = point[0]
+ destHpr = VBase3(point[1], 0.0, 0.0)
+ adjustTrack.append(
+ self.createAdjustInterval(toon, destPos, destHpr))
+ index += 1
+
+ if (len(adjustTrack) > 0):
+ self.notify.debug('creating adjust multitrack')
+ e = Func(self.__handleAdjustDone)
+ track = Sequence(adjustTrack, e, name = self.adjustName)
+ self.storeInterval(track, self.adjustName)
+ track.start(ts)
+ if ToontownBattleGlobals.SkipMovie:
+ track.finish()
+ else:
+ self.notify.warning('adjust() - nobody needed adjusting')
+ self.__adjustDone()
+
+ def __handleAdjustDone(self):
+ self.notify.debug('__handleAdjustDone() - client adjust finished')
+ self.clearInterval(self.adjustName)
+ self.__adjustDone()
+
+ def __stopAdjusting(self):
+ self.notify.debug('__stopAdjusting()')
+ self.clearInterval(self.adjustName)
+ if (self.adjustFsm.getCurrentState().getName() == 'Adjusting'):
+ self.adjustFsm.request('NotAdjusting')
+
+ def __requestAdjustTownBattle(self):
+ self.notify.debug('__requestAdjustTownBattle() curstate = %s' %
+ self.fsm.getCurrentState().getName() )
+ if (self.fsm.getCurrentState().getName() == 'WaitForInput'):
+ self.__adjustTownBattle()
+ else:
+ self.needAdjustTownBattle = 1
+
+ def __adjustTownBattle(self):
+ self.notify.debug('__adjustTownBattle()')
+ if (self.localToonActive() and len(self.activeSuits) > 0):
+ self.notify.debug('__adjustTownBattle() - adjusting town battle')
+ # Determine the indices of lured suits
+ luredSuits = []
+ for suit in self.luredSuits:
+ if suit not in self.activeSuits:
+ self.notify.error('lured suit not in self.activeSuits')
+ luredSuits.append(self.activeSuits.index(suit))
+ # Determine the indices of trapped suits
+ trappedSuits = []
+ for suit in self.activeSuits:
+ if (suit.battleTrap != NO_TRAP):
+ trappedSuits.append(self.activeSuits.index(suit))
+ self.townBattle.adjustCogsAndToons(self.activeSuits,
+ luredSuits, trappedSuits, self.activeToons)
+ # It is possible that townBattleAttacks hasn't been set yet, if
+ # we haven't finished an attack round.
+ if hasattr(self, 'townBattleAttacks'):
+ self.townBattle.updateChosenAttacks(self.townBattleAttacks[0],
+ self.townBattleAttacks[1],
+ self.townBattleAttacks[2],
+ self.townBattleAttacks[3])
+ self.needAdjustTownBattle = 0
+
+ def __adjustDone(self):
+ self.notify.debug('__adjustDone()')
+ # Tell the server we're done adjusting
+ if (self.hasLocalToon()):
+ self.d_adjustDone(base.localAvatar.doId)
+ self.adjustFsm.request('NotAdjusting')
+
+ ##### Adjusting state #####
+
+ def enterAdjusting(self, ts):
+ self.notify.debug('enterAdjusting()')
+ if (self.localToonActive()):
+ self.__stopTimer()
+ self.delayDeleteMembers()
+ self.__adjust(ts, self.__handleAdjustDone)
+
+ return None
+
+ def exitAdjusting(self):
+ self.notify.debug('exitAdjusting()')
+ self.finishInterval(self.adjustName)
+ self._removeMembersKeep()
+ currStateName = self.fsm.getCurrentState().getName()
+ if (currStateName == 'WaitForInput' and self.localToonActive()):
+ self.startTimer()
+
+ return None
+
+ ##### NotAdjusting state #####
+
+ def enterNotAdjusting(self):
+ self.notify.debug('enterNotAdjusting()')
+ return None
+
+ def exitNotAdjusting(self):
+ return None
+
+ # Misc
+
+ def visualize(self):
+ """ visualize()
+ """
+ try:
+ self.isVisualized
+ except:
+ self.isVisualized = 0
+ if (self.isVisualized):
+ self.vis.removeNode()
+ del self.vis
+ self.detachNode()
+ self.isVisualized = 0
+ else:
+ lsegs = LineSegs()
+ lsegs.setColor(.5, .5, 1, 1)
+ lsegs.moveTo(0, 0, 0)
+ for p in BattleBase.allPoints:
+ lsegs.drawTo(p[0], p[1], p[2])
+ p = BattleBase.allPoints[0]
+ lsegs.drawTo(p[0], p[1], p[2])
+ self.vis = self.attachNewNode(lsegs.create())
+ self.reparentTo(render)
+ self.isVisualized = 1
+
+
+ ##### Battle Trigger / Lockout collisions--not used in building battles #####
+
+ def setupCollisions(self, name):
+ self.lockout = CollisionTube(0, 0, 0, 0, 0, 9, 9)
+ lockoutNode = CollisionNode(name)
+ lockoutNode.addSolid(self.lockout)
+ lockoutNode.setCollideMask(ToontownGlobals.WallBitmask)
+ self.lockoutNodePath = self.attachNewNode(lockoutNode)
+ # By default don't activate collision sphere
+ self.lockoutNodePath.detachNode()
+
+ # The lockout bubble should always be tangible; we never want
+ # Toons walking around inside the battle.
+
+ def removeCollisionData(self):
+ del self.lockout
+ self.lockoutNodePath.removeNode()
+ del self.lockoutNodePath
+
+ def enableCollision(self):
+ self.lockoutNodePath.reparentTo(self)
+ if (len(self.toons) < 4):
+ self.accept(self.getCollisionName(),
+ self.__handleLocalToonCollision)
+
+ def __handleLocalToonCollision(self, collEntry):
+ self.notify.debug('localToonCollision')
+ if self.fsm.getCurrentState().getName() == 'Off':
+ self.notify.debug('ignoring collision in Off state')
+ return
+
+ if not base.localAvatar.wantBattles:
+ return
+
+ base.cr.playGame.getPlace().setState('WaitForBattle')
+ toon = base.localAvatar
+ self.d_toonRequestJoin(toon.doId, toon.getPos(self))
+ base.localAvatar.preBattleHpr = base.localAvatar.getHpr(render)
+ # Wait to see if local toon can join
+ self.localToonFsm.request('WaitForServer')
+ self.onWaitingForJoin()
+
+ def onWaitingForJoin(self):
+ """ this is here for level battle to override """
+ pass
+
+ def denyLocalToonJoin(self):
+ self.notify.debug('denyLocalToonJoin()')
+
+ place = self.cr.playGame.getPlace()
+ if place.fsm.getCurrentState().getName() == 'WaitForBattle':
+ place.setState('walk')
+
+ self.localToonFsm.request('NoLocalToon')
+
+ def disableCollision(self):
+ self.ignore(self.getCollisionName())
+ self.lockoutNodePath.detachNode()
+
+ def openBattleCollision(self):
+ if (not self.hasLocalToon()):
+ self.enableCollision()
+
+ def closeBattleCollision(self):
+ self.ignore(self.getCollisionName())
+
+ def getCollisionName(self):
+ return 'enter' + self.lockoutNodePath.getName()
+
diff --git a/toontown/src/battle/DistributedBattleBaseAI.py b/toontown/src/battle/DistributedBattleBaseAI.py
new file mode 100644
index 0000000..c09f2fe
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleBaseAI.py
@@ -0,0 +1,2460 @@
+from otp.ai.AIBase import *
+from direct.distributed.ClockDelta import *
+from BattleBase import *
+from BattleCalculatorAI import *
+from toontown.toonbase.ToontownBattleGlobals import *
+from SuitBattleGlobals import *
+from pandac.PandaModules import *
+import BattleExperienceAI
+from direct.distributed import DistributedObjectAI
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from toontown.ai import DatabaseObject
+from toontown.toon import DistributedToonAI
+from toontown.toon import InventoryBase
+from toontown.toonbase import ToontownGlobals
+import random
+from toontown.toon import NPCToons
+
+# attack properties table
+class DistributedBattleBaseAI(DistributedObjectAI.DistributedObjectAI,
+ BattleBase):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleBaseAI')
+
+ def __init__(self, air, zoneId, finishCallback=None, maxSuits=4,
+ bossBattle=0, tutorialFlag=0, interactivePropTrackBonus = -1):
+ """__init__(air, zoneId, finishCallback, maxSuits, bossBattle)
+ """
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+
+ self.serialNum = 0
+
+ self.zoneId = zoneId
+ self.maxSuits = maxSuits
+ self.setBossBattle(bossBattle)
+ self.tutorialFlag = tutorialFlag
+ self.interactivePropTrackBonus = interactivePropTrackBonus
+
+ # function to call when this battle is about to be destroyed
+ self.finishCallback = finishCallback
+
+ self.avatarExitEvents = []
+ self.responses = {}
+ self.adjustingResponses = {}
+ self.joinResponses = {}
+ self.adjustingSuits = []
+ self.adjustingToons = []
+
+ # Track the number of suits that have ever joined this battle.
+ self.numSuitsEver = 0
+
+ BattleBase.__init__(self)
+
+ self.streetBattle = 1
+ self.pos = Point3(0, 0, 0)
+ self.initialSuitPos = Point3(0, 0, 0)
+
+ self.toonExp = {}
+ self.toonOrigQuests = {}
+ self.toonItems = {}
+ self.toonOrigMerits = {}
+ self.toonMerits = {}
+ self.toonParts = {}
+ self.battleCalc = BattleCalculatorAI(self, tutorialFlag)
+
+ # If there is an invasion, double the exp for the duration of this battle
+ # Now, if the invasion ends midway through this battle, the players will
+ # continue getting credit. This is ok I guess.
+ if self.air.suitInvasionManager.getInvading():
+ mult = getInvasionMultiplier()
+ self.battleCalc.setSkillCreditMultiplier(mult)
+
+ # see if we have the double xp holiday up
+ if self.air.holidayManager.isMoreXpHolidayRunning():
+ mult = getMoreXpHolidayMultiplier()
+ self.battleCalc.setSkillCreditMultiplier(mult)
+
+ self.fsm = None
+
+ self.clearAttacks()
+
+ self.ignoreFaceOffDone = 0
+ self.needAdjust = 0
+ self.movieHasBeenMade = 0
+ self.movieHasPlayed = 0
+ self.rewardHasPlayed = 0
+ self.movieRequested = 0
+
+ self.ignoreResponses = 0
+ self.ignoreAdjustingResponses = 0
+
+ self.taskNames = []
+ self.exitedToons = []
+
+ # Maintain a list of all the suits killed in the battle
+ # for the quest system and the suit page
+ self.suitsKilled = []
+ self.suitsKilledThisBattle = []
+ self.suitsKilledPerFloor = []
+
+ # Maintain a list of all the suits encountered in the battle
+ # for the suit page
+ self.suitsEncountered = []
+ # these will help
+ self.newToons = []
+ self.newSuits = []
+
+ self.numNPCAttacks = 0
+ self.npcAttacks = {}
+
+ self.pets = {}
+
+ self.fsm = ClassicFSM.ClassicFSM('DistributedBattleAI',
+ [State.State('FaceOff',
+ self.enterFaceOff,
+ self.exitFaceOff,
+ ['WaitForInput',
+ 'Resume']),
+ State.State('WaitForJoin',
+ self.enterWaitForJoin,
+ self.exitWaitForJoin,
+ ['WaitForInput', 'Resume']),
+ State.State('WaitForInput',
+ self.enterWaitForInput,
+ self.exitWaitForInput,
+ ['MakeMovie',
+ 'Resume']),
+ State.State('MakeMovie',
+ self.enterMakeMovie,
+ self.exitMakeMovie,
+ ['PlayMovie',
+ 'Resume']),
+ State.State('PlayMovie',
+ self.enterPlayMovie,
+ self.exitPlayMovie,
+ ['WaitForJoin',
+ 'Reward',
+ 'Resume']),
+ State.State('Reward',
+ self.enterReward,
+ self.exitReward,
+ ['Resume']),
+ State.State('Resume',
+ self.enterResume,
+ self.exitResume,
+ []),
+ State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['FaceOff', 'WaitForJoin'])],
+ # Initial state
+ 'Off',
+ # Final state
+ 'Off',
+ )
+
+ self.joinableFsm = ClassicFSM.ClassicFSM('Joinable',
+ [State.State('Joinable',
+ self.enterJoinable,
+ self.exitJoinable,
+ ['Unjoinable']),
+ State.State('Unjoinable',
+ self.enterUnjoinable,
+ self.exitUnjoinable,
+ ['Joinable'])],
+ # Initial state
+ 'Unjoinable',
+ # Final state
+ 'Unjoinable',
+ )
+ self.joinableFsm.enterInitialState()
+
+ self.runableFsm = ClassicFSM.ClassicFSM('Runable',
+ [State.State('Runable',
+ self.enterRunable,
+ self.exitRunable,
+ ['Unrunable']),
+ State.State('Unrunable',
+ self.enterUnrunable,
+ self.exitUnrunable,
+ ['Runable'])],
+ # Initial state
+ 'Unrunable',
+ # Final state
+ 'Unrunable',
+ )
+ self.runableFsm.enterInitialState()
+
+ self.adjustFsm = ClassicFSM.ClassicFSM('Adjust',
+ [State.State('Adjusting',
+ self.enterAdjusting,
+ self.exitAdjusting,
+ ['NotAdjusting', 'Adjusting']),
+ State.State('NotAdjusting',
+ self.enterNotAdjusting,
+ self.exitNotAdjusting,
+ ['Adjusting'])],
+ # Initial state
+ 'NotAdjusting',
+ # Final state
+ 'NotAdjusting',
+ )
+ self.adjustFsm.enterInitialState()
+ self.fsm.enterInitialState()
+
+ self.startTime = globalClock.getRealTime()
+ self.adjustingTimer = Timer()
+
+
+ def clearAttacks(self):
+ """ clearAttacks()
+ """
+ self.toonAttacks = {}
+ self.suitAttacks = getDefaultSuitAttacks()
+
+ def requestDelete(self):
+ if hasattr(self, 'fsm'):
+ # We want to make sure the battle is no longer active once
+ # we start deleting it. If we don't do this, it may
+ # continue to fire off tasks until the delete message
+ # comes back from the server.
+ self.fsm.request('Off')
+ self.__removeTaskName(self.uniqueName('make-movie'))
+ DistributedObjectAI.DistributedObjectAI.requestDelete(self)
+
+ def delete(self):
+ self.notify.debug('deleting battle')
+ self.fsm.request('Off')
+ self.ignoreAll()
+ self.__removeAllTasks()
+ del self.fsm
+ del self.joinableFsm
+ del self.runableFsm
+ del self.adjustFsm
+ self.__cleanupJoinResponses()
+ self.timer.stop()
+ del self.timer
+ self.adjustingTimer.stop()
+ del self.adjustingTimer
+ self.battleCalc.cleanup()
+ del self.battleCalc
+ for suit in self.suits:
+ del suit.battleTrap
+ del self.finishCallback
+ for petProxy in self.pets.values():
+ petProxy.requestDelete()
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def pause(self):
+ self.timer.stop()
+ self.adjustingTimer.stop()
+
+ def unpause(self):
+ self.timer.resume()
+ self.adjustingTimer.resume()
+
+ def abortBattle(self):
+
+ """ Call this function to stop the battle in the middle, no
+ matter what; the toons are sent back to the playground and the
+ suits will fly away (or do whatever is appropriate for this
+ kind of battle). This is normally called only in response to
+ a magic word. """
+
+ self.notify.debug('%s.abortBattle() called.' % (self.doId))
+
+ toonsCopy = self.toons[:]
+ for toonId in toonsCopy:
+ self.__removeToon(toonId)
+
+ if (self.fsm.getCurrentState().getName() == 'PlayMovie' or
+ self.fsm.getCurrentState().getName() == 'MakeMovie'):
+ self.exitedToons.append(toonId)
+
+ # Of course, the last toon is gone now.
+ self.d_setMembers()
+
+ self.b_setState('Resume')
+ self.__removeAllTasks()
+ self.timer.stop()
+ self.adjustingTimer.stop()
+
+ def __removeSuit(self, suit):
+ self.notify.debug('__removeSuit(%d)' % suit.doId)
+ assert(self.suits.count(suit) == 1)
+ self.suits.remove(suit)
+ assert(self.joiningSuits.count(suit) == 0)
+ assert(self.pendingSuits.count(suit) == 0)
+ assert(self.adjustingSuits.count(suit) == 0)
+ assert(self.activeSuits.count(suit) == 1)
+ self.activeSuits.remove(suit)
+ if (self.luredSuits.count(suit) == 1):
+ self.luredSuits.remove(suit)
+ self.suitGone = 1
+ del suit.battleTrap
+
+ def findSuit(self, id):
+ """ findSuit(id)
+ """
+ for s in self.suits:
+ if (s.doId == id):
+ return s
+ return None
+
+ def __removeTaskName(self, name):
+ if (self.taskNames.count(name)):
+ self.taskNames.remove(name)
+ self.notify.debug('removeTaskName() - %s' % name)
+ taskMgr.remove(name)
+
+ def __removeAllTasks(self):
+ for n in self.taskNames:
+ self.notify.debug('removeAllTasks() - %s' % n)
+ taskMgr.remove(n)
+ self.taskNames = []
+
+ def __removeToonTasks(self, toonId):
+ name = self.taskName('running-toon-%d' % toonId)
+ self.__removeTaskName(name)
+ name = self.taskName('to-pending-av-%d' % toonId)
+ self.__removeTaskName(name)
+
+ # These are dummy getters for non-level battles. See toon.dc for
+ # more info.
+ def getLevelDoId(self):
+ return 0
+ def getBattleCellId(self):
+ return 0
+
+ # setPosition()
+
+ def getPosition(self):
+ """getPosition()
+ """
+ self.notify.debug('getPosition() - %s' % self.pos)
+ return [self.pos[0], self.pos[1], self.pos[2]]
+
+ # setInitialSuitPos()
+
+ def getInitialSuitPos(self):
+ """ getInitialSuitPos()
+ """
+ p = []
+ p.append(self.initialSuitPos[0])
+ p.append(self.initialSuitPos[1])
+ p.append(self.initialSuitPos[2])
+ return p
+
+ # setBossBattle
+
+ def setBossBattle(self, bossBattle):
+ """call this before generate"""
+ self.bossBattle = bossBattle
+
+ def getBossBattle(self):
+ return self.bossBattle
+
+ # setState()
+
+ def b_setState(self, state):
+ self.notify.debug('network:setState(%s)' % state)
+ stime = globalClock.getRealTime() + SERVER_BUFFER_TIME
+ self.sendUpdate('setState', [state, globalClockDelta.localToNetworkTime(stime)])
+ self.setState(state)
+
+ def setState(self, state):
+ self.fsm.request(state)
+
+ def getState(self):
+ return [self.fsm.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+
+ # setMembers()
+
+ def d_setMembers(self):
+ self.notify.debug('network:setMembers()')
+ self.sendUpdate('setMembers', self.getMembers())
+
+ def getMembers(self):
+ suits = []
+ for s in self.suits:
+ suits.append(s.doId)
+ joiningSuits = ''
+ for s in self.joiningSuits:
+ joiningSuits += str(suits.index(s.doId))
+ pendingSuits = ''
+ for s in self.pendingSuits:
+ pendingSuits += str(suits.index(s.doId))
+ activeSuits = ''
+ for s in self.activeSuits:
+ activeSuits += str(suits.index(s.doId))
+ luredSuits = ''
+ for s in self.luredSuits:
+ luredSuits += str(suits.index(s.doId))
+ #import pdb; pdb.set_trace()
+ suitTraps = ''
+ for s in self.suits:
+ if (s.battleTrap == NO_TRAP):
+ suitTraps += '9'
+ elif (s.battleTrap == BattleCalculatorAI.TRAP_CONFLICT):
+ suitTraps += '9'
+ else:
+ suitTraps += str(s.battleTrap)
+ toons = []
+ for t in self.toons:
+ toons.append(t)
+ joiningToons = ''
+ for t in self.joiningToons:
+ joiningToons += str(toons.index(t))
+ pendingToons = ''
+ for t in self.pendingToons:
+ pendingToons += str(toons.index(t))
+ activeToons = ''
+ for t in self.activeToons:
+ activeToons += str(toons.index(t))
+ runningToons = ''
+ for t in self.runningToons:
+ runningToons += str(toons.index(t))
+ self.notify.debug('getMembers() - suits: %s joiningSuits: %s pendingSuits: %s activeSuits: %s luredSuits: %s suitTraps: %s toons: %s joiningToons: %s pendingToons: %s activeToons: %s runningToons: %s' % (suits, joiningSuits, pendingSuits, activeSuits, luredSuits, suitTraps, toons, joiningToons, pendingToons, activeToons, runningToons))
+ return ([suits, joiningSuits, pendingSuits, activeSuits, luredSuits,
+ suitTraps, toons, joiningToons, pendingToons, activeToons,
+ runningToons, globalClockDelta.getRealNetworkTime()])
+
+ # adjust()
+
+ def d_adjust(self):
+ self.notify.debug('network:adjust()')
+ self.sendUpdate('adjust', [globalClockDelta.getRealNetworkTime()])
+
+ # setInteractivePropTrackBonus
+
+ def getInteractivePropTrackBonus(self):
+ return self.interactivePropTrackBonus
+
+ # setZoneId
+
+ def getZoneId(self):
+ return self.zoneId
+
+ def getTaskZoneId(self):
+ """ this function is here to allow subclasses override the zoneId
+ that's used to determine task progress """
+ return self.zoneId
+
+ # setMovie
+
+ def d_setMovie(self):
+ self.notify.debug('network:setMovie()')
+ self.sendUpdate('setMovie', self.getMovie())
+ # this seems as good a place as any to update the suit encountered array
+ # (we have to make sure the adjusting is all finished and activeToons us updated)
+ self.__updateEncounteredCogs()
+
+ def getMovie(self):
+ suitIds = []
+ for s in self.activeSuits:
+ suitIds.append(s.doId)
+ p = [self.movieHasBeenMade]
+ p.append(self.activeToons)
+ p.append(suitIds)
+ for t in self.activeToons:
+ if (self.toonAttacks.has_key(t)):
+ ta = self.toonAttacks[t]
+ index = -1
+ id = ta[TOON_ID_COL]
+ if (id != -1):
+ assert(self.activeToons.count(id))
+ index = self.activeToons.index(id)
+ #import pdb; pdb.set_trace()
+ track = ta[TOON_TRACK_COL]
+ if ((track == NO_ATTACK or
+ attackAffectsGroup(track, ta[TOON_LVL_COL])) and
+ track != NPCSOS and track != PETSOS):
+ target = -1
+ if (track == HEAL):
+ # If it's a joke, send a joke index number also
+ if (ta[TOON_LVL_COL] == 1):
+ ta[TOON_HPBONUS_COL] = random.randint(0, 10000)
+ elif (track == SOS or track == NPCSOS or track == PETSOS):
+
+ # We need to pass the actual doId in this case
+ target = ta[TOON_TGT_COL]
+ elif (track == HEAL):
+ if self.activeToons.count(ta[TOON_TGT_COL]) != 0:
+ target = self.activeToons.index(ta[TOON_TGT_COL])
+ else:
+ target = -1
+ else:
+ if suitIds.count(ta[TOON_TGT_COL]) != 0:
+ target = suitIds.index(ta[TOON_TGT_COL])
+ else:
+ target = -1
+ p = p + [index,
+ track,
+ ta[TOON_LVL_COL],
+ target]
+ p = p + ta[4:]
+ else:
+ index = self.activeToons.index(t)
+ attack = getToonAttack(index)
+ p = p + attack
+ for i in range(4 - len(self.activeToons)):
+ p = p + getToonAttack(-1)
+ for sa in self.suitAttacks:
+ index = -1
+ id = sa[SUIT_ID_COL]
+ if (id != -1):
+ assert(suitIds.count(id))
+ index = suitIds.index(id)
+ if (sa[SUIT_ATK_COL] == -1):
+ targetIndex = -1
+ else:
+ targetIndex = sa[SUIT_TGT_COL]
+ assert(targetIndex < len(self.activeToons))
+ if (targetIndex == -1):
+ self.notify.debug('suit attack: %d must be group' % \
+ sa[SUIT_ATK_COL])
+ else:
+ toonId = self.activeToons[targetIndex]
+ p = p + [index,
+ sa[SUIT_ATK_COL],
+ targetIndex]
+ sa[SUIT_TAUNT_COL] = 0
+ if (sa[SUIT_ATK_COL] != -1):
+ suit = self.findSuit(id)
+ assert(suit != None)
+ sa[SUIT_TAUNT_COL] = getAttackTauntIndexFromIndex(suit,
+ sa[SUIT_ATK_COL])
+ p = p + sa[3:]
+ return p
+
+ # setChosenToonAttacks
+
+ def d_setChosenToonAttacks(self):
+ self.notify.debug('network:setChosenToonAttacks()')
+ self.sendUpdate('setChosenToonAttacks', self.getChosenToonAttacks())
+
+ def getChosenToonAttacks(self):
+ ids = []
+ tracks = []
+ levels = []
+ targets = []
+ for t in self.activeToons:
+ if (self.toonAttacks.has_key(t)):
+ ta = self.toonAttacks[t]
+ else:
+ ta = getToonAttack(t)
+ assert(t == ta[TOON_ID_COL])
+ ids.append(t)
+ tracks.append(ta[TOON_TRACK_COL])
+ levels.append(ta[TOON_LVL_COL])
+ targets.append(ta[TOON_TGT_COL])
+ return [ids, tracks, levels, targets]
+
+ # setBattleExperience
+
+ def d_setBattleExperience(self):
+ self.notify.debug('network:setBattleExperience()')
+ self.sendUpdate('setBattleExperience', self.getBattleExperience())
+
+ def getBattleExperience(self):
+ #print ("DistributedBattleBaseAI -getBattleExperience- Active Toons %s" % (self.activeToons))
+ returnValue = BattleExperienceAI.getBattleExperience(
+ 4, self.activeToons, self.toonExp,
+ self.battleCalc.toonSkillPtsGained,
+ self.toonOrigQuests, self.toonItems, self.toonOrigMerits, self.toonMerits,
+ self.toonParts, self.suitsKilled, self.helpfulToons)
+ #print ("DistributedBattleBaseAI -getBattleExperienceEnd- Active Toons %s" % (self.activeToons))
+ return returnValue
+
+ # Add suit
+
+ def getToonUberStatus(self):
+ #UBERCHANGE
+ fieldList = []
+ uberIndex = LAST_REGULAR_GAG_LEVEL + 1
+ for toon in self.activeToons:
+ toonList = []
+ for trackIndex in range(MAX_TRACK_INDEX):
+ toonList.append(toon.inventory.numItem(track, uberIndex))
+ fieldList.append(encodeUber(toonList))
+ return fieldList
+
+ def addSuit(self, suit):
+ self.notify.debug('addSuit(%d)' % suit.doId)
+ self.newSuits.append(suit)
+ self.suits.append(suit)
+
+ # Initialize the suit trap
+ suit.battleTrap = NO_TRAP
+ self.numSuitsEver += 1
+
+ def __joinSuit(self, suit):
+ # calculate the time it will take for the suit to go from
+ # its current position to its pending position in the battle
+ # and create a task to call a function when that time has
+ # passed
+ #
+ # Building battles can have 3 pending suits
+ assert(len(self.pendingSuits) <= 3)
+ assert(self.joiningSuits.count(suit) == 0)
+ #spotIndex = len(self.pendingSuits) + len(self.joiningSuits)
+ self.joiningSuits.append(suit)
+ toPendingTime = MAX_JOIN_T + SERVER_BUFFER_TIME
+ taskName = self.taskName('to-pending-av-%d' % suit.doId)
+ self.__addJoinResponse(suit.doId, taskName)
+
+ self.taskNames.append(taskName)
+ taskMgr.doMethodLater(toPendingTime,
+ self.__serverJoinDone,
+ taskName,
+ extraArgs = (suit.doId, taskName))
+
+ def __serverJoinDone(self, avId, taskName):
+ self.notify.debug('join for av: %d timed out on server' % avId)
+ self.__removeTaskName(taskName)
+ self.__makeAvPending(avId)
+ return Task.done
+
+ def __makeAvPending(self, avId):
+ self.notify.debug('__makeAvPending(%d)' % avId)
+ self.__removeJoinResponse(avId)
+ self.__removeTaskName(self.taskName('to-pending-av-%d' % avId))
+ if (self.toons.count(avId) > 0):
+ assert(self.joiningToons.count(avId) == 1)
+ self.joiningToons.remove(avId)
+ assert(self.pendingToons.count(avId) == 0)
+ self.pendingToons.append(avId)
+ else:
+ suit = self.findSuit(avId)
+ if (suit != None):
+ if(not suit.isEmpty()):
+ # spam debug if the next assert is about to trigger
+ if not (self.joiningSuits.count(suit) == 1):
+ self.notify.warning('__makeAvPending(%d) in zone: %d' % (avId, self.zoneId))
+ self.notify.warning('toons: %s' % (self.toons))
+ self.notify.warning('joining toons: %s' % (self.joiningToons))
+ self.notify.warning('pending toons: %s' % (self.pendingToons))
+ self.notify.warning('suits: %s' % (self.suits))
+ self.notify.warning('joining suits: %s' % (self.joiningSuits))
+ self.notify.warning('pending suits: %s' % (self.pendingSuits))
+ assert(self.joiningSuits.count(suit) == 1)
+ self.joiningSuits.remove(suit)
+ assert(self.pendingSuits.count(suit) == 0)
+ self.pendingSuits.append(suit)
+ else:
+ self.notify.warning('makeAvPending() %d not in toons or suits' \
+ % avId)
+ return
+ self.d_setMembers()
+ self.needAdjust = 1
+ self.__requestAdjust()
+
+ def suitRequestJoin(self, suit):
+ """ suitRequestJoin(suit)
+ """
+ self.notify.debug('suitRequestJoin(%d)' % suit.getDoId())
+ # make sure we're not trying to add a suit that's
+ # already in this battle
+ assert (suit not in self.suits), 'suit already in this battle'
+ if (self.suitCanJoin()):
+ self.addSuit(suit)
+ self.__joinSuit(suit)
+ self.d_setMembers()
+ suit.prepareToJoinBattle()
+ return 1
+ else:
+ self.notify.warning('suitRequestJoin() - not joinable - joinable state: %s max suits: %d' % (self.joinableFsm.getCurrentState().getName(),
+ self.maxSuits))
+ return 0
+
+ # Add/Remove toon
+
+ def addToon(self, avId):
+ print ("DBB-addToon %s" % (avId))
+ # Returns 1 if the toon is successfully added, 0 otherwise.
+
+ self.notify.debug('addToon(%d)' % avId)
+ toon = self.getToon(avId)
+ if (toon == None):
+ return 0
+
+ # Make sure the playground (or estate) toon-up task isn't
+ # still running on this Toon. It shouldn't be, but if it is
+ # we'll get very mysterious effects during the battle movie.
+ toon.stopToonUp()
+
+ # Prepare to handle an unexpected exit by the avatar
+ event = simbase.air.getAvatarExitEvent(avId)
+ self.avatarExitEvents.append(event)
+ self.accept(event, self.__handleUnexpectedExit, extraArgs=[avId])
+
+ # Also handle avatars that manage to escape to the safezone
+ # somehow.
+ event = "inSafezone-%s" % (avId)
+ self.avatarExitEvents.append(event)
+ self.accept(event, self.__handleSuddenExit, extraArgs=[avId, 0])
+
+ self.newToons.append(avId)
+ self.toons.append(avId)
+
+ toon = simbase.air.doId2do.get(avId)
+ if toon:
+ if hasattr(self, "doId"):
+ toon.b_setBattleId(self.doId)
+ else:
+ toon.b_setBattleId(-1)
+ messageToonAdded = ("Battle adding toon %s" % (avId))
+ messenger.send(messageToonAdded, [avId])
+
+ assert(not self.responses.has_key(avId))
+ if (self.fsm != None and
+ self.fsm.getCurrentState().getName() == 'PlayMovie'):
+ self.responses[avId] = 1
+ else:
+ self.responses[avId] = 0
+ assert(not self.adjustingResponses.has_key(avId))
+ self.adjustingResponses[avId] = 0
+
+ # Initialize experience per track
+ if avId not in self.toonExp:
+ p = []
+ for t in Tracks:
+ p.append(toon.experience.getExp(t))
+ self.toonExp[avId] = p
+
+ # Initialize original merits
+ if avId not in self.toonOrigMerits:
+ self.toonOrigMerits[avId] = toon.cogMerits[:]
+
+ # Initialize merits earned
+ if avId not in self.toonMerits:
+ self.toonMerits[avId] = [0, 0, 0, 0]
+
+ # Initialize parts found
+ if avId not in self.toonOrigQuests:
+ # we need to flatten the quests to send them over the wire
+ flattenedQuests = []
+ for quest in toon.quests:
+ flattenedQuests.extend(quest)
+ self.toonOrigQuests[avId] = flattenedQuests
+
+ # Initialize parts found
+ if avId not in self.toonItems:
+ self.toonItems[avId] = ([], [])
+
+ return 1
+
+ def __joinToon(self, avId, pos):
+ # calculate the time it will take for the toon to go from
+ # its current position to its position in the battle and
+ # create a task to call a function when that time has passed
+ #
+ assert(pos != None)
+ #assert(len(self.pendingToons) < 3)
+ assert(self.joiningToons.count(avId) == 0)
+ #spotIndex = len(self.pendingToons) + len(self.joiningToons)
+ self.joiningToons.append(avId)
+ toPendingTime = MAX_JOIN_T + SERVER_BUFFER_TIME
+ taskName = self.taskName('to-pending-av-%d' % avId)
+ self.__addJoinResponse(avId, taskName, toon=1)
+ taskMgr.doMethodLater(toPendingTime,
+ self.__serverJoinDone,
+ taskName,
+ extraArgs = (avId, taskName))
+ self.taskNames.append(taskName)
+
+
+ def __updateEncounteredCogs(self):
+ # If there is a new toon, add all the active suits to the encounter list.
+ for toon in self.activeToons:
+ if toon in self.newToons:
+ # add any suits present to the suits encountered list
+ for suit in self.activeSuits:
+ if hasattr(suit, 'dna'):
+ self.suitsEncountered.append({'type': suit.dna.name,
+ # Put a copy of active toons in the dict
+ # Only they get credit for seeing this suit
+ 'activeToons': self.activeToons[:]})
+ else:
+ self.notify.warning('Suit has no DNA in zone %s: toons involved = %s' % (self.zoneId, self.activeToons))
+ # fail hard
+ return
+ self.newToons.remove(toon)
+
+ # If there is a new suit, add it to all active toons encounter list.
+ for suit in self.activeSuits:
+ if suit in self.newSuits:
+ if hasattr(suit, 'dna'):
+ # add any new suits to all active toons encounter list
+ self.suitsEncountered.append({'type': suit.dna.name,
+ # Put a copy of active toons in the dict
+ # Only they get credit for seeing this suit
+ 'activeToons': self.activeToons[:]})
+ else:
+ self.notify.warning('Suit has no DNA in zone %s: toons involved = %s' % (self.zoneId, self.activeToons))
+ # fail hard
+ return
+ self.newSuits.remove(suit)
+
+
+ def __makeToonRun(self, toonId, updateAttacks):
+ assert(self.activeToons.count(toonId) == 1)
+ self.activeToons.remove(toonId)
+ assert(self.runningToons.count(toonId) == 0)
+ # We want adjusting to occur
+ self.toonGone = 1
+ self.runningToons.append(toonId)
+ taskName = self.taskName('running-toon-%d' % toonId)
+ taskMgr.doMethodLater(TOON_RUN_T,
+ self.__serverRunDone,
+ taskName,
+ extraArgs = (toonId, updateAttacks, taskName))
+ self.taskNames.append(taskName)
+
+ def __serverRunDone(self, toonId, updateAttacks, taskName):
+ self.notify.debug('run for toon: %d timed out on server' % toonId)
+ self.__removeTaskName(taskName)
+ self.__removeToon(toonId)
+ self.d_setMembers()
+ if (len(self.toons) == 0):
+ self.notify.debug('last toon is gone - battle is finished')
+ self.b_setState('Resume')
+ else:
+ if (updateAttacks == 1):
+ self.d_setChosenToonAttacks()
+ self.needAdjust = 1
+ self.__requestAdjust()
+ return Task.done
+
+ def __requestAdjust(self):
+ """ Only the server can initiate an adjust
+ """
+ if (not self.fsm):
+ return
+
+ cstate = self.fsm.getCurrentState().getName()
+ if (cstate == 'WaitForInput' or cstate == 'WaitForJoin'):
+ if (self.adjustFsm.getCurrentState().getName() == 'NotAdjusting'):
+ if (self.needAdjust == 1):
+ self.d_adjust()
+ self.adjustingSuits = []
+ for s in self.pendingSuits:
+ self.adjustingSuits.append(s)
+ self.adjustingToons = []
+ for t in self.pendingToons:
+ self.adjustingToons.append(t)
+ self.adjustFsm.request('Adjusting')
+ else:
+ self.notify.debug('requestAdjust() - dont need to')
+ else:
+ self.notify.debug('requestAdjust() - already adjusting')
+ else:
+ self.notify.debug('requestAdjust() - in state: %s' % cstate)
+
+ def __handleUnexpectedExit(self, avId):
+ disconnectCode = self.air.getAvatarDisconnectReason(avId)
+ self.notify.warning('toon: %d exited unexpectedly, reason %d' % (avId, disconnectCode))
+
+ # We consider the user to have disconnected unfairly if he
+ # exited because of a window closure. For any other reason,
+ # we give him the benefit of a doubt (maybe an internet
+ # hiccup, maybe a client crash).
+ userAborted = (disconnectCode == ToontownGlobals.DisconnectCloseWindow)
+
+ self.__handleSuddenExit(avId, userAborted)
+
+ def __handleSuddenExit(self, avId, userAborted):
+ self.__removeToon(avId, userAborted=userAborted)
+ if (self.fsm.getCurrentState().getName() == 'PlayMovie' or
+ self.fsm.getCurrentState().getName() == 'MakeMovie'):
+ self.exitedToons.append(avId)
+ # See if the last toon is gone
+ self.d_setMembers()
+ if (len(self.toons) == 0):
+ self.notify.debug('last toon is gone - battle is finished')
+ self.__removeAllTasks()
+ self.timer.stop()
+ self.adjustingTimer.stop()
+ self.b_setState('Resume')
+
+ else:
+ self.needAdjust = 1
+ self.__requestAdjust()
+
+ def __removeSuit(self, suit):
+ self.notify.debug('__removeSuit(%d)' % suit.doId)
+ assert(self.suits.count(suit) == 1)
+ self.suits.remove(suit)
+ assert(self.joiningSuits.count(suit) == 0)
+ assert(self.pendingSuits.count(suit) == 0)
+ assert(self.adjustingSuits.count(suit) == 0)
+ assert(self.activeSuits.count(suit) == 1)
+ self.activeSuits.remove(suit)
+ if (self.luredSuits.count(suit) == 1):
+ self.luredSuits.remove(suit)
+ self.suitGone = 1
+ del suit.battleTrap
+
+ def __removeToon(self, toonId, userAborted=0):
+ """remove a toon before victory is achieved (run away, get sad,
+ disconnect)"""
+ self.notify.debug('__removeToon(%d)' % toonId)
+ if self.toons.count(toonId) == 0:
+ return
+
+ assert(self.toons.count(toonId) == 1)
+ # inform the battleCalculator that the toon is leaving the battle
+ # WARNING: this will delete any accumulated experience in the
+ # calc's toonSkillPtsGained dict; for parts of the game that
+ # accumulate experience over multiple battles, this will destroy
+ # the toon's accumulated experience. It seems like a safe assumption
+ # that running away, getting sad, or disconnecting should void a
+ # toon's accumulated experience.
+ self.battleCalc.toonLeftBattle(toonId)
+ self.__removeToonTasks(toonId)
+ self.toons.remove(toonId)
+ if (self.joiningToons.count(toonId) == 1):
+ self.joiningToons.remove(toonId)
+ if (self.pendingToons.count(toonId) == 1):
+ self.pendingToons.remove(toonId)
+ if (self.activeToons.count(toonId) == 1):
+ # Update suitAttack HP indices, which need to match activeToon list.
+ activeToonIdx = self.activeToons.index(toonId)
+ self.notify.debug("removing activeToons[%d], updating suitAttacks SUIT_HP_COL to match" % activeToonIdx)
+ for i in range(len(self.suitAttacks)):
+ if activeToonIdx < len(self.suitAttacks[i][SUIT_HP_COL]):
+ del self.suitAttacks[i][SUIT_HP_COL][activeToonIdx]
+ else:
+ self.notify.warning("suitAttacks %d doesn't have an HP column for active toon index %d" % (i, activeToonIdx))
+ self.activeToons.remove(toonId)
+ if (self.runningToons.count(toonId) == 1):
+ self.runningToons.remove(toonId)
+ if (self.adjustingToons.count(toonId) == 1):
+ self.notify.warning('removeToon() - toon: %d was adjusting!' % \
+ toonId)
+ self.adjustingToons.remove(toonId)
+ self.toonGone = 1
+
+ # Delete this Toon's pet proxy
+ if self.pets.has_key(toonId):
+ self.pets[toonId].requestDelete()
+ del self.pets[toonId]
+
+ # Trigger all the necessary client responses
+ self.__removeResponse(toonId)
+ self.__removeAdjustingResponse(toonId)
+ self.__removeJoinResponses(toonId)
+
+ # Ignore future avatar exit events
+ event = simbase.air.getAvatarExitEvent(toonId)
+ self.avatarExitEvents.remove(event)
+ self.ignore(event)
+
+ # And also stop listening for "avatar escaped" events.
+ event = "inSafezone-%s" % (toonId)
+ self.avatarExitEvents.remove(event)
+ self.ignore(event)
+
+ toon = simbase.air.doId2do.get(toonId)
+ if toon:
+ toon.b_setBattleId(0)
+ messageToonReleased = ("Battle releasing toon %s" % (toon.doId))
+ messenger.send(messageToonReleased, [toon.doId])
+
+ if (not userAborted):
+ # The toon exited the battle, but he may still be in the
+ # game. In case he is, make sure he knows his current HP
+ # and inventory. (He might have crashed out and left the
+ # game, but that's ok too.)
+ toon = self.getToon(toonId)
+ if (toon != None):
+ toon.hpOwnedByBattle = 0
+ toon.d_setHp(toon.hp)
+ toon.d_setInventory(toon.inventory.makeNetString())
+ # Tell the cog page manager about the cogs this toon encountered
+ self.air.cogPageManager.toonEncounteredCogs(toon, self.suitsEncountered,
+ self.getTaskZoneId())
+ else:
+ # In this case, the toon exited the battle abnormally--he
+ # explicitly disconnected. The toon is no longer in
+ # the game, so we can no longer query his current HP or
+ # inventory, and we can't update the cog page manager or
+ # anything like that.
+
+ if len(self.suits) > 0 and not self.streetBattle:
+ # If the toon was in a building battle, and there are
+ # still some suits left to the battle, assume the
+ # player cheated by pulling the plug or something and
+ # impose a penalty. (We don't impose this penalty for
+ # a street battle because the user can always run from
+ # a street battle.)
+
+ self.notify.info("toon %d aborted non-street battle; clearing inventory and hp." % (toonId))
+
+ # This is a little tricky because the toon is already
+ # gone. First, we have to create an empty
+ # DistributedToonAI object to represent the toon.
+ # There is no useful data in this object, so we can't
+ # query his hp or anything.
+ toon = DistributedToonAI.DistributedToonAI(self.air)
+ toon.doId = toonId
+
+ # Now set the inventory and HP to nothing.
+ empty = InventoryBase.InventoryBase(toon)
+ toon.b_setInventory(empty.makeNetString())
+ toon.b_setHp(0)
+
+ # And write these two fields directly to the database.
+ db = DatabaseObject.DatabaseObject(self.air, toonId)
+ db.storeObject(toon, ["setInventory", "setHp"])
+
+ self.notify.info('killing mem leak from temporary DistributedToonAI %d' % toonId)
+ toon.deleteDummy()
+
+
+ def getToon(self, toonId):
+ if (self.air.doId2do.has_key(toonId)):
+ return self.air.doId2do[toonId]
+ else:
+ self.notify.warning('getToon() - toon: %d not in repository!' \
+ % toonId)
+ return None
+
+ # Messages from DistributedBattle
+
+ def toonRequestRun(self):
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ self.notify.debug('ignoring response from toon: %d' % toonId)
+ return
+ self.notify.debug('toonRequestRun(%d)' % toonId)
+ if (not self.isRunable()):
+ self.notify.warning('toonRequestRun() - not runable')
+ return
+ updateAttacks = 0
+ if (self.activeToons.count(toonId) == 0):
+ self.notify.warning('toon tried to run, but not found in activeToons: %d'
+ % toonId)
+ return
+
+ # See if anyone else is trying to heal the running toon
+ for toon in self.activeToons:
+ if (self.toonAttacks.has_key(toon)):
+ ta = self.toonAttacks[toon]
+ track = ta[TOON_TRACK_COL]
+ level = ta[TOON_LVL_COL]
+ if (ta[TOON_TGT_COL] == toonId or
+ (track == HEAL and attackAffectsGroup(track, level) and
+ len(self.activeToons) <= 2)):
+ healerId = ta[TOON_ID_COL]
+ self.notify.debug('resetting toon: %ds attack' % \
+ healerId)
+ self.toonAttacks[toon] = getToonAttack(toon,
+ track=UN_ATTACK)
+ assert(self.responses.has_key(healerId))
+ self.responses[healerId] = 0
+ updateAttacks = 1
+ self.__makeToonRun(toonId, updateAttacks)
+ self.d_setMembers()
+ self.needAdjust = 1
+ self.__requestAdjust()
+
+ def toonRequestJoin(self, x, y, z):
+ toonId = self.air.getAvatarIdFromSender()
+ self.notify.debug('toonRequestJoin(%d)' % toonId)
+ self.signupToon(toonId, x, y, z)
+
+ def toonDied(self):
+ toonId = self.air.getAvatarIdFromSender()
+ self.notify.debug('toonDied(%d)' % toonId)
+
+ if toonId in self.toons:
+ toon = self.getToon(toonId)
+ if toon:
+ toon.hp = -1
+ toon.inventory.zeroInv(1)
+ self.__handleSuddenExit(toonId, 0)
+
+
+ def signupToon(self, toonId, x, y, z):
+ """ signupToon(toonId, x, y, z)
+
+ Adds the toon to the battle. This can be requested directly
+ by the toon (via toonRequestJoin) or by the AI.
+ """
+ if (self.toons.count(toonId)):
+ # If the toon is already part of this battle, ignore the
+ # message completely. Don't even send back a deny
+ # message, which would just confuse the client.
+ return
+
+ if (self.toonCanJoin()):
+ if self.addToon(toonId):
+ self.__joinToon(toonId, Point3(x, y, z))
+ self.d_setMembers()
+ else:
+ self.notify.warning('toonRequestJoin() - not joinable')
+ self.d_denyLocalToonJoin(toonId)
+
+ def d_denyLocalToonJoin(self, toonId):
+ self.notify.debug('network: denyLocalToonJoin(%d)' % toonId)
+ self.sendUpdateToAvatarId(toonId, 'denyLocalToonJoin', [])
+
+ ##### WaitForInput Responses #####
+
+ def resetResponses(self):
+ self.responses = {}
+ for t in self.toons:
+ self.responses[t] = 0
+ self.ignoreResponses = 0
+
+ def allToonsResponded(self):
+ for t in self.toons:
+ assert(self.responses.has_key(t))
+ if (self.responses[t] == 0):
+ return 0
+ self.ignoreResponses = 1
+ return 1
+
+ def __allPendingActiveToonsResponded(self):
+ for t in (self.pendingToons + self.activeToons):
+ assert(self.responses.has_key(t))
+ if (self.responses[t] == 0):
+ return 0
+ self.ignoreResponses = 1
+ return 1
+
+ def __allActiveToonsResponded(self):
+ for t in self.activeToons:
+ assert(self.responses.has_key(t))
+ if (self.responses[t] == 0):
+ return 0
+ self.ignoreResponses = 1
+ return 1
+
+ def __removeResponse(self, toonId):
+ assert(self.responses.has_key(toonId))
+ del self.responses[toonId]
+ if (self.ignoreResponses == 0 and (len(self.toons) > 0)):
+ currStateName = self.fsm.getCurrentState().getName()
+ if (currStateName == 'WaitForInput'):
+ if (self.__allActiveToonsResponded()):
+ self.notify.debug('removeResponse() - dont wait for movie')
+ self.__requestMovie()
+ elif (currStateName == 'PlayMovie'):
+ if (self.__allPendingActiveToonsResponded()):
+ self.notify.debug('removeResponse() - surprise movie done')
+ self.__movieDone()
+ elif (currStateName == 'Reward' or currStateName == 'BuildingReward'):
+ if (self.__allActiveToonsResponded()):
+ self.notify.debug('removeResponse() - surprise reward done')
+ self.handleRewardDone()
+
+ ##### Adjust Responses #####
+
+ def __resetAdjustingResponses(self):
+ self.adjustingResponses = {}
+ for t in self.toons:
+ self.adjustingResponses[t] = 0
+ self.ignoreAdjustingResponses = 0
+
+ def __allAdjustingToonsResponded(self):
+ for t in self.toons:
+ assert(self.adjustingResponses.has_key(t))
+ if (self.adjustingResponses[t] == 0):
+ return 0
+ self.ignoreAdjustingResponses = 1
+ return 1
+
+ def __removeAdjustingResponse(self, toonId):
+ if (self.adjustingResponses.has_key(toonId)):
+ del self.adjustingResponses[toonId]
+ if (self.ignoreAdjustingResponses == 0 and (len(self.toons) > 0)):
+ if (self.__allAdjustingToonsResponded()):
+ self.__adjustDone()
+
+ ##### Join Responses #####
+
+ def __addJoinResponse(self, avId, taskName, toon=0):
+ """ Add self as a responder to any other avatar that is joining
+ the battle if it's a toon, then create a response dictionary
+ for self to join the battle
+ """
+ if (toon == 1):
+ for jr in self.joinResponses.values():
+ jr[avId] = 0
+ assert(not self.joinResponses.has_key(avId))
+ self.joinResponses[avId] = {}
+ for t in self.toons:
+ self.joinResponses[avId][t] = 0
+ self.joinResponses[avId]['taskName'] = taskName
+
+ def __removeJoinResponses(self, avId):
+ """ Remove a response dictionary for self joining the battle (if
+ one exists), and remove self from any other existing response
+ dictionaries
+ """
+ self.__removeJoinResponse(avId)
+ removedOne = 0
+ for j in self.joinResponses.values():
+ if (j.has_key(avId)):
+ del j[avId]
+ removedOne = 1
+ if (removedOne == 1):
+ # See if the join has finished everywhere else
+ for t in self.joiningToons:
+ if (self.__allToonsRespondedJoin(t)):
+ self.__makeAvPending(t)
+
+ def __removeJoinResponse(self, avId):
+ """ Remove a response dictionary for self joining the battle (if
+ one exists)
+ """
+ if (self.joinResponses.has_key(avId)):
+ taskMgr.remove(self.joinResponses[avId]['taskName'])
+ del self.joinResponses[avId]
+
+ def __allToonsRespondedJoin(self, avId):
+ """ Return 1 if all toons in battle have responded that avId has
+ successfully joined and is in the pending list
+ """
+ assert(self.joinResponses.has_key(avId))
+ jr = self.joinResponses[avId]
+ for t in self.toons:
+ assert(jr.has_key(t))
+ if (jr[t] == 0):
+ return 0
+ return 1
+
+ def __cleanupJoinResponses(self):
+ for jr in self.joinResponses.values():
+ taskMgr.remove(jr['taskName'])
+ del jr
+
+ ##### Client Response Messages #####
+
+ def adjustDone(self):
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreAdjustingResponses == 1):
+ self.notify.debug('adjustDone() - ignoring toon: %d' % toonId)
+ return
+ elif (self.adjustFsm.getCurrentState().getName() != 'Adjusting'):
+ self.notify.warning('adjustDone() - in state %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('adjustDone() - toon: %d not in toon list' % \
+ toonId)
+ return
+ assert(self.adjustingResponses.has_key(toonId))
+ self.adjustingResponses[toonId] += 1
+ self.notify.debug('toon: %d done adjusting' % toonId)
+ if (self.__allAdjustingToonsResponded()):
+ self.__adjustDone()
+
+ def timeout(self):
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ self.notify.debug('timeout() - ignoring toon: %d' % toonId)
+ return
+ elif (self.fsm.getCurrentState().getName() != 'WaitForInput'):
+ self.notify.warning('timeout() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('timeout() - toon: %d not in toon list' % \
+ toonId)
+ return
+ self.toonAttacks[toonId] = getToonAttack(toonId)
+ self.d_setChosenToonAttacks()
+ assert(self.responses.has_key(toonId))
+ self.responses[toonId] += 1
+ self.notify.debug('toon: %d timed out' % toonId)
+ if (self.__allActiveToonsResponded()):
+ self.__requestMovie(timeout=1)
+
+ def movieDone(self):
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ self.notify.debug('movieDone() - ignoring toon: %d' % toonId)
+ return
+ elif (self.fsm.getCurrentState().getName() != 'PlayMovie'):
+ self.notify.warning('movieDone() - in state %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('movieDone() - toon: %d not in toon list' % \
+ toonId)
+ return
+ assert(self.responses.has_key(toonId))
+ self.responses[toonId] += 1
+ self.notify.debug('toon: %d done with movie' % toonId)
+ if (self.__allPendingActiveToonsResponded()):
+ self.__movieDone()
+ else:
+ # Reset the timer to give the slowpokes a few seconds
+ # longer than the first (or most recent) toon to reply.
+ self.timer.stop()
+ self.timer.startCallback(TIMEOUT_PER_USER, self.__serverMovieDone)
+
+
+ def rewardDone(self):
+ """ rewardDone()
+ """
+ toonId = self.air.getAvatarIdFromSender()
+ stateName = self.fsm.getCurrentState().getName()
+ if (self.ignoreResponses == 1):
+ self.notify.debug('rewardDone() - ignoring toon: %d' % toonId)
+ return
+ elif (stateName not in ('Reward', 'BuildingReward', 'FactoryReward',
+ 'MintReward', 'StageReward', 'CountryClubReward')):
+ self.notify.warning('rewardDone() - in state %s' % stateName)
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('rewardDone() - toon: %d not in toon list' % \
+ toonId)
+ return
+ assert(self.responses.has_key(toonId))
+ self.responses[toonId] += 1
+ self.notify.debug('toon: %d done with reward' % toonId)
+ if (self.__allActiveToonsResponded()):
+ self.handleRewardDone()
+ else:
+ # Reset the timer to give the slowpokes a few seconds
+ # longer than the first (or most recent) toon to reply.
+ self.timer.stop()
+ self.timer.startCallback(TIMEOUT_PER_USER, self.serverRewardDone)
+
+ def assignRewards(self):
+ if (self.rewardHasPlayed == 1):
+ self.notify.debug('handleRewardDone() - reward has already played')
+ return
+ self.rewardHasPlayed = 1
+
+ BattleExperienceAI.assignRewards(
+ self.activeToons, self.battleCalc.toonSkillPtsGained,
+ self.suitsKilled, self.getTaskZoneId(), self.helpfulToons)
+
+ def joinDone(self, avId):
+ """ joinDone(avId)
+ """
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.toons.count(toonId) == 0):
+ self.notify.warning('joinDone() - toon: %d not in toon list' % \
+ toonId)
+ return
+ if (not self.joinResponses.has_key(avId)):
+ self.notify.debug('joinDone() - no entry for: %d - ignoring: %d' \
+ % (avId, toonId))
+ return
+ jr = self.joinResponses[avId]
+ if (jr.has_key(toonId)):
+ jr[toonId] += 1
+ self.notify.debug('client with localToon: %d done joining av: %d' % \
+ (toonId, avId))
+ if (self.__allToonsRespondedJoin(avId)):
+ self.__makeAvPending(avId)
+
+ def requestAttack(self, track, level, av):
+ """ requestAttack(track, level, av)
+ """
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ self.notify.debug('requestAttack() - ignoring toon: %d' % toonId)
+ return
+ elif (self.fsm.getCurrentState().getName() != 'WaitForInput'):
+ self.notify.warning('requestAttack() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.activeToons.count(toonId) == 0):
+ self.notify.warning('requestAttack() - toon: %d not in toon list' \
+ % toonId)
+ return
+ self.notify.debug('requestAttack(%d, %d, %d, %d)' % (toonId, track, \
+ level, av))
+ toon = self.getToon(toonId)
+ if (toon == None):
+ self.notify.warning('requestAttack() - no toon: %d' % toonId)
+ return
+ assert(toon.inventory != None)
+ validResponse = 1
+ if (track == SOS):
+ # TODO: security breach. We should validate that the
+ # avatar is a friend of the toon here, if possible. Can
+ # we do that?
+ self.notify.debug('toon: %d calls for help' % toonId)
+ self.air.writeServerEvent('friendSOS', toonId, '%s' % (av))
+ self.toonAttacks[toonId] = getToonAttack(toonId, track=SOS,
+ target=av)
+ elif (track == NPCSOS):
+ self.notify.debug('toon: %d calls for help' % toonId)
+ self.air.writeServerEvent('NPCSOS', toonId, '%s' % (av))
+ # Make sure the toon has the friend, and then remove it
+ toon = self.getToon(toonId)
+ if (toon == None):
+ return
+ if (toon.NPCFriendsDict.has_key(av)):
+ npcCollision = 0
+ if (self.npcAttacks.has_key(av)):
+ callingToon = self.npcAttacks[av]
+ if (self.activeToons.count(callingToon) == 1):
+ self.toonAttacks[toonId] = getToonAttack(toonId, track=PASS)
+ npcCollision = 1
+ if (npcCollision == 0):
+ # MPG - in case people hit the back button, we
+ # need to delete SOS toons in movieDone()
+ #toon.NPCFriendsDict[av] -= 1
+ #if (toon.NPCFriendsDict[av] <= 0):
+ # del toon.NPCFriendsDict[av]
+ #toon.d_setNPCFriendsDict(toon.NPCFriendsDict)
+ self.toonAttacks[toonId] = getToonAttack(toonId,
+ track=NPCSOS, level=5, target=av)
+ self.numNPCAttacks += 1
+ self.npcAttacks[av] = toonId
+ #import pdb; pdb.set_trace()
+
+ elif (track == PETSOS):
+ self.notify.debug('toon: %d calls for pet: %d' % (toonId, av))
+ self.air.writeServerEvent('PETSOS', toonId, '%s' % (av))
+ # Make sure the toon has the pet
+ toon = self.getToon(toonId)
+ if (toon == None):
+ return
+ if not self.validate(toonId, (level in toon.petTrickPhrases), 'requestAttack: invalid pet trickId: %s' % (level)):
+ return
+ self.toonAttacks[toonId] = getToonAttack(toonId,
+ track=PETSOS, level=level, target=av)
+ elif (track == UN_ATTACK):
+ self.notify.debug('toon: %d changed its mind' % toonId)
+ self.toonAttacks[toonId] = getToonAttack(toonId, track=UN_ATTACK)
+ if (self.responses.has_key(toonId)):
+ self.responses[toonId] = 0
+ validResponse = 0
+ elif (track == PASS):
+ assert(self.notify.debug('toon: %d passed' % toonId))
+ self.toonAttacks[toonId] = getToonAttack(toonId, track=PASS)
+ elif (track == FIRE):
+ #assert(self.notify.debug('toon: %d passed' % toonId))
+ self.toonAttacks[toonId] = getToonAttack(toonId, track=FIRE, target=av)
+
+ else:
+ # If the track is not one of the above special values, it
+ # must be one of the valid tracks in
+ # ToontownBattleGlobals.
+ if not self.validate(toonId, (track >= 0 and track <= MAX_TRACK_INDEX),
+ 'requestAttack: invalid track %s' % (track)):
+ return
+ if not self.validate(toonId, (level >= 0 and level <= (MAX_LEVEL_INDEX)),
+ 'requestAttack: invalid level %s' % (level)):
+ return
+
+ # For now, we assume that the avId is being correctly
+ # validated downstream of here.
+
+ if (toon.inventory.numItem(track, level) == 0):
+ # TODO: fix BUG: Somehow, this clause is getting executed when
+ # the toon still has one prop left...
+ self.notify.warning('requestAttack() - toon has no item track: \
+ %d level: %d' % (track, level))
+ self.toonAttacks[toonId] = getToonAttack(toonId)
+ return
+
+ if (track == HEAL):
+ # See if the target for the heal is running away
+ if (self.runningToons.count(av) == 1 or
+ (attackAffectsGroup(track, level) and
+ len(self.activeToons) < 2)):
+ assert(self.notify.debug('resetting toon: %ds attack' % \
+ toonId))
+ self.toonAttacks[toonId] = getToonAttack(toonId,
+ track=UN_ATTACK)
+ validResponse = 0
+ else:
+ self.toonAttacks[toonId] = getToonAttack(toonId, track=track,
+ level=level, target=av)
+ else:
+ self.toonAttacks[toonId] = getToonAttack(toonId, track=track,
+ level=level, target=av)
+ if (av == -1 and not attackAffectsGroup(track, level)):
+ # No target yet; it's not yet a complete attack.
+ validResponse = 0
+
+
+ self.d_setChosenToonAttacks()
+ assert(self.responses.has_key(toonId))
+ if (validResponse == 1):
+ self.responses[toonId] += 1
+ self.notify.debug('toon: %d chose an attack' % toonId)
+ if (self.__allActiveToonsResponded()):
+ self.__requestMovie()
+
+ def requestPetProxy(self, av):
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ self.notify.debug('requestPetProxy() - ignoring toon: %d' % toonId)
+ return
+ elif (self.fsm.getCurrentState().getName() != 'WaitForInput'):
+ self.notify.warning('requestPetProxy() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.activeToons.count(toonId) == 0):
+ self.notify.warning('requestPetProxy() - toon: %d not in toon list' \
+ % toonId)
+ return
+ self.notify.debug('requestPetProxy(%s, %s)' % (toonId, av))
+ toon = self.getToon(toonId)
+ if (toon == None):
+ self.notify.warning('requestPetProxy() - no toon: %d' % toonId)
+ return
+
+ petId = toon.getPetId()
+ zoneId = self.zoneId
+ if (petId == av):
+ # See if pet has been generated already
+ #if simbase.air.doId2do.has_key(petId):
+ # Make sure to move it to the new zone
+ #petProxy = simbase.air.doId2do[petId]
+ #simbase.air.sendSetZone(petProxy, zoneId)
+ #petProxy.zoneId = zoneId
+ if not self.pets.has_key(toonId):
+ def handleGetPetProxy(success, petProxy, petId=petId, zoneId=zoneId, toonId=toonId):
+ if success:
+ if petId not in simbase.air.doId2do:
+ simbase.air.requestDeleteDoId(petId)
+ else:
+ petDO = simbase.air.doId2do[petId]
+ petDO.requestDelete()
+ simbase.air.deleteDistObject(petDO)
+ petProxy.dbObject = 1
+ petProxy.generateWithRequiredAndId(petId,
+ self.air.districtId,
+ zoneId)
+ petProxy.broadcastDominantMood()
+ self.pets[toonId] = petProxy
+ else:
+ self.notify.warning("error generating petProxy: %s" % petId)
+ self.getPetProxyObject(petId, handleGetPetProxy)
+
+ # Misc
+
+ def suitCanJoin(self):
+ return ((len(self.suits) < self.maxSuits) and (self.isJoinable()))
+
+ def toonCanJoin(self):
+ return ((len(self.toons) < 4) and (self.isJoinable()))
+
+ def __requestMovie(self, timeout=0):
+ #import pdb; pdb.set_trace()
+ if (self.adjustFsm.getCurrentState().getName() == 'Adjusting'):
+ self.notify.debug('__requestMovie() - in Adjusting')
+ self.movieRequested = 1
+ else:
+ movieDelay = 0
+ if (len(self.activeToons) == 0):
+ self.notify.warning('only pending toons left in battle %s, toons = %s' % (self.doId, self.toons))
+ elif (len(self.activeSuits) == 0):
+ self.notify.warning('only pending suits left in battle %s, suits = %s' % (self.doId, self.suits))
+ elif (len(self.activeToons) > 1 and not timeout):
+ # If there are multiple toons involved in the battle,
+ # pad the start of the movie by 1 second so other
+ # toons can see what the last toon chose as his attack
+ movieDelay = 1
+
+ self.fsm.request('MakeMovie')
+ if movieDelay:
+ taskMgr.doMethodLater(0.8, self.__makeMovie,
+ self.uniqueName('make-movie'))
+ self.taskNames.append(self.uniqueName('make-movie'))
+ else:
+ self.__makeMovie()
+
+
+ def __makeMovie(self, task=None):
+ self.notify.debug('makeMovie()')
+ assert(self.isGenerated())
+ assert(self.fsm.getCurrentState().getName() == 'MakeMovie')
+ # I think the factory battle crash has to do with this doLater firing
+ # after the battle has requested delete and before delete is called.
+ # See commented-out implementation of requestDelete for proposed
+ # solution if this is the case (I could not repro the crash)
+ if self._DOAI_requestedDelete:
+ self.notify.warning(
+ 'battle %s requested delete, then __makeMovie was called!' %
+ self.doId)
+ if hasattr(self, 'levelDoId'):
+ self.notify.warning('battle %s in level %s' % (
+ self.doId, self.levelDoId))
+ return
+
+ self.__removeTaskName(self.uniqueName('make-movie'))
+ if (self.movieHasBeenMade == 1):
+ self.notify.debug('__makeMovie() - movie has already been made')
+ return
+ self.movieRequested = 0
+ self.movieHasBeenMade = 1
+ self.movieHasPlayed = 0
+ self.rewardHasPlayed = 0
+ # Make sure all toons have an attack entry (even if it's a no-attack)
+ for t in self.activeToons:
+ if (not self.toonAttacks.has_key(t)):
+ self.toonAttacks[t] = getToonAttack(t)
+ attack = self.toonAttacks[t]
+
+ # Replace any PASS or UN_ATTACK with a NO_ATTACK
+ if (attack[TOON_TRACK_COL] == PASS or
+ attack[TOON_TRACK_COL] == UN_ATTACK):
+ self.toonAttacks[t] = getToonAttack(t)
+
+ if self.toonAttacks[t][TOON_TRACK_COL] != NO_ATTACK:
+ # so he didn't pass or un attack, he must have done something useful
+ self.addHelpfulToon(t)
+
+ self.battleCalc.calculateRound()
+
+ # Tell the toons how much experience they will earn so far.
+ # Also, from this point on until the end of the movie, the
+ # toons will be allowed to accumulate more than their maxHp,
+ # since we'll fix it up at the end of the movie.
+ for t in self.activeToons:
+ self.sendEarnedExperience(t)
+
+ toon = self.getToon(t)
+ if toon != None:
+ toon.hpOwnedByBattle = 1
+ if toon.immortalMode:
+ # A free toonup first, to guarantee the battle
+ # round won't kill this immortal toon.
+ toon.toonUp(toon.maxHp)
+
+ self.d_setMovie()
+ self.b_setState('PlayMovie')
+ return Task.done
+
+ def sendEarnedExperience(self, toonId):
+ # Sends the experience earned so far to the toon, so he can
+ # update his display to show which gags are clipped by the
+ # experience cap.
+ toon = self.getToon(toonId)
+ if toon != None:
+ expList = self.battleCalc.toonSkillPtsGained.get(toonId, None)
+ if expList == None:
+ toon.d_setEarnedExperience([])
+ else:
+ roundList = []
+ for exp in expList:
+ roundList.append(int(exp + 0.5))
+ toon.d_setEarnedExperience(roundList)
+
+
+ # Each state will have an enter function, an exit function,
+ # and a datagram handler, which will be set during each enter function.
+
+ # Specific State functions
+
+ ##### Off state #####
+
+ def enterOff(self):
+ return None
+
+ def exitOff(self):
+ return None
+
+ ##### FaceOff state #####
+
+ def enterFaceOff(self):
+ return None
+
+ def exitFaceOff(self):
+ return None
+
+ ##### WaitForJoin state #####
+
+ def enterWaitForJoin(self):
+ self.notify.debug('enterWaitForJoin()')
+ if (len(self.activeSuits) > 0):
+ self.b_setState('WaitForInput')
+ else:
+ self.notify.debug('enterWaitForJoin() - no active suits')
+ self.runableFsm.request('Runable')
+ self.resetResponses()
+ self.__requestAdjust()
+ return None
+
+ def exitWaitForJoin(self):
+ return None
+
+ ##### WaitForInput state #####
+
+ def enterWaitForInput(self):
+ self.notify.debug('enterWaitForInput()')
+ self.joinableFsm.request('Joinable')
+ self.runableFsm.request('Runable')
+ self.resetResponses()
+ self.__requestAdjust()
+ if not self.tutorialFlag:
+ # No timers during tutorial.
+ self.timer.startCallback(SERVER_INPUT_TIMEOUT,
+ self.__serverTimedOut)
+ self.npcAttacks = {}
+
+ # handle autoRestock
+ for toonId in self.toons:
+ if bboard.get('autoRestock-%s' % toonId, False):
+ toon = self.air.doId2do.get(toonId)
+ if toon is not None:
+ toon.doRestock(0)
+
+ def exitWaitForInput(self):
+ self.npcAttacks = {}
+ self.timer.stop()
+ return None
+
+ def __serverTimedOut(self):
+ self.notify.debug('wait for input timed out on server')
+ self.ignoreResponses = 1
+ self.__requestMovie(timeout=1)
+
+ ##### MakeMovie state #####
+
+ def enterMakeMovie(self):
+ self.notify.debug('enterMakeMovie()')
+ self.runableFsm.request('Unrunable')
+ self.resetResponses()
+ return None
+
+ def exitMakeMovie(self):
+ return None
+
+ ##### PlayMovie state #####
+
+ def enterPlayMovie(self):
+ self.notify.debug('enterPlayMovie()')
+ self.joinableFsm.request('Joinable')
+ self.runableFsm.request('Unrunable')
+ self.resetResponses()
+ # Estimate an upper bound for the length of the movie
+ movieTime = (TOON_ATTACK_TIME * (len(self.activeToons) + \
+ self.numNPCAttacks) + \
+ SUIT_ATTACK_TIME * len(self.activeSuits) + \
+ SERVER_BUFFER_TIME)
+ self.numNPCAttacks = 0
+ self.notify.debug('estimated upper bound of movie time: %f' % movieTime)
+ self.timer.startCallback(movieTime, self.__serverMovieDone)
+
+ # print out the experience table
+ #print 'tSPG: %s' % self.battleCalc.toonSkillPtsGained
+
+ def __serverMovieDone(self):
+ self.notify.debug('movie timed out on server')
+ self.ignoreResponses = 1
+ self.__movieDone()
+
+ def serverRewardDone(self):
+ self.notify.debug('reward timed out on server')
+ self.ignoreResponses = 1
+ self.handleRewardDone()
+
+ def handleRewardDone(self):
+ self.b_setState('Resume')
+
+ def exitPlayMovie(self):
+ self.timer.stop()
+ return None
+
+ def __movieDone(self):
+ self.notify.debug('__movieDone() - movie is finished')
+ if (self.movieHasPlayed == 1):
+ self.notify.debug('__movieDone() - movie had already finished')
+ return
+ self.movieHasBeenMade = 0
+ self.movieHasPlayed = 1
+ self.ignoreResponses = 1
+ needUpdate = 0
+
+ # Calculate toon experience and remove any dead suits
+ toonHpDict = {}
+ for toon in self.activeToons:
+ toonHpDict[toon] = [0, 0, 0]
+ actualToon = self.getToon(toon)
+ assert(actualToon != None)
+ self.notify.debug("BEFORE ROUND: toon: %d hp: %d" % (toon, actualToon.hp))
+ deadSuits = []
+ trapDict = {}
+ suitsLuredOntoTraps = []
+ npcTrapAttacks = []
+ for activeToon in (self.activeToons + self.exitedToons):
+ if (self.toonAttacks.has_key(activeToon)):
+ attack = self.toonAttacks[activeToon]
+ track = attack[TOON_TRACK_COL]
+ npc_level = None
+ if (track == NPCSOS):
+ track, npc_level, npc_hp = NPCToons.getNPCTrackLevelHp(attack[TOON_TGT_COL])
+ if (track == None):
+ track = NPCSOS
+ elif (track == TRAP):
+ npcTrapAttacks.append(attack)
+ toon = self.getToon(attack[TOON_ID_COL])
+ av = attack[TOON_TGT_COL]
+ if (toon != None and toon.NPCFriendsDict.has_key(av)):
+ toon.NPCFriendsDict[av] -= 1
+ if (toon.NPCFriendsDict[av] <= 0):
+ del toon.NPCFriendsDict[av]
+ toon.d_setNPCFriendsDict(toon.NPCFriendsDict)
+ continue
+ if (track != NO_ATTACK):
+ toonId = attack[TOON_ID_COL]
+ assert(toonId == activeToon)
+ level = attack[TOON_LVL_COL]
+ if (npc_level != None):
+ level = npc_level
+ if (attack[TOON_TRACK_COL] == NPCSOS):
+ toon = self.getToon(toonId)
+ av = attack[TOON_TGT_COL]
+ if (toon != None and toon.NPCFriendsDict.has_key(av)):
+ toon.NPCFriendsDict[av] -= 1
+ if (toon.NPCFriendsDict[av] <= 0):
+ del toon.NPCFriendsDict[av]
+ toon.d_setNPCFriendsDict(toon.NPCFriendsDict)
+ elif (track == PETSOS):
+ pass
+ elif (track == FIRE):
+ pass
+ elif (track != SOS):
+ toon = self.getToon(toonId)
+ if (toon != None):
+ check = toon.inventory.useItem(track, level)
+ if check == -1: #check for cheater
+ self.air.writeServerEvent('suspicious', toonId, 'Toon generating movie for non-existant gag track %s level %s' % (track, level))
+ self.notify.warning("generating movie for non-existant gag track %s level %s! avId: %s" % (track, level, toonId))
+ toon.d_setInventory(toon.inventory.makeNetString())
+ hps = attack[TOON_HP_COL]
+ if (track == SOS):
+ self.notify.debug('toon: %d called for help' % toonId)
+ elif (track == NPCSOS):
+ self.notify.debug('toon: %d called for help' % toonId)
+ elif (track == PETSOS):
+ self.notify.debug('toon: %d called for pet' % toonId)
+ for i in range(len(self.activeToons)):
+ toon = self.getToon(self.activeToons[i])
+ if (toon != None):
+ if (i < len(hps)):
+ hp = hps[i]
+ # If the PETSOS fails, the hp will be -1
+ # so skip to avoid losing hp
+ if hp > 0:
+ toonHpDict[toon.doId][0] += hp
+ self.notify.debug("pet heal: toon: %d healed for hp: %d" % (toon.doId, hp))
+ else:
+ self.notify.warning("Invalid targetIndex %s in hps %s." % (i, hps))
+
+ elif (track == NPC_RESTOCK_GAGS):
+ for at in self.activeToons:
+ toon = self.getToon(at)
+ if (toon != None):
+ toon.inventory.NPCMaxOutInv(npc_level)
+ toon.d_setInventory(toon.inventory.makeNetString())
+ elif (track == HEAL):
+ # Odd level heals affect all toons (except the caster)
+ # except in the case of an NPC heal, which gets all
+ # toons
+ if (levelAffectsGroup(HEAL, level)):
+ for i in range(len(self.activeToons)):
+ at = self.activeToons[i]
+ if (at != toonId or
+ attack[TOON_TRACK_COL] == NPCSOS):
+ toon = self.getToon(at)
+ if (toon != None):
+ if i < len(hps):
+ hp = hps[i]
+ else:
+ self.notify.warning("Invalid targetIndex %s in hps %s." % (i, hps))
+ hp = 0
+ toonHpDict[toon.doId][0] += hp
+ self.notify.debug("HEAL: toon: %d healed for hp: %d" % (toon.doId, hp))
+ else:
+ targetId = attack[TOON_TGT_COL]
+ toon = self.getToon(targetId)
+ if ((toon != None) and
+ (targetId in self.activeToons)):
+ targetIndex = self.activeToons.index(targetId)
+ if targetIndex < len(hps):
+ hp = hps[targetIndex]
+ else:
+ self.notify.warning("Invalid targetIndex %s in hps %s." % (targetIndex, hps))
+ hp = 0
+ toonHpDict[toon.doId][0] += hp
+ else:
+ # Odd level lures affect all suits
+ # Sounds affect all suits
+ # NPC drops affect all suits
+ #import pdb; pdb.set_trace()
+ if (attackAffectsGroup(track, level, attack[TOON_TRACK_COL])):
+ for suit in self.activeSuits:
+ targetIndex = self.activeSuits.index(suit)
+ if targetIndex < 0 or targetIndex >= len(hps):
+ self.notify.warning("Got attack (%s, %s) on target suit %s, but hps has only %s entries: %s" % (track, level, targetIndex, len(hps), hps))
+ else:
+ hp = hps[targetIndex]
+ if (hp > 0 and track == LURE):
+ if suit.battleTrap == UBER_GAG_LEVEL_INDEX:
+ pass
+ #trainTrapTriggered = True
+
+ suit.battleTrap = NO_TRAP
+ needUpdate = 1
+ # Clear out any traps on this suit
+ if (trapDict.has_key(suit.doId)):
+ del trapDict[suit.doId]
+ if (suitsLuredOntoTraps.count(suit) == 0):
+ suitsLuredOntoTraps.append(suit)
+
+ #WARNING, this section of code is duplicated below
+ if (track == TRAP):
+ targetId = suit.doId
+ if (trapDict.has_key(targetId)):
+ trapDict[targetId].append(attack)
+ else:
+ trapDict[targetId] = [attack]
+ needUpdate = 1
+
+ died = attack[SUIT_DIED_COL] & (1<= len(hps):
+ self.notify.warning("Got attack (%s, %s) on target suit %s, but hps has only %s entries: %s" % (track, level, targetIndex, len(hps), hps))
+ else:
+ hp = hps[targetIndex]
+ #WARNING this section of code is duplicated above
+ if (track == TRAP):
+ if (trapDict.has_key(targetId)):
+ trapDict[targetId].append(attack)
+ else:
+ trapDict[targetId] = [attack]
+ if (hp > 0 and track == LURE):
+ oldBattleTrap = target.battleTrap
+ if oldBattleTrap == UBER_GAG_LEVEL_INDEX:
+ #trainTrapTriggered = True
+ pass
+ target.battleTrap = NO_TRAP
+ needUpdate = 1
+ # Clear out any traps on this suit
+ if (trapDict.has_key(target.doId)):
+ del trapDict[target.doId]
+ if (suitsLuredOntoTraps.count(target) == 0):
+ suitsLuredOntoTraps.append(target)
+ #at this point we need to clear out the other suits in a traintrack trap
+ if oldBattleTrap == UBER_GAG_LEVEL_INDEX:
+ for otherSuit in self.activeSuits:
+ if not otherSuit == target:
+ # Clear out any traps on this suit
+ otherSuit.battleTrap = NO_TRAP
+ if (trapDict.has_key(otherSuit.doId)):
+ del trapDict[otherSuit.doId]
+
+ died = attack[SUIT_DIED_COL] & (1< 1)
+ self.notify.debug("movieDone() - traps collided")
+ if (target != None):
+ target.battleTrap = NO_TRAP
+
+ if self.battleCalc.trainTrapTriggered:
+ #necessary when train trap and dollar bill lure used in the same round
+ self.notify.debug('Train trap triggered, clearing all traps')
+ for otherSuit in self.activeSuits:
+ self.notify.debug('suit =%d, oldBattleTrap=%d' %(otherSuit.doId, otherSuit.battleTrap))
+ # Clear out any traps on this suit
+ otherSuit.battleTrap = NO_TRAP
+ #if (trapDict.has_key(otherSuit.doId)):
+ # del trapDict[otherSuit.doId]
+
+ # Update the lured suits list to match that of the battle calculator
+ currLuredSuits = self.battleCalc.getLuredSuits()
+ # See if the list has changed
+ if (len(self.luredSuits) == len(currLuredSuits)):
+ for suit in self.luredSuits:
+ if (currLuredSuits.count(suit.doId) == 0):
+ needUpdate = 1
+ break
+ else:
+ needUpdate = 1
+ self.luredSuits = []
+ for i in currLuredSuits:
+ assert(self.air.doId2do.has_key(i))
+ suit = self.air.doId2do[i]
+ assert(suit in self.suits)
+ self.luredSuits.append(suit)
+ self.notify.debug('movieDone() - suit: %d is lured' % i)
+
+ # Handle NPC traps
+ for attack in npcTrapAttacks:
+ assert(attack[TOON_TRACK_COL] == NPCSOS)
+ track, level, hp = NPCToons.getNPCTrackLevelHp(attack[TOON_TGT_COL])
+ assert(track == TRAP)
+ for suit in self.activeSuits:
+ # NPC traps are laid on suits that are unlured and currently
+ # have no traps in front of them (this includes recently
+ # collided traps)
+ if (self.luredSuits.count(suit) == 0 and
+ suit.battleTrap == NO_TRAP):
+ suit.battleTrap = level
+ # Assume the existence of an NPC trap implies at least one
+ # was placed successfully
+ needUpdate = 1
+
+ for suit in deadSuits:
+ self.notify.debug('removing dead suit: %d' % suit.doId)
+ if suit.isDeleted():
+ self.notify.debug('whoops, suit %d is deleted.' % suit.doId)
+ else:
+ self.notify.debug('suit had revives? %d' % suit.getMaxSkeleRevives())
+ encounter = {'type': suit.dna.name,
+ 'level': suit.getActualLevel(),
+ 'track': suit.dna.dept,
+ 'isSkelecog': suit.getSkelecog(),
+ 'isForeman': suit.isForeman(),
+ 'isVP': 0,
+ 'isCFO': 0,
+ 'isSupervisor': suit.isSupervisor(),
+ 'isVirtual': suit.isVirtual(),
+ 'hasRevives': suit.getMaxSkeleRevives(),
+ # Put a copy of active toons in the dict
+ # Only they get credit for killing this suit
+ 'activeToons': self.activeToons[:],
+ }
+ self.suitsKilled.append(encounter)
+ self.suitsKilledThisBattle.append(encounter)
+
+ self.__removeSuit(suit)
+ needUpdate = 1
+ suit.resume()
+
+ # Handle the case where the last active suit died but another is
+ # joining
+ lastActiveSuitDied = 0
+ if (len(self.activeSuits) == 0 and len(self.pendingSuits) == 0):
+ lastActiveSuitDied = 1
+
+ # Calculate toon hit points and remove any dead toons
+ for i in range(4):
+ attack = self.suitAttacks[i][SUIT_ATK_COL]
+ if (attack != NO_ATTACK):
+ suitId = self.suitAttacks[i][SUIT_ID_COL]
+ assert(suitId != -1)
+ suit = self.findSuit(suitId)
+ if (suit == None):
+ self.notify.warning('movieDone() - suit: %d is gone!' % \
+ suitId)
+ continue
+ if not (hasattr(suit, "dna") and suit.dna):
+ #RAU couldn't reproduce this crash, log it in case it comes up again
+ toonId = self.air.getAvatarIdFromSender()
+ self.notify.warning("_movieDone avoiding crash, sender=%s but suit has no dna" % toonId)
+ self.air.writeServerEvent('suspicious', toonId, '_movieDone avoiding crash, suit has no dna')
+ continue
+
+ adict = getSuitAttack(suit.getStyleName(), suit.getLevel(),
+ attack)
+ hps = self.suitAttacks[i][SUIT_HP_COL]
+ if (adict['group'] == ATK_TGT_GROUP):
+ for activeToon in self.activeToons:
+ toon = self.getToon(activeToon)
+ if (toon != None):
+ targetIndex = self.activeToons.index(activeToon)
+ toonDied = self.suitAttacks[i][TOON_DIED_COL] & \
+ (1<= len(hps):
+ self.notify.warning('DAMAGE: toon %s is no longer in battle!' % (activeToon))
+ else:
+ hp = hps[targetIndex]
+ if (hp > 0):
+ self.notify.debug('DAMAGE: toon: %d hit for dmg: %d' % (activeToon, hp))
+ if (toonDied != 0):
+ toonHpDict[toon.doId][2] = 1
+ toonHpDict[toon.doId][1] += hp
+
+ elif (adict['group'] == ATK_TGT_SINGLE):
+ targetIndex = self.suitAttacks[i][SUIT_TGT_COL]
+ if (targetIndex >= len(self.activeToons)):
+ self.notify.warning('movieDone() - toon: %d gone!' \
+ % targetIndex)
+ break
+ toonId = self.activeToons[targetIndex]
+ toon = self.getToon(toonId)
+ toonDied = self.suitAttacks[i][TOON_DIED_COL] & \
+ (1<= len(hps):
+ self.notify.warning('DAMAGE: toon %s is no longer in battle!' % (toonId))
+ else:
+ hp = hps[targetIndex]
+ if (hp > 0):
+ self.notify.debug('DAMAGE: toon: %d hit for dmg: %d' % (toonId, hp))
+ if (toonDied != 0):
+ toonHpDict[toon.doId][2] = 1
+ toonHpDict[toon.doId][1] += hp
+
+ # Now we go through and ensure client and AI are on the same
+ # page with HP values.
+ deadToons = []
+ for activeToon in self.activeToons:
+ assert(toonHpDict.has_key(activeToon))
+ hp = toonHpDict[activeToon]
+ toon = self.getToon(activeToon)
+ if (toon != None):
+ self.notify.debug("AFTER ROUND: currtoonHP: %d toonMAX: %d hheal: %d damage: %d" % (toon.hp, toon.maxHp, hp[0], hp[1]))
+
+ toon.hpOwnedByBattle = 0
+
+ # hpDelta is the amount of heals applied during
+ # the round, less the amount of damage taken.
+ hpDelta = hp[0] - hp[1]
+ if hpDelta >= 0:
+ toon.toonUp(hpDelta, quietly = 1)
+ else:
+ toon.takeDamage(-hpDelta, quietly = 1)
+
+ if toon.hp <= 0:
+ # If the toon is now dead, get him out of
+ # the battle.
+ self.notify.debug('movieDone() - toon: %d was killed' % \
+ activeToon)
+ # __removeToon() will broadcast the dead toon's inv
+ toon.inventory.zeroInv(1)
+ deadToons.append(activeToon)
+
+ self.notify.debug('AFTER ROUND: toon: %d setHp: %d' % \
+ (toon.doId, toon.hp))
+ for deadToon in deadToons:
+ self.__removeToon(deadToon)
+ needUpdate = 1
+
+ self.clearAttacks()
+
+ # Send an inactive movie message
+ self.d_setMovie()
+ self.d_setChosenToonAttacks()
+
+ # Defined differently in DistributedBattleAI and
+ # DistributedBattleBldgAI
+ self.localMovieDone(needUpdate, deadToons, deadSuits,
+ lastActiveSuitDied)
+
+
+ ##### Reward state #####
+
+ ##### Resume state #####
+
+ def enterResume(self):
+ assert(self.notify.debug('enterResume()'))
+ for suit in self.suits:
+ self.notify.info('battle done, resuming suit: %d' % suit.doId)
+ if suit.isDeleted():
+ self.notify.info('whoops, suit %d is deleted.' % suit.doId)
+ else:
+ suit.resume()
+
+ self.suits = []
+ self.joiningSuits = []
+ self.pendingSuits = []
+ self.adjustingSuits = []
+ self.activeSuits = []
+ self.luredSuits = []
+
+ # Actually, removing the toons from the battle seems to send
+ # them to the playground. So don't do that.
+
+ #self.toons = []
+ #self.joiningToons = []
+ #self.pendingToons = []
+ #self.activeToons = []
+ #self.runningToons = []
+ #self.d_setMembers()
+
+ for toonId in self.toons:
+ toon = simbase.air.doId2do.get(toonId)
+ if toon:
+ toon.b_setBattleId(0)
+ messageToonReleased = ("Battle releasing toon %s" % (toon.doId))
+ messenger.send(messageToonReleased, [toon.doId])
+
+ # Stop responding to avatar exit events
+ for exitEvent in self.avatarExitEvents:
+ self.ignore(exitEvent)
+
+ # Log the suits killed for this battle only, just for
+ # marketing purposes.
+ eventMsg = {}
+ for encounter in self.suitsKilledThisBattle:
+ cog = encounter['type']
+ level = encounter['level']
+
+ msgName = '%s%s' % (cog, level)
+
+ if encounter['isSkelecog']:
+ msgName += "+"
+
+ if eventMsg.has_key(msgName):
+ eventMsg[msgName] += 1
+ else:
+ eventMsg[msgName] = 1
+
+ # Now format the message for the AI.
+ msgText = ''
+ for msgName, count in eventMsg.items():
+ if msgText != '':
+ msgText += ','
+ msgText += '%s%s' % (count, msgName)
+
+ self.air.writeServerEvent(
+ 'battleCogsDefeated', self.doId, "%s|%s" % (msgText,
+ self.getTaskZoneId()))
+
+
+ def exitResume(self):
+ pass
+
+ ########################
+ ##### Joinable ClassicFSM #####
+ ########################
+
+ def isJoinable(self):
+ """ isJoinable()
+ """
+ return (self.joinableFsm.getCurrentState().getName() == 'Joinable')
+
+ ##### Joinable state #####
+
+ # Suits or Toons are allowed to join the battle
+
+ def enterJoinable(self):
+ self.notify.debug('enterJoinable()')
+ return None
+
+ def exitJoinable(self):
+ return None
+
+ ##### Unjoinable state #####
+
+ # Suits or Toons are not allowed to join the battle
+
+ def enterUnjoinable(self):
+ self.notify.debug('enterUnjoinable()')
+ return None
+
+ def exitUnjoinable(self):
+ return None
+
+ ########################
+ ##### Runable ClassicFSM #####
+ ########################
+
+ def isRunable(self):
+ """ isRunable()
+ """
+ return (self.runableFsm.getCurrentState().getName() == 'Runable')
+
+ ##### Runable state #####
+
+ # Suits or Toons are allowed to run from the battle
+
+ def enterRunable(self):
+ self.notify.debug('enterRunable()')
+ return None
+
+ def exitRunable(self):
+ return None
+
+ ##### Unrunable state #####
+
+ # Suits or Toons are not allowed to run from the battle
+
+ def enterUnrunable(self):
+ self.notify.debug('enterUnrunable()')
+ return None
+
+ def exitUnrunable(self):
+ return None
+
+ ######################
+ ##### Adjust ClassicFSM #####
+ ######################
+
+ ##### Adjusting state #####
+
+ def __estimateAdjustTime(self):
+ """ Clear out the pending lists and estimate an upper bound
+ for the time required to adjust
+ """
+ self.needAdjust = 0
+ adjustTime = 0
+
+ if ((len(self.pendingSuits) > 0) or self.suitGone == 1):
+ self.suitGone = 0
+ pos0 = self.suitPendingPoints[0][0]
+ pos1 = self.suitPoints[0][0][0]
+ adjustTime = self.calcSuitMoveTime(pos0, pos1)
+
+ if ((len(self.pendingToons) > 0) or self.toonGone == 1):
+ self.toonGone = 0
+ if (adjustTime == 0):
+ pos0 = self.toonPendingPoints[0][0]
+ pos1 = self.toonPoints[0][0][0]
+ adjustTime = self.calcToonMoveTime(pos0, pos1)
+
+ return adjustTime
+
+ def enterAdjusting(self):
+ self.notify.debug('enterAdjusting()')
+ self.timer.stop()
+ self.__resetAdjustingResponses()
+ self.adjustingTimer.startCallback(self.__estimateAdjustTime() +
+ SERVER_BUFFER_TIME, self.__serverAdjustingDone)
+ return None
+
+ def __serverAdjustingDone(self):
+ if (self.needAdjust == 1):
+ self.adjustFsm.request('NotAdjusting')
+ self.__requestAdjust()
+ else:
+ self.notify.debug('adjusting timed out on the server')
+ self.ignoreAdjustingResponses = 1
+ self.__adjustDone()
+
+ def exitAdjusting(self):
+ currStateName = self.fsm.getCurrentState().getName()
+ if (currStateName == 'WaitForInput'):
+ self.timer.restart()
+ elif (currStateName == 'WaitForJoin'):
+ self.b_setState('WaitForInput')
+ self.adjustingTimer.stop()
+ return None
+
+ def __addTrainTrapForNewSuits(self):
+ #we need to make sure battleTrap is set for any new suits that join, in case we have a train track trap
+ hasTrainTrap = False
+ trapInfo = None
+ for otherSuit in self.activeSuits:
+ if otherSuit.battleTrap == UBER_GAG_LEVEL_INDEX:
+ hasTrainTrap = True
+ if hasTrainTrap:
+ for curSuit in self.activeSuits:
+ if not curSuit.battleTrap == UBER_GAG_LEVEL_INDEX:
+ oldBattleTrap = curSuit.battleTrap
+ curSuit.battleTrap = UBER_GAG_LEVEL_INDEX
+ self.battleCalc.addTrainTrapForJoiningSuit(curSuit.doId)
+ self.notify.debug('setting traintrack trap for joining suit %d oldTrap=%s' % (curSuit.doId, oldBattleTrap))
+
+ def __adjustDone(self):
+ for s in self.adjustingSuits:
+ assert(self.pendingSuits.count(s) == 1)
+ self.pendingSuits.remove(s)
+ assert(self.activeSuits.count(s) == 0)
+ self.activeSuits.append(s)
+ self.adjustingSuits = []
+ for toon in self.adjustingToons:
+ if (self.pendingToons.count(toon) == 1):
+ self.pendingToons.remove(toon)
+ else:
+ self.notify.warning('adjustDone() - toon: %d not pending!' % \
+ toon.doId)
+ if (self.activeToons.count(toon) == 0):
+ self.activeToons.append(toon)
+ # In case ignoreResponses was set to 1 during adjusting,
+ # we need to clear it because a new toon is now active
+ self.ignoreResponses = 0
+ # Welcome, toon! Here's your experience earned so far.
+ self.sendEarnedExperience(toon)
+ else:
+ self.notify.warning('adjustDone() - toon: %d already active!' \
+ % toon.doId)
+ self.adjustingToons = []
+
+ self.__addTrainTrapForNewSuits()
+
+
+ self.d_setMembers()
+ self.adjustFsm.request('NotAdjusting')
+ if (self.needAdjust == 1):
+ self.notify.debug('__adjustDone() - need to adjust again')
+ self.__requestAdjust()
+
+ ##### NotAdjusting state #####
+
+ def enterNotAdjusting(self):
+ self.notify.debug('enterNotAdjusting()')
+ if (self.movieRequested == 1):
+ # Make sure last toon didn't just run and adjusting may have
+ # resulted in a new active toon who needs to choose an attack
+ if (len(self.activeToons) > 0 and
+ self.__allActiveToonsResponded()):
+ self.__requestMovie()
+ return None
+
+ def exitNotAdjusting(self):
+ return None
+
+ def getPetProxyObject(self, petId, callback):
+ """get an instance of a pet
+ callback must accept (success, pet)
+ pet is undefined if !success
+
+ On success, pet MUST be instantiated with
+ DistributedObjectAI.generateWithRequiredAndId, using the
+ correct pet doId.
+ """
+ doneEvent = 'readPet-%s' % self._getNextSerialNum()
+ dbo = DatabaseObject.DatabaseObject(
+ self.air, petId, doneEvent=doneEvent)
+ pet = dbo.readPetProxy()
+
+ def handlePetProxyRead(dbo, retCode, callback=callback, pet=pet):
+ success = (retCode == 0)
+ if not success:
+ self.notify.warning('pet DB read failed')
+ pet = None
+ callback(success, pet)
+ self.acceptOnce(doneEvent, handlePetProxyRead)
+
+ def _getNextSerialNum(self):
+ num = self.serialNum
+ self.serialNum += 1
+ return num
diff --git a/toontown/src/battle/DistributedBattleBldg.py b/toontown/src/battle/DistributedBattleBldg.py
new file mode 100644
index 0000000..604ffd8
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleBldg.py
@@ -0,0 +1,313 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+
+from direct.actor import Actor
+from toontown.suit import SuitDNA
+from direct.directnotify import DirectNotifyGlobal
+import DistributedBattleBase
+from toontown.toon import TTEmote
+from otp.avatar import Emote
+from toontown.toonbase import TTLocalizer
+import MovieUtil
+from direct.fsm import State
+from toontown.suit import Suit
+import SuitBattleGlobals
+import random
+from toontown.toonbase import ToontownGlobals
+
+class DistributedBattleBldg(DistributedBattleBase.DistributedBattleBase):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedBattleBldg')
+
+ camFOFov = 30.0
+ camFOPos = Point3(0, -10, 4)
+
+ def __init__(self, cr):
+ """__init__(cr)
+ """
+ #townBattle = cr.playGame.hood.place.townBattle
+ townBattle = cr.playGame.getPlace().townBattle
+ DistributedBattleBase.DistributedBattleBase.__init__(self, cr,
+ townBattle)
+ self.streetBattle = 0
+
+ # Add a new reward state to the battle ClassicFSM
+ self.fsm.addState(State.State('BuildingReward',
+ self.enterBuildingReward,
+ self.exitBuildingReward,
+ ['Resume']))
+ offState = self.fsm.getStateNamed('Off')
+ offState.addTransition('BuildingReward')
+ playMovieState = self.fsm.getStateNamed('PlayMovie')
+ playMovieState.addTransition('BuildingReward')
+
+ def generate(self):
+ """ generate()
+ """
+ DistributedBattleBase.DistributedBattleBase.generate(self)
+ #dbgBattleMarkers = loader.loadModel("dbgBattleMarkers.egg")
+ #dbgBattleMarkers.reparentTo(self)
+
+ def setBossBattle(self, value):
+ self.bossBattle = value
+ if self.bossBattle:
+ self.battleMusic = base.loadMusic(
+ 'phase_7/audio/bgm/encntr_suit_winning_indoor.mid')
+ else:
+ self.battleMusic = base.loadMusic(
+ 'phase_7/audio/bgm/encntr_general_bg_indoor.mid')
+ base.playMusic(self.battleMusic, looping=1, volume=0.9)
+
+ def disable(self):
+ """ disable()
+ """
+ DistributedBattleBase.DistributedBattleBase.disable(self)
+ self.battleMusic.stop()
+
+ def delete(self):
+ """ delete()
+ """
+ DistributedBattleBase.DistributedBattleBase.delete(self)
+ del self.battleMusic
+
+ def buildJoinPointList(self, avPos, destPos, toon=0):
+ """ buildJoinPointList(avPos, destPos, toon)
+
+ This function is called when suits or toons ask to join the
+ battle and need to figure out how to walk to their selected
+ pending point (destPos). It builds a list of points the
+ avatar should walk through in order to get there. If the list
+ is empty, the avatar will walk straight there.
+ """
+ # For building battles, suits and toons are already set up to
+ # walk pretty much straight to their join spot. Always return
+ # an empty list here.
+ return []
+
+ # Each state will have an enter function, an exit function,
+ # and a datagram handler, which will be set during each enter function.
+
+ # Specific State functions
+
+ ##### Off state #####
+
+ ##### FaceOff state #####
+
+ def __faceOff(self, ts, name, callback):
+ if (len(self.suits) == 0):
+ self.notify.warning('__faceOff(): no suits.')
+ return
+ if (len(self.toons) == 0):
+ self.notify.warning('__faceOff(): no toons.')
+ return
+
+ # Do the suits faceoff
+ # TODO: get the actual position of the elevator door
+ elevatorPos = self.toons[0].getPos()
+
+ if (len(self.suits) == 1):
+ leaderIndex = 0
+ else:
+ if (self.bossBattle == 1):
+ leaderIndex = 1 # __genSuitInfos ensures that multi-suit boss battles will have boss in index 1 (a middle position)
+ else:
+ # otherwise __genSuitInfos ensures nothing, so pick the suit with the highest type to be the leader
+ maxTypeNum = -1
+ for suit in self.suits:
+ suitTypeNum = SuitDNA.getSuitType(suit.dna.name)
+ if (maxTypeNum < suitTypeNum):
+ maxTypeNum = suitTypeNum
+ leaderIndex = self.suits.index(suit)
+
+ delay = FACEOFF_TAUNT_T
+ suitTrack = Parallel()
+ suitLeader = None
+
+ for suit in self.suits:
+ suit.setState('Battle')
+ suitIsLeader = 0
+ oneSuitTrack = Sequence()
+ # Suits stop what they're doing and look at the toons
+ oneSuitTrack.append(Func(suit.loop, 'neutral'))
+ oneSuitTrack.append(Func(suit.headsUp, elevatorPos))
+
+ # Only the suit leader taunts the toons
+ if (self.suits.index(suit) == leaderIndex):
+ suitLeader = suit
+ suitIsLeader = 1
+
+ # TODO: have an inside of building taunt here
+ if (self.bossBattle == 1):
+ taunt = TTLocalizer.BattleBldgBossTaunt
+ else:
+ taunt = SuitBattleGlobals.getFaceoffTaunt(suit.getStyleName(), suit.doId)
+
+ oneSuitTrack.append(Func(suit.setChatAbsolute,
+ taunt, CFSpeech | CFTimeout))
+
+ # Move all suits into position after taunt delay
+ destPos, destHpr = self.getActorPosHpr(suit, self.suits)
+ oneSuitTrack.append(Wait(delay))
+ if (suitIsLeader == 1):
+ oneSuitTrack.append(Func(suit.clearChat))
+ oneSuitTrack.append(self.createAdjustInterval(suit, destPos, destHpr))
+ suitTrack.append(oneSuitTrack)
+
+
+ # Do the toons faceoff
+ toonTrack = Parallel()
+ for toon in self.toons:
+ oneToonTrack = Sequence()
+ destPos, destHpr = self.getActorPosHpr(toon, self.toons)
+ oneToonTrack.append(Wait(delay))
+ oneToonTrack.append(self.createAdjustInterval(
+ toon, destPos, destHpr, toon=1, run=1))
+ toonTrack.append(oneToonTrack)
+
+ # Put the camera somewhere
+ camTrack = Sequence()
+ def setCamFov(fov):
+ base.camLens.setFov(fov)
+ camTrack.append(Func(camera.wrtReparentTo, suitLeader))
+
+ camTrack.append(Func(setCamFov, self.camFOFov))
+
+ suitHeight = suitLeader.getHeight()
+ suitOffsetPnt = Point3(0, 0, suitHeight)
+
+ # empirical hack to pick a mid-height view, left in to sortof match the old view
+ MidTauntCamHeight = suitHeight*0.66
+ MidTauntCamHeightLim = suitHeight-1.8
+ if (MidTauntCamHeight < MidTauntCamHeightLim):
+ MidTauntCamHeight = MidTauntCamHeightLim
+
+ TauntCamY = 18
+ TauntCamX = 0
+ TauntCamHeight = random.choice((MidTauntCamHeight, 1, 11))
+
+ camTrack.append(Func(camera.setPos,
+ TauntCamX, TauntCamY, TauntCamHeight))
+ camTrack.append(Func(camera.lookAt, suitLeader, suitOffsetPnt))
+
+ camTrack.append(Wait(delay))
+ camPos = Point3(0, -6, 4)
+ camHpr = Vec3(0, 0, 0)
+ camTrack.append(Func(camera.reparentTo, base.localAvatar))
+ camTrack.append(Func(setCamFov, ToontownGlobals.DefaultCameraFov))
+ camTrack.append(Func(camera.setPosHpr, camPos, camHpr))
+
+ mtrack = Parallel(suitTrack, toonTrack, camTrack)
+ done = Func(callback)
+ track = Sequence(mtrack, done, name = name)
+ track.start(ts)
+ self.storeInterval(track, name)
+
+ def enterFaceOff(self, ts):
+ assert(self.notify.debug('enterFaceOff()'))
+ if (len(self.toons) > 0 and
+ base.localAvatar == self.toons[0]):
+ Emote.globalEmote.disableAll(self.toons[0], "dbattlebldg, enterFaceOff")
+ self.delayDeleteMembers()
+ self.__faceOff(ts, self.faceOffName, self.__handleFaceOffDone)
+ return None
+
+ def __handleFaceOffDone(self):
+ self.notify.debug('FaceOff done')
+ # Only the toon that initiated the battle needs to reply
+ self.d_faceOffDone(base.localAvatar.doId)
+
+ def exitFaceOff(self):
+ self.notify.debug('exitFaceOff()')
+ if (len(self.toons) > 0 and
+ base.localAvatar == self.toons[0]):
+ Emote.globalEmote.releaseAll(self.toons[0], "dbattlebldg exitFaceOff")
+ self.clearInterval(self.faceOffName)
+ self._removeMembersKeep()
+ camera.wrtReparentTo(self)
+ base.camLens.setFov(self.camFov)
+ return None
+
+ ##### WaitForInput state #####
+
+ ##### PlayMovie state #####
+
+ ##### Reward state #####
+
+ def __playReward(self, ts, callback):
+ toonTracks = Parallel()
+ for toon in self.toons:
+ toonTracks.append(Sequence(Func(toon.loop, 'victory'),
+ Wait(FLOOR_REWARD_TIMEOUT),
+ Func(toon.loop, 'neutral')))
+ name = self.uniqueName('floorReward')
+ track = Sequence(toonTracks,
+ Func(callback), name=name)
+ camera.setPos(0, 0, 1)
+ camera.setHpr(180, 10, 0)
+ self.storeInterval(track, name)
+ track.start(ts)
+
+ def enterReward(self, ts):
+ self.notify.debug('enterReward()')
+ self.delayDeleteMembers()
+ self.__playReward(ts, self.__handleFloorRewardDone)
+ return None
+
+ def __handleFloorRewardDone(self):
+ return None
+
+ def exitReward(self):
+ self.notify.debug('exitReward()')
+ # In case the server finished first
+ self.clearInterval(self.uniqueName('floorReward'))
+ self._removeMembersKeep()
+ NametagGlobals.setMasterArrowsOn(1)
+ for toon in self.toons:
+ toon.startSmooth()
+ return None
+
+ ##### BuildingReward state #####
+
+ def enterBuildingReward(self, ts):
+ assert(self.notify.debug('enterBuildingReward()'))
+ self.delayDeleteMembers()
+ if (self.hasLocalToon()):
+ NametagGlobals.setMasterArrowsOn(0)
+ self.movie.playReward(ts, self.uniqueName('building-reward'),
+ self.__handleBuildingRewardDone)
+ return None
+
+ def __handleBuildingRewardDone(self):
+ assert(self.notify.debug('Building reward done'))
+ if (self.hasLocalToon()):
+ self.d_rewardDone(base.localAvatar.doId)
+ self.movie.resetReward()
+
+ # Now request our local battle object enter the Resume state,
+ # which frees us from the battle. The distributed object may
+ # not enter the Resume state yet (it has to wait until all the
+ # toons involved have reported back up), but there's no reason
+ # we have to wait around for that.
+ self.fsm.request('Resume')
+
+ def exitBuildingReward(self):
+ # In case we're observing and the server cuts us off
+ # this guarantees all final animations get started and things
+ # get cleaned up
+ self.movie.resetReward(finish=1)
+ self._removeMembersKeep()
+ NametagGlobals.setMasterArrowsOn(1)
+ return None
+
+ ##### Resume state #####
+
+ def enterResume(self, ts=0):
+ assert(self.notify.debug('enterResume()'))
+ if (self.hasLocalToon()):
+ self.removeLocalToon()
+ return None
+
+ def exitResume(self):
+ return None
diff --git a/toontown/src/battle/DistributedBattleBldgAI.py b/toontown/src/battle/DistributedBattleBldgAI.py
new file mode 100644
index 0000000..914fbf6
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleBldgAI.py
@@ -0,0 +1,273 @@
+from otp.ai.AIBase import *
+from direct.distributed.ClockDelta import *
+from BattleBase import *
+from BattleCalculatorAI import *
+from toontown.toonbase.ToontownBattleGlobals import *
+from SuitBattleGlobals import *
+from direct.showbase.PythonUtil import addListsByValue
+import DistributedBattleBaseAI
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+import random
+from direct.fsm import State
+from direct.fsm import ClassicFSM, State
+from direct.showbase import PythonUtil
+
+# attack properties table
+class DistributedBattleBldgAI(DistributedBattleBaseAI.DistributedBattleBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleBldgAI')
+
+ def __init__(self, air, zoneId, roundCallback=None,
+ finishCallback=None, maxSuits=4, bossBattle=0):
+ """__init__(air, zoneId, suits, toonIds, finishCallback, maxSuits,
+ bossBattle)
+ """
+ DistributedBattleBaseAI.DistributedBattleBaseAI.__init__(self, air,
+ zoneId, finishCallback, maxSuits, bossBattle)
+ self.streetBattle = 0
+ self.roundCallback = roundCallback
+
+ # Add a new reward state to the battle ClassicFSM
+ self.fsm.addState(State.State('BuildingReward',
+ self.enterBuildingReward,
+ self.exitBuildingReward,
+ ['Resume']))
+ playMovieState = self.fsm.getStateNamed('PlayMovie')
+ playMovieState.addTransition('BuildingReward')
+
+## # Add a state for reserve suits to join to the battle ClassicFSM
+## self.fsm.addState(State.State('ReservesJoining',
+## self.enterReservesJoining,
+## self.exitReservesJoining,
+## ['WaitForInput']))
+## playMovieState.addTransition('ReservesJoining')
+
+ self.elevatorPos = Point3(0, -30, 0)
+ self.resumeNeedUpdate = 0
+
+ def setInitialMembers(self, toonIds, suits):
+ for suit in suits:
+ self.addSuit(suit)
+ for toonId in toonIds:
+ self.addToon(toonId)
+
+ self.fsm.request('FaceOff')
+
+ def delete(self):
+ del self.roundCallback
+ DistributedBattleBaseAI.DistributedBattleBaseAI.delete(self)
+
+ def faceOffDone(self):
+ """ faceOffDone(toonId)
+ """
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ self.notify.debug('faceOffDone() - ignoring toon: %d' % toonId)
+ return
+ elif (self.fsm.getCurrentState().getName() != 'FaceOff'):
+ self.notify.warning('faceOffDone() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('faceOffDone() - toon: %d not in toon list' % \
+ toonId)
+ return
+ assert(self.responses.has_key(toonId))
+ self.responses[toonId] += 1
+ self.notify.debug('toon: %d done facing off' % toonId)
+ if not self.ignoreFaceOffDone:
+ if (self.allToonsResponded()):
+ self.handleFaceOffDone()
+
+ else:
+ # Reset the timer to give the slowpokes a few seconds
+ # longer than the first (or most recent) toon to reply.
+ self.timer.stop()
+ self.timer.startCallback(TIMEOUT_PER_USER, self.__serverFaceOffDone)
+
+ # Each state will have an enter function, an exit function,
+ # and a datagram handler, which will be set during each enter function.
+
+ # Specific State functions
+
+ ##### Off state #####
+
+ ##### FaceOff state #####
+
+ # original suit and toon face off and then walk to positions,
+ # other toons or suits walk directly to wait positions
+
+ def enterFaceOff(self):
+ self.notify.debug('enterFaceOff()')
+ self.joinableFsm.request('Joinable')
+ self.runableFsm.request('Unrunable')
+ # From here on out DistributedBattleAI only controls DistributedSuitAI
+ # DistributedBattle controls DistributedSuit
+ self.timer.startCallback(self.calcToonMoveTime(self.pos,
+ self.elevatorPos) + FACEOFF_TAUNT_T + \
+ SERVER_BUFFER_TIME,
+ self.__serverFaceOffDone)
+ return None
+
+ def __serverFaceOffDone(self):
+ self.notify.debug('faceoff timed out on server')
+ self.ignoreFaceOffDone = 1
+ self.handleFaceOffDone()
+
+ def exitFaceOff(self):
+ self.timer.stop()
+ self.resetResponses()
+ return None
+
+ def handleFaceOffDone(self):
+ assert(len(self.activeSuits) == 0)
+ for suit in self.suits:
+ self.activeSuits.append(suit)
+ assert(len(self.activeToons) == 0)
+ for toon in self.toons:
+ self.activeToons.append(toon)
+ # Set the toon's earned experience so far.
+ self.sendEarnedExperience(toon)
+ self.d_setMembers()
+ self.b_setState('WaitForInput')
+
+ ##### WaitForJoin state #####
+
+ ##### WaitForInput state #####
+
+ ##### PlayMovie state #####
+
+ def localMovieDone(self, needUpdate, deadToons, deadSuits, lastActiveSuitDied):
+ assert(self.notify.debug('localMovieDone(%s, %s, %s, %s)' % (needUpdate, deadToons, deadSuits, lastActiveSuitDied)))
+ # Stop the server timeout for the movie
+ self.timer.stop()
+
+ self.resumeNeedUpdate = needUpdate
+ self.resumeDeadToons = deadToons
+ self.resumeDeadSuits = deadSuits
+ self.resumeLastActiveSuitDied = lastActiveSuitDied
+
+ if (len(self.toons) == 0):
+ # Toons lost - close up shop
+ assert(self.notify.debug('last toon gone, destroying building'))
+ self.d_setMembers()
+ self.b_setState('Resume')
+
+ else:
+ # Calculate the total hp of all the suits to see if any
+ # reserves need to join
+ assert(self.roundCallback != None)
+ totalHp = 0
+ for suit in self.suits:
+ if (suit.currHP > 0):
+ totalHp += suit.currHP
+ # Signal the suit interior that the round is over and wait to
+ # hear what to do next
+ self.roundCallback(self.activeToons, totalHp, deadSuits)
+
+ def __goToResumeState(self, task):
+ self.b_setState('Resume')
+
+ def resume(self, currentFloor=0, topFloor=0):
+ assert(self.notify.debug('resuming the battle'))
+ if (len(self.suits) == 0):
+ # Toons won - award experience, etc.
+ assert(self.resumeNeedUpdate == 1)
+
+ self.d_setMembers()
+
+ self.suitsKilledPerFloor.append(self.suitsKilledThisBattle)
+
+ if (topFloor == 0):
+ self.b_setState('Reward')
+ else:
+ # calculate and assign the merits and item recoveries by floor
+ for floorNum, cogsThisFloor in PythonUtil.enumerate(self.suitsKilledPerFloor):
+ for toonId in self.activeToons:
+ toon = self.getToon(toonId)
+ if toon:
+ # Append the recovered and not recovered items to their respective lists
+ recovered, notRecovered = self.air.questManager.recoverItems(
+ toon, cogsThisFloor, self.zoneId)
+ self.toonItems[toonId][0].extend(recovered)
+ self.toonItems[toonId][1].extend(notRecovered)
+ # the new merit list must be added by value to the cumulative list
+ meritArray = self.air.promotionMgr.recoverMerits(
+ toon, cogsThisFloor, self.zoneId, getCreditMultiplier(floorNum))
+
+ if toonId in self.helpfulToons:
+ self.toonMerits[toonId] = addListsByValue(self.toonMerits[toonId], meritArray)
+ else:
+ self.notify.debug("toon %d not helpful, skipping merits" % toonId)
+
+ self.d_setBattleExperience()
+ self.b_setState('BuildingReward')
+ else:
+ # Continue with the battle
+ if (self.resumeNeedUpdate == 1):
+ self.d_setMembers()
+ if ((len(self.resumeDeadSuits) > 0 and
+ self.resumeLastActiveSuitDied == 0) or
+ (len(self.resumeDeadToons) > 0)):
+ self.needAdjust = 1
+ # Wait for input will call __requestAdjust()
+ self.setState('WaitForJoin')
+
+ self.resumeNeedUpdate = 0
+ self.resumeDeadToons = []
+ self.resumeDeadSuits = []
+ self.resumeLastActiveSuitDied = 0
+
+ ##### ReservesJoining state #####
+
+ def enterReservesJoining(self, ts=0):
+ assert(self.notify.debug('enterReservesJoining()'))
+ return None
+
+ def exitReservesJoining(self, ts=0):
+ return None
+
+ ##### Reward state #####
+
+ def enterReward(self):
+ assert(self.notify.debug('enterReward()'))
+
+ # In the building battles, we don't expect any toons to send a
+ # done message before this (short) timer expires. This is
+ # just the between-floor reward dance, very brief.
+ self.timer.startCallback(FLOOR_REWARD_TIMEOUT, self.serverRewardDone)
+ return None
+
+ def exitReward(self):
+ self.timer.stop()
+ return None
+
+ ##### BuildingReward state #####
+
+ def enterBuildingReward(self):
+ assert(self.notify.debug('enterBuildingReward()'))
+ self.resetResponses()
+ self.assignRewards()
+
+ # Set an upper timeout for the reward movie. If no toons
+ # report back by this time, call it done anyway.
+ self.timer.startCallback(BUILDING_REWARD_TIMEOUT, self.serverRewardDone)
+ return None
+
+ def exitBuildingReward(self):
+ return None
+
+ ##### Resume state #####
+
+ def enterResume(self):
+ DistributedBattleBaseAI.DistributedBattleBaseAI.enterResume(self)
+
+ assert(self.finishCallback != None)
+ self.finishCallback(self.zoneId, self.activeToons)
+
+ def exitResume(self):
+ DistributedBattleBaseAI.DistributedBattleBaseAI.exitResume(self)
+
+ taskName = self.taskName('finish')
+ taskMgr.remove(taskName)
diff --git a/toontown/src/battle/DistributedBattleDiners.py b/toontown/src/battle/DistributedBattleDiners.py
new file mode 100644
index 0000000..008a1ba
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleDiners.py
@@ -0,0 +1,217 @@
+import random
+from pandac.PandaModules import VBase3, Point3
+from direct.interval.IntervalGlobal import Sequence, Wait, Func, Parallel, Track, \
+ LerpPosInterval, ProjectileInterval, SoundInterval, ActorInterval
+from direct.directnotify import DirectNotifyGlobal
+from toontown.battle import DistributedBattleFinal
+from toontown.suit import SuitTimings
+from toontown.toonbase import ToontownGlobals
+from toontown.battle import BattleProps
+
+# attack properties table
+class DistributedBattleDiners(DistributedBattleFinal.DistributedBattleFinal):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleDiners')
+
+ def __init__(self, cr):
+ """Create the diners battle."""
+ DistributedBattleFinal.DistributedBattleFinal.__init__(self, cr)
+ self.initialReservesJoiningDone = False
+
+ #debug only remove this
+ base.dbw = self
+
+ def announceGenerate(self):
+ DistributedBattleFinal.DistributedBattleFinal.announceGenerate(self)
+ self.moveSuitsToInitialPos()
+
+ def showSuitsJoining(self, suits, ts, name, callback):
+ """Show the diners joining the battle, handle initial case too."""
+ if len(suits) == 0 and not self.initialReservesJoiningDone:
+ # this is a valid case, avoid the assert
+ self.initialReservesJoiningDone = True;
+ self.doInitialSuitsJoining(ts, name, callback)
+ return
+ self.showSuitsFalling(suits, ts, name, callback)
+
+ def doInitialSuitsJoining(self, ts, name, callback):
+ done = Func(callback)
+ if (self.hasLocalToon()):
+ # Parent the camera to the battle and position it to watch the
+ # suits join.
+ camera.reparentTo(self)
+
+ # Choose either a left or a right view at random.
+ if random.choice([0, 1]):
+ camera.setPosHpr(20, -4, 7, 60, 0, 0)
+ else:
+ camera.setPosHpr(-20, -4, 7, -60, 0, 0)
+ track = Sequence(Wait(0.5), done, name = name)
+ track.start(ts)
+ self.storeInterval(track, name)
+
+ def moveSuitsToInitialPos(self):
+ """Force the inital suits to be in the right spot."""
+ battlePts = self.suitPoints[len(self.suitPendingPoints)-1]
+ for i in xrange(len(self.suits)):
+ suit = self.suits[i]
+ suit.reparentTo(self)
+ destPos, destHpr = self.getActorPosHpr(suit, self.suits)
+ suit.setPos(destPos)
+ suit.setHpr(destHpr)
+ #self.suits[i].setPos(self.bossCog,battlePts[i][0])
+ #self.suits[i].setH(battlePts[i][1])
+
+ def showSuitsFalling(self, suits, ts, name, callback):
+ assert(len(suits) > 0)
+
+ if self.bossCog == None:
+ # Hmm, no boss cog? Maybe not generated yet.
+ return
+
+ suitTrack = Parallel()
+
+ delay = 0
+ for suit in suits:
+ """
+ This is done by the AI now.
+ if self.battleNumber == 2:
+ # Battle 2 features skelecogs only.
+ suit.makeSkeleton()
+ suit.corpMedallion.hide()
+ suit.healthBar.show()
+ """
+
+ suit.setState('Battle')
+ #RAU lawbot boss battle hack,
+ if suit.dna.dept == 'l':
+ suit.reparentTo(self.bossCog)
+ suit.setPos(0, 0, 0)
+
+ #suit.setScale(3.8 / suit.height)
+
+ # Move all suits into position
+ if suit in self.joiningSuits:
+ i = len(self.pendingSuits) + self.joiningSuits.index(suit)
+ destPos, h = self.suitPendingPoints[i]
+ destHpr = VBase3(h, 0, 0)
+ else:
+ destPos, destHpr = self.getActorPosHpr(suit, self.suits)
+
+ startPos = destPos + Point3(0,0,(SuitTimings.fromSky *
+ ToontownGlobals.SuitWalkSpeed))
+ self.notify.debug('startPos for %s = %s' % (suit, startPos))
+ suit.reparentTo(self)
+ suit.setPos(startPos)
+ suit.headsUp(self)
+
+ moveIval = Sequence()
+ chairInfo = self.bossCog.claimOneChair()
+ if chairInfo:
+ moveIval = self.createDinerMoveIval(suit,destPos, chairInfo)
+
+ suitTrack.append(Track(
+ (delay, Sequence(moveIval,
+ Func(suit.loop, 'neutral')))
+ ))
+ delay += 1
+
+ if (self.hasLocalToon()):
+ # Parent the camera to the battle and position it to watch the
+ # suits join.
+ camera.reparentTo(self)
+
+ # Choose a back angle to see the diners flying to their battle position
+ self.notify.debug('self.battleSide =%s' % self.battleSide)
+ camHeading = -20
+ camX = -4
+ if self.battleSide == 0:
+ camHeading = 20
+ camX = 4
+ camera.setPosHpr(camX, -15, 7, camHeading, 0, 0)
+
+
+ done = Func(callback)
+ track = Sequence(suitTrack, done,
+ name = name)
+ track.start(ts)
+ self.storeInterval(track, name)
+
+ def createDinerMoveIval(self, suit, destPos, chairInfo):
+ """Return an interval of a diner moving to his destPos."""
+ # adapted from suit.beginSupaFlyMovie
+
+ # calculate some times used to manipulate the suit's landing
+ # animation
+ #
+ dur = suit.getDuration('landing')
+ fr = suit.getFrameRate('landing')
+
+ landingDur = dur
+
+ totalDur = 7.3
+ # length of time in animation spent in the air
+ animTimeInAir = totalDur - dur
+ flyingDur = animTimeInAir
+
+ # length of time in animation spent impacting and reacting to
+ # the ground
+ impactLength = dur - animTimeInAir
+
+ tableIndex = chairInfo[0]
+ chairIndex = chairInfo[1]
+ table = self.bossCog.tables[tableIndex]
+ chairLocator = table.chairLocators[chairIndex]
+ chairPos = chairLocator.getPos(self)
+ chairHpr = chairLocator.getHpr(self)
+ suit.setPos(chairPos)
+ table.setDinerStatus(chairIndex, table.HIDDEN)
+ suit.setHpr(chairHpr)
+ wayPoint = (chairPos + destPos) / 2.0
+ wayPoint.setZ(wayPoint.getZ() + 20)
+
+ moveIval = Sequence(
+ Func(suit.headsUp, self),
+ Func(suit.pose, 'landing', 0),
+ ProjectileInterval(suit, duration = flyingDur, startPos = chairPos,
+ endPos = destPos, gravityMult=0.25),
+ ActorInterval(suit, 'landing'),
+ )
+
+
+ # now create info for the propeller's animation
+ #
+ if suit.prop == None:
+ suit.prop = BattleProps.globalPropPool.getProp('propeller')
+ propDur = suit.prop.getDuration('propeller')
+ lastSpinFrame = 8
+ fr = suit.prop.getFrameRate('propeller')
+ # time from beginning of anim at which propeller plays its spin
+ spinTime = lastSpinFrame/fr
+ # time from beginning of anim at which propeller starts to close
+ openTime = (lastSpinFrame + 1) / fr
+
+ # now create the propeller animation intervals that will go in
+ # the third and final track
+ #
+ suit.attachPropeller()
+ propTrack = Parallel(
+ SoundInterval(suit.propInSound, duration = flyingDur,
+ node = suit),
+ Sequence(ActorInterval(suit.prop, 'propeller',
+ constrainedLoop = 1,
+ duration = flyingDur+1,
+ startTime = 0.0,
+ endTime = spinTime),
+ ActorInterval(suit.prop, 'propeller',
+ duration = landingDur,
+ startTime = openTime),
+ Func(suit.detachPropeller),
+ ),
+ )
+
+ result = Parallel(moveIval,
+ propTrack,
+ )
+ return result
+
diff --git a/toontown/src/battle/DistributedBattleDinersAI.py b/toontown/src/battle/DistributedBattleDinersAI.py
new file mode 100644
index 0000000..da550fe
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleDinersAI.py
@@ -0,0 +1,31 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.battle import DistributedBattleFinalAI
+
+# attack properties table
+class DistributedBattleDinersAI(DistributedBattleFinalAI.DistributedBattleFinalAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleDinersAI')
+
+ def __init__(self, air, bossCog, roundCallback,
+ finishCallback, battleSide):
+ DistributedBattleFinalAI.DistributedBattleFinalAI.__init__(
+ self, air, bossCog, roundCallback, finishCallback, battleSide)
+
+ def startBattle(self, toonIds, suits):
+ self.joinableFsm.request('Joinable')
+ for toonId in toonIds:
+ if self.addToon(toonId):
+ self.activeToons.append(toonId)
+
+ # We have to be sure to tell the players that they're active
+ # before we start adding suits.
+ self.d_setMembers()
+
+ for suit in suits:
+ self.pendingSuits.append(suit)
+ #joined =self.suitRequestJoin(suit)
+ #assert(joined)
+
+ self.d_setMembers()
+ self.needAdjust =1
+ self.b_setState('ReservesJoining')
diff --git a/toontown/src/battle/DistributedBattleFinal.py b/toontown/src/battle/DistributedBattleFinal.py
new file mode 100644
index 0000000..40d9379
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleFinal.py
@@ -0,0 +1,324 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+
+from direct.actor import Actor
+from toontown.distributed import DelayDelete
+from direct.directnotify import DirectNotifyGlobal
+import DistributedBattleBase
+import MovieUtil
+from toontown.suit import Suit
+import SuitBattleGlobals
+from toontown.toonbase import ToontownBattleGlobals
+from toontown.toonbase import ToontownGlobals
+from direct.fsm import State
+import random
+
+class DistributedBattleFinal(DistributedBattleBase.DistributedBattleBase):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleFinal')
+
+ def __init__(self, cr):
+ """__init__(cr)
+ """
+ townBattle = cr.playGame.hood.loader.townBattle
+ DistributedBattleBase.DistributedBattleBase.__init__(self, cr,
+ townBattle)
+ self.setupCollisions(self.uniqueBattleName('battle-collide'))
+
+ self.bossCog = None
+ self.bossCogRequest = None
+ self.streetBattle = 0
+
+ self.joiningSuitsName = self.uniqueBattleName('joiningSuits')
+
+ # Add a new ReservesJoining state to the battle ClassicFSM
+ self.fsm.addState(State.State('ReservesJoining',
+ self.enterReservesJoining,
+ self.exitReservesJoining,
+ ['WaitForJoin']))
+ offState = self.fsm.getStateNamed('Off')
+ offState.addTransition('ReservesJoining')
+ waitForJoinState = self.fsm.getStateNamed('WaitForJoin')
+ waitForJoinState.addTransition('ReservesJoining')
+ playMovieState = self.fsm.getStateNamed('PlayMovie')
+ playMovieState.addTransition('ReservesJoining')
+
+ def generate(self):
+ """ generate()
+ """
+ DistributedBattleBase.DistributedBattleBase.generate(self)
+ #dbgBattleMarkers = loader.loadModel("dbgBattleMarkers.egg")
+ #dbgBattleMarkers.reparentTo(self)
+
+ def disable(self):
+ """ disable()
+ """
+ DistributedBattleBase.DistributedBattleBase.disable(self)
+ base.cr.relatedObjectMgr.abortRequest(self.bossCogRequest)
+ self.bossCogRequest = None
+ self.bossCog = None
+
+ def delete(self):
+ """ delete()
+ """
+ DistributedBattleBase.DistributedBattleBase.delete(self)
+ self.removeCollisionData()
+
+ ##### Messages From The Server #####
+
+ def setBossCogId(self, bossCogId):
+ self.bossCogId = bossCogId
+
+ if base.cr.doId2do.has_key(bossCogId):
+ # This would be risky if we had toons entering the zone during
+ # a battle--but since all the toons are always there from the
+ # beginning, we can be confident that the BossCog has already
+ # been generated by the time we receive the generate for its
+ # associated battles.
+ #RAU slightly modified so I can spy on the lawbot boss battle
+ tempBossCog = base.cr.doId2do[bossCogId]
+ self.__gotBossCog([tempBossCog])
+ else:
+ self.notify.debug('doing relatedObjectMgr.request for bossCog')
+ self.bossCogRequest = base.cr.relatedObjectMgr.requestObjects(
+ [bossCogId], allCallback = self.__gotBossCog)
+
+
+
+ def __gotBossCog(self, bossCogList):
+ self.bossCogRequest = None
+ self.bossCog = bossCogList[0]
+
+ # Now that we know the BossCog, we can check to see if we
+ # should have enabled collisions when we went into the
+ # NoLocalToon state.
+ currStateName = self.localToonFsm.getCurrentState().getName()
+ if currStateName == 'NoLocalToon' and self.bossCog.hasLocalToon():
+ self.enableCollision()
+
+ def setBattleNumber(self, battleNumber):
+ self.battleNumber = battleNumber
+
+ def setBattleSide(self, battleSide):
+ self.battleSide = battleSide
+
+ def setMembers(self, suits, suitsJoining, suitsPending, suitsActive,
+ suitsLured, suitTraps,
+ toons, toonsJoining, toonsPending, toonsActive,
+ toonsRunning, timestamp):
+ if self.battleCleanedUp():
+ return
+
+ oldtoons = DistributedBattleBase.DistributedBattleBase.setMembers(self,
+ suits, suitsJoining, suitsPending, suitsActive, suitsLured,
+ suitTraps,
+ toons, toonsJoining, toonsPending, toonsActive, toonsRunning,
+ timestamp)
+
+ # If the battle is full, we need to make the collision sphere
+ # tangible so other toons can't walk through the battle
+ if (len(self.toons) == 4 and len(oldtoons) < 4):
+ self.notify.debug('setMembers() - battle is now full of toons')
+ self.closeBattleCollision()
+ elif (len(self.toons) < 4 and len(oldtoons) == 4):
+ self.openBattleCollision()
+
+ # Each state will have an enter function, an exit function,
+ # and a datagram handler, which will be set during each enter function.
+
+ def makeSuitJoin(self, suit, ts):
+ """ makeSuitJoin(suit, ts)
+ """
+ self.notify.debug('makeSuitJoin(%d)' % suit.doId)
+
+ # We override this function from the base class to play no
+ # interval. Instead, we synchronize the animation of all of
+ # the joining suits in the ReservesJoining state.
+
+ self.joiningSuits.append(suit)
+ if (self.hasLocalToon()):
+ self.d_joinDone(base.localAvatar.doId, suit.doId)
+
+ def showSuitsJoining(self, suits, ts, name, callback):
+ assert(len(suits) > 0)
+
+ if self.bossCog == None:
+ # Hmm, no boss cog? Maybe not generated yet.
+ return
+
+ if self.battleSide:
+ openDoor = Func(self.bossCog.doorB.request, 'open')
+ closeDoor = Func(self.bossCog.doorB.request, 'close')
+ else:
+ openDoor = Func(self.bossCog.doorA.request, 'open')
+ closeDoor = Func(self.bossCog.doorA.request, 'close')
+
+ suitTrack = Parallel()
+
+ delay = 0
+ for suit in suits:
+ """
+ This is done by the AI now.
+ if self.battleNumber == 2:
+ # Battle 2 features skelecogs only.
+ suit.makeSkeleton()
+ suit.corpMedallion.hide()
+ suit.healthBar.show()
+ """
+
+ suit.setState('Battle')
+ #RAU lawbot boss battle hack,
+ if suit.dna.dept == 'l':
+ suit.reparentTo(self.bossCog)
+ suit.setPos(0, 0, 0)
+
+ suit.setPos(self.bossCog, 0, 0, 0)
+ suit.headsUp(self)
+
+ suit.setScale(3.8 / suit.height)
+
+ # Move all suits into position
+ if suit in self.joiningSuits:
+ i = len(self.pendingSuits) + self.joiningSuits.index(suit)
+ destPos, h = self.suitPendingPoints[i]
+ destHpr = VBase3(h, 0, 0)
+ else:
+ destPos, destHpr = self.getActorPosHpr(suit, self.suits)
+
+ suitTrack.append(Track(
+ (delay, self.createAdjustInterval(suit, destPos, destHpr)),
+ (delay + 1.5, suit.scaleInterval(1.5, 1))
+ ))
+ delay += 1
+
+ if (self.hasLocalToon()):
+ # Parent the camera to the battle and position it to watch the
+ # suits join.
+ camera.reparentTo(self)
+
+ # Choose either a left or a right view at random.
+ if random.choice([0, 1]):
+ camera.setPosHpr(20, -4, 7, 60, 0, 0)
+ else:
+ camera.setPosHpr(-20, -4, 7, -60, 0, 0)
+
+ done = Func(callback)
+ track = Sequence(openDoor, suitTrack, closeDoor, done,
+ name = name)
+ track.start(ts)
+ self.storeInterval(track, name)
+
+ # Specific State functions
+
+ ##### Off state #####
+
+ ##### WaitForInput state #####
+
+ ##### PlayMovie state #####
+
+ ##### Reward state #####
+
+ def __playReward(self, ts, callback):
+ toonTracks = Parallel()
+ for toon in self.toons:
+ toonTracks.append(Sequence(Func(toon.loop, 'victory'),
+ Wait(FLOOR_REWARD_TIMEOUT),
+ Func(toon.loop, 'neutral')))
+ name = self.uniqueName('floorReward')
+ track = Sequence(toonTracks, name=name)
+
+ if self.hasLocalToon():
+ camera.setPos(0, 0, 1)
+ camera.setHpr(180, 10, 0)
+
+ track += [
+ self.bossCog.makeEndOfBattleMovie(self.hasLocalToon()),
+ Func(callback)]
+
+ self.storeInterval(track, name)
+ track.start(ts)
+
+ def enterReward(self, ts):
+ self.notify.debug('enterReward()')
+ self.disableCollision()
+ self.delayDeleteMembers()
+ self.__playReward(ts, self.__handleFloorRewardDone)
+ return None
+
+ def __handleFloorRewardDone(self):
+ return None
+
+ def exitReward(self):
+ self.notify.debug('exitReward()')
+ # In case the server finished first
+ self.clearInterval(self.uniqueName('floorReward'), finish = 1)
+ self._removeMembersKeep()
+ NametagGlobals.setMasterArrowsOn(1)
+ for toon in self.toons:
+ toon.startSmooth()
+ return None
+
+ ##### Resume state #####
+
+ def enterResume(self, ts=0):
+ assert(self.notify.debug('enterResume()'))
+ if (self.hasLocalToon()):
+ self.removeLocalToon()
+
+ self.fsm.requestFinalState()
+
+ def exitResume(self):
+ return None
+
+ ##### ReservesJoining state #####
+
+ def enterReservesJoining(self, ts=0):
+ assert(self.notify.debug('enterReservesJoining()'))
+
+ # Show a movie with the cogs emerging from the BossCog's belly.
+ self.delayDeleteMembers()
+ self.showSuitsJoining(self.joiningSuits, ts, self.joiningSuitsName,
+ self.__reservesJoiningDone)
+
+ def __reservesJoiningDone(self):
+ self._removeMembersKeep()
+ self.doneBarrier()
+
+ def exitReservesJoining(self):
+ self.clearInterval(self.joiningSuitsName)
+
+ #########################
+ ##### LocalToon ClassicFSM #####
+ #########################
+
+ ##### HasLocalToon state #####
+
+ ##### NoLocalToon state #####
+
+ def enterNoLocalToon(self):
+ self.notify.debug('enterNoLocalToon()')
+
+ # Enable battle collision sphere, but only if localToon is
+ # known to the BossCog.
+ if self.bossCog != None and \
+ self.bossCog.hasLocalToon():
+ self.enableCollision()
+ else:
+ self.disableCollision()
+
+ return None
+
+ def exitNoLocalToon(self):
+ # Disable battle collision sphere
+ self.disableCollision()
+ return None
+
+ ##### WaitForServer state #####
+
+ def enterWaitForServer(self):
+ self.notify.debug('enterWaitForServer()')
+ return None
+
+ def exitWaitForServer(self):
+ return None
diff --git a/toontown/src/battle/DistributedBattleFinalAI.py b/toontown/src/battle/DistributedBattleFinalAI.py
new file mode 100644
index 0000000..98af28c
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleFinalAI.py
@@ -0,0 +1,191 @@
+from otp.ai.AIBase import *
+from BattleBase import *
+from BattleCalculatorAI import *
+from toontown.toonbase.ToontownBattleGlobals import *
+from SuitBattleGlobals import *
+
+import DistributedBattleBaseAI
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import State
+from direct.showbase.PythonUtil import addListsByValue
+import random
+import types
+
+# attack properties table
+class DistributedBattleFinalAI(DistributedBattleBaseAI.DistributedBattleBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleFinalAI')
+
+ def __init__(self, air, bossCog, roundCallback,
+ finishCallback, battleSide):
+ DistributedBattleBaseAI.DistributedBattleBaseAI.__init__(
+ self, air, bossCog.zoneId, finishCallback)
+ self.bossCogId = bossCog.doId
+ self.battleNumber = bossCog.battleNumber
+ self.battleSide = battleSide
+ self.streetBattle = 0
+ self.roundCallback = roundCallback
+ self.elevatorPos = Point3(0, 0, 0)
+ self.pos = Point3(0, 30, 0)
+
+ self.resumeNeedUpdate = 0
+
+ # Add a state for reserve suits to join to the battle ClassicFSM
+ self.fsm.addState(State.State('ReservesJoining',
+ self.enterReservesJoining,
+ self.exitReservesJoining,
+ ['WaitForJoin']))
+ offState = self.fsm.getStateNamed('Off')
+ offState.addTransition('ReservesJoining')
+ waitForJoinState = self.fsm.getStateNamed('WaitForJoin')
+ waitForJoinState.addTransition('ReservesJoining')
+ playMovieState = self.fsm.getStateNamed('PlayMovie')
+ playMovieState.addTransition('ReservesJoining')
+
+ def getBossCogId(self):
+ return self.bossCogId
+
+ def getBattleNumber(self):
+ return self.battleNumber
+
+ def getBattleSide(self):
+ return self.battleSide
+
+ def startBattle(self, toonIds, suits):
+ self.joinableFsm.request('Joinable')
+ for toonId in toonIds:
+ if self.addToon(toonId):
+ self.activeToons.append(toonId)
+
+ # We have to be sure to tell the players that they're active
+ # before we start adding suits.
+ self.d_setMembers()
+
+ for suit in suits:
+ joined = self.suitRequestJoin(suit)
+ assert(joined)
+
+ self.d_setMembers()
+ self.b_setState('ReservesJoining')
+
+ # Each state will have an enter function, an exit function,
+ # and a datagram handler, which will be set during each enter function.
+
+ # Specific State functions
+
+ ##### Off state #####
+
+ ##### WaitForJoin state #####
+
+ ##### WaitForInput state #####
+
+ ##### PlayMovie state #####
+
+ def localMovieDone(self, needUpdate, deadToons, deadSuits, lastActiveSuitDied):
+ # Stop the server timeout for the movie
+ self.timer.stop()
+
+ self.resumeNeedUpdate = needUpdate
+ self.resumeDeadToons = deadToons
+ self.resumeDeadSuits = deadSuits
+ self.resumeLastActiveSuitDied = lastActiveSuitDied
+
+ if (len(self.toons) == 0):
+ # Toons lost - close up shop
+ self.d_setMembers()
+ self.b_setState('Resume')
+ else:
+ # Calculate the total hp of all the suits to see if any
+ # reserves need to join
+ assert(self.roundCallback != None)
+ totalHp = 0
+ for suit in self.suits:
+ if (suit.currHP > 0):
+ totalHp += suit.currHP
+ # Signal the suit interior that the round is over and wait to
+ # hear what to do next
+ self.roundCallback(self.activeToons, totalHp, deadSuits)
+
+ def resume(self, joinedReserves):
+ assert(self.notify.debug('resuming the battle'))
+ if len(joinedReserves) != 0:
+ for info in joinedReserves:
+ joined = self.suitRequestJoin(info[0])
+ assert(joined)
+ self.d_setMembers()
+ self.b_setState('ReservesJoining')
+
+ elif (len(self.suits) == 0):
+ # Toons won - award experience, etc.
+ assert(self.resumeNeedUpdate == 1)
+ battleMultiplier = getBossBattleCreditMultiplier(self.battleNumber)
+ for toonId in self.activeToons:
+ toon = self.getToon(toonId)
+ if toon:
+ # Append the recovered and not recovered items to their respective lists
+ recovered, notRecovered = self.air.questManager.recoverItems(
+ toon, self.suitsKilledThisBattle, self.zoneId)
+ self.toonItems[toonId][0].extend(recovered)
+ self.toonItems[toonId][1].extend(notRecovered)
+ # No need to recover merits - you are about to get a promotion!
+ #meritArray = self.air.promotionMgr.recoverMerits(
+ # toon, self.suitsKilledThisBattle, self.zoneId, battleMultiplier)
+ #self.toonMerits[toonId] = addListsByValue(self.toonMerits[toonId], meritArray)
+ self.d_setMembers()
+ self.d_setBattleExperience()
+ self.b_setState('Reward')
+
+ else:
+ # Continue with the battle
+ if (self.resumeNeedUpdate == 1):
+ self.d_setMembers()
+ if ((len(self.resumeDeadSuits) > 0 and
+ self.resumeLastActiveSuitDied == 0) or
+ (len(self.resumeDeadToons) > 0)):
+ self.needAdjust = 1
+ # Wait for input will call __requestAdjust()
+ self.setState('WaitForJoin')
+
+ self.resumeNeedUpdate = 0
+ self.resumeDeadToons = []
+ self.resumeDeadSuits = []
+ self.resumeLastActiveSuitDied = 0
+
+ ##### ReservesJoining state #####
+
+ def enterReservesJoining(self, ts=0):
+ assert(self.notify.debug('enterReservesJoining()'))
+ self.beginBarrier("ReservesJoining", self.toons, 15,
+ self.__doneReservesJoining)
+
+ def __doneReservesJoining(self, avIds):
+ self.b_setState('WaitForJoin')
+
+ def exitReservesJoining(self, ts=0):
+ return None
+
+ ##### Reward state #####
+
+ def enterReward(self):
+ assert(self.notify.debug('enterReward()'))
+
+ # In the building battles, we don't expect any toons to send a
+ # done message before this (short) timer expires. This is
+ # just the between-floor reward dance, very brief.
+ self.timer.startCallback(FLOOR_REWARD_TIMEOUT + 5, self.serverRewardDone)
+ return None
+
+ def exitReward(self):
+ self.timer.stop()
+ return None
+
+ ##### Resume state #####
+
+ def enterResume(self):
+ self.joinableFsm.request('Unjoinable')
+ self.runableFsm.request('Unrunable')
+ DistributedBattleBaseAI.DistributedBattleBaseAI.enterResume(self)
+
+ assert(self.finishCallback != None)
+ self.finishCallback(self.zoneId, self.activeToons)
diff --git a/toontown/src/battle/DistributedBattleWaiters.py b/toontown/src/battle/DistributedBattleWaiters.py
new file mode 100644
index 0000000..e5f5399
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleWaiters.py
@@ -0,0 +1,139 @@
+import random
+from pandac.PandaModules import VBase3, Point3
+from direct.interval.IntervalGlobal import Sequence, Wait, Func, Parallel, Track
+from direct.directnotify import DirectNotifyGlobal
+from toontown.battle import DistributedBattleFinal
+from toontown.suit import SuitTimings
+from toontown.toonbase import ToontownGlobals
+
+# attack properties table
+class DistributedBattleWaiters(DistributedBattleFinal.DistributedBattleFinal):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleWaiters')
+
+ def __init__(self, cr):
+ """Create the waiters battle."""
+ DistributedBattleFinal.DistributedBattleFinal.__init__(self, cr)
+ self.initialReservesJoiningDone = False
+
+ #debug only remove this
+ base.dbw = self
+
+ def announceGenerate(self):
+ DistributedBattleFinal.DistributedBattleFinal.announceGenerate(self)
+ for suit in self.suits:
+ suit.makeWaiter()
+ self.moveSuitsToInitialPos()
+
+ def showSuitsJoining(self, suits, ts, name, callback):
+ """Show the waiters joining the battle, handle initial case too."""
+ assert self.notify.debugStateCall(self)
+ if len(suits) == 0 and not self.initialReservesJoiningDone:
+ # this is a valid case, avoid the assert
+ self.initialReservesJoiningDone = True;
+ self.doInitialSuitsJoining(ts, name, callback)
+ return
+ self.showSuitsFalling(suits, ts, name, callback)
+
+ def doInitialSuitsJoining(self, ts, name, callback):
+ assert self.notify.debugStateCall(self)
+ done = Func(callback)
+ if (self.hasLocalToon()):
+ # Parent the camera to the battle and position it to watch the
+ # suits join.
+ self.notify.debug('parenting camera to distributed battle waiters')
+ camera.reparentTo(self)
+
+ # Choose either a left or a right view at random.
+ if random.choice([0, 1]):
+ camera.setPosHpr(20, -4, 7, 60, 0, 0)
+ else:
+ camera.setPosHpr(-20, -4, 7, -60, 0, 0)
+ track = Sequence(Wait(0.5), done, name = name)
+ track.start(ts)
+ self.storeInterval(track, name)
+
+ def moveSuitsToInitialPos(self):
+ """Force the inital suits to be in the right spot."""
+ #import pdb; pdb.set_trace()
+ battlePts = self.suitPoints[len(self.suitPendingPoints)-1]
+ for i in xrange(len(self.suits)):
+ suit = self.suits[i]
+ suit.reparentTo(self)
+ destPos, destHpr = self.getActorPosHpr(suit, self.suits)
+ suit.setPos(destPos)
+ suit.setHpr(destHpr)
+
+ def showSuitsFalling(self, suits, ts, name, callback):
+ assert(len(suits) > 0)
+
+ if self.bossCog == None:
+ # Hmm, no boss cog? Maybe not generated yet.
+ return
+
+ suitTrack = Parallel()
+
+ delay = 0
+ for suit in suits:
+ """
+ This is done by the AI now.
+ if self.battleNumber == 2:
+ # Battle 2 features skelecogs only.
+ suit.makeSkeleton()
+ suit.corpMedallion.hide()
+ suit.healthBar.show()
+ """
+ suit.makeWaiter()
+ suit.setState('Battle')
+ #RAU lawbot boss battle hack,
+ if suit.dna.dept == 'l':
+ suit.reparentTo(self.bossCog)
+ suit.setPos(0, 0, 0)
+
+ #suit.setScale(3.8 / suit.height)
+
+ # Move all suits into position
+ if suit in self.joiningSuits:
+ i = len(self.pendingSuits) + self.joiningSuits.index(suit)
+ destPos, h = self.suitPendingPoints[i]
+ destHpr = VBase3(h, 0, 0)
+ else:
+ destPos, destHpr = self.getActorPosHpr(suit, self.suits)
+
+ startPos = destPos + Point3(0,0,(SuitTimings.fromSky *
+ ToontownGlobals.SuitWalkSpeed))
+ self.notify.debug('startPos for %s = %s' % (suit, startPos))
+ suit.reparentTo(self)
+ suit.setPos(startPos)
+ suit.headsUp(self)
+
+ flyIval = suit.beginSupaFlyMove(destPos, True,'flyIn')
+ suitTrack.append(Track(
+ #(delay, self.createAdjustInterval(suit, destPos, destHpr)),
+ (delay, Sequence(flyIval,
+ Func(suit.loop, 'neutral')))
+ ))
+ delay += 1
+
+ if (self.hasLocalToon()):
+ # Parent the camera to the battle and position it to watch the
+ # suits join.
+ camera.reparentTo(self)
+
+ # Choose either a left or a right view at random.
+ if random.choice([0, 1]):
+ camera.setPosHpr(20, -4, 7, 60, 0, 0)
+ else:
+ camera.setPosHpr(-20, -4, 7, -60, 0, 0)
+
+ done = Func(callback)
+ track = Sequence( suitTrack, done,
+ name = name)
+ track.start(ts)
+ self.storeInterval(track, name)
+
+ def enterWaitForInput(self, ts=0):
+ """Wait for input from the toons."""
+ DistributedBattleFinal.DistributedBattleFinal.enterWaitForInput(self,ts)
+ if self.hasLocalToon():
+ camera.reparentTo(self)
diff --git a/toontown/src/battle/DistributedBattleWaitersAI.py b/toontown/src/battle/DistributedBattleWaitersAI.py
new file mode 100644
index 0000000..39dcca1
--- /dev/null
+++ b/toontown/src/battle/DistributedBattleWaitersAI.py
@@ -0,0 +1,31 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.battle import DistributedBattleFinalAI
+
+# attack properties table
+class DistributedBattleWaitersAI(DistributedBattleFinalAI.DistributedBattleFinalAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBattleWaitersAI')
+
+ def __init__(self, air, bossCog, roundCallback,
+ finishCallback, battleSide):
+ DistributedBattleFinalAI.DistributedBattleFinalAI.__init__(
+ self, air, bossCog, roundCallback, finishCallback, battleSide)
+
+ def startBattle(self, toonIds, suits):
+ self.joinableFsm.request('Joinable')
+ for toonId in toonIds:
+ if self.addToon(toonId):
+ self.activeToons.append(toonId)
+
+ # We have to be sure to tell the players that they're active
+ # before we start adding suits.
+ self.d_setMembers()
+
+ for suit in suits:
+ self.pendingSuits.append(suit)
+ #joined =self.suitRequestJoin(suit)
+ #assert(joined)
+
+ self.d_setMembers()
+ self.needAdjust =1
+ self.b_setState('ReservesJoining')
diff --git a/toontown/src/battle/Fanfare.py b/toontown/src/battle/Fanfare.py
new file mode 100644
index 0000000..a7cf8d3
--- /dev/null
+++ b/toontown/src/battle/Fanfare.py
@@ -0,0 +1,308 @@
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from BattleSounds import *
+from toontown.toon.ToonDNA import *
+from toontown.suit.SuitDNA import *
+from direct.particles.ParticleEffect import *
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+
+import MovieUtil
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+import BattleParticles
+from toontown.toonbase import ToontownGlobals
+import RewardPanel
+
+notify = DirectNotifyGlobal.directNotify.newCategory('Fanfare')
+
+"""
+ Fanfare is centered around a given toon. It causes two trumpets
+ to appear and play some music, as well as a ball of confetti to show,
+ open, and shower the toon with confetti. A message box can then
+ appear, showing text and an image.
+
+ The message box is initially hidden
+"""
+
+###############################################################
+# methods used to set up the message panel
+###############################################################
+# main panel, this is also where the toon's name shows up
+
+"""
+ NOTE: The message panel is initially hidden.
+"""
+def makePanel(toon, showToonName):
+ panel = DirectFrame(relief=None, geom=DGG.getDefaultDialogGeom(),
+ geom_color=ToontownGlobals.GlobalDialogColor,
+ geom_scale = (1.75, 1, 0.75), pos = (0, 0, 0.587))
+
+ panel.initialiseoptions(RewardPanel)
+ panel.setTransparency(1)
+ panel.hide()
+
+ if showToonName is 1:
+ panel.avNameLabel = DirectLabel(
+ parent = panel,
+ relief = None,
+ pos = Vec3(0, 0, 0.3),
+ text = toon.getName(),
+ text_scale = 0.08,
+ )
+ return panel
+
+# where a little text message can be placed, parented to the above frame
+def makeMessageBox(panel, message, messagePos, messageScale, wordwrap = 100):
+ panel.itemFrame = DirectFrame(
+ parent = panel,
+ relief = None,
+ text = message,
+ text_pos = messagePos,
+ text_scale = messageScale,
+ text_wordwrap = wordwrap
+ )
+
+# where an image can be placed, parented to the above itemFrame
+def makeImageBox(frame, image, imagePos, imageScale):
+ frame.imageIcon = image.copyTo(frame)
+ frame.imageIcon.setPos(imagePos)
+ frame.imageIcon.setScale(imageScale)
+
+###############################################################
+# public methods to be called by other classes
+# returns an interval that has the partyBall, trumpets, and confetti,
+# and a RewardPanel that can be manipulated however the caller wishes
+# this was done in case the RewardPanel is intended to be shown
+# to just the fanfare'd toon, or to all toons around them.
+###############################################################
+
+# this just does a Fanfare with no message box
+def makeFanfare(delay, toon):
+ return doFanfare(delay, toon, None)
+
+# example of call from another class
+#Fanfare.makeFanfareWithMessageImage(0, base.localAvatar, 1, "This is the message", Vec2(0,0.2),
+ #0.08, base.localAvatar.inventory.buttonLookup(1, 1), Vec3(0,0,0), 4)
+
+# this does a Fanfare and brings up a box with a message.
+# user specifies what, where and how big the message is
+# messagePos of Vec2(0,.2) and messageScale of 0.08
+# will make text appear under the toon name
+# @return an (interval, None)
+def makeFanfareWithMessage(delay, toon, showToonName, message, messagePos, messageScale, wordwrap = 100):
+ panel = makePanel(toon, showToonName)
+ makeMessageBox(panel, message, messagePos, messageScale, wordwrap)
+ return doFanfare(delay, toon, panel)
+
+# this does a fanfare and brings up a box with an image.
+# user specifies what, where, and how big the image is
+# imagePos of Vec3(0,0,0) and imageScale of 3
+# using the an inventory button appears
+# @return an (interval, RewardPanel)
+def makeFanfareWithImage(delay, toon, showToonName, image, imagePos, imageScale, wordwrap = 100):
+ panel = makePanel(toon, showToonName)
+ makeMessageBox(panel, "", Vec3(0,0,0), 1, wordwrap)
+ makeImageBox(panel.itemFrame, image, imagePos, imageScale)
+ return doFanfare(delay, toon, panel)
+
+# this does a Fanfare and brings up a box with a message and image
+# user specifies messages and image properties
+# @return an (interval, RewardPanel)
+def makeFanfareWithMessageImage(delay, toon, showToonName, message, messagePos,
+ messageScale, image, imagePos, imageScale, wordwrap = 100):
+ panel = makePanel(toon, showToonName)
+ makeMessageBox(panel, message, messagePos, messageScale, wordwrap)
+ makeImageBox(panel.itemFrame, image, imagePos, imageScale)
+ return doFanfare(delay, toon, panel)
+
+###############################################################
+# this creates the interval for the fanfare
+###############################################################
+
+# @return an (interval, RewardPanel)
+def doFanfare(delay, toon, panel):
+
+ fanfareNode = toon.attachNewNode('fanfareNode')
+ partyBall = fanfareNode.attachNewNode('partyBall')
+ headparts = toon.getHeadParts()
+
+ pos = headparts[2].getPos(fanfareNode)
+
+ # the party ball is two halves of a sphere that open up
+ partyBallLeft = globalPropPool.getProp('partyBall')
+ partyBallLeft.reparentTo(partyBall)
+ partyBallLeft.setScale(.8)
+ partyBallLeft.setH(90)
+ partyBallLeft.setColorScale(1,0,0,0)
+
+ partyBallRight = globalPropPool.getProp('partyBall')
+ partyBallRight.reparentTo(partyBall)
+ partyBallRight.setScale(.8)
+ partyBallRight.setH(-90)
+ partyBallRight.setColorScale(1,1,0,0)
+
+ # positioned above the head of the toon
+ partyBall.setZ(pos.getZ()+3.2)
+
+ # the ball shakes before it opens
+ ballShake1 = Sequence(
+ Parallel(LerpHprInterval(partyBallLeft, duration=.2,startHpr=Vec3(90,0,0),
+ hpr=Vec3(90,10,0), blendType='easeInOut'),
+ LerpHprInterval(partyBallRight, duration=.2,startHpr=Vec3(-90,0,0),
+ hpr=Vec3(-90,-10,0), blendType='easeInOut')),
+ Parallel(LerpHprInterval(partyBallLeft, duration=.2,startHpr=Vec3(90,10,0),
+ hpr=Vec3(90,-10,0), blendType='easeInOut'),
+ LerpHprInterval(partyBallRight, duration=.2,startHpr=Vec3(-90,-10,0),
+ hpr=Vec3(-90,10,0), blendType='easeInOut')),
+ Parallel(LerpHprInterval(partyBallLeft, duration=.2,startHpr=Vec3(90,-10,0),
+ hpr=Vec3(90,0,0), blendType='easeInOut'),
+ LerpHprInterval(partyBallRight, duration=.2,startHpr=Vec3(-90,10,0),
+ hpr=Vec3(-90,0,0), blendType='easeInOut')))
+ ballShake2 = Sequence(
+ Parallel(LerpHprInterval(partyBallLeft, duration=.2,startHpr=Vec3(90,0,0),
+ hpr=Vec3(90,-10,0), blendType='easeInOut'),
+ LerpHprInterval(partyBallRight, duration=.2,startHpr=Vec3(-90,0,0),
+ hpr=Vec3(-90,10,0), blendType='easeInOut')),
+ Parallel(LerpHprInterval(partyBallLeft, duration=.2,startHpr=Vec3(90,-10,0),
+ hpr=Vec3(90,10,0), blendType='easeInOut'),
+ LerpHprInterval(partyBallRight, duration=.2,startHpr=Vec3(-90,10,0),
+ hpr=Vec3(-90,-10,0), blendType='easeInOut')),
+ Parallel(LerpHprInterval(partyBallLeft, duration=.2,startHpr=Vec3(90,10,0),
+ hpr=Vec3(90,0,0), blendType='easeInOut'),
+ LerpHprInterval(partyBallRight, duration=.2,startHpr=Vec3(-90,-10,0),
+ hpr=Vec3(-90,0,0), blendType='easeInOut')))
+
+ openBall = Parallel(LerpHprInterval(partyBallLeft, duration=.2,startHpr=Vec3(90,0,0), hpr=Vec3(90,30,0)),
+ LerpHprInterval(partyBallRight, duration=.2,startHpr=Vec3(-90,0,0), hpr=Vec3(-90,30,0)))
+ confettiNode = fanfareNode.attachNewNode('confetti')
+ confettiNode.setScale(3)
+ confettiNode.setZ(pos.getZ()+2.5)
+
+ # this method is used for the trumpet blowing. It is just a scale in/out
+ def longshake(models,num,duration):
+ inShake = getScaleBlendIntervals(models,duration=duration,startScale=.23,
+ endScale=.2,blendType='easeInOut')
+ outShake = getScaleBlendIntervals(models,duration=duration,startScale=.2,
+ endScale=.23,blendType='easeInOut')
+ i = 1
+ seq = Sequence()
+ while i < num:
+ if i % 2 == 0:
+ seq.append(inShake)
+ else:
+ seq.append(outShake)
+ i+=1
+ return seq
+
+ # just a way of getting two scale intervals for two different LOD models
+ def getScaleBlendIntervals(props, duration, startScale, endScale, blendType):
+ tracks = Parallel()
+ for prop in props:
+ tracks.append(LerpScaleInterval(prop, duration, endScale,
+ startScale=startScale,blendType=blendType))
+ return tracks
+
+ # creation of the two trumpets
+ trumpetNode = fanfareNode.attachNewNode('trumpetNode')
+
+ trumpet1 = globalPropPool.getProp('bugle')
+ trumpet2 = MovieUtil.copyProp(trumpet1)
+ trumpet1.reparentTo(trumpetNode)
+ trumpet1.setScale(.2)
+ # this should make it look at the player the fanfare centers on
+ trumpet1.setPos(2,2,1)
+ trumpet1.setHpr(120,65,0)
+
+ trumpet2.reparentTo(trumpetNode)
+ trumpet2.setScale(.2)
+ trumpet2.setPos(-2,2,1)
+ trumpet2.setHpr(-120,65,0)
+
+ # trumpets are initially transparent
+ trumpetNode.setTransparency(1)
+ trumpetNode.setColor(1,1,1,0)
+
+ trumpturn1 = LerpHprInterval(trumpet1,duration=4,startHpr=Vec3(80,15,0),
+ hpr=Vec3(150,40,0))
+ trumpturn2 = LerpHprInterval(trumpet2,duration=4,startHpr=Vec3(-80,15,0),
+ hpr=Vec3(-150,40,0))
+
+ trumpetTurn = Parallel(trumpturn1, trumpturn2)
+
+ #######################################################################
+ # CONFETTI PARTICLE EFFECT
+ #######################################################################
+
+ BattleParticles.loadParticles()
+ confettiBlue = BattleParticles.createParticleEffect('Confetti')
+ confettiBlue.reparentTo(confettiNode)
+ blue_p0 = confettiBlue.getParticlesNamed('particles-1')
+ blue_p0.renderer.getColorInterpolationManager().addConstant(0.0,1.0,Vec4(0.0,0.0,1.0,1.0),1)
+
+ confettiYellow = BattleParticles.createParticleEffect('Confetti')
+ confettiYellow.reparentTo(confettiNode)
+ yellow_p0 = confettiYellow.getParticlesNamed('particles-1')
+ yellow_p0.renderer.getColorInterpolationManager().addConstant(0.0,1.0,Vec4(1.0,1.0,0.0,1.0),1)
+
+ confettiRed = BattleParticles.createParticleEffect('Confetti')
+ confettiRed.reparentTo(confettiNode)
+ red_p0 = confettiRed.getParticlesNamed('particles-1')
+ red_p0.renderer.getColorInterpolationManager().addConstant(0.0,1.0,Vec4(1.0,0.0,0.0,1.0),1)
+
+ #######################################################################
+
+ trumpetsAppear = LerpColorInterval(trumpetNode,.3,startColor=Vec4(1,1,0,0),
+ color=Vec4(1,1,0,1))
+ trumpetsVanish = LerpColorInterval(trumpetNode,.3,startColor=Vec4(1,1,0,1),
+ color=Vec4(1,1,0,0))
+
+ # loads sounds, puts crabHorn at the Horn part, cutting out the scuttle
+ crabHorn = globalBattleSoundCache.getSound('King_Crab.mp3')
+ drumroll = globalBattleSoundCache.getSound('SZ_MM_drumroll.mp3')
+ fanfare = globalBattleSoundCache.getSound('SZ_MM_fanfare.mp3')
+ crabHorn.setTime(1.5)
+
+
+ partyBall.setTransparency(1)
+ partyBall.setColorScale(1,1,1,1)
+
+ # ball intervals
+ ballAppear = Parallel(LerpColorScaleInterval(partyBallLeft,.3,startColorScale=Vec4(1,0,0,0),colorScale=Vec4(1,0,0,1)),
+ LerpColorScaleInterval(partyBallRight,.3,startColorScale=Vec4(1,1,0,0),colorScale=Vec4(1,1,0,1)))
+ ballVanish = Parallel(LerpColorScaleInterval(partyBallLeft,.3,startColorScale=Vec4(1,0,0,1),colorScale=Vec4(1,0,0,0)),
+ LerpColorScaleInterval(partyBallRight,.3,startColorScale=Vec4(1,1,0,1),colorScale=Vec4(1,1,0,0)))
+
+ # the trumpets playing and the sound for the trumpets
+ play = Parallel(SoundInterval(crabHorn, startTime=1.5, duration=4.0, node=toon),Sequence(Wait(.25),longshake([trumpet1,trumpet2],3,.2),
+ Wait(.5),longshake([trumpet1,trumpet2],3,.2),Wait(.5),
+ longshake([trumpet1,trumpet2],9,.1),longshake([trumpet1,trumpet2],3,.2)))
+
+ # particle interval
+ killParticles = Parallel(Func(blue_p0.setLitterSize,0),Func(red_p0.setLitterSize,0),Func(yellow_p0.setLitterSize,0))
+ p = Parallel(ParticleInterval(confettiBlue, confettiNode, worldRelative=0, duration=3, cleanup = True),
+ ParticleInterval(confettiRed, confettiNode, worldRelative=0, duration=3, cleanup = True),
+ ParticleInterval(confettiYellow, confettiNode, worldRelative=0, duration=3, cleanup = True))
+ pOff = Parallel(Func(confettiBlue.remove),
+ Func(confettiRed.remove),
+ Func(confettiYellow.remove))
+ partInterval = Parallel(p,Sequence(Wait(1.7),killParticles,Wait(1.3),
+ pOff,Func(p.finish)),
+ Sequence(Wait(3),Parallel(ballVanish)))
+
+ # sets up main interval
+ seq1 = Parallel(Sequence(Wait(delay+4.1),SoundInterval(drumroll, node=toon), Wait(.25),SoundInterval(fanfare,node=toon)),
+ Sequence(Wait(delay),trumpetsAppear,Wait(3),ballAppear,Wait(.5),
+ ballShake1,Wait(.1),ballShake2,Wait(.2),Wait(.1),
+ Parallel(openBall,partInterval),Func(fanfareNode.remove)))
+
+ seq = Parallel(seq1, Sequence(Wait(delay),Parallel(trumpetTurn,Sequence(Wait(.5),play)),
+ Wait(.5),trumpetsVanish))
+
+ # if we need to show a panel, we return the panel we created, otherwise we return the None
+ if panel != None:
+ return (seq, panel)
+
+ return (seq, None)
+
diff --git a/toontown/src/battle/FireCogPanel.py b/toontown/src/battle/FireCogPanel.py
new file mode 100644
index 0000000..cccb814
--- /dev/null
+++ b/toontown/src/battle/FireCogPanel.py
@@ -0,0 +1,219 @@
+from toontown.toonbase.ToontownBattleGlobals import *
+from toontown.toonbase import ToontownGlobals
+from direct.fsm import StateData
+from direct.directnotify import DirectNotifyGlobal
+from toontown.battle import BattleBase
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from toontown.toonbase import TTLocalizer
+
+class FireCogPanel(StateData.StateData):
+ """TownBattleChooseAvatarPanel
+ This is the panel used for choosing a avatar to attack.
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory('ChooseAvatarPanel')
+
+ def __init__(self, doneEvent):
+ self.notify.debug("Init choose panel...")
+ StateData.StateData.__init__(self, doneEvent)
+ # How many avatars in the battle?
+ self.numAvatars = 0
+ # Which was picked?
+ self.chosenAvatar = 0
+ # Is this for toons? (or suits?)
+ self.toon = 0
+ self.loaded = 0
+ return
+
+ def load(self):
+ gui = loader.loadModel("phase_3.5/models/gui/battle_gui")
+ self.frame = DirectFrame(
+ relief = None,
+ image = gui.find("**/BtlPick_TAB"),
+ image_color = Vec4(1,0.2,0.2,1),
+ )
+ self.frame.hide()
+
+ self.statusFrame = DirectFrame(
+ parent = self.frame,
+ relief = None,
+ image = gui.find("**/ToonBtl_Status_BG"),
+ image_color = Vec4(0.5,0.9,0.5,1),
+ pos = (0.611, 0, 0),
+ )
+
+ self.textFrame = DirectFrame(
+ parent = self.frame,
+ relief = None,
+ image = gui.find("**/PckMn_Select_Tab"),
+ image_color = Vec4(1,1,0,1),
+ image_scale = (1.0,1.0,2.0),
+ text = "",
+ text_fg = Vec4(0,0,0,1),
+ text_pos = (0,0.02,0),
+ text_scale = TTLocalizer.FCPtextFrameScale,
+ pos = (-0.013, 0, 0.013),
+ )
+
+ self.textFrame['text'] = (TTLocalizer.FireCogTitle % (localAvatar.getPinkSlips()))
+
+ self.avatarButtons = []
+ for i in range(4):
+ button = DirectButton(
+ parent = self.frame,
+ relief = None,
+ text = "",
+ text_fg = Vec4(0,0,0,1),
+ text_scale = 0.067,
+ text_pos = (0,-0.015,0),
+ textMayChange = 1,
+ image_scale = (1.0,1.0,1.0),
+ image = (gui.find("**/PckMn_Arrow_Up"),
+ gui.find("**/PckMn_Arrow_Dn"),
+ gui.find("**/PckMn_Arrow_Rlvr")),
+ command = self.__handleAvatar,
+ extraArgs = [i],
+ )
+
+ button.setScale(1,1,1)
+ button.setPos(0,0,0.2)
+ self.avatarButtons.append(button)
+
+ self.backButton = DirectButton(
+ parent = self.frame,
+ relief = None,
+ image = (gui.find("**/PckMn_BackBtn"),
+ gui.find("**/PckMn_BackBtn_Dn"),
+ gui.find("**/PckMn_BackBtn_Rlvr")),
+ pos = (-0.647, 0, 0.006),
+ scale = 1.05,
+ text = TTLocalizer.TownBattleChooseAvatarBack,
+ text_scale = 0.05,
+ text_pos = (0.01,-0.012),
+ text_fg = Vec4(0,0,0.8,1),
+ command = self.__handleBack,
+ )
+
+ gui.removeNode()
+ self.loaded = 1
+ return
+
+ def unload(self):
+ """unload(self)
+ """
+ if self.loaded:
+ self.frame.destroy()
+ del self.frame
+ del self.statusFrame
+ del self.textFrame
+ del self.avatarButtons
+ del self.backButton
+ self.loaded = 0
+ return
+
+ def enter(self, numAvatars, localNum=None, luredIndices=None, trappedIndices=None, track=None, fireCosts = None):
+ if not self.loaded:
+ self.load()
+ # Show the panel
+ self.frame.show()
+ # Place the buttons
+ # Suits that are lured should not be available to select for
+ # certain attacks
+ invalidTargets = []
+ if not self.toon:
+ if (len(luredIndices) > 0):
+ # You can't place a trap in front of a suit that is already lured
+ if (track == BattleBase.TRAP or track == BattleBase.LURE):
+ invalidTargets += luredIndices
+ if (len(trappedIndices) > 0):
+ # You can't place a trap in front of a suit that is already trapped
+ if (track == BattleBase.TRAP):
+ invalidTargets += trappedIndices
+ self.__placeButtons(numAvatars, invalidTargets, localNum, fireCosts)
+ # Force chat balloons to the margins while this is up.
+ # NametagGlobals.setOnscreenChatForced(1)
+ return
+
+ def exit(self):
+ # Hide the panel
+ self.frame.hide()
+ # NametagGlobals.setOnscreenChatForced(0)
+ return
+
+ def __handleBack(self):
+ doneStatus = {'mode' : 'Back'}
+ messenger.send(self.doneEvent, [doneStatus])
+ return
+
+ def __handleAvatar(self, avatar):
+ doneStatus = {'mode' : 'Avatar',
+ 'avatar' : avatar}
+ messenger.send(self.doneEvent, [doneStatus])
+ return
+
+ def adjustCogs(self, numAvatars, luredIndices, trappedIndices, track):
+ # Suits that are lured should not be available to select for
+ # certain attacks
+ invalidTargets = []
+ if (len(luredIndices) > 0):
+ # You can't place a trap in front of a suit that is already lured
+ if (track == BattleBase.TRAP or track == BattleBase.LURE):
+ invalidTargets += luredIndices
+ if (len(trappedIndices) > 0):
+ # You can't place a trap in front of a suit that is already trapped
+ if (track == BattleBase.TRAP):
+ invalidTargets += trappedIndices
+ self.__placeButtons(numAvatars, invalidTargets, None)
+ return
+
+ def adjustToons(self, numToons, localNum):
+ self.__placeButtons(numToons, [], localNum)
+ return
+
+ def __placeButtons(self, numAvatars, invalidTargets, localNum, fireCosts):
+ # Place the buttons. NOTE: Remember, from the toons point of view
+ # the avatars are numbered from right to left.
+
+ canfire = 0
+ for i in range(4):
+ # Only show the button if this avatar is in the battle
+ # and he is not in the invalidTargets list
+ if ((numAvatars > i) and (i not in invalidTargets) and (i != localNum)):
+ self.avatarButtons[i].show()
+ self.avatarButtons[i]['text'] = ""#("%s" % (fireCosts[i]))
+ if fireCosts[i] <= localAvatar.getPinkSlips():
+ self.avatarButtons[i]['state'] = DGG.NORMAL
+ self.avatarButtons[i]['text_fg'] = (0,0,0,1)
+ canfire = 1
+ else:
+ self.avatarButtons[i]['state'] = DGG.DISABLED
+ self.avatarButtons[i]['text_fg'] = (1.0,0,0,1)
+ else:
+ self.avatarButtons[i].hide()
+ if canfire:
+ self.textFrame['text'] = (TTLocalizer.FireCogTitle % (localAvatar.getPinkSlips()))
+ else:
+ self.textFrame['text'] = (TTLocalizer.FireCogLowTitle % (localAvatar.getPinkSlips()))
+
+
+ # Evenly positions the buttons on the bar
+ if numAvatars == 1:
+ self.avatarButtons[0].setX(0)
+ elif numAvatars == 2:
+ self.avatarButtons[0].setX(0.2)
+ self.avatarButtons[1].setX(-0.2)
+ elif numAvatars == 3:
+ self.avatarButtons[0].setX(0.4)
+ self.avatarButtons[1].setX(0.0)
+ self.avatarButtons[2].setX(-0.4)
+ elif numAvatars == 4:
+ self.avatarButtons[0].setX(0.6)
+ self.avatarButtons[1].setX(0.2)
+ self.avatarButtons[2].setX(-0.2)
+ self.avatarButtons[3].setX(-0.6)
+ else:
+ self.notify.error("Invalid number of avatars: %s" % numAvatars)
+
+ return None
+
+
diff --git a/toontown/src/battle/HealJokes.py b/toontown/src/battle/HealJokes.py
new file mode 100644
index 0000000..e58cfe5
--- /dev/null
+++ b/toontown/src/battle/HealJokes.py
@@ -0,0 +1,4 @@
+
+from toontown.toonbase import TTLocalizer
+toonHealJokes = TTLocalizer.ToonHealJokes
+
diff --git a/toontown/src/battle/Movie.py b/toontown/src/battle/Movie.py
new file mode 100644
index 0000000..be1385f
--- /dev/null
+++ b/toontown/src/battle/Movie.py
@@ -0,0 +1,1108 @@
+from toontown.toonbase.ToontownBattleGlobals import *
+from BattleBase import *
+from direct.interval.IntervalGlobal import *
+
+from direct.showbase import DirectObject
+import MovieFire
+import MovieSOS
+import MovieNPCSOS
+import MoviePetSOS
+import MovieHeal
+import MovieTrap
+import MovieLure
+import MovieSound
+import MovieThrow
+import MovieSquirt
+import MovieDrop
+import MovieSuitAttacks
+import MovieToonVictory
+import PlayByPlayText
+import BattleParticles
+from toontown.distributed import DelayDelete
+import BattleExperience
+from SuitBattleGlobals import *
+
+from direct.directnotify import DirectNotifyGlobal
+import RewardPanel
+import random
+import MovieUtil
+from toontown.toon import Toon
+from toontown.toonbase import ToontownGlobals
+from toontown.toontowngui import TTDialog
+import copy
+from toontown.toonbase import TTLocalizer
+from toontown.toon import NPCToons
+
+camPos = Point3(14, 0, 10)
+camHpr = Vec3(89, -30, 0)
+
+randomBattleTimestamp = base.config.GetBool('random-battle-timestamp', 0)
+
+class Movie(DirectObject.DirectObject):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('Movie')
+
+ def __init__(self, battle):
+ self.battle = battle
+ self.track = None
+ self.rewardPanel = None
+ self.playByPlayText = PlayByPlayText.PlayByPlayText()
+ self.playByPlayText.hide()
+ self.renderProps = []
+
+ self.hasBeenReset = 0
+ self.reset()
+ self.rewardHasBeenReset = 0
+ self.resetReward()
+
+ def cleanup(self):
+ self.reset()
+ self.resetReward()
+ self.battle = None
+ if (self.playByPlayText != None):
+ self.playByPlayText.cleanup()
+ self.playByPlayText = None
+ if (self.rewardPanel != None):
+ self.rewardPanel.cleanup()
+ self.rewardPanel = None
+
+ def needRestoreColor(self):
+ self.restoreColor = 1
+
+ def clearRestoreColor(self):
+ self.restoreColor = 0
+
+ def needRestoreHips(self):
+ self.restoreHips = 1
+
+ def clearRestoreHips(self):
+ self.restoreHips = 0
+
+ def needRestoreHeadScale(self):
+ self.restoreHeadScale = 1
+
+ def clearRestoreHeadScale(self):
+ self.restoreHeadScale = 0
+
+ def needRestoreToonScale(self):
+ self.restoreToonScale = 1
+
+ def clearRestoreToonScale(self):
+ self.restoreToonScale = 0
+
+ def needRestoreParticleEffect(self, effect):
+ self.specialParticleEffects.append(effect)
+
+ def clearRestoreParticleEffect(self, effect):
+ if (self.specialParticleEffects.count(effect) > 0):
+ self.specialParticleEffects.remove(effect)
+
+ def needRestoreRenderProp(self, prop):
+ self.renderProps.append(prop)
+
+ def clearRenderProp(self, prop):
+ if (self.renderProps.count(prop) > 0):
+ self.renderProps.remove(prop)
+
+ def restore(self):
+ """
+ This method should be called when the movie
+ needs to be terminated early and we need to restore any
+ toons to the state that they would be in if the movie
+ had continued
+ """
+ # Speculation: all this work is no longer needed now that
+ # interval.finish() guarantees completion.
+ return
+
+ assert(self.notify.debug('restore()'))
+ for toon in self.battle.activeToons:
+ # Undo any change in animation
+ toon.loop('neutral')
+
+ # Undo any change in position
+ origPos, origHpr = self.battle.getActorPosHpr(toon)
+ toon.setPosHpr(self.battle, origPos, origHpr)
+
+ # Remove any props from the toon's hands
+ hands = toon.getRightHands()[:]
+ hands += toon.getLeftHands()
+ for hand in hands:
+ props = hand.getChildren()
+ for prop in props:
+ # Don't remove the sticker book!
+ if (prop.getName() != 'book'):
+ MovieUtil.removeProp(prop)
+
+ # Undo FillWithLead, HotAir, Fired, Withdrawal, RubOut,
+ # FountainPen
+ if (self.restoreColor == 1):
+ assert(self.notify.debug('restore color for toon: %d' % \
+ toon.doId))
+ headParts = toon.getHeadParts()
+ torsoParts = toon.getTorsoParts()
+ legsParts = toon.getLegsParts()
+ partsList = [headParts, torsoParts, legsParts]
+ for parts in partsList:
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ nextPart.clearColorScale()
+ nextPart.clearTransparency()
+
+ # Undo RedTape
+ if (self.restoreHips == 1):
+ assert(self.notify.debug('restore hips for toon: %d' % \
+ toon.doId))
+ parts = toon.getHipsParts()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ props = nextPart.getChildren()
+ for prop in props:
+ if (prop.getName() == 'redtape-tube.egg'):
+ MovieUtil.removeProp(prop)
+
+ # Unshrink HeadShrink
+ if (self.restoreHeadScale == 1):
+ assert(self.notify.debug('restore head scale for toon: %d' % \
+ toon.doId))
+
+ headScale = ToontownGlobals.toonHeadScales[toon.style.getAnimal()]
+ for lod in toon.getLODNames():
+ toon.getPart('head', lod).setScale(headScale)
+
+ # Unshrink Downsize, restore toon back to proportion (1)
+ if (self.restoreToonScale == 1):
+ assert(self.notify.debug('restore toon scale for toon: %d' % \
+ toon.doId))
+ toon.setScale(1)
+
+ # In case the toon's head or arm parts have been altered, as with ReOrg,
+ # we restore those values
+
+ # Undo ReOrg of head parts, restore toon head parts back to correct places
+ assert(self.notify.debug('restore toon head parts for toon: %d' % toon.doId))
+
+ # Restore the position and hpr of the parts of the head, which
+ # were originally pos = 0, 0, 0 and hpr = -18.435, 0, 0
+ headParts = toon.getHeadParts()
+ for partNum in range(0, headParts.getNumPaths()):
+ part = headParts.getPath(partNum)
+ part.setHpr(0, 0, 0)#, startHpr = part.getHpr())
+ part.setPos(0, 0, 0)
+
+ assert(self.notify.debug('restore toon arm parts for toon: %d' % toon.doId))
+
+ # Now restore the hpr on the arm, sleeve, and hand parts,
+ # which were all originally hpr = 0, 0, 0
+ arms = toon.findAllMatches('**/arms')
+ sleeves = toon.findAllMatches('**/sleeves')
+ hands = toon.findAllMatches('**/hands')
+ for partNum in range(0, arms.getNumPaths()):
+ armPart = arms.getPath(partNum)
+ sleevePart = sleeves.getPath(partNum)
+ handsPart = hands.getPath(partNum)
+ armPart.setHpr(0, 0, 0)
+ sleevePart.setHpr(0, 0, 0)
+ handsPart.setHpr(0, 0, 0)
+
+
+ for suit in self.battle.activeSuits:
+ # Kludgey hack around mystery crash. Why are we cleaning up
+ # suits in this case anyway?
+ if suit._Actor__animControlDict != None:
+ # Undo any change in animation
+ suit.loop('neutral')
+ # A trap prop is no longer fresh
+ suit.battleTrapIsFresh = 0
+
+ # Undo any change in position
+ origPos, origHpr = self.battle.getActorPosHpr(suit)
+ suit.setPosHpr(self.battle, origPos, origHpr)
+
+ # Remove any props from the suit's hands
+ hands = [suit.getRightHand(), suit.getLeftHand()]
+ for hand in hands:
+ props = hand.getChildren()
+ for prop in props:
+ MovieUtil.removeProp(prop)
+
+ # Clean up any special particle effects
+ # RazzleDazzle, Rolodex
+ for effect in self.specialParticleEffects:
+ if (effect != None):
+ assert(self.notify.debug('restore particle effect: %s' % \
+ effect.getName()))
+ effect.cleanup()
+ self.specialParticleEffects = []
+
+ # Remove any props that are parented to render
+ for prop in self.renderProps:
+ MovieUtil.removeProp(prop)
+ self.renderProps = []
+
+ def _deleteTrack(self):
+ if self.track:
+ DelayDelete.cleanupDelayDeletes(self.track)
+ self.track = None
+
+ def reset(self, finish=0):
+ if (self.hasBeenReset == 1):
+ assert(self.notify.debug('reset() - movie was previously reset'))
+ return
+ self.hasBeenReset = 1
+ self.stop()
+ self._deleteTrack()
+ if (finish == 1):
+ self.restore()
+ self.toonAttackDicts = []
+ self.suitAttackDicts = []
+ self.restoreColor = 0
+ self.restoreHips = 0
+ self.restoreHeadScale = 0
+ self.restoreToonScale = 0
+ self.specialParticleEffects = []
+ for prop in self.renderProps:
+ MovieUtil.removeProp(prop)
+ self.renderProps = []
+
+ def resetReward(self, finish=0):
+ if (self.rewardHasBeenReset == 1):
+ assert(self.notify.debug(
+ 'resetReward() - movie was previously reset'))
+ return
+ self.rewardHasBeenReset = 1
+
+ self.stop()
+ self._deleteTrack()
+ if (finish == 1):
+ self.restore()
+ self.toonRewardDicts = []
+ if (self.rewardPanel != None):
+ self.rewardPanel.destroy()
+ self.rewardPanel = None
+
+ def play(self, ts, callback):
+ """ play(ts)
+ Play the toon and suit attacks and responses in order of
+ increasing entertainment value
+ """
+ self.hasBeenReset = 0
+ ptrack = Sequence()
+ camtrack = Sequence()
+
+ # Decide which side of the battle the camera should film from during this
+ # series of attacks
+ if (random.random() > 0.5):
+ MovieUtil.shotDirection = 'left'
+ else:
+ MovieUtil.shotDirection = 'right'
+
+ # Make sure that any traps on suits are not regarded as freshly thrown in this round
+ for s in self.battle.activeSuits:
+ s.battleTrapIsFresh = 0
+
+ (tattacks, tcam) = self.__doToonAttacks()
+ if (tattacks):
+ ptrack.append(tattacks)
+ camtrack.append(tcam)
+ (sattacks, scam) = self.__doSuitAttacks()
+ if (sattacks):
+ ptrack.append(sattacks)
+ camtrack.append(scam)
+ ptrack.append(Func(callback))
+ self._deleteTrack()
+ self.track = Sequence(ptrack, name='movie-track-%d' % self.battle.doId)
+ if (self.battle.localToonPendingOrActive()):
+ self.track = Parallel(self.track,
+ Sequence(camtrack),
+ name='movie-track-with-cam-%d' % self.battle.doId)
+
+ if (randomBattleTimestamp == 1):
+ randNum = random.randint(0, 99)
+ dur = self.track.getDuration()
+ ts = (float(randNum)/100.0) * dur
+ assert(self.notify.debug('play() - random timestamp: %f' % ts))
+
+ # Store a DelayDelete object within the track for each
+ # distributed object in the battle, so the track can execute
+ # without running into grief.
+ self.track.delayDeletes = []
+ for suit in self.battle.suits:
+ self.track.delayDeletes.append(DelayDelete.DelayDelete(suit, 'Movie.play'))
+ for toon in self.battle.toons:
+ self.track.delayDeletes.append(DelayDelete.DelayDelete(toon, 'Movie.play'))
+
+ self.track.start(ts)
+ return None
+
+
+ def finish(self):
+ """ finish()
+ End the battle movie before it's done playing
+ """
+ self.track.finish()
+ return None
+
+
+ def playReward(self, ts, name, callback):
+ self.rewardHasBeenReset = 0
+ ptrack = Sequence()
+ camtrack = Sequence()
+ self.rewardPanel = RewardPanel.RewardPanel(name)
+ self.rewardPanel.hide()
+
+ (victory, camVictory) = MovieToonVictory.doToonVictory(
+ self.battle.localToonActive(),
+ self.battle.activeToons,
+ self.toonRewardIds,
+ self.toonRewardDicts,
+ self.deathList,
+ self.rewardPanel,
+ 1,
+ self.uberList,
+ self.helpfulToonsList)
+ if (victory):
+ ptrack.append(victory)
+ camtrack.append(camVictory)
+ ptrack.append(Func(callback))
+ self._deleteTrack()
+ self.track = Sequence(ptrack,
+ name='movie-reward-track-%d' % self.battle.doId)
+ if (self.battle.localToonActive()):
+ self.track = Parallel(self.track,
+ camtrack,
+ name = 'movie-reward-track-with-cam-%d' % self.battle.doId)
+ self.track.delayDeletes = []
+ for t in self.battle.activeToons:
+ self.track.delayDeletes.append(DelayDelete.DelayDelete(t, 'Movie.playReward'))
+ self.track.start(ts)
+ return None
+
+ def playTutorialReward(self, ts, name, callback):
+ """
+ A special function for playing the tutorial reward movie after the
+ tutorial battle.
+ """
+ # import pdb; pdb.set_trace()
+ self.rewardHasBeenReset = 0
+ self.rewardPanel = RewardPanel.RewardPanel(name)
+ self.rewardCallback = callback
+ # Generate quest progress intervals
+ # These aren't needed until playTutorialReward_3 but we will generate them here
+ # before the avatar's quest list is updated. If we wait until playTutorialReward_3
+ # then getQuestIntervalList will return an empty list because base.localAvatar.quests
+ # will have already been updated and the number of earned cogs will compute to be zero
+ self.questList = self.rewardPanel.getQuestIntervalList(
+ base.localAvatar,
+ # Flunky, Level 1, first toon was involved, not a skelecog
+ [0, 1, 1, 0],
+ [base.localAvatar],
+ base.localAvatar.quests[0],
+ [],
+ [base.localAvatar.getDoId()])
+
+
+ # Position the camera
+ camera.setPosHpr(0, 8, base.localAvatar.getHeight() * 0.66,
+ 179, 15, 0)
+ # Start phase one of the movie
+ self.playTutorialReward_1()
+
+ def playTutorialReward_1(self):
+ # Create a dialog box
+ self.tutRewardDialog_1 = TTDialog.TTDialog(
+ text = TTLocalizer.MovieTutorialReward1,
+ command = self.playTutorialReward_2,
+ style = TTDialog.Acknowledge,
+ fadeScreen = None,
+ pos = (0.65, 0, 0.5),
+ scale = 0.8,
+ )
+ self.tutRewardDialog_1.hide()
+ # play the first movie.
+ self._deleteTrack()
+ self.track = Sequence(name='tutorial-reward-1')
+ self.track.append(Func(self.rewardPanel.initGagFrame,
+ base.localAvatar,
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0]))
+ self.track += self.rewardPanel.getTrackIntervalList(base.localAvatar, THROW_TRACK, 0, 1, 0)
+ self.track.append(Func(self.tutRewardDialog_1.show))
+ self.track.start()
+ return
+
+ def playTutorialReward_2(self, value):
+ self.tutRewardDialog_1.cleanup()
+
+ # Create a dialog box
+ self.tutRewardDialog_2 = TTDialog.TTDialog(
+ text = TTLocalizer.MovieTutorialReward2,
+ command = self.playTutorialReward_3,
+ style = TTDialog.Acknowledge,
+ fadeScreen = None,
+ pos = (0.65, 0, 0.5),
+ scale = 0.8,
+ )
+ self.tutRewardDialog_2.hide()
+ # play the second movie.
+ self._deleteTrack()
+ self.track = Sequence(name='tutorial-reward-2')
+ self.track.append(Wait(1.0))
+ self.track += self.rewardPanel.getTrackIntervalList(base.localAvatar, SQUIRT_TRACK, 0, 1, 0)
+ self.track.append(Func(self.tutRewardDialog_2.show))
+ self.track.start()
+ return
+
+ def playTutorialReward_3(self, value):
+ self.tutRewardDialog_2.cleanup()
+ from toontown.toon import Toon
+ from toontown.toon import ToonDNA
+ def doneChat1(page, elapsed = 0):
+ self.track2.start()
+ def doneChat2(elapsed):
+ self.track2.pause()
+ self.track3.start()
+ def uniqueName(hook):
+ return "TutorialTom-" + hook
+ self.tutorialTom = Toon.Toon()
+ dna = ToonDNA.ToonDNA()
+ dnaList = ("dll" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,2 ,6 ,2 ,6 ,2 ,16)
+ dna.newToonFromProperties(*dnaList)
+ self.tutorialTom.setDNA(dna)
+ self.tutorialTom.setName(TTLocalizer.NPCToonNames[20000])
+ self.tutorialTom.uniqueName = uniqueName
+
+ if base.config.GetString("language", "english") == "japanese":
+ self.tomDialogue03 = base.loadSfx("phase_3.5/audio/dial/CC_tom_movie_tutorial_reward01.mp3")
+ self.tomDialogue04 = base.loadSfx("phase_3.5/audio/dial/CC_tom_movie_tutorial_reward02.mp3")
+ self.tomDialogue05 = base.loadSfx("phase_3.5/audio/dial/CC_tom_movie_tutorial_reward03.mp3")
+ self.musicVolume = base.config.GetFloat(
+ "tutorial-music-volume", 0.5)
+ else:
+ self.tomDialogue03 = None
+ self.tomDialogue04 = None
+ self.tomDialogue05 = None
+ self.musicVolume = 0.9
+
+ # Need to lower battle music during dialogue
+ music = base.cr.playGame.place.loader.battleMusic
+
+ # import pdb; pdb.set_trace()
+
+ # Quest list is generated in playTutorialReward before the avatar's quest description
+ # is updated to reflect defeating the flunky
+ if self.questList:
+ self.track1 = Sequence(
+ Wait(1.0),
+ Func(self.rewardPanel.initQuestFrame,
+ base.localAvatar,
+ copy.deepcopy(base.localAvatar.quests)),
+ Wait(1.0),
+ Sequence(*self.questList),
+ Wait(1.0),
+ Func(self.rewardPanel.hide),
+ Func(camera.setPosHpr, render,
+ 34, 19.88, 3.48, -90, -2.36, 0),
+ Func(base.localAvatar.animFSM.request, "neutral"),
+ Func(base.localAvatar.setPosHpr,
+ 40.31, 22.00, -0.47, 150.00, 360.00, 0.00),
+ Wait(0.5),
+ Func(self.tutorialTom.reparentTo, render),
+ Func(self.tutorialTom.show),
+ Func(self.tutorialTom.setPosHpr,
+ 40.29, 17.9, -0.47, 11.31, 0.00, 0.07),
+ Func(self.tutorialTom.animFSM.request,'TeleportIn'),
+ Wait(1.5169999999999999),
+ Func(self.tutorialTom.animFSM.request, 'neutral'),
+ Func(self.acceptOnce,
+ self.tutorialTom.uniqueName("doneChatPage"),
+ doneChat1),
+ Func(self.tutorialTom.addActive),
+ Func(music.setVolume, self.musicVolume),
+ Func(self.tutorialTom.setLocalPageChat,
+ TTLocalizer.MovieTutorialReward3, 0, None,
+ [self.tomDialogue03]),
+ name='tutorial-reward-3a')
+ self.track2 = Sequence(
+ Func(self.acceptOnce,
+ self.tutorialTom.uniqueName("doneChatPage"),
+ doneChat2),
+ Func(self.tutorialTom.setLocalPageChat,
+ TTLocalizer.MovieTutorialReward4, 1, None,
+ [self.tomDialogue04]),
+ Func(self.tutorialTom.setPlayRate, 1.5, "right-hand-start"),
+ Func(self.tutorialTom.play,"right-hand-start"),
+ Wait(self.tutorialTom.getDuration("right-hand-start")/1.5),
+ Func(self.tutorialTom.loop,"right-hand"),
+ name='tutorial-reward-3b')
+ self.track3 = Parallel(
+ Sequence(
+ Func(self.tutorialTom.setPlayRate, -1.8,
+ "right-hand-start"),
+ Func(self.tutorialTom.play,"right-hand-start"),
+ Wait(self.tutorialTom.getDuration("right-hand-start")/1.8),
+ Func(self.tutorialTom.animFSM.request,'neutral'),
+ name='tutorial-reward-3ca'
+ ),
+ Sequence(
+ Wait(0.5),
+ Func(self.tutorialTom.setChatAbsolute, TTLocalizer.MovieTutorialReward5,
+ CFSpeech | CFTimeout, self.tomDialogue05),
+ Wait(1.0),
+ Func(self.tutorialTom.animFSM.request,'TeleportOut'),
+ Wait(self.tutorialTom.getDuration("teleport")),
+ Wait(1.0),
+ Func(self.playTutorialReward_4, 0),
+ name='tutorial-reward-3cb'
+ ),
+ name='tutorial-reward-3c')
+
+ self.track1.start()
+ else:
+ self.playTutorialReward_4(0)
+ return
+
+ def playTutorialReward_4(self, value):
+ # Point toon at toon headquarters
+ base.localAvatar.setH(270)
+ self.tutorialTom.removeActive()
+ self.tutorialTom.delete()
+ self.questList = None
+ self.rewardCallback()
+ return
+
+ def stop(self):
+ """ stop()
+ """
+ if (self.track):
+ self.track.finish()
+ self._deleteTrack()
+ # These next two are probably not needed.
+ if (self.rewardPanel):
+ self.rewardPanel.hide()
+ if (self.playByPlayText):
+ self.playByPlayText.hide()
+
+ def __doToonAttacks(self):
+ """ __doToonAttacks()
+ Create a track of all toon attacks in the proper order
+ """
+ assert(self.notify.debug("doToonAttacks"))
+ if base.config.GetBool("want-toon-attack-anims", 1):
+ track = Sequence(name='toon-attacks')
+ camTrack = Sequence(name='toon-attacks-cam')
+
+ (ival, camIval) = MovieFire.doFires(self.__findToonAttack(FIRE))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+
+ (ival, camIval) = MovieSOS.doSOSs(self.__findToonAttack(SOS))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ (ival, camIval) = MovieNPCSOS.doNPCSOSs(self.__findToonAttack(NPCSOS))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ (ival, camIval) = MoviePetSOS.doPetSOSs(self.__findToonAttack(PETSOS))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ hasHealBonus = self.battle.getInteractivePropTrackBonus() == HEAL
+ (ival, camIval) = MovieHeal.doHeals(self.__findToonAttack(HEAL), hasHealBonus)
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ (ival, camIval) = MovieTrap.doTraps(self.__findToonAttack(TRAP))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ (ival, camIval) = MovieLure.doLures(self.__findToonAttack(LURE))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ (ival, camIval) = MovieSound.doSounds(self.__findToonAttack(SOUND))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ (ival, camIval) = MovieThrow.doThrows(self.__findToonAttack(THROW))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ (ival, camIval) = MovieSquirt.doSquirts(
+ self.__findToonAttack(SQUIRT))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ (ival, camIval) = MovieDrop.doDrops(self.__findToonAttack(DROP))
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ if (len(track) == 0):
+ return (None, None)
+ else:
+ return (track, camTrack)
+ else:
+ return (None, None)
+
+
+ def genRewardDicts(self,
+ id0, origExp0, earnedExp0, origQuests0, items0, missedItems0,
+ origMerits0, merits0, parts0,
+ id1, origExp1, earnedExp1, origQuests1, items1, missedItems1,
+ origMerits1, merits1, parts1,
+ id2, origExp2, earnedExp2, origQuests2, items2, missedItems2,
+ origMerits2, merits2, parts2,
+ id3, origExp3, earnedExp3, origQuests3, items3, missedItems3,
+ origMerits3, merits3, parts3,
+ deathList, uberList, helpfulToonsList):
+ assert(self.notify.debug("deathList: " + str(deathList)))
+ self.deathList = deathList
+ self.helpfulToonsList = helpfulToonsList
+ entries = ((id0, origExp0, earnedExp0, origQuests0, items0, missedItems0,
+ origMerits0, merits0, parts0),
+ (id1, origExp1, earnedExp1, origQuests1, items1, missedItems1,
+ origMerits1, merits1, parts1),
+ (id2, origExp2, earnedExp2, origQuests2, items2, missedItems2,
+ origMerits2, merits2, parts2),
+ (id3, origExp3, earnedExp3, origQuests3, items3, missedItems3,
+ origMerits3, merits3, parts3))
+ self.toonRewardDicts = BattleExperience.genRewardDicts(entries)
+ self.toonRewardIds = [id0, id1, id2, id3]
+ self.uberList = uberList
+ #import pdb; pdb.set_trace()
+
+ def genAttackDicts(self, toons, suits,
+ id0, tr0, le0, tg0, hp0, ac0, hpb0, kbb0, died0, revive0,
+ id1, tr1, le1, tg1, hp1, ac1, hpb1, kbb1, died1, revive1,
+ id2, tr2, le2, tg2, hp2, ac2, hpb2, kbb2, died2, revive2,
+ id3, tr3, le3, tg3, hp3, ac3, hpb3, kbb3, died3, revive3,
+ sid0, at0, stg0, dm0, sd0, sb0, st0,
+ sid1, at1, stg1, dm1, sd1, sb1, st1,
+ sid2, at2, stg2, dm2, sd2, sb2, st2,
+ sid3, at3, stg3, dm3, sd3, sb3, st3):
+ """ genAttackDicts()
+ """
+ assert(self.notify.debug('genAttackDicts()'))
+ if (self.track and self.track.isPlaying()):
+ self.notify.warning('genAttackDicts() - track is playing!')
+ toonAttacks = ((id0, tr0, le0, tg0, hp0, ac0, hpb0, kbb0, died0, revive0),
+ (id1, tr1, le1, tg1, hp1, ac1, hpb1, kbb1, died1, revive1),
+ (id2, tr2, le2, tg2, hp2, ac2, hpb2, kbb2, died2, revive2),
+ (id3, tr3, le3, tg3, hp3, ac3, hpb3, kbb3, died3, revive3))
+ self.__genToonAttackDicts(toons, suits, toonAttacks)
+ suitAttacks = ((sid0, at0, stg0, dm0, sd0, sb0, st0),
+ (sid1, at1, stg1, dm1, sd1, sb1, st1),
+ (sid2, at2, stg2, dm2, sd2, sb2, st2),
+ (sid3, at3, stg3, dm3, sd3, sb3, st3))
+ self.__genSuitAttackDicts(toons, suits, suitAttacks)
+
+ def __genToonAttackDicts(self, toons, suits, toonAttacks):
+ """ Create a list of dictionaries for the
+ toon attacks, sorted by increasing level
+ """
+
+ assert(self.notify.debug('genToonAttackDicts() - toons: %s suits: %s toon attacks: %s' % (toons, suits, toonAttacks)))
+ for ta in toonAttacks:
+ targetGone = 0
+ track = ta[TOON_TRACK_COL]
+ if (track != NO_ATTACK):
+ adict = {}
+ toonIndex = ta[TOON_ID_COL]
+ assert(toonIndex < len(toons))
+ toonId = toons[toonIndex]
+ assert(toonId != -1)
+ toon = self.battle.findToon(toonId)
+ if (toon == None):
+ continue
+ level = ta[TOON_LVL_COL]
+ adict['toon'] = toon
+ adict['track'] = track
+ adict['level'] = level
+ hps = ta[TOON_HP_COL]
+ kbbonuses = ta[TOON_KBBONUS_COL]
+ # If it's an NPCSOS with a normal toon attack, do some
+ # extra prep work first
+ if (track == NPCSOS):
+ # This will indicate attack was NPCSOS after we change
+ # the track
+ adict['npcId'] = ta[TOON_TGT_COL]
+ toonId = ta[TOON_TGT_COL]
+ track, npc_level, npc_hp = NPCToons.getNPCTrackLevelHp(adict['npcId'])
+ if (track == None):
+ track = NPCSOS
+ adict['track'] = track
+ adict['level'] = npc_level
+ elif (track == PETSOS):
+ petId = ta[TOON_TGT_COL]
+ adict['toonId'] = toonId
+ adict['petId'] = petId
+ if (track == SOS):
+ # For an SOS, the target is a toonHandle to a friend
+ targetId = ta[TOON_TGT_COL]
+ assert(targetId != -1)
+ # We can only show the name of the toon being called
+ # to the toon calling for help and the toon being called
+ if (targetId == base.localAvatar.doId):
+ target = base.localAvatar
+ adict['targetType'] = 'callee'
+ elif (toon == base.localAvatar):
+ target = base.cr.identifyAvatar(targetId)
+ assert(target != None)
+ adict['targetType'] = 'caller'
+ else:
+ target = None
+ adict['targetType'] = 'observer'
+ adict['target'] = target
+ elif (track == NPCSOS or
+ track == NPC_COGS_MISS or
+ track == NPC_TOONS_HIT or
+ track == NPC_RESTOCK_GAGS or
+ track == PETSOS):
+ adict['special'] = 1
+ toonHandles = []
+ for t in toons:
+ if (t != -1):
+ target = self.battle.findToon(t)
+ if (target == None):
+ continue
+ # NPC_TOONS_HIT is like Heal - it only works
+ # on other toons
+ if (track == NPC_TOONS_HIT and t == toonId):
+ continue
+ toonHandles.append(target)
+ adict['toons'] = toonHandles
+ suitHandles = []
+ for s in suits:
+ if (s != -1):
+ target = self.battle.findSuit(s)
+ if (target == None):
+ continue
+ suitHandles.append(target)
+ adict['suits'] = suitHandles
+ if (track == PETSOS):
+ del adict['special']
+ targets = []
+ for t in toons:
+ if (t != -1):
+ target = self.battle.findToon(t)
+ if (target == None):
+ continue
+ tdict = {}
+ tdict['toon'] = target
+ assert(toons.index(t) < len(hps))
+ tdict['hp'] = hps[toons.index(t)]
+ self.notify.debug("PETSOS: toon: %d healed for hp: %d" % (target.doId, hps[toons.index(t)]))
+ targets.append(tdict)
+ if (len(targets) > 0):
+ adict['target'] = targets
+ elif (track == HEAL):
+ # Odd level heals affect all toons (except the caster)
+ if (levelAffectsGroup(HEAL, level)):
+ targets = []
+ for t in toons:
+ if (t != toonId and t != -1):
+ target = self.battle.findToon(t)
+ if (target == None):
+ continue
+ tdict = {}
+ tdict['toon'] = target
+ assert(toons.index(t) < len(hps))
+ tdict['hp'] = hps[toons.index(t)]
+ self.notify.debug("HEAL: toon: %d healed for hp: %d" % (target.doId, hps[toons.index(t)]))
+ targets.append(tdict)
+ if (len(targets) > 0):
+ adict['target'] = targets
+ else:
+ targetGone = 1
+ #import pdb; pdb.set_trace()
+ else:
+ targetIndex = ta[TOON_TGT_COL]
+ if targetIndex < 0:
+ targetGone = 1
+ else:
+ assert(targetIndex < len(toons))
+ targetId = toons[targetIndex]
+ assert(targetId != -1)
+ target = self.battle.findToon(targetId)
+ if (target != None):
+ tdict = {}
+ tdict['toon'] = target
+ assert(targetIndex < len(hps))
+ tdict['hp'] = hps[targetIndex]
+ adict['target'] = tdict
+ else:
+ targetGone = 1
+ #import pdb; pdb.set_trace()
+ else:
+ # Odd level lures affect all suits
+ # Sounds affect all suits
+ # NPC drops and traps affect all suits
+ if (attackAffectsGroup(track, level, ta[TOON_TRACK_COL])):
+ targets = []
+ for s in suits:
+ if (s != -1):
+ target = self.battle.findSuit(s)
+ assert(target != None)
+ if (ta[TOON_TRACK_COL] == NPCSOS):
+ if (track == LURE and
+ self.battle.isSuitLured(target) == 1):
+ continue
+ elif (track == TRAP and
+ (self.battle.isSuitLured(target) == 1 or
+ target.battleTrap != NO_TRAP)):
+ continue
+ targetIndex = suits.index(s)
+ sdict = {}
+ sdict['suit'] = target
+ assert(targetIndex < len(hps))
+ sdict['hp'] = hps[targetIndex]
+ if (ta[TOON_TRACK_COL] == NPCSOS and
+ track == DROP and hps[targetIndex] == 0):
+ continue
+ sdict['kbbonus'] = kbbonuses[targetIndex]
+ sdict['died'] = ta[SUIT_DIED_COL] & \
+ (1< (suitIndex+1)):
+ for si in range(suitIndex+1, lenSuits):
+ asuit = self.battle.activeSuits[si]
+ if (self.battle.isSuitLured(asuit) == 0):
+ rightSuits.append(asuit)
+ sdict['leftSuits'] = leftSuits
+ sdict['rightSuits'] = rightSuits
+ assert(targetIndex < len(hps))
+ sdict['hp'] = hps[targetIndex]
+ sdict['kbbonus'] = kbbonuses[targetIndex]
+ sdict['died'] = ta[SUIT_DIED_COL] & (1< blevel):
+ return 1
+ elif (alevel < blevel):
+ return -1
+ return 0
+ self.toonAttackDicts.sort(compFunc)
+
+ def __findToonAttack(self, track):
+ """ Return a list of dictionaries for the
+ specified track, sorted by increasing level
+ """
+ assert(self.notify.debug("__findToonAttack"))
+ setCapture = 0
+ tp = []
+ for ta in self.toonAttackDicts:
+ if (ta['track'] == track or
+ (track == NPCSOS and ta.has_key('special'))):
+ assert self.notify.debug("tp.append(ta)")
+ tp.append(ta)
+ if track == SQUIRT:
+ setCapture = 1
+ #import pdb; pdb.set_trace()
+
+ # Do a special sort for TRAP attacks to ensure all non-NPC
+ # traps happen before NPC traps (if any)
+ if (track == TRAP):
+ sortedTraps = []
+ for attack in tp:
+ if (not attack.has_key('npcId')):
+ sortedTraps.append(attack)
+ for attack in tp:
+ if (attack.has_key('npcId')):
+ sortedTraps.append(attack)
+ assert(len(sortedTraps) == len(tp))
+ tp = sortedTraps
+
+ if setCapture:
+ #import pdb; pdb.set_trace()
+ pass
+
+ return tp
+
+ def __genSuitAttackDicts(self, toons, suits, suitAttacks):
+ """ Create a list of dictionaries for the suit attacks, sorted
+ by increasing level
+ """
+ assert(self.notify.debug('genSuitAttackDicts() - toons: %s suits: %s suit attacks: %s' % (toons, suits, suitAttacks)))
+ for sa in suitAttacks:
+ targetGone = 0
+ attack = sa[SUIT_ATK_COL]
+ if (attack != NO_ATTACK):
+ suitIndex = sa[SUIT_ID_COL]
+ assert(suitIndex < len(suits))
+ suitId = suits[suitIndex]
+ assert(suitId != -1)
+ suit = self.battle.findSuit(suitId)
+ if (suit == None):
+ self.notify.error('suit: %d not in battle!' % suitId)
+ adict = getSuitAttack(suit.getStyleName(), suit.getLevel(),
+ attack)
+ adict['suit'] = suit
+ adict['battle'] = self.battle
+ adict['playByPlayText'] = self.playByPlayText
+ adict['taunt'] = sa[SUIT_TAUNT_COL]
+ hps = sa[SUIT_HP_COL]
+ if (adict['group'] == ATK_TGT_GROUP):
+ assert(self.notify.debug(
+ 'genSuitAttackDicts() - group: %s' % toons))
+ targets = []
+ for t in toons:
+ if (t != -1):
+ target = self.battle.findToon(t)
+ if (target == None):
+ continue
+ targetIndex = toons.index(t)
+ tdict = {}
+ tdict['toon'] = target
+ assert(targetIndex < len(hps))
+ tdict['hp'] = hps[targetIndex]
+ self.notify.debug("DAMAGE: toon: %d hit for hp: %d" % (target.doId, hps[targetIndex]))
+ toonDied = sa[TOON_DIED_COL] & (1< 0):
+ adict['target'] = targets
+ else:
+ targetGone = 1
+ elif (adict['group'] == ATK_TGT_SINGLE):
+ targetIndex = sa[SUIT_TGT_COL]
+ assert(targetIndex < len(toons))
+ targetId = toons[targetIndex]
+ assert(targetId != -1)
+ target = self.battle.findToon(targetId)
+ if (target == None):
+ targetGone = 1
+ break
+ tdict = {}
+ tdict['toon'] = target
+ assert(targetIndex < len(hps))
+ tdict['hp'] = hps[targetIndex]
+ self.notify.debug("DAMAGE: toon: %d hit for hp: %d" % (target.doId, hps[targetIndex]))
+ toonDied = sa[TOON_DIED_COL] & (1< (toonIndex+1)):
+ for ti in range(toonIndex+1, lenToons):
+ leftToons.append(self.battle.activeToons[ti])
+ tdict['leftToons'] = leftToons
+ tdict['rightToons'] = rightToons
+ adict['target'] = tdict
+ else:
+ self.notify.warning('got suit attack not group or single!')
+ if (targetGone == 0):
+ self.suitAttackDicts.append(adict)
+ else:
+ self.notify.warning('genSuitAttackDicts() - target gone!')
+
+ """
+ # this sort needs to be done by the AI or it will result
+ # in a bug where multiple anims that kill a toon are played
+ # in the wrong order ('cogsmack')
+
+ # Sort the dictionaries by the level of the attacking suit
+ def compFunc(a, b):
+ alevel = a['suit'].getActualLevel()
+ blevel = b['suit'].getActualLevel()
+ if (alevel > blevel):
+ return 1
+ elif (alevel < blevel):
+ return -1
+ return 0
+ self.suitAttackDicts.sort(compFunc)
+ """
+
+ def __doSuitAttacks(self):
+ """ __doSuitAttacks()
+ Create a track of all suit attacks
+ """
+ if base.config.GetBool("want-suit-anims", 1):
+ track = Sequence(name = 'suit-attacks')
+ camTrack = Sequence(name = 'suit-attacks-cam')
+ for a in self.suitAttackDicts:
+ (ival, camIval) = MovieSuitAttacks.doSuitAttack(a)
+ if (ival):
+ track.append(ival)
+ camTrack.append(camIval)
+ if (len(track) == 0):
+ return (None, None)
+ return (track, camTrack)
+ else:
+ return (None, None)
+
+
+
+
diff --git a/toontown/src/battle/MovieCamera.py b/toontown/src/battle/MovieCamera.py
new file mode 100644
index 0000000..327d7b3
--- /dev/null
+++ b/toontown/src/battle/MovieCamera.py
@@ -0,0 +1,1530 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from toontown.toonbase.ToontownBattleGlobals import *
+from SuitBattleGlobals import *
+
+from direct.directnotify import DirectNotifyGlobal
+import random
+import MovieUtil
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieCamera')
+
+
+###########################################################
+## _ _ _ ___ _ _
+## | || |___ __ _| | / __| |_ ___| |_ ___
+## | __ / -_) _` | | \__ \ ' \/ _ \ _(_-<
+## |_||_\___\__,_|_| |___/_||_\___/\__/__/
+##
+###########################################################
+
+
+def chooseHealShot(heals, attackDuration):
+ isUber = 0
+
+ for heal in heals:
+ if (heal["level"] == 6) and not (heal.get("petId")):
+ isUber = 1
+
+ # Compose the track
+ if isUber:
+ print("is uber")
+ # Pick an open shot
+ openShot = chooseHealOpenShot(heals, attackDuration, isUber)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # if the high dive is involved we want the gag to control camera and we
+ #cut straight to the closing shot.
+ # Pick a close shot
+ closeShot = chooseHealCloseShot(heals,
+ openDuration, openName, attackDuration * 3 , isUber)
+ track = Sequence(closeShot)
+ else:
+ # Pick an open shot
+ openShot = chooseHealOpenShot(heals, attackDuration, isUber)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # Pick a close shot
+ closeShot = chooseHealCloseShot(heals,
+ openDuration, openName, attackDuration, isUber)
+ track = Sequence(openShot, closeShot)
+ # Ensure we composed it to the right length
+ #assert track.getDuration() == attackDuration
+ # Return it
+ return track
+
+def chooseHealOpenShot(heals, attackDuration, isUber = 0):
+ # Setup
+ numHeals = len(heals)
+ av = None
+ duration = 2.8
+ if isUber:
+ duration = 5.0
+ # General purpose shots
+ shotChoices = [
+ toonGroupShot,
+ #allGroupLowShot, this is a bad choice
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+def chooseHealMidShot(heals, attackDuration, isUber = 0):
+ # Setup
+ numHeals = len(heals)
+ av = None
+ duration = 2.1
+ if isUber:
+ duration = 2.1
+ # General purpose shots
+ shotChoices = [
+ toonGroupHighShot,
+ #allGroupLowShot, this is a bad choice
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+def chooseHealCloseShot(heals,
+ openDuration, openName, attackDuration, isUber = 0):
+ # Setup
+ av = None
+ duration = attackDuration - openDuration
+ # General purpose shots
+ shotChoices = [
+ toonGroupShot,
+ # allGroupLowDiagonalShot, this is a bad choice
+ ]
+ if isUber:
+ shotChoices = [
+ allGroupLowShot,
+ # allGroupLowDiagonalShot, this is a bad choice
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+###########################################################
+## _____ ___ _ _
+## |_ _| _ __ _ _ __ / __| |_ ___| |_ ___
+## | || '_/ _` | '_ \ \__ \ ' \/ _ \ _(_-<
+## |_||_| \__,_| .__/ |___/_||_\___/\__/__/
+## |_|
+###########################################################
+
+
+def chooseTrapShot(traps, attackDuration, enterDuration = 0,
+ exitDuration = 0):
+ enterShot = chooseNPCEnterShot(traps, enterDuration)
+ # Pick an open shot
+ openShot = chooseTrapOpenShot(traps, attackDuration)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # Pick a close shot
+ closeShot = chooseTrapCloseShot(traps,
+ openDuration, openName, attackDuration)
+ exitShot = chooseNPCExitShot(traps, exitDuration)
+ # Compose the track
+ track = Sequence(enterShot, openShot, closeShot, exitShot)
+ # Ensure we composed it to the right length
+ #assert track.getDuration() == attackDuration
+ # Return it
+ return track
+
+def chooseTrapOpenShot(traps, attackDuration):
+ # Setup
+ numTraps = len(traps)
+ av = None
+ duration = 3.0
+ # General purpose shots
+ shotChoices = [
+ allGroupLowShot,
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+def chooseTrapCloseShot(traps,
+ openDuration, openName, attackDuration):
+ # Setup
+ av = None
+ duration = attackDuration - openDuration
+ # General purpose shots
+ shotChoices = [
+ allGroupLowShot,
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+###########################################################
+## _ ___ _ _
+## | | _ _ _ _ ___ / __| |_ ___| |_ ___
+## | |_| || | '_/ -_) \__ \ ' \/ _ \ _(_-<
+## |____\_,_|_| \___| |___/_||_\___/\__/__/
+##
+###########################################################
+
+def chooseLureShot(lures, attackDuration, enterDuration = 0.0,
+ exitDuration = 0.0):
+ enterShot = chooseNPCEnterShot(lures, enterDuration)
+ # Pick an open shot
+ openShot = chooseLureOpenShot(lures, attackDuration)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # Pick a close shot
+ closeShot = chooseLureCloseShot(lures,
+ openDuration, openName, attackDuration)
+ exitShot = chooseNPCExitShot(lures, exitDuration)
+ # Compose the track
+ track = Sequence(enterShot, openShot, closeShot, exitShot)
+ # Ensure we composed it to the right length
+ #assert track.getDuration() == attackDuration
+ # Return it
+ return track
+
+def chooseLureOpenShot(lures, attackDuration):
+ # Setup
+ numLures = len(lures)
+ av = None
+ duration = 3.0
+
+
+ # General purpose shots
+ shotChoices = [
+ allGroupLowShot,
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+def chooseLureCloseShot(lures,
+ openDuration, openName, attackDuration):
+ # Setup
+ av = None
+ duration = attackDuration - openDuration
+
+ #figure out if any of the suits have a traintrack trap
+ #if we do the shot choices should be different
+ hasTrainTrackTrap = False
+ battle = lures[0]['battle']
+ for suit in battle.suits:
+ if hasattr(suit,'battleTrap') and suit.battleTrap == UBER_GAG_LEVEL_INDEX:
+ hasTrainTrackTrap = True
+
+ if hasTrainTrackTrap:
+ shotChoices = [
+ avatarBehindHighRightShot,
+ ]
+ av = lures[0]['toon']
+ pass
+ else:
+ # General purpose shots
+ shotChoices = [
+ allGroupLowShot,
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+###########################################################
+## ___ _ ___ _ _
+## / __| ___ _ _ _ _ __| | / __| |_ ___| |_ ___
+## \__ \/ _ \ || | ' \/ _` | \__ \ ' \/ _ \ _(_-<
+## |___/\___/\_,_|_||_\__,_| |___/_||_\___/\__/__/
+##
+###########################################################
+
+def chooseSoundShot(sounds, targets, attackDuration, enterDuration = 0.0,
+ exitDuration = 0.0):
+ enterShot = chooseNPCEnterShot(sounds, enterDuration)
+ # Pick an open shot
+ openShot = chooseSoundOpenShot(sounds, targets, attackDuration)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # Pick a close shot
+ closeShot = chooseSoundCloseShot(sounds, targets,
+ openDuration, openName, attackDuration)
+ exitShot = chooseNPCExitShot(sounds, exitDuration)
+ # Compose the track
+ track = Sequence(enterShot, openShot, closeShot, exitShot)
+ # Ensure we composed it to the right length
+ #assert track.getDuration() == attackDuration
+ # Return it
+ return track
+
+def chooseSoundOpenShot(sounds, targets, attackDuration):
+ # Setup
+ duration = 3.1
+ isUber = 0
+ for sound in sounds:
+ if sound["level"] == 6 :
+ isUber = 1
+ duration = 5.0
+ #import pdb; pdb.set_trace()
+ numSounds = len(sounds)
+ av = None
+
+ # The single toon case
+ if numSounds == 1:
+ # The attacking Toon
+ av = sounds[0]['toon']
+ # Single toon choices
+ if isUber:
+ shotChoices = [
+ avatarCloseUpThreeQuarterRightShotWide,
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ shotChoices = [
+ avatarCloseUpThreeQuarterRightShot,
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi toon case
+ elif numSounds >= 2 and numSounds <= 4:
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of sounds: %s" % numSounds)
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+def chooseSoundCloseShot(sounds, targets,
+ openDuration, openName, attackDuration):
+ # Setup
+ numSuits = len(targets)
+ av = None
+ duration = attackDuration - openDuration
+ # The single suit case
+ if numSuits == 1:
+ # The attacked suit
+ av = targets[0]['suit']
+ # Single suit choices
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterLeftShot,
+
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi suit case
+ elif numSuits >= 2 and numSuits <= 4:
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of suits: %s" % numSuits)
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+###########################################################
+## _____ _ ___ _ _
+## |_ _| |_ _ _ _____ __ __ / __| |_ ___| |_ ___
+## | | | ' \| '_/ _ \ V V / \__ \ ' \/ _ \ _(_-<
+## |_| |_||_|_| \___/\_/\_/ |___/_||_\___/\__/__/
+##
+###########################################################
+
+def chooseThrowShot(throws, suitThrowsDict, attackDuration):
+ # Pick an open shot
+ openShot = chooseThrowOpenShot(throws, suitThrowsDict, attackDuration)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # Pick a close shot
+ closeShot = chooseThrowCloseShot(throws, suitThrowsDict,
+ openDuration, openName, attackDuration)
+ # Compose the track
+ track = Sequence(openShot, closeShot)
+ # Ensure we composed it to the right length
+ #assert track.getDuration() == attackDuration
+ # Return it
+ return track
+
+def chooseThrowOpenShot(throws, suitThrowsDict, attackDuration):
+ # Setup
+ numThrows = len(throws)
+ av = None
+ duration = 3.0
+ # The single toon case
+ if numThrows == 1:
+ # The attacking Toon
+ av = throws[0]['toon']
+ # Single toon choices
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterRightShot,
+ avatarBehindShot,
+
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi toon case
+ elif numThrows >= 2 and numThrows <= 4:
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of throws: %s" % numThrows)
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+
+ # Set up the play by play text
+ # Whoops! No play by play for toons, since the multi-toon case is
+ # confusing...
+ #pbpText = attack['playByPlayText']
+ #pbpTrack = pbpText.getShowInterval(attack['prettyName'], duration)
+
+ #mtrack = Parallel(track, pbpTrack)
+
+ return track
+
+def chooseThrowCloseShot(throws, suitThrowsDict,
+ openDuration, openName, attackDuration):
+ # Setup
+ numSuits = len(suitThrowsDict)
+ av = None
+ duration = attackDuration - openDuration
+ # The single suit case
+ if numSuits == 1:
+ # The attacked suit
+ av = base.cr.doId2do[suitThrowsDict.keys()[0]]
+ # Single suit choices
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterLeftShot,
+
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi suit case (RAU we could get 0 for uber throw)
+ elif (numSuits >= 2 and numSuits <= 4) or (numSuits==0):
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of suits: %s" % numSuits)
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+##########################################################################
+## ___ _ _ ___ _ _
+## / __| __ _ _ _(_)_ _| |_ / __| |_ ___| |_ ___
+## \__ \/ _` | || | | '_| _| \__ \ ' \/ _ \ _(_-<
+## |___/\__, |\_,_|_|_| \__| |___/_||_\___/\__/__/
+## |_|
+##########################################################################
+
+def chooseSquirtShot(squirts, suitSquirtsDict, attackDuration):
+ # Pick an open shot
+ openShot = chooseSquirtOpenShot(squirts, suitSquirtsDict, attackDuration)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # Pick a close shot
+ closeShot = chooseSquirtCloseShot(squirts, suitSquirtsDict,
+ openDuration, openName, attackDuration)
+ # Compose the track
+ track = Sequence(openShot, closeShot)
+ # Ensure we composed it to the right length
+ #assert track.getDuration() == attackDuration
+ # Return it
+ return track
+
+def chooseSquirtOpenShot(squirts, suitSquirtsDict, attackDuration):
+ # Setup
+ numSquirts = len(squirts)
+ av = None
+ duration = 3.0
+ # The single toon case
+ if numSquirts == 1:
+ # The attacking Toon
+ av = squirts[0]['toon']
+ # Single toon choices
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterRightShot,
+ avatarBehindShot,
+
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi toon case
+ elif numSquirts >= 2 and numSquirts <= 4:
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of squirts: %s" % numSquirts)
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+def chooseSquirtCloseShot(squirts, suitSquirtsDict,
+ openDuration, openName, attackDuration):
+ # Setup
+ numSuits = len(suitSquirtsDict)
+ av = None
+ duration = attackDuration - openDuration
+ # The single suit case
+ if numSuits == 1:
+ # The attacked suit
+ av = base.cr.doId2do[suitSquirtsDict.keys()[0]]
+ # Single suit choices
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterLeftShot,
+
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi suit case
+ elif numSuits >= 2 and numSuits <= 4:
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of suits: %s" % numSuits)
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+
+##########################################################################
+## ___ ___ _ _
+## | \ _ _ ___ _ __ / __| |_ ___| |_ ___
+## | |) | '_/ _ \ '_ \ \__ \ ' \/ _ \ _(_-<
+## |___/|_| \___/ .__/ |___/_||_\___/\__/__/
+## |_|
+##########################################################################
+
+def chooseDropShot(drops, suitDropsDict, attackDuration, enterDuration = 0.0,
+ exitDuration = 0.0):
+ enterShot = chooseNPCEnterShot(drops, enterDuration)
+ # Pick an open shot
+ openShot = chooseDropOpenShot(drops, suitDropsDict, attackDuration)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # Pick a close shot
+ closeShot = chooseDropCloseShot(drops, suitDropsDict,
+ openDuration, openName, attackDuration)
+ exitShot = chooseNPCExitShot(drops, exitDuration)
+ # Compose the track
+ track = Sequence(enterShot, openShot, closeShot, exitShot)
+ # Ensure we composed it to the right length
+ #assert track.getDuration() == attackDuration
+ # Return it
+ return track
+
+def chooseDropOpenShot(drops, suitDropsDict, attackDuration):
+ # Setup
+ numDrops = len(drops)
+ av = None
+ duration = 3.0
+ # The single toon case
+ if numDrops == 1:
+ # The attacking Toon
+ av = drops[0]['toon']
+ # Single toon choices
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterRightShot,
+ avatarBehindShot,
+
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi toon case (Uber drop gag can give 0 single drops)
+ elif (numDrops >= 2 and numDrops <= 4) or (numDrops == 0):
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of drops: %s" % numDrops)
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+def chooseDropCloseShot(drops, suitDropsDict,
+ openDuration, openName, attackDuration):
+ # Setup
+ numSuits = len(suitDropsDict)
+ av = None
+ duration = attackDuration - openDuration
+ # The single suit case
+ if numSuits == 1:
+ # The attacked suit
+ av = base.cr.doId2do[suitDropsDict.keys()[0]]
+ # Single suit choices
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterLeftShot,
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi toon case (Uber drop gag can give 0 single drops)
+ elif (numSuits >= 2 and numSuits <= 4) or (numSuits == 0):
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of suits: %s" % numSuits)
+ # Pick a shot and return it
+ choice = random.choice(shotChoices)
+ track = choice(av, duration)
+ return track
+
+##########################################################################
+# NPC Enter and Exit Shots
+##########################################################################
+
+def chooseNPCEnterShot(enters, entersDuration):
+ # Setup
+ av = None
+ duration = entersDuration
+ # General purpose shots
+ shotChoices = [
+ toonGroupShot,
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+def chooseNPCExitShot(exits, exitsDuration):
+ # Setup
+ av = None
+ duration = exitsDuration
+ # General purpose shots
+ shotChoices = [
+ toonGroupShot,
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+##########################################################################
+## ___ _ _ ___ _ _
+## / __|_ _(_) |_ / __| |_ ___| |_ ___
+## \__ \ || | | _| \__ \ ' \/ _ \ _(_-<
+## |___/\_,_|_|\__| |___/_||_\___/\__/__/
+##
+##########################################################################
+
+def chooseSuitShot(attack, attackDuration):
+ groupStatus = attack['group']
+ target = attack['target']
+ # If the attack is a single attack, can focus in on the target toon
+ if groupStatus == ATK_TGT_SINGLE:
+ toon = target['toon']
+ suit = attack['suit']
+ name = attack['id']
+ battle = attack['battle']
+ camTrack = Sequence()
+
+ # The default camera operation of a random opening and closing shot
+ def defaultCamera(attack=attack, attackDuration=attackDuration,
+ openShotDuration=3.5, target=target):
+ if attack['group'] == ATK_TGT_GROUP:
+ return randomGroupAttackCam(attack['suit'], target, attack['battle'],
+ attackDuration, openShotDuration)
+# return randomCameraSelection(attack['suit'], attack,
+# attackDuration, openShotDuration)
+ else:
+ return randomAttackCam(attack['suit'], target['toon'], attack['battle'],
+ attackDuration, openShotDuration, 'suit')
+
+ # Pick an open shot, based on the attack
+ if (name == AUDIT):
+ camTrack.append(defaultCamera())
+ elif (name == BITE):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == BOUNCE_CHECK):
+ camTrack.append(defaultCamera())
+ elif (name == BRAIN_STORM):
+ camTrack.append(defaultCamera(openShotDuration=2.4))
+ elif (name == BUZZ_WORD):
+ camTrack.append(defaultCamera(openShotDuration=4.7))
+ elif (name == CALCULATE):
+ camTrack.append(defaultCamera())
+ elif (name == CANNED):
+ camTrack.append(defaultCamera(openShotDuration=2.9))
+ elif (name == CHOMP):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == CLIPON_TIE):
+ camTrack.append(defaultCamera(openShotDuration=3.3))
+ elif (name == CRUNCH):
+ camTrack.append(defaultCamera(openShotDuration=3.4))
+ elif (name == DEMOTION):
+ camTrack.append(defaultCamera(openShotDuration=1.7))
+ elif (name == DOUBLE_TALK):
+ camTrack.append(defaultCamera(openShotDuration=3.9))
+ elif (name == EVICTION_NOTICE):
+ camTrack.append(defaultCamera(openShotDuration=3.2))
+ elif (name == EVIL_EYE):
+ camTrack.append(defaultCamera(openShotDuration=2.7))
+ elif (name == FILIBUSTER):
+ camTrack.append(defaultCamera(openShotDuration=2.7))
+ elif (name == FILL_WITH_LEAD):
+ camTrack.append(defaultCamera(openShotDuration=3.2))
+ elif (name == FINGER_WAG):
+ camTrack.append(defaultCamera(openShotDuration=2.3))
+ elif (name == FIRED):
+ camTrack.append(defaultCamera(openShotDuration=1.7))
+ elif (name == FOUNTAIN_PEN):
+ camTrack.append(defaultCamera(openShotDuration=2.6))
+ elif (name == FREEZE_ASSETS):
+ camTrack.append(defaultCamera(openShotDuration=2.5))
+ elif (name == HALF_WINDSOR):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == HEAD_SHRINK):
+ camTrack.append(defaultCamera(openShotDuration=1.3))
+ elif (name == GLOWER_POWER):
+ camTrack.append(defaultCamera(openShotDuration=1.4))
+ elif (name == GUILT_TRIP):
+ camTrack.append(defaultCamera(openShotDuration=0.9))
+ elif (name == HANG_UP):
+ camTrack.append(defaultCamera(openShotDuration=5.1))
+ elif (name == HOT_AIR):
+ camTrack.append(defaultCamera(openShotDuration=2.5))
+ elif (name == JARGON):
+ camTrack.append(defaultCamera())
+ elif (name == LEGALESE):
+ camTrack.append(defaultCamera(openShotDuration=1.5))
+ elif (name == LIQUIDATE):
+ camTrack.append(defaultCamera(openShotDuration=2.5))
+ elif (name == MARKET_CRASH):
+ camTrack.append(defaultCamera(openShotDuration=2.9))
+ elif (name == MUMBO_JUMBO):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == PARADIGM_SHIFT):
+ camTrack.append(defaultCamera(openShotDuration=1.6))
+ elif (name == PECKING_ORDER):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == PLAY_HARDBALL):
+ camTrack.append(defaultCamera(openShotDuration=2.3))
+ elif (name == PICK_POCKET):
+ camTrack.append(allGroupLowShot(suit, 2.7))
+ elif (name == PINK_SLIP):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == POUND_KEY):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == POWER_TIE):
+ camTrack.append(defaultCamera(openShotDuration=2.4))
+ elif (name == POWER_TRIP):
+ camTrack.append(defaultCamera(openShotDuration=1.1))
+ elif (name == QUAKE):
+ shakeIntensity = 5.15
+ quake = 1 # Quake applies an extra side shake for greater damage effect
+ camTrack.append(suitCameraShakeShot(suit, attackDuration,
+ shakeIntensity, quake))
+ elif (name == RAZZLE_DAZZLE):
+ camTrack.append(defaultCamera(openShotDuration=2.2))
+ elif (name == RED_TAPE):
+ camTrack.append(defaultCamera(openShotDuration=3.5))
+ elif (name == RE_ORG):
+ camTrack.append(defaultCamera(openShotDuration=1.1))
+ elif (name == RESTRAINING_ORDER):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == ROLODEX):
+ camTrack.append(defaultCamera())
+ elif (name == RUBBER_STAMP):
+ camTrack.append(defaultCamera(openShotDuration=3.2))
+ elif (name == RUB_OUT):
+ camTrack.append(defaultCamera(openShotDuration=2.2))
+ elif (name == SACKED):
+ camTrack.append(defaultCamera(openShotDuration=2.9))
+ elif (name == SCHMOOZE):
+ camTrack.append(defaultCamera(openShotDuration=2.8))
+ elif (name == SHAKE):
+ shakeIntensity = 1.75
+ camTrack.append(suitCameraShakeShot(suit, attackDuration,
+ shakeIntensity))
+ elif (name == SHRED):
+ camTrack.append(defaultCamera(openShotDuration=4.1))
+ elif (name == SPIN):
+ camTrack.append(defaultCamera(openShotDuration=1.7))
+ elif (name == SYNERGY):
+ camTrack.append(defaultCamera(openShotDuration=1.7))
+ elif (name == TABULATE):
+ camTrack.append(defaultCamera())
+ elif (name == TEE_OFF):
+ camTrack.append(defaultCamera(openShotDuration=4.5))
+ elif (name == TREMOR):
+ shakeIntensity = 0.25
+ camTrack.append(suitCameraShakeShot(suit, attackDuration, shakeIntensity))
+ elif (name == WATERCOOLER):
+ camTrack.append(defaultCamera())
+ elif (name == WITHDRAWAL):
+ camTrack.append(defaultCamera(openShotDuration=1.2))
+ elif (name == WRITE_OFF):
+ camTrack.append(defaultCamera())
+ else:
+ notify.warning('unknown attack id in chooseSuitShot: %d using default cam' \
+ % name)
+ camTrack.append(defaultCamera())
+
+ # Set up the play by play text
+ pbpText = attack['playByPlayText']
+ displayName = TTLocalizer.SuitAttackNames[attack['name']]
+ pbpTrack = pbpText.getShowInterval(displayName, 3.5)
+ return Parallel(camTrack, pbpTrack)
+
+def chooseSuitCloseShot(attack,
+ openDuration, openName, attackDuration):
+ # Setup
+ av = None
+ duration = attackDuration - openDuration
+ if (duration < 0): # If the duration is negative (bug), safely make it negligible
+ duration = 0.000001
+ groupStatus = attack['group']
+ diedTrack = None
+ # The single toon case
+ if groupStatus == ATK_TGT_SINGLE:
+ # The attacked toon
+ av = attack['target']['toon']
+ # Single suit choices
+ shotChoices = [
+ #avatarBehindShot,
+ #avatarBehindHighShot,
+ avatarCloseUpThreeQuarterRightShot,
+
+ #allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ died = attack['target']['died']
+ if died != 0:
+ # If av died, make a parallel track
+ pbpText = attack['playByPlayText']
+ diedText = av.getName() + " was defeated!"
+ diedTextList = [diedText]
+ diedTrack = pbpText.getToonsDiedInterval(diedTextList,
+ duration)
+
+ # The multi toon case
+ elif groupStatus == ATK_TGT_GROUP:
+ av = None
+ # Multi toon choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ deadToons = []
+ targetDicts = attack['target']
+ for targetDict in targetDicts:
+ died = targetDict['died']
+ if died != 0:
+ deadToons.append(targetDict['toon'])
+ if len(deadToons) > 0:
+ pbpText = attack['playByPlayText']
+ diedTextList = []
+ for toon in deadToons:
+ pbpText = attack['playByPlayText']
+ diedTextList.append(toon.getName() + " was defeated!")
+ diedTrack = pbpText.getToonsDiedInterval(diedTextList, duration)
+ else:
+ notify.error("Bad groupStatus: %s" % groupStatus)
+
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ if diedTrack == None:
+ return track
+ else:
+ mtrack = Parallel(track, diedTrack)
+ return mtrack
+
+def makeShot(x, y, z, h, p, r, duration, other=None, name='makeShot'):
+ """ makeShot() creates a held shot, with keyword arg for a relative held shot """
+ if other:
+ return heldRelativeShot(other, x, y, z, h, p, r, duration, name)
+ else:
+ return heldShot(x, y, z, h, p, r, duration, name)
+
+def focusShot(x, y, z, duration, target, other=None, splitFocusPoint=None, name='focusShot'):
+ """ focusShot() creates a held shot with camera focused on target arg """
+ track = Sequence()
+ if other:
+ track.append(Func(camera.setPos, other, Point3(x, y, z)))
+ else:
+ track.append(Func(camera.setPos, Point3(x, y, z)))
+
+ if splitFocusPoint:
+ track.append(Func(focusCameraBetweenPoints, target, splitFocusPoint))
+ else:
+ track.append(Func(camera.lookAt, target))
+ track.append(Wait(duration))
+
+ return track
+
+def moveShot(x, y, z, h, p, r, duration, other=None, name='moveShot'):
+ """ moveShot() creates a moving shot from the current position to the args provided """
+ return motionShot(x, y, z, h, p, r, duration, other, name)
+
+def focusMoveShot(x, y, z, duration, target, other=None, name='focusMoveShot'):
+ """ focusMoveShot() creates a moving shot from the current position to focus on the
+ target arg provided """
+ camera.setPos(Point3(x, y, z))
+ camera.lookAt(target)
+ hpr = camera.getHpr()
+ return motionShot(x, y, z, hpr[0], hpr[1], hpr[2],
+ duration, other, name)
+
+##########################################################################
+## ___ ___ ___ ___ _ _
+## / __|/ _ \/ __| / __| |_ ___| |_ ___
+## \__ \ (_) \__ \ \__ \ ' \/ _ \ _(_-<
+## |___/\___/|___/ |___/_||_\___/\__/__/
+##
+##########################################################################
+
+def chooseSOSShot(av, duration):
+ shotChoices = [
+ avatarCloseUpThreeQuarterRightShot,
+ avatarBehindShot,
+ avatarBehindHighShot,
+
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # Pick a shot and return it
+ track = apply(random.choice(shotChoices), [av, duration])
+ return track
+
+
+##########################################################################
+## ___ _ ___ _ _
+## | _ \_____ __ ____ _ _ _ __| | / __| |_ ___| |_ ___
+## | / -_) V V / _` | '_/ _` | \__ \ ' \/ _ \ _(_-<
+## |_|_\___|\_/\_/\__,_|_| \__,_| |___/_||_\___/\__/__/
+##
+##########################################################################
+
+def chooseRewardShot(av, duration, allowGroupShot = 1):
+ # We actually return an interval that chooses the reward shot
+ # on-the-fly, rather than prechoosing it. This is because the
+ # avatar in question may wander away during the reward movie.
+
+ def chooseRewardShotNow(av):
+ if av.playingAnim == "victory" or not allowGroupShot:
+ # The avatar is still dancing; choose an avatar shot.
+ shotChoices = [(0, 8, av.getHeight() * 0.66, 179, 15, 0),
+ (5.2, 5.45, av.getHeight() * 0.66, 131.5, 3.6, 0)]
+ shot = random.choice(shotChoices)
+ camera.setPosHpr(av, *shot)
+ else:
+ # The avatar has stopped dancing; give a group shot.
+ camera.setPosHpr(10, 0, 10, 115, -30, 0)
+
+ return Sequence(Func(chooseRewardShotNow, av),
+ Wait(duration))
+
+##########################################################################
+## ___ _ _ _ ___ _ _
+## / __|___ _ _ ___ _ _ __ _| | | | | |___ ___ / __| |_ ___| |_ ___
+## | (_ / -_) ' \/ -_) '_/ _` | | | |_| (_- -_) \__ \ ' \/ _ \ _(_-<
+## \___\___|_||_\___|_| \__,_|_| \___//__/\___| |___/_||_\___/\__/__/
+##
+##########################################################################
+
+def heldShot(x, y, z, h, p, r, duration, name="heldShot"):
+ track = Sequence(name = name)
+
+ # Let the camera display the toons
+ track.append(Func(camera.setPosHpr, x, y, z, h, p, r))
+
+ # Hold that pose
+ track.append(Wait(duration))
+
+ return track
+
+def heldRelativeShot(other, x, y, z, h, p, r, duration,
+ name="heldRelativeShot"):
+ track = Sequence(name = name)
+
+ # Let the camera display the toons
+ track.append(Func(camera.setPosHpr, other, x, y, z, h, p, r))
+
+ # Hold that pose
+ track.append(Wait(duration))
+
+ return track
+
+def motionShot(x, y, z, h, p, r, duration, other=None, name="motionShot"):
+ if other: # If an other parent exists, use it
+ posTrack = LerpPosInterval(camera, duration, pos=Point3(x, y, z),
+ other=other)
+ hprTrack = LerpHprInterval(camera, duration, hpr=Point3(h, p, r),
+ other=other)
+ else:
+ posTrack = LerpPosInterval(camera, duration, pos=Point3(x, y, z))
+ hprTrack = LerpHprInterval(camera, duration, hpr=Point3(h, p, r))
+ return Parallel(posTrack, hprTrack)
+
+def allGroupShot(avatar, duration):
+ return heldShot(10, 0, 10, 89, -30, 0, duration, "allGroupShot")
+
+def allGroupLowShot(avatar, duration):
+ return heldShot(15, 0, 3, 89, 0, 0, duration, "allGroupLowShot")
+
+def allGroupLowDiagonalShot(avatar, duration):
+ return heldShot(7, 5, 6, 119, -30, 0, duration, "allGroupLowShot")
+
+def toonGroupShot(avatar, duration):
+ return heldShot(10, 0, 10, 115, -30, 0, duration, "toonGroupShot")
+
+def toonGroupHighShot(avatar, duration):
+ #return heldShot(10, 0, 30, 115, -60, 0, duration, "toonGroupHighShot")
+ return heldShot(5, 0, 1, 115, 45, 0, duration, "toonGroupHighShot")
+
+def suitGroupShot(avatar, duration):
+ return heldShot(10, 0, 10, 65, -30, 0, duration, "suitGroupShot")
+
+def suitGroupLowLeftShot(avatar, duration):
+ return heldShot(8.4, -3.85, 2.75, 36.3, 3.25, 0, duration,
+ "suitGroupLowLeftShot")
+
+# Good for toon throws, and some suit magic attacks.
+def suitGroupThreeQuarterLeftBehindShot(avatar, duration):
+ if (random.random() > 0.5): # 50% chance
+ x = 12.37
+ h = 134.61
+ else:
+ x = -12.37
+ h = -134.61
+
+ return heldShot(x, 11.5, 8.16, h, -22.70, 0, duration,
+ "suitGroupThreeQuarterLeftBehindShot")
+
+def suitWakeUpShot(avatar, duration):
+ return heldShot(10, -5, 10, 65, -30, 0, duration, "suitWakeUpShot")
+
+def suitCameraShakeShot(avatar, duration, shakeIntensity, quake=0):
+ track = Sequence(name = "suitShakeCameraShot")
+ if (quake == 1):
+ shakeDelay = 1.1
+ numShakes = 4
+ else:
+ shakeDelay = 0.3
+ numShakes = 5
+ postShakeDelay = 0.5
+ shakeTime = (duration-shakeDelay-postShakeDelay) / numShakes
+ shakeDuration = shakeTime * (1./numShakes)
+ shakeWaitInterval = shakeTime * ((numShakes-1.)/numShakes)
+
+ def shakeCameraTrack(intensity, shakeWaitInterval=shakeWaitInterval, quake=quake,
+ shakeDuration=shakeDuration, numShakes=numShakes):
+ vertShakeTrack = Sequence(
+ Wait(shakeWaitInterval),
+ Func(camera.setZ, camera.getZ()+intensity/2),
+ Wait(shakeDuration/2),
+ Func(camera.setZ, camera.getZ()-intensity),
+ Wait(shakeDuration/2),
+ Func(camera.setZ, camera.getZ()+intensity/2),
+ )
+ horizShakeTrack = Sequence(
+ Wait(shakeWaitInterval-shakeDuration/2),
+ Func(camera.setY, camera.getY()+intensity/4),
+ Wait(shakeDuration/2),
+ Func(camera.setY, camera.getY()-intensity/2),
+ Wait(shakeDuration/2),
+ Func(camera.setY, camera.getY()+intensity/4),
+ Wait(shakeDuration/2),
+ Func(camera.lookAt, Point3(0, 0, 0)),
+ )
+
+ shakeTrack = Sequence()
+ for i in range(0, numShakes):
+ if (quake == 0):
+ shakeTrack.append(vertShakeTrack)
+ else:
+ shakeTrack.append(Parallel(vertShakeTrack, horizShakeTrack))
+
+ return shakeTrack
+
+ # Allow a reasonable degree of randomness in the camera's positioning
+ x = 10 + random.random()*3
+ if (random.random() > 0.5):
+ x = -x
+ z = 7 + random.random()*3
+ # Let the camera display the toons
+ track.append(Func(camera.setPos, x, -5, z))
+
+ # Point the camera at the center of the battle, to shoot all the actors
+ track.append(Func(camera.lookAt, Point3(0, 0, 0)))
+ track.append(Wait(shakeDelay)) # Wait before shaking
+ # Now shake the camera (up and down)
+ track.append(shakeCameraTrack(shakeIntensity))
+ track.append(Wait(postShakeDelay)) # Hold pose
+ return track
+
+def avatarCloseUpShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ 0, 8, avatar.getHeight() * 0.66,
+ 179, 15, 0,
+ duration, "avatarCloseUpShot")
+
+# Useful for throws and button pushes
+def avatarCloseUpThrowShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ 3, 8, avatar.getHeight() * 0.66,
+ 159, 3.6, 0,
+ duration, "avatarCloseUpThrowShot")
+
+# Throws, button pushes, squirt, and suit attacks
+# NOTE: For the course of a battle, toons should have "right" shots,
+# and suits should have "left" shots, or vice versa
+def avatarCloseUpThreeQuarterRightShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ 5.2, 5.45, avatar.getHeight() * 0.66,
+ 131.5, 3.6, 0,
+ duration, "avatarCloseUpThreeQuarterRightShot")
+
+def avatarCloseUpThreeQuarterRightShotWide(avatar, duration):
+ return heldRelativeShot(avatar,
+ 7.2, 8.45, avatar.getHeight() * 0.66,
+ 131.5, 3.6, 0,
+ duration, "avatarCloseUpThreeQuarterRightShot")
+
+def avatarCloseUpThreeQuarterLeftShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ -5.2, 5.45, avatar.getHeight() * 0.66,
+ -131.5, 3.6, 0,
+ duration, "avatarCloseUpThreeQuarterLeftShot")
+
+def avatarCloseUpThreeQuarterRightFollowShot(avatar, duration):
+ track = Sequence(name = "avatarCloseUpThreeQuarterRightFollowShot")
+ track.append(
+ heldRelativeShot(avatar,
+ 5.2, 5.45, avatar.getHeight() * 0.66,
+ 131.5, 3.6, 0,
+ duration * 0.65)
+ )
+
+ track.append(
+ LerpHprInterval(nodePath=camera,
+ other=avatar,
+ duration=duration * 0.2,
+ hpr=Point3(110, 3.6, 0),
+ blendType="easeInOut")
+ )
+
+ track.append(Wait(duration * 0.25))
+ return track
+
+
+def avatarCloseUpZoomShot(avatar, duration):
+ track = Sequence("avatarCloseUpZoomShot")
+ # Let the camera display the toons
+ track.append(
+ LerpPosHprInterval(nodePath=camera,
+ other=avatar,
+ duration=duration/2,
+ startPos=Point3(0, 10, avatar.getHeight()),
+ startHpr=Point3(179, -10, 0),
+ pos=Point3(0, 6, avatar.getHeight()),
+ hpr=Point3(179, -10, 0),
+ blendType="easeInOut")
+ )
+
+ track.append(Wait(duration/2))
+
+ return track
+
+def avatarBehindShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ 5, -7, avatar.getHeight(),
+ 40, -12, 0,
+ duration, "avatarBehindShot")
+
+def avatarBehindHighShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ -4, -7, 5 + avatar.getHeight(),
+ -30, -35, 0,
+ duration, "avatarBehindHighShot")
+
+def avatarBehindHighRightShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ 4, -7, 5 + avatar.getHeight(),
+ 30, -35, 0,
+ duration, "avatarBehindHighShot")
+
+def avatarBehindThreeQuarterRightShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ 7.67, -8.52, avatar.getHeight() * 0.66,
+ 25, 7.5, 0,
+ duration, "avatarBehindThreeQuarterRightShot")
+
+###
+# Implementation of camera "packages", which are simply a series of held and
+# motion shots capturing the action of an attack
+###
+
+
+
+def avatarSideFollowAttack(suit, toon, duration, battle):
+ # Three part timing on an attack: the windup, the projection, the impact
+ # Use slightly random range for windupDuration, between 0-20% of total duration
+ windupDuration = duration * (0.1+random.random()*0.1)
+ projectDuration = duration * 0.75
+ impactDuration = duration - windupDuration - projectDuration
+
+ # Choose focal points of the actors for the camera
+ suitHeight = suit.getHeight()
+ toonHeight = toon.getHeight()
+ suitCentralPoint = suit.getPos(battle)
+ suitCentralPoint.setZ(suitCentralPoint.getZ() + suitHeight*0.75)
+ toonCentralPoint = toon.getPos(battle)
+ toonCentralPoint.setZ(toonCentralPoint.getZ() + toonHeight*0.75)
+
+ # Vary the x-coordinates of the camera
+ initialX = random.randint(12, 14)
+ finalX = random.randint(7, 8)
+ # Vary the y-coordinates of the camera
+ initialY = finalY = random.randint(-3, 0)
+ # Vary the z-coordinates of the camera
+ initialZ = suitHeight*0.5 + random.random()*suitHeight
+ finalZ = toonHeight*0.5 + random.random()*toonHeight
+ # 50% chance we'll view the action from stage left or stage right
+ if (random.random() > 0.5):
+ initialX = -initialX
+ finalX = - finalX
+
+ return Sequence(
+ focusShot(initialX, initialY, initialZ, windupDuration, suitCentralPoint), # windup
+ focusMoveShot(finalX, finalY, finalZ, projectDuration, toonCentralPoint), # throw
+ Wait(impactDuration), # impact
+ )
+
+def focusCameraBetweenPoints(point1, point2):
+ """ focusCameraBetweenPoints() finds a bisection point between the arg points provided
+ and focuses the camera on this central point """
+ if (point1[0] > point2[0]):
+ x = point2[0] + (point1[0]-point2[0])*0.5
+ else:
+ x = point1[0] + (point2[0]-point1[0])*0.5
+ if (point1[1] > point2[1]):
+ y = point2[1] + (point1[1]-point2[1])*0.5
+ else:
+ y = point1[1] + (point2[1]-point1[1])*0.5
+ if (point1[2] > point2[2]):
+ z = point2[2] + (point1[2]-point2[2])*0.5
+ else:
+ z = point1[2] + (point2[2]-point1[2])*0.5
+ camera.lookAt(Point3(x, y, z))
+
+
+def randomCamera(suit, toon, battle, attackDuration, openShotDuration):
+ """ randomCamera() places the camera in random, though effective, position to capture
+ the action of an attack, first shooting the attacker and then the defender """
+
+ return randomAttackCam(suit, toon, battle, attackDuration,
+ openShotDuration, 'suit')
+
+def randomAttackCam(suit, toon, battle, attackDuration, openShotDuration,
+ attackerString='suit'):
+ """ randomCamSuitAttack() places the camera in a random, though effective position to
+ shoot the action of an attacker, if you don't specify the attacker as
+ either 'toon' or 'suit', it defaults to suit """
+
+ if openShotDuration > attackDuration: # Ensure that it's not too long
+ openShotDuration = attackDuration
+ closeShotDuration = attackDuration - openShotDuration
+
+ if (attackerString == 'suit'):
+ attacker = suit
+ defender = toon
+ defenderString = 'toon'
+ else:
+ attacker = toon
+ defender = suit
+ defenderString = 'suit'
+
+ randomDouble = random.random()
+ if (randomDouble > 0.6): # 40% chance
+ openShot = randomActorShot(attacker, battle, openShotDuration, attackerString)
+ elif (randomDouble > 0.2): # 40% chance
+ openShot = randomOverShoulderShot(suit, toon, battle,
+ openShotDuration, focus=attackerString)
+ else: # 20% chance
+ openShot = randomSplitShot(attacker, defender, battle, openShotDuration)
+
+ randomDouble = random.random()
+ if (randomDouble > 0.6): # 40% chance
+ closeShot = randomActorShot(defender, battle, closeShotDuration, defenderString)
+ elif (randomDouble > 0.2): # 40% chance
+ closeShot = randomOverShoulderShot(suit, toon, battle,
+ closeShotDuration, focus=defenderString)
+ else: # 20% chance
+ closeShot = randomSplitShot(attacker, defender, battle, closeShotDuration)
+
+ return Sequence(openShot, closeShot)
+
+def randomGroupAttackCam(suit, targets, battle, attackDuration, openShotDuration):
+ """ randomGroupAttackCam() places the camera in a random, though effective position to
+ shoot the action of a suit attacking a group of toons (targets) """
+
+ if openShotDuration > attackDuration: # Ensure that it's not too long
+ openShotDuration = attackDuration
+ closeShotDuration = attackDuration - openShotDuration
+
+ # First we shoot the attacking suit
+ openShot = randomActorShot(suit, battle, openShotDuration, 'suit', groupShot=0)
+ closeShot = randomToonGroupShot(targets, suit, closeShotDuration, battle)
+ return Sequence(openShot, closeShot)
+
+def randomActorShot(actor, battle, duration, actorType, groupShot=0):
+ """ randomActorShot() creates a random though effective shot for an actor in
+ a battle, specified by arg actor of type actorType ('suit' or 'toon'). This
+ function defaults to single shots of the primary actor, but a groupShot will
+ pull the camera out some more."""
+
+ height = actor.getHeight()
+ centralPoint = actor.getPos(battle)
+ centralPoint.setZ(centralPoint.getZ() + height*0.75)
+
+ if (actorType == 'suit'):
+ x = 4 + random.random()*8
+ y = -2 - random.random()*4
+ z = height*0.5 + random.random()*height*1.5
+ if (groupShot == 1):
+ y = -4 #y - 3 # - random.random()*4
+ z = height*0.5 # z + 2 # + random.random()*3
+ else:
+ x = 2 + random.random()*8
+ y = -2 + random.random()*3
+ z = height + random.random()*height*1.5
+ if (groupShot == 1):
+ y = y + 3 # + random.random()*4
+ z = height*0.5 #z + 2 # + random.random()*3
+ if (MovieUtil.shotDirection == 'left'):
+ x = -x
+
+ return focusShot(x, y, z, duration, centralPoint)
+
+def randomSplitShot(suit, toon, battle, duration):
+ """ randomSplitShot() places the camera in random, though effective, position to capture
+ both primary actors in an attack """
+
+ suitHeight = suit.getHeight()
+ toonHeight = toon.getHeight()
+ suitCentralPoint = suit.getPos(battle)
+ suitCentralPoint.setZ(suitCentralPoint.getZ() + suitHeight*0.75)
+ toonCentralPoint = toon.getPos(battle)
+ toonCentralPoint.setZ(toonCentralPoint.getZ() + toonHeight*0.75)
+
+ x = 9 + random.random()*2
+ y = -2 - random.random()*2
+ z = suitHeight*0.5 + random.random()*suitHeight
+ if (MovieUtil.shotDirection == 'left'):
+ x = -x
+
+ return focusShot(x, y, z, duration, toonCentralPoint,
+ splitFocusPoint=suitCentralPoint)
+
+
+def randomOverShoulderShot(suit, toon, battle, duration, focus):
+ """ randomOverShouldShot() creates a random, though effective, camera shot for a battle
+ shooting the actor specified in arg focus ('toon' or 'suit') over the shoulder of
+ the other actor in the attack """
+
+ suitHeight = suit.getHeight()
+ toonHeight = toon.getHeight()
+ suitCentralPoint = suit.getPos(battle)
+ suitCentralPoint.setZ(suitCentralPoint.getZ() + suitHeight*0.75)
+ toonCentralPoint = toon.getPos(battle)
+ toonCentralPoint.setZ(toonCentralPoint.getZ() + toonHeight*0.75)
+
+ x = 2 + random.random()*10
+ if (focus == 'toon'):
+ y = 8 + random.random()*6
+ z = suitHeight*1.2 + random.random()*suitHeight
+ else:
+ y = -10 - random.random()*6
+ z = toonHeight*1.5 # toonHeight*1.5 + random.random()*toonHeight
+ if (MovieUtil.shotDirection == 'left'):
+ x = -x
+
+ return focusShot(x, y, z, duration, toonCentralPoint,
+ splitFocusPoint=suitCentralPoint)
+
+
+def randomCameraSelection(suit, attack, attackDuration, openShotDuration):
+ """ randomCameraSelection() makes a random selection from a list of possible
+ camera shots """
+
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterLeftShot,
+ allGroupLowShot,
+ suitGroupLowLeftShot,
+ avatarBehindHighShot,
+ ]
+
+ if openShotDuration > attackDuration: # Ensure that its not too long
+ openShotDuration = attackDuration
+ closeShotDuration = attackDuration - openShotDuration
+
+ openShot = apply(random.choice(shotChoices), [suit, openShotDuration])
+ closeShot = chooseSuitCloseShot(attack, closeShotDuration,
+ openShot.getName(), attackDuration)
+ return Sequence(openShot, closeShot)
+
+
+def randomToonGroupShot(toons, suit, duration, battle):
+ """ randomToonGroupShot() creates a random, though effective camera shot for a group
+ of toons (2, 3, or 4) in a battle """
+
+ # Grab the average height of these toons for the best shot
+ sum = 0
+ for t in toons:
+ toon = t['toon']
+ height = toon.getHeight()
+ sum = sum + height
+ avgHeight = sum / len(toons) * 0.75 # multiply by 0.75 to get the chest of the toon
+
+ # We shoot from the opposite side of the attacking suit
+ suitPos = suit.getPos(battle)
+ x = 1 + random.random()*6
+ if (suitPos.getX() > 0):
+ x = -x
+
+ # We'll either shoot a close up or far back over the shoulders of the suits
+ if (random.random() > 0.5): # 50% chance
+ y = 4 + random.random()*1
+ z = avgHeight + random.random()*6
+ else:
+ y = 11 + random.random()*2
+ z = 13 + random.random()*2
+ focalPoint = Point3(0, -4, avgHeight)
+ return focusShot(x, y, z, duration, focalPoint)
+
+
+###########################################################
+##
+## Fire Shots
+##
+###########################################################
+
+def chooseFireShot(throws, suitThrowsDict, attackDuration):
+ # Pick an open shot
+ openShot = chooseFireOpenShot(throws, suitThrowsDict, attackDuration)
+ openDuration = openShot.getDuration()
+ openName = openShot.getName()
+ # Pick a close shot
+ closeShot = chooseFireCloseShot(throws, suitThrowsDict,
+ openDuration, openName, attackDuration)
+ # Compose the track
+ track = Sequence(openShot, closeShot)
+ # Ensure we composed it to the right length
+ #assert track.getDuration() == attackDuration
+ # Return it
+ return track
+
+def chooseFireOpenShot(throws, suitThrowsDict, attackDuration):
+ # Setup
+ numThrows = len(throws)
+ av = None
+ duration = 3.0
+ # The single toon case
+ if numThrows == 1:
+ # The attacking Toon
+ av = throws[0]['toon']
+ # Single toon choices
+ shotChoices = [
+ avatarCloseUpThrowShot,
+ avatarCloseUpThreeQuarterRightShot,
+ avatarBehindShot,
+
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi toon case
+ elif numThrows >= 2 and numThrows <= 4:
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of throws: %s" % numThrows)
+ # Pick a shot and return it
+ shotChoice = random.choice(shotChoices)
+ track = apply(shotChoice, [av, duration])
+ print ("chooseFireOpenShot %s" % (shotChoice))
+
+ # Set up the play by play text
+ # Whoops! No play by play for toons, since the multi-toon case is
+ # confusing...
+ #pbpText = attack['playByPlayText']
+ #pbpTrack = pbpText.getShowInterval(attack['prettyName'], duration)
+
+ #mtrack = Parallel(track, pbpTrack)
+
+ return track
+
+def chooseFireCloseShot(throws, suitThrowsDict,
+ openDuration, openName, attackDuration):
+ # Setup
+ numSuits = len(suitThrowsDict)
+ av = None
+ duration = attackDuration - openDuration
+ # The single suit case
+ if numSuits == 1:
+ # The attacked suit
+ av = base.cr.doId2do[suitThrowsDict.keys()[0]]
+ # Single suit choices
+ shotChoices = [
+ avatarCloseUpFireShot,
+ avatarCloseUpThreeQuarterLeftFireShot,
+
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ # The multi suit case (RAU we could get 0 for uber throw)
+ elif (numSuits >= 2 and numSuits <= 4) or (numSuits==0):
+ # Multi suit choices
+ shotChoices = [
+ allGroupLowShot,
+ suitGroupThreeQuarterLeftBehindShot,
+ ]
+ else:
+ notify.error("Bad number of suits: %s" % numSuits)
+ # Pick a shot and return it
+ shotChoice = random.choice(shotChoices)
+ track = apply(shotChoice, [av, duration])
+ print ("chooseFireOpenShot %s" % (shotChoice))
+ return track
+
+# Useful for throws and button pushes
+def avatarCloseUpFireShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ 7, 17, avatar.getHeight() * 0.66,
+ 159, 3.6, 0,
+ duration, "avatarCloseUpFireShot")
+
+def avatarCloseUpThreeQuarterLeftFireShot(avatar, duration):
+ return heldRelativeShot(avatar,
+ -8.2, 8.45, avatar.getHeight() * 0.66,
+ -131.5, 3.6, 0,
+ duration, "avatarCloseUpThreeQuarterLeftShot")
+
diff --git a/toontown/src/battle/MovieDrop.py b/toontown/src/battle/MovieDrop.py
new file mode 100644
index 0000000..b39d507
--- /dev/null
+++ b/toontown/src/battle/MovieDrop.py
@@ -0,0 +1,678 @@
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from BattleSounds import *
+
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+import MovieUtil
+import MovieNPCSOS
+from MovieUtil import calcAvgSuitPos
+from direct.showutil import Effects
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieDrop')
+
+
+hitSoundFiles = ('AA_drop_flowerpot.mp3',
+ 'AA_drop_sandbag.mp3',
+ 'AA_drop_anvil.mp3',
+ 'AA_drop_bigweight.mp3',
+ 'AA_drop_safe.mp3',
+ 'AA_drop_piano.mp3',
+ 'AA_drop_boat.mp3') #UBER
+
+missSoundFiles = ('AA_drop_flowerpot_miss.mp3',
+ 'AA_drop_sandbag_miss.mp3',
+ 'AA_drop_anvil_miss.mp3',
+ 'AA_drop_bigweight_miss.mp3',
+ 'AA_drop_safe_miss.mp3',
+ 'AA_drop_piano_miss.mp3', #UBER
+ 'AA_drop_boat_miss.mp3')
+
+# time offsets
+tDropShadow = 1.3
+tSuitDodges = 2.45 + tDropShadow
+tObjectAppears = 3.0 + tDropShadow
+tButtonPressed = 2.44
+
+# durations
+dShrink = 0.3
+dShrinkOnMiss = 0.1
+
+dPropFall = 0.6
+
+objects = ('flowerpot',
+ 'sandbag',
+ 'anvil',
+ 'weight',
+ 'safe',
+ 'piano',
+ 'ship') #Uber
+objZOffsets = (0.75, 0.75, 0., 0., 0., 0., 0.0) #UBER
+objStartingScales = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0) #UBER
+
+landFrames = (12, 4, 1, 11, 11, 11, 2)
+shoulderHeights = {'a': 13.28 / 4.,
+ 'b': 13.74 / 4.,
+ 'c': 10.02 / 4.}
+
+def doDrops(drops):
+ """ Drops occur in the following order:
+ a) by suit, in order of increasing number of drops per suit
+ 1) level 1 drops, right to left, (TOON_DROP_DELAY later)
+ 2) level 2 drops, right to left, (TOON_DROP_DELAY later)
+ 3) level 3 drops, right to left, (TOON_DROP_DELAY later)
+ etc.
+ b) next suit, (TOON_DROP_SUIT_DELAY later)
+ """
+ if (len(drops) == 0):
+ return (None, None)
+
+
+ npcArrivals, npcDepartures, npcs = MovieNPCSOS.doNPCTeleports(drops)
+
+ # Group the drops by targeted suit
+ suitDropsDict = {}
+ groupDrops = []
+ for drop in drops:
+ track = drop['track']
+ level = drop['level']
+ targets = drop['target']
+ if (len(targets) == 1):
+ suitId = targets[0]['suit'].doId
+ if (suitDropsDict.has_key(suitId)):
+ suitDropsDict[suitId].append((drop, targets[0]))
+ else:
+ suitDropsDict[suitId] = [(drop, targets[0])]
+ elif level <= MAX_LEVEL_INDEX and attackAffectsGroup(track,level):
+ groupDrops.append(drop)
+ else:
+ # We're dealing with an NPC drop, which can have multiple
+ # targets
+ for target in targets:
+ suitId = target['suit'].doId
+ if (suitDropsDict.has_key(suitId)):
+ otherDrops = suitDropsDict[suitId]
+ alreadyInList = 0
+ for oDrop in otherDrops:
+ if (oDrop[0]['toon'] == drop['toon']):
+ alreadyInList = 1
+ if (alreadyInList == 0):
+ suitDropsDict[suitId].append((drop, target))
+ else:
+ suitDropsDict[suitId] = [(drop, target)]
+ suitDrops = suitDropsDict.values()
+
+ # Sort the suits based on the number of drops per suit
+ def compFunc(a, b):
+ if (len(a) > len(b)):
+ return 1
+ elif (len(a) < len(b)):
+ return -1
+ return 0
+ suitDrops.sort(compFunc)
+ delay = 0.0
+ mtrack = Parallel(name = 'toplevel-drop')
+ npcDrops = {}
+ for st in suitDrops:
+ if (len(st) > 0):
+ ival = __doSuitDrops(st, npcs, npcDrops)
+ if (ival):
+ mtrack.append(Sequence(Wait(delay), ival))
+ delay = delay + TOON_DROP_SUIT_DELAY
+
+ dropTrack = Sequence(npcArrivals, mtrack, npcDepartures)
+ camDuration = mtrack.getDuration()
+
+ #we do the group drops after all the single drops have gone
+ if groupDrops:
+ ival = __doGroupDrops(groupDrops)
+ dropTrack.append(ival)
+ camDuration += ival.getDuration()
+
+
+ enterDuration = npcArrivals.getDuration()
+ exitDuration = npcDepartures.getDuration()
+ camTrack = MovieCamera.chooseDropShot(drops, suitDropsDict, camDuration,
+ enterDuration, exitDuration)
+
+
+ return (dropTrack, camTrack)
+
+def __getSoundTrack(level, hitSuit, node=None):
+ #level: the level of attack, int 0-5
+ #hitSuit: does the attack hit toon, bool
+
+ if hitSuit:
+ soundEffect = globalBattleSoundCache.getSound(hitSoundFiles[level])
+ else:
+ soundEffect = globalBattleSoundCache.getSound(missSoundFiles[level])
+
+ soundTrack = Sequence()
+
+ if soundEffect:
+ buttonSound = globalBattleSoundCache.getSound('AA_drop_trigger_box.mp3')
+ fallingSound = None
+ buttonDelay = tButtonPressed - 0.3
+ fallingDuration = 1.5
+ if not level == UBER_GAG_LEVEL_INDEX:
+ #boat drop has the whistle built in
+ fallingSound = globalBattleSoundCache.getSound('incoming_whistleALT.mp3')
+
+ soundTrack.append(Wait(buttonDelay))
+ soundTrack.append(SoundInterval(buttonSound, duration = 0.67, node=node))
+ if fallingSound:
+ soundTrack.append(SoundInterval(fallingSound, duration=fallingDuration, node=node))
+ if not level == UBER_GAG_LEVEL_INDEX:
+ #the dropping effects seems to be timed at the start of the press, not after
+ soundTrack.append(SoundInterval(soundEffect, node=node))
+
+ if level == UBER_GAG_LEVEL_INDEX:
+ if hitSuit:
+ uberDelay = tButtonPressed
+ else:
+ uberDelay = tButtonPressed - 0.1
+ oldSoundTrack = soundTrack
+ soundTrack = Parallel()
+ soundTrack.append(oldSoundTrack)
+
+ uberTrack = Sequence()
+ uberTrack.append(Wait(uberDelay))
+ uberTrack.append(SoundInterval(soundEffect, node=node))
+
+ soundTrack.append(uberTrack)
+ else:
+ soundTrack.append(Wait(0.1)) # dummy interval
+
+ return soundTrack
+
+def __doSuitDrops(dropTargetPairs, npcs, npcDrops):
+ """ __doSuitDrops(drops)
+ 1 or more toons drop at the same target suit
+ Note: attacks are sorted by increasing level (as are toons)
+ Returns a track with toon drops in the following order:
+ 1) level 1 drops, right to left, (TOON_DROP_DELAY later)
+ 2) level 2 drops, right to left, (TOON_DROP_DELAY later)
+ etc.
+ """
+ toonTracks = Parallel()
+ delay = 0.0
+ alreadyDodged = 0
+ alreadyTeased = 0
+ for dropTargetPair in dropTargetPairs:
+ drop = dropTargetPair[0]
+ level = drop['level']
+ objName = objects[level]
+ target = dropTargetPair[1]
+ track = __dropObjectForSingle(drop, delay, objName, level, alreadyDodged,
+ alreadyTeased, npcs, target, npcDrops)
+ if (track):
+ toonTracks.append(track)
+ delay += TOON_DROP_DELAY
+ hp = target['hp']
+ if (hp <= 0):
+ if (level >= 3):
+ alreadyTeased = 1
+ else:
+ alreadyDodged = 1
+ return toonTracks
+
+def __doGroupDrops(groupDrops):
+ """ __doSuitDrops(drops)
+ 1 or more toons drop at the same target suit
+ Note: attacks are sorted by increasing level (as are toons)
+ Returns a track with toon drops in the following order:
+ 1) level 1 drops, right to left, (TOON_DROP_DELAY later)
+ 2) level 2 drops, right to left, (TOON_DROP_DELAY later)
+ etc.
+ """
+ #import pdb; pdb.set_trace()
+
+ toonTracks = Parallel()
+ delay = 0.0
+ alreadyDodged = 0
+ alreadyTeased = 0
+
+ for drop in groupDrops:
+ battle = drop['battle']
+ level = drop['level']
+ #calculate center position, then figure out which suit is closest to it
+ centerPos = calcAvgSuitPos(drop)
+ targets = drop['target']
+ numTargets = len(targets)
+ closestTarget = -1
+ nearestDistance = 100000.0
+ for i in range(numTargets):
+ suit = drop['target'][i]['suit']
+ suitPos = suit.getPos(battle)
+ displacement = Vec3(centerPos)
+ displacement -= suitPos
+ distance = displacement.lengthSquared()
+ if distance < nearestDistance:
+ closestTarget = i
+ nearestDistance = distance
+
+ #we have the suit to drop on
+ track = __dropGroupObject(drop,delay, closestTarget, alreadyDodged, alreadyTeased)
+ if (track):
+ toonTracks.append(track)
+ #delay += TOON_DROP_DELAY
+ delay = delay + TOON_DROP_SUIT_DELAY
+ hp = drop['target'][closestTarget]['hp']
+ if (hp <= 0):
+ if (level >= 3):
+ alreadyTeased = 1
+ else:
+ alreadyDodged = 1
+ pass
+
+
+ #for dropTargetPair in dropTargetPairs:
+ # drop = dropTargetPair[0]
+ # level = drop['level']
+ # objName = objects[level]
+ # target = dropTargetPair[1]
+ # track = __dropObject(drop, delay, objName, level, alreadyDodged,
+ # alreadyTeased, npcs, target, npcDrops)
+ # if (track):
+ # toonTracks.append(track)
+ # delay += TOON_DROP_DELAY
+ # hp = target['hp']
+ # if (hp <= 0):
+ # if (level >= 3):
+ # alreadyTeased = 1
+ # else:
+ # alreadyDodged = 1
+
+ return toonTracks
+
+def __dropGroupObject(drop, delay, closestTarget, alreadyDodged, alreadyTeased):
+
+ level = drop['level']
+ objName = objects[level]
+ target = drop['target'][closestTarget]
+ suit = drop['target'][closestTarget]['suit']
+ npcDrops = {}
+ npcs = []
+
+ returnedParallel = __dropObject(drop, delay, objName, level, alreadyDodged,
+ alreadyTeased, npcs, target, npcDrops)
+
+ for i in range(len(drop['target'])):
+ target = drop['target'][i]
+ suitTrack =__createSuitTrack(drop, delay, level, alreadyDodged, alreadyTeased, target, npcs)
+ if suitTrack:
+ returnedParallel.append(suitTrack)
+
+
+ return returnedParallel
+
+def __dropObjectForSingle(drop, delay, objName, level, alreadyDodged, alreadyTeased,
+ npcs, target, npcDrops):
+ #import pdb; pdb.set_trace()
+ singleDropParallel = __dropObject( drop, delay, objName, level, alreadyDodged, alreadyTeased,
+ npcs, target, npcDrops)
+ suitTrack = __createSuitTrack(drop, delay, level, alreadyDodged, alreadyTeased, target, npcs)
+
+ if suitTrack:
+ singleDropParallel.append(suitTrack)
+
+ return singleDropParallel
+
+def __dropObject(drop, delay, objName, level, alreadyDodged, alreadyTeased,
+ npcs, target, npcDrops):
+ toon = drop['toon']
+ repeatNPC = 0
+ battle = drop['battle']
+ if (drop.has_key('npc')):
+ toon = drop['npc']
+ if (npcDrops.has_key(toon)):
+ repeatNPC = 1
+ else:
+ npcDrops[toon] = 1
+ origHpr = Vec3(0, 0, 0)
+ else:
+ origHpr = toon.getHpr(battle)
+ hpbonus = drop['hpbonus']
+ suit = target['suit']
+ hp = target['hp']
+ hitSuit = (hp > 0)
+ died = target['died']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ kbbonus = target['kbbonus']
+ suitPos = suit.getPos(battle)
+
+ majorObject = (level >= 3)
+
+ if (repeatNPC == 0):
+ button = globalPropPool.getProp('button')
+ buttonType = globalPropPool.getPropType('button')
+ button2 = MovieUtil.copyProp(button)
+ buttons = [button, button2]
+ hands = toon.getLeftHands()
+
+ object = globalPropPool.getProp(objName)
+ objectType = globalPropPool.getPropType(objName)
+
+ # The safe and weight are a bit too big
+ if (objName == 'weight'):
+ object.setScale(object.getScale()*0.75)
+ elif (objName == 'safe'):
+ object.setScale(object.getScale()*0.85)
+
+ # The object will likely animate far from its initial bounding
+ # volume while it drops. To work around this bug, we artificially
+ # change the object's bounding volume to be really big. In fact,
+ # we make it infinite, so it will never be culled while it's
+ # onscreen.
+ node = object.node()
+ node.setBounds(OmniBoundingVolume())
+ node.setFinal(1)
+
+ # create the soundTrack
+ soundTrack = __getSoundTrack(level, hitSuit, toon)
+
+ # toon pulls the button out, presses it, and puts it away
+ toonTrack = Sequence()
+ if (repeatNPC == 0):
+ toonFace = Func(toon.headsUp, battle, suitPos)
+ toonTrack.append(Wait(delay))
+ toonTrack.append(toonFace)
+ toonTrack.append(ActorInterval(toon, 'pushbutton'))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.setHpr, battle, origHpr))
+
+ # button scales up in toon's hand as he takes it out, and
+ # scales down to nothing as it is put away
+ buttonTrack = Sequence()
+ if (repeatNPC == 0):
+ buttonShow = Func(MovieUtil.showProps, buttons, hands)
+ buttonScaleUp = LerpScaleInterval(button, 1.0, button.getScale(), startScale = Point3(0.01, 0.01, 0.01))
+ buttonScaleDown = LerpScaleInterval(button, 1.0, Point3(0.01, 0.01, 0.01), startScale = button.getScale())
+ buttonHide = Func(MovieUtil.removeProps, buttons)
+ buttonTrack.append(Wait(delay))
+ buttonTrack.append(buttonShow)
+ buttonTrack.append(buttonScaleUp)
+ buttonTrack.append(Wait(2.5))
+ buttonTrack.append(buttonScaleDown)
+ buttonTrack.append(buttonHide)
+
+
+ # object appears above suit
+ objectTrack = Sequence()
+
+ def posObject(object, suit, level, majorObject, miss,
+ battle=battle):
+ object.reparentTo(battle)
+ # Options for positioning a drop:
+ # 1) Any successful drop on an unlured suit - strikes at suit battle pos
+ # 2) Unsuccessful drops on an unlured suit that are of the largest three -
+ # these strike behind the suit (who shouldn't dodge)
+ # 3) The first three (smallest drops) on a lured suit strike the battle pos,
+ # where the larger ones get bumped back a bit.
+ if (battle.isSuitLured(suit)):
+ # suit is lured, strike at battle position
+ suitPos, suitHpr = battle.getActorPosHpr(suit)
+ object.setPos(suitPos)
+ object.setHpr(suitHpr)
+ if (level >= 3): # bump back larger drops
+ object.setY(object.getY() + 2)
+ else:
+ object.setPos(suit.getPos(battle))
+ object.setHpr(suit.getHpr(battle))
+ if (miss and level >= 3):
+ object.setY(object.getY(battle) + 5)
+
+ if not majorObject:
+ if not miss:
+ shoulderHeight = shoulderHeights[suit.style.body] * suit.scale
+ object.setZ(object.getPos(battle)[2] + shoulderHeight)
+ # fix up the Z offset of the prop
+ object.setZ(object.getPos(battle)[2] + objZOffsets[level])
+
+ # the object will need to scale down to nothing at some point
+ # and then it will immediately get deleted
+ # since we already have an animation interval playing,
+ # we need to put the scale on a separate track.
+ # to avoid cases where the object has been deleted,
+ # and the scale interval is still trying to scale the
+ # object, we'll put the animation and scale intervals into
+ # separate tracks, combine them into a track, and
+ # put the hide interval after the track.
+ objectTrack.append(Func(battle.movie.needRestoreRenderProp, object))
+ objInit = Func(posObject, object, suit, level, majorObject, (hp <= 0))
+ objectTrack.append(Wait(delay + tObjectAppears))
+ objectTrack.append(objInit)
+
+ # we can assume that all drop props are animated
+ if hp > 0 or (level == 1 or level == 2):
+ # prop hits the suit
+ #import pdb; pdb.set_trace()
+ if hasattr(object,'getAnimControls'):
+ animProp = ActorInterval(object, objName)
+ shrinkProp = LerpScaleInterval(object, dShrink, Point3(0.01, 0.01, 0.01), startScale = object.getScale())
+ objAnimShrink = ParallelEndTogether(animProp, shrinkProp)
+ objectTrack.append(objAnimShrink)
+ else:
+ # donald boat currently does not have animation
+ startingScale = objStartingScales[level]
+ object2 = MovieUtil.copyProp(object)
+ posObject(object2, suit, level, majorObject, (hp <= 0))
+ endingPos = object2.getPos()
+ startPos = Point3( endingPos[0], endingPos[1] , endingPos[2] + 5)
+ startHpr = object2.getHpr()
+ endHpr = Point3( startHpr[0] + 90, startHpr[1], startHpr[2])
+
+ animProp = LerpPosInterval(object, landFrames[level]/24.0,
+ endingPos, startPos = startPos)
+ shrinkProp = LerpScaleInterval(object, dShrink, Point3(0.01, 0.01, 0.01), startScale = startingScale)
+
+ bounceProp = Effects.createZBounce(object, 2, endingPos, 0.5, 1.5)
+
+ #objAnimShrink = ParallelEndTogether(animProp, shrinkProp)
+ objAnimShrink = Sequence( Func(object.setScale, startingScale),
+ Func(object.setH, endHpr[0]),
+ animProp,
+ bounceProp,
+ Wait(1.5),
+ shrinkProp)
+
+
+ objectTrack.append(objAnimShrink)
+
+ MovieUtil.removeProp( object2)
+
+
+ else:
+ # prop misses the suit
+ # only play the animation up to the point where it lands
+ if hasattr(object,'getAnimControls'):
+ animProp = ActorInterval(object, objName, duration=landFrames[level]/24.)
+ def poseProp(prop, animName, level):
+ prop.pose(animName, landFrames[level])
+ poseProp = Func(poseProp, object, objName, level)
+ wait = Wait(1.0)
+ shrinkProp = LerpScaleInterval(object, dShrinkOnMiss, Point3(0.01, 0.01, 0.01), startScale = object.getScale())
+ objectTrack.append(animProp)
+ objectTrack.append(poseProp)
+ objectTrack.append(wait)
+ objectTrack.append(shrinkProp)
+ else:
+ #donald boat currently does not have animation
+ startingScale = objStartingScales[level]
+ object2 = MovieUtil.copyProp(object)
+ posObject(object2, suit, level, majorObject, (hp <= 0))
+ endingPos = object2.getPos()
+ startPos = Point3( endingPos[0], endingPos[1] , endingPos[2] + 5)
+ startHpr = object2.getHpr()
+ endHpr = Point3( startHpr[0] + 90, startHpr[1], startHpr[2])
+
+
+ animProp = LerpPosInterval(object, landFrames[level]/24.0,
+ endingPos, startPos = startPos)
+ shrinkProp = LerpScaleInterval(object, dShrinkOnMiss, Point3(0.01, 0.01, 0.01), startScale = startingScale)
+
+ bounceProp = Effects.createZBounce(object, 2, endingPos, 0.5, 1.5)
+
+ #objAnimShrink = ParallelEndTogether(animProp, shrinkProp)
+ objAnimShrink = Sequence( Func(object.setScale, startingScale),
+ Func(object.setH, endHpr[0]),
+ animProp,
+ bounceProp,
+ Wait(1.5),
+ shrinkProp)
+ objectTrack.append(objAnimShrink)
+ MovieUtil.removeProp( object2)
+
+
+ objectTrack.append(Func(MovieUtil.removeProp, object))
+ objectTrack.append(Func(battle.movie.clearRenderProp, object))
+
+ # we will see a shadow scale up before the object drops
+ dropShadow = MovieUtil.copyProp(suit.getShadowJoint())
+ if (level == 0):
+ dropShadow.setScale(0.5)
+ elif (level <= 2):
+ dropShadow.setScale(0.8)
+ elif (level == 3):
+ dropShadow.setScale(2.0)
+ elif (level == 4):
+ dropShadow.setScale(2.3)
+ else:
+ dropShadow.setScale(3.6)
+
+ def posShadow(dropShadow=dropShadow, suit=suit, battle=battle, hp=hp, level=level):
+ dropShadow.reparentTo(battle)
+ if (battle.isSuitLured(suit)):
+ # suit is lured, shadow at battle position
+ suitPos, suitHpr = battle.getActorPosHpr(suit)
+ dropShadow.setPos(suitPos)
+ dropShadow.setHpr(suitHpr)
+ if (level >= 3): # bump back larger drops
+ dropShadow.setY(dropShadow.getY() + 2)
+ else:
+ dropShadow.setPos(suit.getPos(battle))
+ dropShadow.setHpr(suit.getHpr(battle))
+ if (hp <= 0 and level >= 3):
+ dropShadow.setY(dropShadow.getY(battle) + 5)
+ # Raise the drop shadow to curb level
+ dropShadow.setZ(dropShadow.getZ() + 0.5)
+
+ shadowTrack = Sequence(
+ Wait(delay+tButtonPressed),
+ Func(battle.movie.needRestoreRenderProp, dropShadow),
+ Func(posShadow),
+ LerpScaleInterval(dropShadow, tObjectAppears - tButtonPressed,
+ dropShadow.getScale(), startScale = Point3(0.01, 0.01, 0.01)),
+ Wait(0.3),
+ Func(MovieUtil.removeProp, dropShadow),
+ Func(battle.movie.clearRenderProp, dropShadow),
+ )
+
+ return Parallel(toonTrack, soundTrack, buttonTrack, objectTrack, shadowTrack)
+
+def __createSuitTrack(drop, delay,level, alreadyDodged, alreadyTeased,
+ target, npcs):
+ toon = drop['toon']
+ if (drop.has_key('npc')):
+ toon = drop['npc']
+ battle = drop['battle']
+
+ majorObject = (level >= 3)
+ suit = target['suit']
+ hp = target['hp']
+ hitSuit = (hp > 0)
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ kbbonus = target['kbbonus']
+ hpbonus = drop['hpbonus']
+
+ # 4 options for a suit in a drop attack:
+ # 1) It takes damage and reacts (hp > 0)
+ # 2) It is lured, thus drop misses so suit does nothing (kbbonus == 0). This is
+ # detected using a hack in the kbbonus. There is no actual kickback bonus involved
+ # for drops, but if this kbbonus value is set to 0 instead of -1 (in the battle
+ # calculator), then we've specified that the suit is lured
+ # 3) The suit dodges and is reacting to the first drop to dodge, detected when the
+ # variable alreadyDodged == 0
+ # 4) The suit would dodge but is already dodging first drop, detected when the
+ # variable alreadyDodged == 1
+ if (hp > 0):
+ # Suit takes damage (for each drop that hits)
+ suitTrack = Sequence()
+ showDamage = Func(suit.showHpText, -hp, openEnded=0)
+ updateHealthBar = Func(suit.updateHealthBar, hp)
+ if majorObject:
+ anim = 'flatten'
+ else:
+ anim = 'drop-react'
+ suitReact = ActorInterval(suit, anim)
+ suitTrack.append(Wait(delay+tObjectAppears))
+ suitTrack.append(showDamage)
+ suitTrack.append(updateHealthBar)
+ suitGettingHit = Parallel( suitReact)
+ if level == UBER_GAG_LEVEL_INDEX:
+ gotHitSound = globalBattleSoundCache.getSound('AA_drop_boat_cog.mp3')
+ suitGettingHit.append(SoundInterval(gotHitSound, node=toon))
+ suitTrack.append(suitGettingHit)
+ # Create a bonus track if there is an hp bonus
+ bonusTrack = None
+ if (hpbonus > 0):
+ bonusTrack = Sequence(Wait(delay + tObjectAppears + 0.75),
+ Func(suit.showHpText,
+ -hpbonus, 1, openEnded=0))
+ if (revived != 0):
+ suitTrack.append(MovieUtil.createSuitReviveTrack(suit, toon,
+ battle, npcs))
+ elif (died != 0):
+ suitTrack.append(MovieUtil.createSuitDeathTrack(suit, toon,
+ battle, npcs))
+ else:
+ suitTrack.append(Func(suit.loop, 'neutral'))
+
+
+ if (bonusTrack != None):
+ suitTrack = Parallel(suitTrack, bonusTrack)
+
+ elif (kbbonus == 0):
+ # If suit is lured, doesn't need to dodge and certainly won't get hit
+ suitTrack = Sequence(
+ Wait(delay+tObjectAppears),
+ Func(MovieUtil.indicateMissed, suit, 0.6),
+ Func(suit.loop, 'neutral'),
+ )
+ else:
+ # Conditions regarding dodging:
+ # 1) The suit will dodge only once with multiple drops in the same attack,
+ # so we only dodge if we haven't already (alreadyDodged==0)
+ # 2) The suit will not NEED to dodge if attacked by a larger drop (which fall
+ # behind the suit on a miss rather than having the suit dodge
+ # Special conditions:
+ # 1) If there's a large drop followed by a small one at some point, we can allow
+ # the suit to start teasing and then dogde, this looks fine
+ # 2) If there's a small drop followed by a large drop at some point, we don't allow
+ # the suit to tease, doesn't look right
+ # other suits may need to dodge as well
+
+ # First check if suit started dodging, if so, do not add any another reaction
+ if (alreadyDodged > 0):
+ return None #Parallel(toonTrack, soundTrack, buttonTrack, objectTrack, shadowTrack)
+
+ # Check for large drops
+ if (level >= 3): # the larger drops, suit doesn't dodge, but teases instead
+ # But if we've already started to tease, don't tease more than once
+ if (alreadyTeased > 0):
+ return None #Parallel(toonTrack, soundTrack, buttonTrack, objectTrack, shadowTrack)
+ else:
+ suitTrack = MovieUtil.createSuitTeaseMultiTrack(
+ suit, delay=delay+tObjectAppears)
+
+ else: # small drop, so dodge
+ suitTrack = MovieUtil.createSuitDodgeMultitrack(
+ delay + tSuitDodges, suit, leftSuits, rightSuits)
+
+ return suitTrack
+
diff --git a/toontown/src/battle/MovieFire.py b/toontown/src/battle/MovieFire.py
new file mode 100644
index 0000000..5ca3bb4
--- /dev/null
+++ b/toontown/src/battle/MovieFire.py
@@ -0,0 +1,567 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from BattleSounds import *
+from toontown.toon.ToonDNA import *
+from toontown.suit.SuitDNA import *
+
+from direct.directnotify import DirectNotifyGlobal
+import random
+import MovieCamera
+import MovieUtil
+from MovieUtil import calcAvgSuitPos
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieThrow')
+
+# still need miss 'hitting' sounds to be separated from the 'throw' sounds
+# for creampie and fruitpie, wholecreampie and whole fruitpie and bdaycake
+# so the sound and animation can be synced at the end of variable-length throw
+# until we get these sounds, we just use these 'only' sounds as placeholders
+hitSoundFiles = ('AA_tart_only.mp3',
+ 'AA_slice_only.mp3',
+ 'AA_slice_only.mp3',
+ 'AA_slice_only.mp3',
+ 'AA_slice_only.mp3',
+ 'AA_wholepie_only.mp3',
+ 'AA_wholepie_only.mp3',) #UBER
+
+# need miss 'hitting' sounds to be separated from the 'throw' sounds
+# so the sound and animation can be synced at the end of variable-length throw
+# until we get these sounds, we just use 'AA_pie_throw_only.mp3' for all misses
+#missSoundFiles = ('AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',)
+
+#tPieLeavesHand = 2.755
+tPieLeavesHand = 2.7
+tPieHitsSuit = 3.0
+tSuitDodges = 2.45
+
+# length of pie flight path when you miss relative to length
+# of pie flight when you hit the suit
+ratioMissToHit = 1.5
+# this is based on the [0..1] time range of the pie flight when it misses
+tPieShrink = 0.7
+
+pieFlyTaskName = "MovieThrow-pieFly"
+
+
+def addHit (dict, suitId, hitCount):
+ if (dict.has_key(suitId)):
+ dict[suitId] += hitCount
+ else:
+ dict[suitId] = hitCount
+
+def doFires(fires):
+
+ """ Throws occur in the following order:
+ a) by suit, in order of increasing number of throws per suit
+ 1) level 1 throws, right to left, (TOON_THROW_DELAY later)
+ 2) level 2 throws, right to left, (TOON_THROW_DELAY later)
+ 3) level 3 throws, right to left, (TOON_THROW_DELAY later)
+ etc.
+ b) next suit, (TOON_THROW_SUIT_DELAY later)
+ """
+
+ #import pdb; pdb.set_trace()
+ if (len(fires) == 0):
+ return (None, None)
+
+ # Group the throws by targeted suit
+ suitFiresDict = {}
+ # throw['toon'] is the thrower.
+
+ for fire in fires:
+ suitId = fire['target']['suit'].doId
+ if (suitFiresDict.has_key(suitId)):
+ suitFiresDict[suitId].append(fire)
+ else:
+ suitFiresDict[suitId] = [fire]
+
+ # A list of lists of throws grouped by suit
+ suitFires = suitFiresDict.values()
+ # Sort the suits based on the number of throws per suit
+ def compFunc(a, b):
+ if (len(a) > len(b)):
+ return 1
+ elif (len(a) < len(b)):
+ return -1
+ return 0
+ suitFires.sort(compFunc)
+
+ #since we have group throws now, we calculate how
+ #many times each suit gets hit over here
+ totalHitDict = {}
+ singleHitDict = {}
+ groupHitDict = {}
+
+ for fire in fires:
+ if 1:
+ suitId = fire['target']['suit'].doId
+ if fire['target']['hp'] > 0:
+ addHit(singleHitDict,suitId,1)
+ addHit(totalHitDict,suitId,1)
+ else:
+ addHit(singleHitDict,suitId,0)
+ addHit(totalHitDict,suitId,0)
+
+ notify.debug('singleHitDict = %s' % singleHitDict)
+ notify.debug('groupHitDict = %s' % groupHitDict)
+ notify.debug('totalHitDict = %s' % totalHitDict)
+
+
+ # Apply attacks in order
+ delay = 0.0
+ mtrack = Parallel()
+ firedTargets = []
+ for sf in suitFires:
+ if (len(sf) > 0):
+ ival = __doSuitFires(sf)
+ if (ival):
+ mtrack.append(Sequence(Wait(delay), ival))
+ delay = delay + TOON_FIRE_SUIT_DELAY
+
+ retTrack = Sequence()
+ retTrack.append(mtrack)
+
+
+
+
+ camDuration = retTrack.getDuration()
+ camTrack = MovieCamera.chooseFireShot(fires, suitFiresDict,
+ camDuration)
+ return (retTrack, camTrack)
+
+def __doSuitFires(fires):
+ """ __doSuitThrows(throws)
+ Create the intervals for the attacks on a particular suit.
+ 1 or more toons can throw at the same target suit
+ Note: attacks are sorted by increasing level (as are toons)
+ Returns a multitrack with toon throws in the following order:
+ 1) level 1 throws, right to left, (TOON_THROW_DELAY later)
+ 2) level 2 throws, right to left, (TOON_THROW_DELAY later)
+ etc.
+ The intervals actually overlap in the case of multiple hits,
+ prematurely cutting off the end of the react animation.
+ """
+ toonTracks = Parallel()
+ delay = 0.0
+ # See if suit is hit multiple times, if it is, don't show stun animation
+ hitCount = 0
+ for fire in fires:
+ if fire['target']['hp'] > 0:
+ # Hit, continue counting
+ hitCount += 1
+ else:
+ # Miss, no need to think about stun effect
+ break
+ suitList = []
+
+ for fire in fires:
+ if not (fire['target']['suit'] in suitList):
+ suitList.append(fire['target']['suit'])
+
+ for fire in fires:
+ showSuitCannon = 1
+ if not (fire['target']['suit'] in suitList):
+ showSuitCannon = 0
+ else:
+ suitList.remove(fire['target']['suit'])
+ tracks = __throwPie(fire, delay, hitCount, showSuitCannon)
+ if (tracks):
+ for track in tracks:
+ toonTracks.append(track)
+ delay = delay + TOON_THROW_DELAY
+ return toonTracks
+
+def __showProp(prop, parent, pos):
+ prop.reparentTo(parent)
+ prop.setPos(pos)
+
+def __animProp(props, propName, propType):
+ if 'actor' == propType:
+ for prop in props:
+ prop.play(propName)
+ elif 'model' == propType:
+ pass
+ else:
+ notify.error("No such propType as: %s" % propType)
+
+def __billboardProp(prop):
+ scale = prop.getScale()
+ #prop.lookAt(camera)
+ prop.setBillboardPointWorld()
+ prop.setScale(scale)
+
+def __suitMissPoint(suit, other=render):
+ # this is a point just above the suit's head
+ pnt = suit.getPos(other)
+ pnt.setZ(pnt[2] + (suit.getHeight() * 1.3))
+ return pnt
+
+def __propPreflight(props, suit, toon, battle):
+ assert(len(props) >= 2)
+ prop = props[0]
+ # make sure the 0 lod toon is in the right pose of the animation
+ toon.update(0)
+ # take the prop from the toon
+ prop.wrtReparentTo(battle)
+ props[1].reparentTo(hidden)
+
+ # make the top of the pie point along +Y
+ for ci in range(prop.getNumChildren()):
+ prop.getChild(ci).setHpr(0, -90, 0)
+
+ # figure out where it's going to end up
+ targetPnt = MovieUtil.avatarFacePoint(suit, other=battle)
+
+ # make the pie point towards the suit's face
+ prop.lookAt(targetPnt)
+
+def __propPreflightGroup(props, suits, toon, battle):
+ """
+ same as __propPreflight, but face the avg suit pt
+ """
+ assert(len(props) >= 2)
+ prop = props[0]
+ # make sure the 0 lod toon is in the right pose of the animation
+ toon.update(0)
+ # take the prop from the toon
+ prop.wrtReparentTo(battle)
+ props[1].reparentTo(hidden)
+
+ # make the top of the pie point along +Y
+ for ci in range(prop.getNumChildren()):
+ prop.getChild(ci).setHpr(0, -90, 0)
+
+ # figure out where it's going to end up
+ avgTargetPt = Point3(0,0,0)
+ for suit in suits:
+ avgTargetPt += MovieUtil.avatarFacePoint(suit, other=battle)
+ avgTargetPt /= len(suits)
+
+ # make the pie point towards the suit's face
+ prop.lookAt(avgTargetPt)
+
+
+def __piePreMiss(missDict, pie, suitPoint, other=render):
+ # called after propPreFlight
+ missDict['pie'] = pie
+ missDict['startScale'] = pie.getScale()
+ missDict['startPos'] = pie.getPos(other)
+ v = Vec3(suitPoint - missDict['startPos'])
+ endPos = missDict['startPos'] + (v * ratioMissToHit)
+ missDict['endPos'] = endPos
+
+def __pieMissLerpCallback(t, missDict):
+ pie = missDict['pie']
+ newPos = (missDict['startPos'] * (1.0 - t)) + (missDict['endPos'] * t)
+ if t < tPieShrink:
+ tScale = 0.0001
+ else:
+ tScale = (t - tPieShrink) / (1.0 - tPieShrink)
+ newScale = (missDict['startScale'] * max((1.0 - tScale), 0.01))
+ pie.setPos(newPos)
+ pie.setScale(newScale)
+
+
+def __piePreMissGroup(missDict, pies, suitPoint, other=render):
+ """
+ Same as __piePreMiss, but handles multiple pie parts
+ # called after propPreFlight
+ """
+
+ missDict['pies'] = pies
+ missDict['startScale'] = pies[0].getScale()
+ missDict['startPos'] = pies[0].getPos(other)
+ v = Vec3(suitPoint - missDict['startPos'])
+ endPos = missDict['startPos'] + (v * ratioMissToHit)
+ missDict['endPos'] = endPos
+
+ notify.debug('startPos=%s' % missDict['startPos'])
+ notify.debug('v=%s' % v)
+ notify.debug('endPos=%s' % missDict['endPos'])
+
+def __pieMissGroupLerpCallback(t, missDict):
+ """
+ Same as __pieMissLerpCallback, but handles multiple pie parts
+ """
+
+ pies = missDict['pies']
+ newPos = (missDict['startPos'] * (1.0 - t)) + (missDict['endPos'] * t)
+ if t < tPieShrink:
+ tScale = 0.0001
+ else:
+ tScale = (t - tPieShrink) / (1.0 - tPieShrink)
+ newScale = (missDict['startScale'] * max((1.0 - tScale), 0.01))
+
+ for pie in pies:
+ pie.setPos(newPos)
+ pie.setScale(newScale)
+
+
+
+
+
+def __getSoundTrack(level, hitSuit, node=None):
+
+ throwSound = globalBattleSoundCache.getSound('AA_drop_trigger_box.mp3')
+
+ throwTrack = Sequence(Wait(2.15), SoundInterval(throwSound, node=node))
+
+ return throwTrack
+
+
+def __throwPie(throw, delay, hitCount, showCannon = 1):
+ #print ("__throwPie %s" % (showCannon))
+
+ toon = throw['toon']
+ hpbonus = throw['hpbonus']
+ target = throw['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ sidestep = throw['sidestep']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ level = throw['level']
+ battle = throw['battle']
+ suitPos = suit.getPos(battle)
+ origHpr = toon.getHpr(battle)
+ notify.debug('toon: %s throws tart at suit: %d for hp: %d died: %d' % \
+ (toon.getName(), suit.doId, hp, died))
+
+ pieName = pieNames[0]
+
+ hitSuit = (hp > 0)
+
+ #pie = globalPropPool.getProp(pieName)
+ #pieType = globalPropPool.getPropType(pieName)
+ #pie2 = MovieUtil.copyProp(pie)
+ #pies = [pie, pie2]
+ #hands = toon.getRightHands()
+
+ #splatName = 'splat-' + pieName
+ #if pieName == 'wedding-cake':
+ # splatName = 'splat-birthday-cake'
+ #splat = globalPropPool.getProp(splatName)
+ #splatType = globalPropPool.getPropType(splatName)
+
+
+ button = globalPropPool.getProp('button')
+ buttonType = globalPropPool.getPropType('button')
+ button2 = MovieUtil.copyProp(button)
+ buttons = [button, button2]
+ hands = toon.getLeftHands()
+
+
+ # make the toon throw the pie
+ toonTrack = Sequence()
+ toonFace = Func(toon.headsUp, battle, suitPos)
+ toonTrack.append(Wait(delay))
+ toonTrack.append(toonFace)
+ toonTrack.append(ActorInterval(toon, 'pushbutton'))
+ toonTrack.append(ActorInterval(toon, 'wave', duration= 2.0))
+ toonTrack.append(ActorInterval(toon, 'duck'))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.setHpr, battle, origHpr))
+
+ buttonTrack = Sequence()
+
+ buttonShow = Func(MovieUtil.showProps, buttons, hands)
+ buttonScaleUp = LerpScaleInterval(button, 1.0, button.getScale(), startScale = Point3(0.01, 0.01, 0.01))
+ buttonScaleDown = LerpScaleInterval(button, 1.0, Point3(0.01, 0.01, 0.01), startScale = button.getScale())
+ buttonHide = Func(MovieUtil.removeProps, buttons)
+ buttonTrack.append(Wait(delay))
+ buttonTrack.append(buttonShow)
+ buttonTrack.append(buttonScaleUp)
+ buttonTrack.append(Wait(2.5))
+ buttonTrack.append(buttonScaleDown)
+ buttonTrack.append(buttonHide)
+
+ soundTrack = __getSoundTrack(level, hitSuit, toon)
+
+ suitResponseTrack = Sequence()
+ reactIval = Sequence()
+ if showCannon:
+ showDamage = Func(suit.showHpText, -hp, openEnded=0)
+ updateHealthBar = Func(suit.updateHealthBar, hp)
+ # If the suit gets knocked back, animate it
+ # No stun animation shown here
+ cannon = loader.loadModel("phase_4/models/minigames/toon_cannon")
+ barrel = cannon.find("**/cannon")
+ barrel.setHpr(0,90,0)
+
+ cannonHolder = render.attachNewNode('CannonHolder')
+ cannon.reparentTo(cannonHolder)
+ cannon.setPos(0,0,-8.6)
+ cannonHolder.setPos(suit.getPos(render))
+ cannonHolder.setHpr(suit.getHpr(render))
+ cannonAttachPoint = barrel.attachNewNode('CannonAttach')
+ kapowAttachPoint = barrel.attachNewNode('kapowAttach')
+ scaleFactor = 1.6
+ iScale = 1 / scaleFactor
+ barrel.setScale(scaleFactor,1,scaleFactor)
+ cannonAttachPoint.setScale(iScale, 1, iScale)
+ cannonAttachPoint.setPos(0,6.7,0)
+ kapowAttachPoint.setPos(0,-0.5,1.9)
+ suit.reparentTo(cannonAttachPoint)
+ suit.setPos(0,0,0)
+ suit.setHpr(0,-90,0)
+ suitLevel = suit.getActualLevel()
+ deep = 2.5 + (suitLevel * 0.20)
+
+ #import pdb; pdb.set_trace()
+
+ #print "Fire anim suit name: %s" % (suit.dna.name)
+ suitScale = 0.90
+ import math
+ suitScale = 0.9 - (math.sqrt(suitLevel) * 0.10)
+ #if suit.dna.name == 'bf':
+ # suitScale = 0.80
+ #elif suit.dna.name == 'cr':
+ # suitScale = 0.75
+ sival = [] # Suit interval of its animation
+ posInit = cannonHolder.getPos()
+ posFinal = Point3(posInit[0] + 0.0, posInit[1] + 0.0, posInit[2] + 7.0)
+ kapow = globalPropPool.getProp('kapow')
+
+ kapow.reparentTo(kapowAttachPoint)
+ kapow.hide()
+ kapow.setScale(0.25)
+ kapow.setBillboardPointEye()
+
+
+ smoke = loader.loadModel(
+ "phase_4/models/props/test_clouds")
+ smoke.reparentTo(cannonAttachPoint)
+ smoke.setScale(.5)
+ smoke.hide()
+ smoke.setBillboardPointEye()
+
+ soundBomb = base.loadSfx("phase_4/audio/sfx/MG_cannon_fire_alt.mp3")
+ playSoundBomb = SoundInterval(soundBomb,node=cannonHolder)
+
+ soundFly = base.loadSfx("phase_4/audio/sfx/firework_whistle_01.mp3")
+ playSoundFly = SoundInterval(soundFly,node=cannonHolder)
+
+ soundCannonAdjust = base.loadSfx("phase_4/audio/sfx/MG_cannon_adjust.mp3")
+ playSoundCannonAdjust = SoundInterval(soundCannonAdjust, duration = 0.6 ,node=cannonHolder)
+
+ soundCogPanic = base.loadSfx("phase_5/audio/sfx/ENC_cogafssm.mp3")
+ playSoundCogPanic = SoundInterval(soundCogPanic,node=cannonHolder)
+
+
+ reactIval = Parallel( ActorInterval(suit, 'pie-small-react'),
+ Sequence(
+ Wait(0.0),
+ LerpPosInterval(cannonHolder, 2.0, posFinal,
+ startPos = posInit, blendType='easeInOut'),
+ Parallel(
+ LerpHprInterval(barrel, 0.6, Point3(0,45,0), startHpr=Point3(0,90,0) , blendType='easeIn'),
+ playSoundCannonAdjust,
+ ),
+ Wait(2.0),
+ Parallel(
+ LerpHprInterval(barrel, 0.6, Point3(0,90,0), startHpr=Point3(0,45,0) , blendType='easeIn'),
+ playSoundCannonAdjust,
+ ),
+ LerpPosInterval(cannonHolder, 1.0, posInit,
+ startPos = posFinal, blendType='easeInOut'),
+
+ ),
+ Sequence(
+ Wait(0.0),
+ Parallel(
+ ActorInterval(suit, 'flail'),
+ suit.scaleInterval(1.0, suitScale),
+ LerpPosInterval(suit, 0.25, Point3(0,-1.0,0.0)),
+ Sequence(
+ Wait(0.25),
+ Parallel(
+ playSoundCogPanic,
+ LerpPosInterval(suit, 1.50, Point3(0,-deep,0.0), blendType='easeIn'),
+ ),
+ ),
+ ),
+ Wait(2.5),
+
+ Parallel(
+ playSoundBomb,
+ playSoundFly,
+ Sequence(
+ Func(smoke.show),
+ Parallel(LerpScaleInterval(smoke, .5, 3),
+ LerpColorScaleInterval(smoke, .5, Vec4(2,2,2,0))),
+ Func(smoke.hide),
+ ),
+ Sequence(
+ Func(kapow.show),
+ ActorInterval(kapow, 'kapow'),
+ Func(kapow.hide),
+ ),
+ LerpPosInterval(suit, 3.00, Point3(0,150.0,0.0)),
+ suit.scaleInterval(3.0, 0.01),
+ ),
+ Func(suit.hide),
+ )
+ )
+
+ if (hitCount == 1):
+ sival = Sequence(
+ Parallel(
+ reactIval,
+ MovieUtil.createSuitStunInterval(suit, 0.3, 1.3),
+ ),
+ Wait(0.0),
+ Func(cannonHolder.remove),
+ )
+ else:
+ sival = reactIval
+ #sival = ActorInterval(suit, 'pie-small-react')
+ suitResponseTrack.append(Wait(delay + tPieHitsSuit))
+ suitResponseTrack.append(showDamage)
+ suitResponseTrack.append(updateHealthBar)
+ suitResponseTrack.append(sival)
+ # Make a bonus track for any hp bonus
+ bonusTrack = Sequence(Wait(delay + tPieHitsSuit))
+ if (kbbonus > 0):
+ bonusTrack.append(Wait(0.75))
+ bonusTrack.append(Func(suit.showHpText, -kbbonus, 2, openEnded=0))
+ if (hpbonus > 0):
+ bonusTrack.append(Wait(0.75))
+ bonusTrack.append(Func(suit.showHpText, -hpbonus, 1, openEnded=0))
+
+ if 0:
+ if (revived != 0):
+ suitResponseTrack.append(MovieUtil.createSuitReviveTrack(suit, toon, battle))
+ elif (died != 0):
+ suitResponseTrack.append(MovieUtil.createSuitDeathTrack(suit, toon, battle))
+ else:
+ suitResponseTrack.append(Func(suit.loop, 'neutral'))
+
+ suitResponseTrack = Parallel(suitResponseTrack, bonusTrack)
+
+
+
+ # Since it's possible for there to be simultaneous throws, we only
+ # want the suit to dodge one time at most. Thus if the suit is
+ # not hit (dodges) and that dodge is not from the first dodge
+ # (which has delay=0) then we don't add another suit reaction.
+ # Otherwise, we add the suit track as normal
+
+
+ return [toonTrack, soundTrack, buttonTrack, suitResponseTrack]
+
+
+
+
+
+
+
diff --git a/toontown/src/battle/MovieHeal.py b/toontown/src/battle/MovieHeal.py
new file mode 100644
index 0000000..2430f2e
--- /dev/null
+++ b/toontown/src/battle/MovieHeal.py
@@ -0,0 +1,784 @@
+from direct.interval.IntervalGlobal import *
+from BattleProps import *
+from BattleSounds import *
+from BattleBase import *
+
+from direct.directnotify import DirectNotifyGlobal
+import MovieCamera
+import random
+import MovieUtil
+import BattleParticles
+import HealJokes
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase.ToontownBattleGlobals import AvPropDamage
+from toontown.toon import NPCToons
+import MovieNPCSOS
+from toontown.effects import Splash
+from direct.task import Task
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieHeal')
+
+soundFiles = ('AA_heal_tickle.mp3',
+ 'AA_heal_telljoke.mp3',
+ 'AA_heal_smooch.mp3',
+ 'AA_heal_happydance.mp3',
+ 'AA_heal_pixiedust.mp3',
+ 'AA_heal_juggle.mp3',
+ 'AA_heal_High_Dive.mp3')#UBER
+
+healPos = Point3(0, 0, 0)
+healHpr = Vec3(180.0, 0, 0)
+runHealTime = 1.0
+
+
+def doHeals(heals, hasInteractivePropHealBonus):
+ """ doHeals(heals)
+ Heals occur in the following order:
+ 1) level 1 heals one at a time, from right to left
+ 2) level 2 heals one at a time, from right to left
+ etc.
+ """
+ #print("do Heals")
+ if (len(heals) == 0):
+ return (None, None)
+ track = Sequence()
+ for h in heals:
+ ival = __doHealLevel(h, hasInteractivePropHealBonus)
+ if (ival):
+ track.append(ival)
+
+ camDuration = track.getDuration()
+ camTrack = MovieCamera.chooseHealShot(heals, camDuration)
+ return (track, camTrack)
+
+def __doHealLevel(heal, hasInteractivePropHealBonus):
+ """ __doHealLevel(heal)
+ """
+ #print("doHealLevel")
+ level = heal['level']
+ if (level == 0):
+ return __healTickle(heal, hasInteractivePropHealBonus)
+ elif (level == 1):
+ return __healJoke(heal, hasInteractivePropHealBonus)
+ elif (level == 2):
+ return __healSmooch(heal, hasInteractivePropHealBonus)
+ elif (level == 3):
+ return __healDance(heal, hasInteractivePropHealBonus)
+ elif (level == 4):
+ return __healSprinkle(heal, hasInteractivePropHealBonus)
+ elif (level == 5):
+ return __healJuggle(heal, hasInteractivePropHealBonus)
+ elif (level == 6):
+ return __healDive(heal, hasInteractivePropHealBonus) #UBER
+ return None
+
+def __runToHealSpot(heal):
+ """ generate a track that does the following:
+ a) face the heal spot
+ b) run to the heal spot
+ c) turn to face the target
+ """
+ toon = heal['toon']
+ battle = heal['battle']
+ level = heal['level']
+
+ origPos, origHpr = battle.getActorPosHpr(toon)
+ runAnimI = ActorInterval(toon, 'run', duration=runHealTime)
+ a = Func(toon.headsUp, battle, healPos)
+ b = Parallel(runAnimI, LerpPosInterval(
+ toon, runHealTime, healPos, other=battle))
+
+ # For group heals, face the center of the group
+ if levelAffectsGroup(HEAL, level):#(level % 2):
+ c = Func(toon.setHpr, battle, healHpr)
+ else:
+ # For single heals, face the target toon
+ target = heal['target']['toon']
+ targetPos = target.getPos(battle)
+ c = Func(toon.headsUp, battle, targetPos)
+ return Sequence(a, b, c)
+
+def __returnToBase(heal):
+ """ generate a track that does the following:
+ a) face the toons starting place
+ b) run to the starting place
+ c) turn to face the center of the battle
+ """
+ toon = heal['toon']
+ battle = heal['battle']
+ origPos, origHpr = battle.getActorPosHpr(toon)
+ #print 'ORIG_HPR: %s' % origHpr
+
+ runAnimI = ActorInterval(toon, 'run', duration=runHealTime)
+ a = Func(toon.headsUp, battle, origPos)
+ b = Parallel(runAnimI, LerpPosInterval(
+ toon, runHealTime, origPos, other=battle))
+ c = Func(toon.setHpr, battle, origHpr)
+ d = Func(toon.loop, 'neutral')
+ return Sequence(a, b, c, d)
+
+def __healToon(toon, hp, ineffective, hasInteractivePropHealBonus):
+ notify.debug('healToon() - toon: %d hp: %d ineffective: %d' % \
+ (toon.doId, hp, ineffective))
+ if (ineffective == 1):
+ laughter = random.choice(TTLocalizer.MovieHealLaughterMisses)
+ else:
+ maxDam=AvPropDamage[0][1][0][1]
+ if (hp >= maxDam-1):
+ laughter = random.choice(TTLocalizer.MovieHealLaughterHits2)
+ else:
+ laughter = random.choice(TTLocalizer.MovieHealLaughterHits1)
+ toon.setChatAbsolute(laughter, CFSpeech | CFTimeout)
+ if (hp > 0 and toon.hp != None):
+ toon.toonUp(hp, hasInteractivePropHealBonus)
+ else:
+ notify.debug('__healToon() - toon: %d hp: %d' % (toon.doId, hp))
+
+def __getPartTrack(particleEffect, startDelay, durationDelay, partExtraArgs):
+ """ This function returns the default particle track for a suit attack
+ animation. Arguments:
+ startDelay = time delay before particle effect begins
+ durationDelay = time delay before particles are cleaned up
+ partExtraArgs = extraArgs for startParticleEffect function, the first
+ element of which is always the particle effect (function relies on this)
+ """
+ pEffect = partExtraArgs[0]
+ parent = partExtraArgs[1]
+ if (len(partExtraArgs) == 3):
+ worldRelative = partExtraArgs[2]
+ else:
+ worldRelative = 1
+ return Sequence(
+ Wait(startDelay),
+ ParticleInterval(pEffect, parent, worldRelative,
+ duration=durationDelay, cleanup = True),
+ )
+
+def __getSoundTrack(level, delay, duration=None, node=None):
+ #level: the level of attack, int 0-5
+ #delay: time delay before playing sound
+
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+
+ soundIntervals = Sequence()
+
+ if soundEffect:
+ if duration:
+ playSound = SoundInterval(soundEffect, duration=duration, node=node)
+ else:
+ playSound = SoundInterval(soundEffect, node=node)
+ soundIntervals.append(Wait(delay))
+ soundIntervals.append(playSound)
+
+ return soundIntervals
+
+
+def __healTickle(heal,hasInteractivePropHealBonus):
+ """ __healTickle(heal)
+ """
+ toon = heal['toon']
+ target = heal['target']['toon']
+ hp = heal['target']['hp']
+ ineffective = heal['sidestep']
+ level = heal['level']
+
+ # Make a 'sandwich' around the track specific interval
+ track = Sequence(__runToHealSpot(heal))
+ feather = globalPropPool.getProp('feather')
+ feather2 = MovieUtil.copyProp(feather)
+ feathers = [feather, feather2]
+ hands = toon.getRightHands()
+
+ def scaleFeathers(feathers, toon=toon, target=target):
+ toon.pose('tickle', 63)
+ toon.update(0) # make sure LOD 0 is posed
+ hand = toon.getRightHands()[0]
+ horizDistance = Vec3(hand.getPos(render) - target.getPos(render))
+ horizDistance.setZ(0)
+ distance = horizDistance.length()
+ if target.style.torso[0] == 's':
+ distance -= 0.5 # for fat toons
+ else:
+ distance -= 0.3 # for skinny toons
+ featherLen = 2.4
+ scale = distance / (featherLen * hand.getScale(render)[0])
+ for feather in feathers:
+ feather.setScale(scale)
+
+ tFeatherScaleUp = 0.5
+ dFeatherScaleUp = 0.5
+ dFeatherScaleDown = 0.5
+ featherTrack = Parallel(
+ MovieUtil.getActorIntervals(feathers, 'feather'),
+ Sequence(Wait(tFeatherScaleUp),
+ Func(MovieUtil.showProps, feathers, hands),
+ Func(scaleFeathers, feathers),
+ MovieUtil.getScaleIntervals(feathers, dFeatherScaleUp,
+ MovieUtil.PNT3_NEARZERO,
+ feathers[0].getScale),
+ ),
+ Sequence(Wait(toon.getDuration('tickle') - dFeatherScaleDown),
+ MovieUtil.getScaleIntervals(feathers, dFeatherScaleDown,
+ None, MovieUtil.PNT3_NEARZERO)
+ ),
+ )
+
+ tHeal = 3.
+
+ mtrack = Parallel(featherTrack,
+ ActorInterval(toon, 'tickle'),
+ __getSoundTrack(level, 1, node=toon),
+ Sequence(Wait(tHeal),
+ Func(__healToon, target, hp, ineffective,hasInteractivePropHealBonus),
+ ActorInterval(target, 'cringe',
+ startTime=20./target.getFrameRate('cringe')
+ )),
+
+ )
+
+ track.append(mtrack)
+ track.append(Func(MovieUtil.removeProps, feathers))
+ track.append(__returnToBase(heal))
+ track.append(Func(target.clearChat))
+ return track
+
+def __healJoke(heal, hasInteractivePropHealBonus):
+ """ __healJoke(heal)
+ """
+ toon = heal['toon']
+ targets = heal['target']
+ ineffective = heal['sidestep']
+ level = heal['level']
+ jokeIndex = heal['hpbonus'] % len(HealJokes.toonHealJokes)
+
+ # Make a 'sandwich' around the track specific interval
+ track = Sequence(__runToHealSpot(heal))
+
+ # start a multitrack
+ tracks = Parallel()
+
+ # frame
+ fSpeakPunchline = 58
+
+ tSpeakSetup = 0.
+ tSpeakPunchline = 3.
+ dPunchLine = 3.
+ tTargetReact = tSpeakPunchline + 1.
+ dTargetLaugh = 1.5
+ tRunBack = tSpeakPunchline + dPunchLine
+
+ tDoSoundAnimation = tSpeakPunchline - \
+ (float(fSpeakPunchline) / toon.getFrameRate('sound'))
+
+ # megaphone track
+ megaphone = globalPropPool.getProp('megaphone')
+ megaphone2 = MovieUtil.copyProp(megaphone)
+ megaphones = [megaphone, megaphone2]
+ hands = toon.getRightHands()
+
+ dMegaphoneScale = 0.5
+
+ tracks.append(Sequence(
+ Wait(tDoSoundAnimation),
+ Func(MovieUtil.showProps, megaphones, hands),
+ MovieUtil.getScaleIntervals(megaphones, dMegaphoneScale,
+ MovieUtil.PNT3_NEARZERO, MovieUtil.PNT3_ONE),
+ Wait(toon.getDuration('sound') - (2.0 * dMegaphoneScale)),
+ MovieUtil.getScaleIntervals(megaphones, dMegaphoneScale,
+ MovieUtil.PNT3_ONE, MovieUtil.PNT3_NEARZERO),
+ Func(MovieUtil.removeProps, megaphones)
+ ))
+
+ # toon track
+ tracks.append(Sequence(
+ Wait(tDoSoundAnimation),
+ ActorInterval(toon, 'sound')
+ ))
+
+ # add sound
+ soundTrack = __getSoundTrack(level, 2.0, node=toon)
+ tracks.append(soundTrack)
+
+ assert(jokeIndex < len(HealJokes.toonHealJokes))
+ joke = HealJokes.toonHealJokes[jokeIndex]
+ # the set-up
+ tracks.append(Sequence(
+ Wait(tSpeakSetup),
+ Func(toon.setChatAbsolute, joke[0], CFSpeech | CFTimeout),
+ ))
+ # the punchline
+ tracks.append(Sequence(
+ Wait(tSpeakPunchline),
+ Func(toon.setChatAbsolute, joke[1], CFSpeech | CFTimeout),
+ ))
+
+ # do the target toon reaction(s)
+ reactTrack = Sequence(
+ Wait(tTargetReact),
+ )
+ for target in targets:
+ targetToon = target['toon']
+ hp = target['hp']
+ reactTrack.append(
+ Func(__healToon, targetToon, hp, ineffective, hasInteractivePropHealBonus)
+ )
+ reactTrack.append(Wait(dTargetLaugh))
+ for target in targets:
+ targetToon = target['toon']
+ reactTrack.append(Func(targetToon.clearChat))
+ tracks.append(reactTrack)
+
+ tracks.append(Sequence(
+ Wait(tRunBack),
+ Func(toon.clearChat),
+ *__returnToBase(heal)))
+
+ # lay down the multitrack
+ track.append(tracks)
+
+ return track
+
+def __healSmooch(heal, hasInteractivePropHealBonus):
+ """ __healSmooch(heal)
+ """
+ toon = heal['toon']
+ target = heal['target']['toon']
+ level = heal['level']
+ hp = heal['target']['hp']
+ ineffective = heal['sidestep']
+
+ # Make a 'sandwich' around the track specific interval
+ track = Sequence(__runToHealSpot(heal))
+
+ lipstick = globalPropPool.getProp('lipstick')
+ lipstick2 = MovieUtil.copyProp(lipstick)
+ lipsticks = [lipstick, lipstick2]
+ rightHands = toon.getRightHands()
+ dScale = 0.5
+ lipstickTrack = Sequence(
+ Func(MovieUtil.showProps,
+ lipsticks, rightHands,
+ Point3(-0.27, -0.24, -0.95),
+ Point3(-118, -10.6, -25.9)),
+ MovieUtil.getScaleIntervals(lipsticks, dScale,
+ MovieUtil.PNT3_NEARZERO, MovieUtil.PNT3_ONE),
+ Wait(toon.getDuration('smooch') - (2.*dScale)),
+ MovieUtil.getScaleIntervals(lipsticks, dScale,
+ MovieUtil.PNT3_ONE, MovieUtil.PNT3_NEARZERO),
+ Func(MovieUtil.removeProps, lipsticks),
+ )
+
+ lips = globalPropPool.getProp('lips')
+ dScale = 0.5
+ tLips = 2.5
+ tThrow = 115. / toon.getFrameRate('smooch')
+ dThrow = 0.5
+
+ def getLipPos(toon=toon):
+ toon.pose('smooch', 57)
+ toon.update(0)
+ hand = toon.getRightHands()[0]
+ return hand.getPos(render)
+
+ lipsTrack = Sequence(
+ Wait(tLips),
+ Func(MovieUtil.showProp, lips, render, getLipPos),
+ Func(lips.setBillboardPointWorld),
+ LerpScaleInterval(lips, dScale, Point3(3, 3, 3),
+ startScale=MovieUtil.PNT3_NEARZERO),
+ Wait((tThrow - tLips) - dScale),
+ LerpPosInterval(lips, dThrow, Point3(target.getPos() +
+ Point3(0, 0, target.getHeight()))),
+ Func(MovieUtil.removeProp, lips),
+ )
+
+ delay = tThrow + dThrow
+ mtrack = Parallel(lipstickTrack,
+ lipsTrack,
+ __getSoundTrack(level, 2, node=toon),
+ Sequence(ActorInterval(toon, 'smooch'), *__returnToBase(heal)),
+ Sequence(Wait(delay), ActorInterval(target, 'conked')),
+ Sequence(Wait(delay), Func(__healToon,
+ target, hp, ineffective, hasInteractivePropHealBonus)),
+ )
+ track.append(mtrack)
+ #track.append(__returnToBase(heal))
+ track.append(Func(target.clearChat))
+ return track
+
+def __healDance(heal, hasInteractivePropHealBonus):
+ """ __healDance(heal)
+ """
+ toon = heal['toon']
+ targets = heal['target']
+ ineffective = heal['sidestep']
+ level = heal['level']
+
+ # Make a 'sandwich' around the track specific interval
+ track = Sequence(__runToHealSpot(heal))
+ delay = 3.0
+ first = 1
+ targetTrack = Sequence()
+ for target in targets:
+ targetToon = target['toon']
+ hp = target['hp']
+ reactIval = Func(__healToon, targetToon, hp, ineffective, hasInteractivePropHealBonus)
+ if (first):
+ targetTrack.append(Wait(delay))
+ first = 0
+ targetTrack.append(reactIval)
+
+ hat = globalPropPool.getProp('hat')
+ hat2 = MovieUtil.copyProp(hat)
+ hats = [hat, hat2]
+ cane = globalPropPool.getProp('cane')
+ cane2 = MovieUtil.copyProp(cane)
+ canes = [cane, cane2]
+ leftHands = toon.getLeftHands()
+ rightHands = toon.getRightHands()
+ dScale = 0.5
+ propTrack = Sequence(
+ Func(MovieUtil.showProps,
+ hats, rightHands,
+ Point3(0.23, 0.09, 0.69),
+ Point3(180, 0, 0)),
+ Func(MovieUtil.showProps,
+ canes, leftHands,
+ Point3(-0.28, 0., 0.14),
+ Point3(0., 0., -150.)),
+ MovieUtil.getScaleIntervals(hats + canes, dScale,
+ MovieUtil.PNT3_NEARZERO, MovieUtil.PNT3_ONE),
+ Wait(toon.getDuration('happy-dance') - (2.*dScale)),
+ MovieUtil.getScaleIntervals(hats + canes, dScale,
+ MovieUtil.PNT3_ONE, MovieUtil.PNT3_NEARZERO),
+ Func(MovieUtil.removeProps, hats + canes),
+ )
+
+ mtrack = Parallel(propTrack,
+ ActorInterval(toon, 'happy-dance'),
+ __getSoundTrack(level, 0.2, duration=6.4, node=toon),
+ targetTrack)
+
+ # wait a split second before dancing
+ track.append(Func(toon.loop, 'neutral'))
+ track.append(Wait(0.1))
+
+ track.append(mtrack)
+ track.append(__returnToBase(heal))
+ for target in targets:
+ targetToon = target['toon']
+ track.append(Func(targetToon.clearChat))
+ return track
+
+def __healSprinkle(heal, hasInteractivePropHealBonus):
+ """ __healSprinkle(heal)
+ """
+ toon = heal['toon']
+ target = heal['target']['toon']
+ hp = heal['target']['hp']
+ ineffective = heal['sidestep']
+ level = heal['level']
+
+ # Make a 'sandwich' around the track specific interval
+ track = Sequence(__runToHealSpot(heal))
+
+ sprayEffect = BattleParticles.createParticleEffect(file='pixieSpray')
+ dropEffect = BattleParticles.createParticleEffect(file='pixieDrop')
+ explodeEffect = BattleParticles.createParticleEffect(file='pixieExplode')
+ poofEffect = BattleParticles.createParticleEffect(file='pixiePoof')
+ wallEffect = BattleParticles.createParticleEffect(file='pixieWall')
+
+ def face90(toon=toon, target=target):
+ # turn the toon so that the right side
+ # of his body faces his target
+ vec = Point3(target.getPos() - toon.getPos())
+ vec.setZ(0)
+ temp = vec[0]
+ vec.setX(-vec[1])
+ vec.setY(temp)
+ targetPoint = Point3(toon.getPos() + vec)
+ toon.headsUp(render, targetPoint)
+
+ delay = 2.5
+ mtrack = Parallel(
+ __getPartTrack(sprayEffect, 1.5, 0.5, [sprayEffect, toon, 0]),
+ __getPartTrack(dropEffect, 1.9, 2., [dropEffect, target, 0]),
+ __getPartTrack(explodeEffect, 2.7, 1.0, [explodeEffect, toon, 0]),
+ __getPartTrack(poofEffect, 3.4, 1.0, [poofEffect, target, 0]),
+ __getPartTrack(wallEffect, 4.05, 1.2, [wallEffect, toon, 0]),
+ __getSoundTrack(level, 2, duration=4.1, node=toon),
+ Sequence(Func(face90),
+ ActorInterval(toon, 'sprinkle-dust')),
+ Sequence(Wait(delay),
+ Func(__healToon, target, hp, ineffective, hasInteractivePropHealBonus)),
+ )
+ track.append(mtrack)
+ track.append(__returnToBase(heal))
+ track.append(Func(target.clearChat))
+ return track
+
+def __healJuggle(heal, hasInteractivePropHealBonus):
+ """ __healJuggle(heal)
+ """
+ # Determine if this is an NPC heal
+ #print("heal Juggle Anim")
+ npcId = 0
+ if (heal.has_key('npcId')):
+ npcId = heal['npcId']
+ toon = NPCToons.createLocalNPC(npcId)
+ if (toon == None):
+ return None
+ else:
+ toon = heal['toon']
+ targets = heal['target']
+ ineffective = heal['sidestep']
+ level = heal['level']
+
+ # Make a 'sandwich' around the track specific interval
+ if (npcId != 0):
+ track = Sequence(MovieNPCSOS.teleportIn(heal, toon))
+ else:
+ track = Sequence(__runToHealSpot(heal))
+ delay = 4.0
+ first = 1
+ targetTrack = Sequence()
+ for target in targets:
+ targetToon = target['toon']
+ #hp = min(targetToon.hp + target['hp'], targetToon.maxHp)
+ hp = target['hp']
+ reactIval = Func(__healToon, targetToon, hp, ineffective, hasInteractivePropHealBonus)
+ if (first == 1):
+ targetTrack.append(Wait(delay))
+ first = 0
+
+ targetTrack.append(reactIval)
+
+ cube = globalPropPool.getProp('cubes')
+ cube2 = MovieUtil.copyProp(cube)
+ cubes = [cube, cube2]
+ hips = [toon.getLOD(toon.getLODNames()[0]).find("**/joint_hips"),
+ toon.getLOD(toon.getLODNames()[1]).find("**/joint_hips"),
+ ]
+ cubeTrack = Sequence(
+ Func(MovieUtil.showProps, cubes, hips),
+ MovieUtil.getActorIntervals(cubes, 'cubes'),
+ Func(MovieUtil.removeProps, cubes),
+ )
+
+ mtrack = Parallel(cubeTrack,
+ __getSoundTrack(level, 0.7, duration=7.7, node=toon),
+ ActorInterval(toon, 'juggle'),
+ targetTrack)
+ track.append(mtrack)
+ if (npcId != 0):
+ track.append(MovieNPCSOS.teleportOut(heal, toon))
+ else:
+ track.append(__returnToBase(heal))
+ for target in targets:
+ targetToon = target['toon']
+ track.append(Func(targetToon.clearChat))
+ return track
+
+def __healDive(heal, hasInteractivePropHealBonus):
+ """ __healJuggle(heal)
+ """
+ # Determine if this is an NPC heal
+ #print("heal Dive Anim")
+ # Splash object for when toon hits the water
+ splash = Splash.Splash(render) #remember to destroy
+ splash.reparentTo(render)
+ #import pdb; pdb.set_trace()
+ npcId = 0
+ if (heal.has_key('npcId')):
+ npcId = heal['npcId']
+ toon = NPCToons.createLocalNPC(npcId)
+ if (toon == None):
+ return None
+ else:
+ toon = heal['toon']
+ targets = heal['target']
+ ineffective = heal['sidestep']
+ level = heal['level']
+
+ #print("toonScale %s" % (toon.getBodyScale()))
+
+ # Make a 'sandwich' around the track specific interval
+ if (npcId != 0):
+ track = Sequence(MovieNPCSOS.teleportIn(heal, toon))
+ else:
+ track = Sequence(__runToHealSpot(heal))
+ delay = 7.0
+ first = 1
+ targetTrack = Sequence()
+ for target in targets:
+ targetToon = target['toon']
+ #hp = min(targetToon.hp + target['hp'], targetToon.maxHp)
+ hp = target['hp']
+ reactIval = Func(__healToon, targetToon, hp, ineffective, hasInteractivePropHealBonus)
+ if (first == 1):
+ targetTrack.append(Wait(delay))
+ first = 0
+
+ targetTrack.append(reactIval)
+
+ thisBattle = heal['battle']
+ toonsInBattle = thisBattle.toons
+
+ glass = globalPropPool.getProp('glass')
+ glass.setScale(4.0)
+ glass.setHpr(0.0, 90.0, 0.0)
+ ladder = globalPropPool.getProp('ladder')#MovieUtil.copyProp(cube)
+ #placeNode = MovieUtil.copyProp(glass)
+ placeNode = NodePath("lookNode")
+ diveProps = [glass, ladder]#, placeNode]
+ ladderScale = (toon.getBodyScale() / 0.66)
+ scaleUpPoint = Point3(.50, .5 , .45) * ladderScale
+ basePos = toon.getPos()
+
+ glassOffset = Point3(0,1.1,0.2)
+ glassToonOffset = Point3(0,1.2,0.2)
+ splashOffset = Point3(0,1.0,0.4)
+ ladderOffset = Point3(0,4,0)
+ ladderToonSep = Point3(0,1,0) * ladderScale
+ diveOffset = Point3(0,0,10)
+ divePos = add3(add3(ladderOffset, diveOffset), ladderToonSep)
+ ladder.setH(toon.getH())
+ glassPos = render.getRelativePoint(toon, glassOffset)#add3(basePos, glassOffset)
+ glassToonPos = render.getRelativePoint(toon, glassToonOffset)
+ ladderPos = render.getRelativePoint(toon, ladderOffset)
+ climbladderPos = render.getRelativePoint(toon, add3(ladderOffset, ladderToonSep))#add3(basePos, ladderOffset)
+ divePos = render.getRelativePoint(toon, divePos)
+ topDivePos = render.getRelativePoint(toon, diveOffset)
+
+ lookBase = render.getRelativePoint(toon, ladderOffset)
+ lookTop = render.getRelativePoint(toon, add3(ladderOffset, diveOffset))
+ LookGlass = render.getRelativePoint(toon, glassOffset)
+
+ splash.setPos(splashOffset)
+
+ walkToLadderTime = 1.0
+ climbTime = 5.0
+ diveTime = 1.0
+ ladderGrowTime = 1.5
+ splash.setPos(glassPos)
+ toonNode = toon.getGeomNode()
+ #nameTagNode = NodePath(toon.nametag.getNametag3d())
+ #nameTagNode = NodePath(toon.getHeadParts()[0])
+ #placeNode = NodePath("lookNode")
+
+ placeNode.reparentTo(render)
+ placeNode.setScale(5.0)
+ #placeNode.attachNewNode("lookNode")
+ placeNode.setPos(toon.getPos(render))
+ placeNode.setHpr(toon.getHpr(render))
+
+ toonscale = toonNode.getScale()
+ toonFacing = toon.getHpr()
+
+ #for someToon in toonsInBattle:
+ # someToon.startStareAt(nameTagNode, Point3(0,0,3))
+ #toonsLook(toonsInBattle, placeNode, Point3(0,0,3))
+
+
+ propTrack = Sequence(
+ #Func(MovieUtil.showProps, cubes, hips),
+ Func(MovieUtil.showProp, glass, render, glassPos),
+ Func(MovieUtil.showProp, ladder, render, ladderPos),
+ Func(toonsLook, toonsInBattle, placeNode, Point3(0,0,0)),
+ Func(placeNode.setPos, lookBase),
+ LerpScaleInterval(ladder, ladderGrowTime, scaleUpPoint,
+ startScale=MovieUtil.PNT3_NEARZERO),
+ Func(placeNode.setPos, lookTop),
+
+ Wait(2.1),
+ MovieCamera.toonGroupHighShot(None, 0),
+ Wait(2.1),
+ Func(placeNode.setPos, LookGlass),
+ Wait(0.4),
+ MovieCamera.allGroupLowShot(None, 0),
+ Wait(1.8),
+
+
+ LerpScaleInterval(ladder, ladderGrowTime, MovieUtil.PNT3_NEARZERO,
+ startScale=scaleUpPoint),
+ Func(MovieUtil.removeProps, diveProps),
+ #Func(MovieUtil.removeProps, placeNode),
+ )
+
+ mtrack = Parallel(propTrack,
+ __getSoundTrack(level, 0.6, duration=9.0, node=toon),
+ Sequence(
+ Parallel(
+ Sequence(
+ ActorInterval(toon, 'walk', loop = 0, duration = walkToLadderTime),
+ ActorInterval(toon, 'neutral', loop = 0, duration = 0.1),
+ ),
+ LerpPosInterval(toon, walkToLadderTime, climbladderPos),
+ Wait(ladderGrowTime),
+ ),
+ Parallel(
+ ActorInterval(toon, 'climb', loop = 0, endFrame = 116),
+ Sequence(
+ Wait(4.6),
+ #LerpScaleInterval(toon, diveTime*0.1, 0.1),
+ #Func(toon.doToonAlphaColorScale, VBase4(1, 0.0, 1, 0.0), 0.5),
+ Func(toonNode.setTransparency, 1),
+
+ LerpColorScaleInterval(toonNode, 0.25, VBase4(1, 1.0, 1, 0.0), blendType = 'easeInOut'),
+ LerpScaleInterval(toonNode, 0.01, 0.1, startScale = toonscale),
+
+
+ LerpHprInterval(toon, 0.01, toonFacing),
+ LerpPosInterval(toon, 0.0, glassToonPos),
+ Func(toonNode.clearTransparency),
+ Func(toonNode.clearColorScale),
+ Parallel(
+ ActorInterval(toon, 'swim', loop = 1, startTime = 0.0, endTime = 1.00),
+ Wait(1.0),
+ ),
+ #
+ ),
+ Sequence(
+ Wait(4.6),
+ Func(splash.play),
+ Wait(1.0),
+ Func(splash.destroy),
+ ),
+ ),
+ #ActorInterval(toon, 'walk', loop = 1, duration=walkToLadderTime),
+ #ActorInterval(toon, 'swim', loop = 1, duration=climbTime),
+ #ActorInterval(toon, 'swim', loop = 1, duration=diveTime*1.0),
+ #LerpScaleInterval(toon, diveTime*0.1, 0.1),
+
+ Wait(0.5),
+ Parallel(
+ #LerpHprInterval(toon, 0.1, Point3(0,0,0)),
+ ActorInterval(toon, 'jump', loop = 0, startTime = 0.2),
+ LerpScaleInterval(toonNode, 0.5, toonscale, startScale = 0.1),
+ Func(stopLook, toonsInBattle),
+ )
+ ),
+ targetTrack)
+ track.append(mtrack)
+ if (npcId != 0):
+ track.append(MovieNPCSOS.teleportOut(heal, toon))
+ else:
+ track.append(__returnToBase(heal))
+ for target in targets:
+ targetToon = target['toon']
+ track.append(Func(targetToon.clearChat))
+ return track
+
+def add3(t1, t2):
+ returnThree = Point3(t1[0]+t2[0], t1[1]+t2[1], t1[2]+t2[2])
+ return returnThree
+
+def stopLook(toonsInBattle):
+ for someToon in toonsInBattle:
+ someToon.stopStareAt()
+
+def toonsLook(toons, someNode, offset):
+ #print("toonsLook")
+ for someToon in toons:
+ #someToon.stopStareAt()
+ someToon.startStareAt(someNode, offset)
diff --git a/toontown/src/battle/MovieLure.py b/toontown/src/battle/MovieLure.py
new file mode 100644
index 0000000..002b6c6
--- /dev/null
+++ b/toontown/src/battle/MovieLure.py
@@ -0,0 +1,983 @@
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from toontown.suit.SuitBase import *
+from toontown.toon.ToonDNA import *
+from BattleSounds import *
+
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+import MovieUtil
+from toontown.toonbase import ToontownBattleGlobals
+import BattleParticles
+import BattleProps
+import MovieNPCSOS
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieLures')
+
+def safeWrtReparentTo(nodePath, parent):
+ if nodePath and not nodePath.isEmpty():
+ nodePath.wrtReparentTo(parent)
+
+def doLures(lures):
+ """ doLures(lures)
+ Lures occur in the following order:
+ 1) level 1 lures one at a time, from right to left
+ 2) level 2 lures one at a time, from right to left
+ etc.
+ """
+ if (len(lures) == 0):
+ return (None, None)
+
+ npcArrivals, npcDepartures, npcs = MovieNPCSOS.doNPCTeleports(lures)
+
+ mtrack = Parallel()
+ for l in lures:
+ ival = __doLureLevel(l, npcs)
+ if (ival):
+ mtrack.append(ival)
+
+ lureTrack = Sequence(npcArrivals, mtrack, npcDepartures)
+
+ camDuration = mtrack.getDuration()
+ enterDuration = npcArrivals.getDuration()
+ exitDuration = npcDepartures.getDuration()
+ camTrack = MovieCamera.chooseLureShot(lures, camDuration, enterDuration,
+ exitDuration)
+ return (lureTrack, camTrack)
+
+def __doLureLevel(lure, npcs):
+ """ __doLureLevel(lure)
+ """
+ level = lure['level']
+ if (level == 0):
+ return __lureOneDollar(lure)
+ elif (level == 1):
+ return __lureSmallMagnet(lure)
+ elif (level == 2):
+ return __lureFiveDollar(lure)
+ elif (level == 3):
+ return __lureLargeMagnet(lure)
+ elif (level == 4):
+ return __lureTenDollar(lure)
+ elif (level == 5):
+ return __lureHypnotize(lure, npcs)
+ elif (level == 6):
+ return __lureSlideshow(lure, npcs) #UBER
+ return None
+
+def getSoundTrack(fileName, delay=0.01, duration=None, node=None):
+ """ This functions returns the standard sound track, which involves one sound
+ effect with a possible delay beforehand and an optional duration specification.
+ """
+
+ soundEffect = globalBattleSoundCache.getSound(fileName)
+
+ if duration:
+ return Sequence(Wait(delay),
+ SoundInterval(soundEffect, duration=duration, node=node))
+ else:
+ return Sequence(Wait(delay),
+ SoundInterval(soundEffect, node=node))
+
+
+def __createFishingPoleMultiTrack(lure, dollar, dollarName):
+ toon = lure['toon']
+ target = lure['target']
+ battle = lure['battle']
+ sidestep = lure['sidestep']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ suit = target['suit']
+ targetPos = suit.getPos(battle)
+ died = target['died']
+ revived = target['revived']
+ reachAnimDuration = 3.5
+ trapProp = suit.battleTrapProp
+ pole = globalPropPool.getProp('fishing-pole')
+ pole2 = MovieUtil.copyProp(pole)
+ poles = [pole, pole2]
+ hands = toon.getRightHands()
+ def positionDollar(dollar, suit):
+ dollar.reparentTo(suit)
+ dollar.setPos(0, MovieUtil.SUIT_LURE_DOLLAR_DISTANCE, 0)
+
+ dollarTrack = Sequence(
+ Func(positionDollar, dollar, suit),
+ Func(dollar.wrtReparentTo, battle),
+ ActorInterval(dollar, dollarName, duration=3),
+ # Slowly lower dollar in front of the suit
+ getSplicedLerpAnimsTrack(dollar, dollarName, 0.7, 2.0, startTime=3),
+ # Quickly rip the dollar away before the suit has a change to grab it
+ LerpPosInterval(dollar, 0.2, Point3(0, -10, 7)),
+ Func(MovieUtil.removeProp, dollar),
+ )
+
+ poleTrack = Sequence(
+ Func(MovieUtil.showProps, poles, hands),
+ ActorInterval(pole, 'fishing-pole'),
+ Func(MovieUtil.removeProps, poles),
+ )
+
+ toonTrack = Sequence(
+ Func(toon.headsUp, battle, targetPos), # Face toon
+ ActorInterval(toon, 'battlecast'), # perform the casting animation
+ Func(toon.loop, 'neutral'), # Face toon
+ )
+
+ tracks = Parallel(dollarTrack, poleTrack, toonTrack)
+ # See if lure succeeds
+ if (sidestep == 0):
+ # See if suit animates to this particular lure (or another one that
+ # also succeeds on the same suit)
+ if (kbbonus == 1 or hp > 0):
+ suitTrack = Sequence()
+ opos, ohpr = battle.getActorPosHpr(suit)
+ # The suit travels during the reach animation, so we must renew
+ # its position to where it ends up after reaching
+ reachDist = MovieUtil.SUIT_LURE_DISTANCE
+ reachPos = Point3(opos[0], opos[1]-reachDist, opos[2])
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ # Wait before reaching for the dollar
+ suitTrack.append(Wait(3.5))
+
+ # Special case, if the suit is "big and tall" (type C and not
+ # 'mm' or 'sc'), then the suit appears to travel more during the
+ # reach animation (3.5 instead of 2.6). Therefore, we must
+ # actually push these suits back slightly as they move forward to
+ # make sure they get to the correct spot but still look right
+ # doing so
+ suitName = suit.getStyleName()
+ retardPos, retardHpr = battle.getActorPosHpr(suit)
+ retardPos.setY(retardPos.getY() + MovieUtil.SUIT_EXTRA_REACH_DISTANCE)
+
+ if (suitName in MovieUtil.largeSuits):
+ moveTrack = lerpSuit(suit, 0.0, reachAnimDuration/2.5, retardPos,
+ battle, trapProp)
+ reachTrack = ActorInterval(suit, 'reach',
+ duration=reachAnimDuration)# Reach for the dollar
+ suitTrack.append(Parallel(moveTrack, reachTrack))
+ else: # Otherwise just do the reach animation with no
+ # artificial movement
+ suitTrack.append(ActorInterval(suit, 'reach',
+ duration=reachAnimDuration)) # Reach for the dollar
+
+ # Be sure to reparent the trapProp properly away from the
+ # suit during movement and then back to the suit once
+ # completed
+ if trapProp:
+ suitTrack.append(Func(trapProp.wrtReparentTo, battle))
+ suitTrack.append(Func(suit.setPos, battle, reachPos))
+ if trapProp:
+ suitTrack.append(Func(trapProp.wrtReparentTo, suit))
+ suit.battleTrapProp = trapProp
+
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ suitTrack.append(Func(battle.lureSuit, suit))
+
+ if (hp > 0):
+ suitTrack.append(__createSuitDamageTrack(battle, suit, hp, lure, trapProp))
+
+ if (revived != 0):
+ suitTrack.append(MovieUtil.createSuitReviveTrack(suit, toon, battle))
+ if (died != 0):
+ suitTrack.append(MovieUtil.createSuitDeathTrack(suit, toon, battle))
+ tracks.append(suitTrack)
+
+ else: # If the lure fails
+ tracks.append(Sequence(Wait(3.7),
+ Func(MovieUtil.indicateMissed, suit)))
+ tracks.append(getSoundTrack('TL_fishing_pole.mp3', delay=0.5, node=toon))
+
+ return tracks
+
+def __createMagnetMultiTrack(lure, magnet, pos, hpr, scale, isSmallMagnet=1):
+ toon = lure['toon']
+ battle = lure['battle']
+ sidestep = lure['sidestep']
+ targets = lure['target']
+ tracks = Parallel()
+
+ # Create a track for the toon
+ tracks.append(Sequence(
+ ActorInterval(toon, 'hold-magnet'),
+ Func(toon.loop, 'neutral'),
+ ))
+
+ # Create a track for the magnet
+ hands = toon.getLeftHands()
+ magnet2 = MovieUtil.copyProp(magnet)
+ magnets = [magnet, magnet2]
+ magnetTrack = Sequence(
+ Wait(0.7), # Wait before showing the magnet
+ Func(MovieUtil.showProps, magnets,
+ hands, pos, hpr, scale),
+ Wait(6.3), # Wait while magnet is being used
+ Func(MovieUtil.removeProps, magnets))
+ tracks.append(magnetTrack)
+
+ for target in targets:
+ suit = target['suit']
+ trapProp = suit.battleTrapProp
+ # See if lure succeeds
+ if (sidestep == 0):
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ # See if suit animates to this particular lure (or another one that
+ # also succeeds on the same suit)
+ if (kbbonus == 1 or hp > 0):
+ suitDelay = 2.6
+ suitMoveDuration = 0.8
+ suitTrack = Sequence()
+ opos, ohpr = battle.getActorPosHpr(suit)
+ reachDist = MovieUtil.SUIT_LURE_DISTANCE
+ reachPos = Point3(opos[0], opos[1]-reachDist, opos[2])
+ numShakes = 3
+ shakeTotalDuration = 0.8
+ shakeDuration = shakeTotalDuration / float(numShakes)
+
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ suitTrack.append(Wait(suitDelay))
+ suitTrack.append(ActorInterval(suit, 'landing', startTime=2.37,
+ endTime=1.82))
+ for i in range(0, numShakes):
+ suitTrack.append(ActorInterval(
+ suit, 'landing', startTime=1.82, endTime=1.16,
+ duration=shakeDuration))
+
+ suitTrack.append(ActorInterval(
+ suit, 'landing', startTime=1.16, endTime=0.7))
+ suitTrack.append(ActorInterval(
+ suit, 'landing', startTime=0.7, duration=1.3))
+
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ suitTrack.append(Func(battle.lureSuit, suit))
+ if (hp > 0):
+ suitTrack.append(__createSuitDamageTrack(battle, suit,
+ hp, lure, trapProp))
+ if (revived != 0):
+ suitTrack.append(MovieUtil.createSuitReviveTrack(suit, toon, battle))
+ elif (died != 0):
+ suitTrack.append(MovieUtil.createSuitDeathTrack(suit, toon, battle))
+
+ tracks.append(suitTrack)
+ # Must lerp the suit during the magnet animation
+ tracks.append(lerpSuit(suit, suitDelay+0.55+shakeTotalDuration,
+ suitMoveDuration, reachPos, battle, trapProp))
+ else: # If lure fails
+ tracks.append(Sequence(Wait(3.7),
+ Func(MovieUtil.indicateMissed, suit)))
+
+ if (isSmallMagnet == 1): # using the small magnet
+ tracks.append(getSoundTrack('TL_small_magnet.mp3', delay=0.7, node=toon))
+ else:
+ tracks.append(getSoundTrack('TL_large_magnet.mp3', delay=0.7, node=toon))
+
+ return tracks
+
+def __createHypnoGogglesMultiTrack(lure, npcs = []):
+ toon = lure['toon']
+ if (lure.has_key('npc')):
+ toon = lure['npc']
+ targets = lure['target']
+ battle = lure['battle']
+ sidestep = lure['sidestep']
+ goggles = globalPropPool.getProp('hypno-goggles')
+ goggles2 = MovieUtil.copyProp(goggles)
+ bothGoggles = [goggles, goggles2]
+ pos = Point3(-1.03, 1.04, -0.30)
+ hpr = Point3(-96.55, 36.14, -170.59)
+ scale = Point3(1.5, 1.5, 1.5)
+ hands = toon.getLeftHands()
+
+ gogglesTrack = Sequence(
+ Wait(0.6), # Wait a bit to appear
+ Func(MovieUtil.showProps, bothGoggles,
+ hands, pos, hpr, scale),
+ ActorInterval(goggles, 'hypno-goggles', duration=2.2),
+ Func(MovieUtil.removeProps, bothGoggles),
+ )
+
+ toonTrack = Sequence(
+ ActorInterval(toon, 'hypnotize'),
+ Func(toon.loop, 'neutral'),
+ )
+ tracks = Parallel(gogglesTrack, toonTrack)
+
+ #print "hypno!!!!"
+ #print targets
+
+ for target in targets:
+ suit = target['suit']
+ trapProp = suit.battleTrapProp
+ # See if lure succeeds
+ if (sidestep == 0):
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ # See if suit animates to this particular lure (or another one that
+ # also succeeds on the same suit)
+ if (kbbonus == 1 or hp > 0):
+ suitTrack = Sequence()
+ suitDelay = 1.6
+ suitAnimDuration = 1.5
+ opos, ohpr = battle.getActorPosHpr(suit)
+ reachDist = MovieUtil.SUIT_LURE_DISTANCE
+ reachPos = Point3(opos[0], opos[1]-reachDist, opos[2])
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ # Wait before being hypnotized
+ suitTrack.append(Wait(suitDelay))
+ suitTrack.append(ActorInterval(suit, 'hypnotized', duration=3.1))
+ suitTrack.append(Func(suit.setPos, battle, reachPos))
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ suitTrack.append(Func(battle.lureSuit, suit))
+ if (hp > 0):
+ suitTrack.append(__createSuitDamageTrack(battle, suit,
+ hp, lure, trapProp))
+ if (revived != 0):
+ suitTrack.append(MovieUtil.createSuitReviveTrack(suit, toon, battle, npcs))
+ elif (died != 0):
+ suitTrack.append(MovieUtil.createSuitDeathTrack(suit, toon, battle, npcs))
+
+ tracks.append(suitTrack)
+ # Must lerp the suit during the hypnotize animation
+ tracks.append(lerpSuit(suit, suitDelay+1.7, 0.7, reachPos,
+ battle, trapProp))
+ else: # If lure fails
+ tracks.append(Sequence(Wait(2.3),
+ Func(MovieUtil.indicateMissed, suit, 1.1)))
+ tracks.append(getSoundTrack('TL_hypnotize.mp3', delay=0.5, node=toon))
+
+
+ return tracks
+
+def __lureOneDollar(lure):
+ """ __lureOneDollar(lure)
+ """
+ dollarProp = '1dollar'
+ dollar = globalPropPool.getProp(dollarProp)
+ return __createFishingPoleMultiTrack(lure, dollar, dollarProp)
+
+def __lureSmallMagnet(lure):
+ """ __lureSmallMagnet(lure)
+ """
+ magnet = globalPropPool.getProp('small-magnet')
+ pos = Point3(-0.27, 0.19, 0.29)
+ hpr = Point3(-90.0, 84.17, -180.0)
+ scale = Point3(0.85, 0.85, 0.85)
+ return __createMagnetMultiTrack(lure, magnet, pos, hpr, scale, isSmallMagnet=1)
+
+def __lureFiveDollar(lure):
+ """ __lureFiveDollar(lure)
+ """
+ dollarProp = '5dollar'
+ dollar = globalPropPool.getProp(dollarProp)
+ return __createFishingPoleMultiTrack(lure, dollar, dollarProp)
+
+def __lureLargeMagnet(lure):
+ """ __lureLargeMagnet(lure)
+ """
+ magnet = globalPropPool.getProp('big-magnet')
+ pos = Point3(-0.27, 0.08, 0.29)
+ hpr = Point3(-90.0, 84.17, -180)
+ scale = Point3(1.32, 1.32, 1.32)
+ return __createMagnetMultiTrack(lure, magnet, pos, hpr, scale, isSmallMagnet=0)
+
+def __lureTenDollar(lure):
+ """ __lureTenDollar(lure)
+ """
+ dollarProp = '10dollar'
+ dollar = globalPropPool.getProp(dollarProp)
+ return __createFishingPoleMultiTrack(lure, dollar, dollarProp)
+
+def __lureHypnotize(lure, npcs = []):
+ """ __lureHypnotize(lure)
+ """
+ return __createHypnoGogglesMultiTrack(lure, npcs)
+
+
+def __lureSlideshow(lure, npcs):
+ """ __lureSlideshow(lure, npcs)
+ """
+ return __createSlideshowMultiTrack(lure, npcs)
+
+
+def __createSuitDamageTrack(battle, suit, hp, lure, trapProp):
+ """ __createSuitDamageIvals(suit, hp, battle)
+ """
+ # This function creates intervals for the suit taking damage
+ # If trapProp doesn't exist, do nothing, with no reaction
+ #import pdb; pdb.set_trace()
+ if ((trapProp == None) or trapProp.isEmpty()):
+ return Func(suit.loop, 'neutral')
+
+ # Make sure to leave the trapProp in place (not parented to the suit)
+ # while the suit performs its animation
+ trapProp.wrtReparentTo(battle)
+
+ # Now we learn which level trap we're using to use the appropriate suit reaction
+ trapTrack = ToontownBattleGlobals.TRAP_TRACK
+ trapLevel = suit.battleTrap
+ trapTrackNames = ToontownBattleGlobals.AvProps[trapTrack]
+ trapName = trapTrackNames[trapLevel]
+
+ result = Sequence()
+ def reparentTrap(trapProp=trapProp, battle=battle):
+ if trapProp and not trapProp.isEmpty():
+ trapProp.wrtReparentTo(battle)
+ result.append(Func(reparentTrap))
+
+ # Now detect if our trap was just thrown in this round, or leftover from before.
+ # If just thrown, our cordinates for the banana, marbles, and tnt will be based off of
+ # render, not battle, thus we make parent equal to render instead of the default battle.
+ # And if the trap is fresh and is the quicksand, trapdoor, or rake, we create a hidden
+ # dummy trapProp in the right position to obtain the correct coordinates for those
+ # animations.
+ parent = battle
+ if (suit.battleTrapIsFresh == 1):
+ if (trapName == 'quicksand' or trapName == 'trapdoor'):
+ trapProp.hide()
+ trapProp.reparentTo(suit)
+ trapProp.setPos(Point3(0, MovieUtil.SUIT_TRAP_DISTANCE, 0))
+ trapProp.setHpr(Point3(0, 0, 0))
+ trapProp.wrtReparentTo(battle)
+ elif (trapName == 'rake'):
+ trapProp.hide()
+ trapProp.reparentTo(suit)
+ trapProp.setPos(0, MovieUtil.SUIT_TRAP_RAKE_DISTANCE, 0)
+ # reorient the rake correctly to be stepped on
+ trapProp.setHpr(Point3(0, 270, 0))
+ trapProp.setScale(Point3(0.7, 0.7, 0.7))
+ # The rake must be a specific distance from each suit for that
+ # suit to be able to walk into the rake
+ rakeOffset = MovieUtil.getSuitRakeOffset(suit)
+ trapProp.setY(trapProp.getY() + rakeOffset)
+ else:
+ parent = render
+
+ # Now decide which trap this is and use the different suit animation reactions
+ if (trapName == 'banana'):
+ slidePos = trapProp.getPos(parent)
+ slidePos.setY(slidePos.getY() - 5.1)
+ moveTrack = Sequence(
+ Wait(0.1), # Wait before sliding the peel
+ LerpPosInterval(trapProp, 0.1, slidePos, other=battle),
+ )
+ animTrack = Sequence(
+ ActorInterval(trapProp, 'banana', startTime=3.1), # Animate banana spinning
+ Wait(1.1), # Wait a bit before scaling banana away
+ LerpScaleInterval(trapProp, 1, Point3(0.01, 0.01, 0.01)),
+ )
+ suitTrack = ActorInterval(suit, 'slip-backward')
+ damageTrack = Sequence(
+ Wait(0.5),
+ Func(suit.showHpText, -hp, openEnded=0),
+ Func(suit.updateHealthBar, hp),
+ )
+ soundTrack = Sequence(
+ SoundInterval(globalBattleSoundCache.getSound('AA_pie_throw_only.mp3'), duration=0.55, node=suit),
+ SoundInterval(globalBattleSoundCache.getSound('Toon_bodyfall_synergy.mp3'), node=suit),
+ )
+ result.append(Parallel(moveTrack, animTrack, suitTrack, damageTrack, soundTrack))
+ elif ((trapName == 'rake') or (trapName == 'rake-react')):
+ # Also need to make the rake pop up when stepped on, then fall back down
+ hpr = trapProp.getHpr(parent)
+ upHpr = Vec3(hpr[0], 179.9999, hpr[2])
+ bounce1Hpr = Vec3(hpr[0], 120, hpr[2])
+ bounce2Hpr = Vec3(hpr[0], 100, hpr[2])
+
+ rakeTrack = Sequence(
+ Wait(0.5),
+ LerpHprInterval(trapProp, 0.1, upHpr, startHpr=hpr),
+ Wait(0.7),
+ LerpHprInterval(trapProp, 0.4, hpr, startHpr=upHpr),
+ LerpHprInterval(trapProp, 0.15, bounce1Hpr, startHpr=hpr),
+ LerpHprInterval(trapProp, 0.05, hpr, startHpr=bounce1Hpr),
+ LerpHprInterval(trapProp, 0.15, bounce2Hpr, startHpr=hpr),
+ LerpHprInterval(trapProp, 0.05, hpr, startHpr=bounce2Hpr),
+ Wait(0.2),
+ LerpScaleInterval(trapProp, 0.2, Point3(0.01, 0.01, 0.01)),
+ )
+ rakeAnimDuration = 3.125
+ suitTrack = ActorInterval(suit, 'rake-react',
+ duration=rakeAnimDuration)
+ damageTrack = Sequence(
+ Wait(0.5),
+ Func(suit.showHpText, -hp, openEnded=0),
+ Func(suit.updateHealthBar, hp),
+ )
+ soundTrack = getSoundTrack('TL_step_on_rake.mp3', delay=0.6, node=suit)
+ result.append(Parallel(rakeTrack, suitTrack, damageTrack, soundTrack))
+ elif (trapName == 'marbles'):
+ slidePos = trapProp.getPos(parent)
+ slidePos.setY(slidePos.getY() - 6.5)
+ moveTrack = Sequence(
+ Wait(0.1), # Wait a bit before sliding the marbles
+ LerpPosInterval(trapProp, 0.8, slidePos, other=battle),
+ Wait(1.1),
+ LerpScaleInterval(trapProp, 1, Point3(0.01, 0.01, 0.01)),
+ )
+ animTrack = ActorInterval(trapProp, 'marbles', startTime=3.1)
+ suitTrack = ActorInterval(suit, 'slip-backward')
+ damageTrack = Sequence(
+ Wait(0.5),
+ Func(suit.showHpText, -hp, openEnded=0),
+ Func(suit.updateHealthBar, hp),
+ )
+ soundTrack = Sequence(
+ SoundInterval(globalBattleSoundCache.getSound('AA_pie_throw_only.mp3'), duration=0.55, node=suit),
+ SoundInterval(globalBattleSoundCache.getSound('Toon_bodyfall_synergy.mp3'), node=suit),
+ )
+ result.append(Parallel(moveTrack, animTrack, suitTrack, damageTrack, soundTrack))
+ elif (trapName == 'quicksand'):
+ sinkPos1 = trapProp.getPos(battle)
+ sinkPos2 = trapProp.getPos(battle)
+ dropPos = trapProp.getPos(battle) # Where suit drops from after the quicksand
+ landPos = trapProp.getPos(battle) # Where the suit lands from the drop
+ sinkPos1.setZ(sinkPos1.getZ() - 3.1)
+ sinkPos2.setZ(sinkPos2.getZ() - 9.1)
+ dropPos.setZ(dropPos.getZ() + 15)
+
+ # We grab the name tag so we can hide it while the suit is in the quicksand
+ nameTag = suit.find("**/joint_nameTag")
+ trapTrack = Sequence(
+ Wait(2.4),
+ LerpScaleInterval(trapProp, 0.8, Point3(0.01, 0.01, 0.01)),
+ )
+ moveTrack = Sequence(
+ Wait(0.9), # Wait before starting to sink
+ LerpPosInterval(suit, 0.9, sinkPos1, other=battle), # Start to sink a bit
+ LerpPosInterval(suit, 0.4, sinkPos2, other=battle), # Fall in quicksand
+ Func(suit.setPos, battle, dropPos),
+ Func(suit.wrtReparentTo, hidden),
+ Wait(1.1), # Wait while stuck in the quicksand
+ Func(suit.wrtReparentTo, battle),
+ LerpPosInterval(suit, 0.3, landPos, other=battle), # Drop back down
+ )
+ animTrack = Sequence(
+ ActorInterval(suit, 'flail'),
+ ActorInterval(suit, 'flail', startTime=1.1),
+ Wait(0.7),
+ ActorInterval(suit, 'slip-forward', duration=2.1),
+ )
+ damageTrack = Sequence(
+ Wait(3.5),
+ Func(suit.showHpText, -hp, openEnded=0),
+ Func(suit.updateHealthBar, hp),
+ )
+ soundTrack = Sequence(
+ Wait(0.7),
+ SoundInterval(globalBattleSoundCache.getSound('TL_quicksand.mp3'), node=suit),
+ Wait(0.1),
+ SoundInterval(globalBattleSoundCache.getSound('Toon_bodyfall_synergy.mp3'), node=suit),
+ )
+ result.append(Parallel(trapTrack, moveTrack, animTrack, damageTrack, soundTrack))
+ elif (trapName == 'trapdoor'):
+ sinkPos = trapProp.getPos(battle)
+ dropPos = trapProp.getPos(battle) # Where suit drops from after the trap door
+ landPos = trapProp.getPos(battle) # Where the suit lands from the drop
+ sinkPos.setZ(sinkPos.getZ() - 9.1)
+ dropPos.setZ(dropPos.getZ() + 15)
+
+ trapTrack = Sequence(
+ Wait(2.4),
+ LerpScaleInterval(trapProp, 0.8, Point3(0.01, 0.01, 0.01))
+ )
+ moveTrack = Sequence(
+ Wait(2.2), # Wait before falling through the trap door
+ LerpPosInterval(suit, 0.4, sinkPos, other=battle), # Fall through trap door
+ Func(suit.setPos, battle, dropPos),
+ Func(suit.wrtReparentTo, hidden),
+ Wait(1.6), # Wait while through the trap door
+ Func(suit.wrtReparentTo, battle),
+ LerpPosInterval(suit, 0.3, landPos, other=battle), # Drop back down
+ )
+
+ animTrack = Sequence(
+ # Suit quickly looks down
+ getSplicedLerpAnimsTrack(suit, 'flail', 0.7, 0.25),
+ # Spring the trap door (make it go black)
+ Func(trapProp.setColor, Vec4(0, 0, 0, 1)),
+ # Suit slowly looks back up with resignation
+ ActorInterval(suit, 'flail', startTime=0.7, endTime=0),
+ # Suspenseful wait
+ ActorInterval(suit, 'neutral', duration=0.5),
+ # Suit falls through the trap door
+ ActorInterval(suit, 'flail', startTime=1.1),
+ Wait(1.1),
+ ActorInterval(suit, 'slip-forward', duration=2.1),
+ )
+
+ damageTrack = Sequence(
+ Wait(3.5),
+ Func(suit.showHpText, -hp, openEnded=0),
+ Func(suit.updateHealthBar, hp),
+ )
+ soundTrack = Sequence(
+ Wait(0.8),
+ SoundInterval(globalBattleSoundCache.getSound('TL_trap_door.mp3'), node=suit),
+ Wait(0.8),
+ SoundInterval(globalBattleSoundCache.getSound('Toon_bodyfall_synergy.mp3'), node=suit),
+ )
+ result.append(Parallel(trapTrack, moveTrack, animTrack, damageTrack, soundTrack))
+ elif (trapName == 'tnt'):
+ tntTrack = ActorInterval(trapProp, 'tnt')
+ explosionTrack = Sequence(
+ Wait(2.3),
+ createTNTExplosionTrack(battle, trapProp=trapProp,
+ relativeTo=parent),
+ )
+ suitTrack = Sequence(
+ ActorInterval(suit, 'flail', duration=0.7),
+ ActorInterval(suit, 'flail', startTime=0.7, endTime=0.0),
+ ActorInterval(suit, 'neutral', duration=0.4),
+ ActorInterval(suit, 'flail', startTime=0.6, endTime=0.7),
+ Wait(0.4),
+ ActorInterval(suit, 'slip-forward', startTime=2.48, duration=0.1),
+ Func(battle.movie.needRestoreColor),
+ Func(suit.setColorScale, Vec4(0, 0, 0, 1)),
+ Func(trapProp.reparentTo, hidden),
+ ActorInterval(suit, 'slip-forward', startTime=2.58),
+ Func(suit.clearColorScale),
+ Func(trapProp.sparksEffect.cleanup),
+ Func(battle.movie.clearRestoreColor),
+ )
+ damageTrack = Sequence(
+ Wait(2.3),
+ Func(suit.showHpText, -hp, openEnded=0),
+ Func(suit.updateHealthBar, hp),
+ )
+ explosionSound = base.loadSfx("phase_3.5/audio/sfx/ENC_cogfall_apart.mp3")
+ soundTrack = Sequence(
+ SoundInterval(globalBattleSoundCache.getSound('TL_dynamite.mp3'), duration=2.0, node=suit),
+ SoundInterval(explosionSound, duration=0.6, node=suit),
+ )
+ result.append(Parallel(tntTrack, suitTrack, damageTrack,
+ explosionTrack, soundTrack))
+
+ elif trapName == 'traintrack':
+ trainInterval = createIncomingTrainInterval(battle, suit, hp, lure, trapProp)
+ result.append(trainInterval)
+ else:
+ notify.warning('unknown trapName: %s detected on suit: %s' % (trapName, suit))
+
+ suit.battleTrapProp = trapProp
+ assert notify.debug('adding battle.removeTrap for suit %d' % suit.doId)
+ result.append(Func(battle.removeTrap, suit, True))
+ result.append(Func(battle.unlureSuit, suit))
+ result.append(__createSuitResetPosTrack(suit, battle))
+ result.append(Func(suit.loop, 'neutral'))
+
+ if trapName == 'traintrack':
+ #a bit of a hack, when a suit is joining, the train track still stays, really make sure it goes away
+ #TODO this nukes the train tunnels shrinking, figure out a better way
+ result.append(Func(MovieUtil.removeProp, trapProp))
+
+ return result
+
+def __createSuitResetPosTrack(suit, battle):
+ resetPos, resetHpr = battle.getActorPosHpr(suit)
+ moveDist = Vec3(suit.getPos(battle) - resetPos).length()
+ moveDuration = 0.5
+ walkTrack = Sequence(
+ # First face the right direction, then walk backwards
+ Func(suit.setHpr, battle, resetHpr),
+ ActorInterval(suit, 'walk', startTime=1, duration=moveDuration, endTime=0.0001),
+ Func(suit.loop, 'neutral')
+ )
+ # Actually move the suit
+ moveTrack = LerpPosInterval(suit, moveDuration, resetPos, other=battle)
+ return Parallel(walkTrack, moveTrack)
+
+def getSplicedLerpAnimsTrack(object, animName, origDuration, newDuration,
+ startTime=0, fps=30):
+ """
+ This function returns a Sequence splicing together an animation
+ over a modified frame of time. This allows an animation to be
+ shortened or lengthened (if you can tolerate any resulting rushed
+ or choppy animation. This function increases an animation time by
+ inserting a uniform time interval before successive ActorInterval
+ calls. It decreases time by progressing the animation time forward
+ faster than real-time (basically uniformly skipping frames).
+
+ Arguments:
+ object = object to perform th animation
+ animName = name of the animation to lengthen/shorten
+ origDuration = original time duration the animation should normally play in
+ newDuration = lengthened or shortened time for the new animation
+ startTime = startTime for the animation
+ fps = usually held constant, helps determine number of actor intervals to use
+ """
+ track = Sequence()
+ addition = 0 # Addition will be added to the startTime to move animation forward
+ numIvals = origDuration * fps # Number of actor intervals to use
+ # The timeInterval is what to add before each actor interval to delay time
+ timeInterval = newDuration / numIvals
+ # The animInterval is how much the animation progresses forward each interval
+ animInterval = origDuration / numIvals
+ for i in range(0, numIvals):
+ track.append(Wait(timeInterval))
+ track.append(ActorInterval(object, animName, startTime=startTime+addition,
+ duration=animInterval))
+ addition += animInterval # Add addition to push the animation forward
+ return track
+
+
+def lerpSuit(suit, delay, duration, reachPos, battle, trapProp):
+ """ This function moves a suit, taking care to reparent a trap prop (if there)
+ so that it doesn't slide with the suit"""
+
+ track = Sequence()
+ # If trapProp does exist, reparent it to the battle
+ if trapProp:
+ # we need to use safeWrtReparentTo, the trap may disappear
+ # due to a collision with the railroad
+ track.append(Func(safeWrtReparentTo,trapProp, battle))
+
+ track.append(Wait(delay))
+ track.append(LerpPosInterval(suit, duration, reachPos, other=battle))
+
+ if trapProp:
+ if trapProp.getName() == 'traintrack':
+ notify.debug('UBERLURE MovieLure.lerpSuit deliberately not parenting trainTrack to suit')
+ else:
+ track.append(Func(safeWrtReparentTo,trapProp, suit))
+ suit.battleTrapProp = trapProp
+
+ return track
+
+def createTNTExplosionTrack(parent, explosionPoint=None, trapProp=None, relativeTo=render):
+ explosionTrack = Sequence()
+ explosion = BattleProps.globalPropPool.getProp('kapow')
+ explosion.setBillboardPointEye()
+ if not explosionPoint:
+ if trapProp:
+ explosionPoint = trapProp.getPos(relativeTo)
+ explosionPoint.setZ(explosionPoint.getZ() + 2.3)
+ else:
+ explosionPoint = Point3(0, 3.6, 2.1)
+ explosionTrack.append(Func(explosion.reparentTo, parent))
+ explosionTrack.append(Func(explosion.setPos, explosionPoint))
+ explosionTrack.append(Func(explosion.setScale, 0.11))
+ explosionTrack.append(ActorInterval(explosion, 'kapow'))
+ explosionTrack.append(Func(MovieUtil.removeProp, explosion))
+ return explosionTrack
+
+
+TRAIN_STARTING_X = -7.131 #-7.13081 #feet empirically determined to be tunnel distance
+TRAIN_TUNNEL_END_X = 7.1 #7.09926
+TRAIN_TRAVEL_DISTANCE = 45 #feet
+TRAIN_SPEED = 35.0 #feet per second
+TRAIN_DURATION = TRAIN_TRAVEL_DISTANCE / TRAIN_SPEED #feet
+TRAIN_MATERIALIZE_TIME = 3 # seconds
+TOTAL_TRAIN_TIME = TRAIN_DURATION + TRAIN_MATERIALIZE_TIME
+
+def createSuitReactionToTrain(battle, suit,hp,lure,trapProp):
+ toon = lure['toon']
+ retval = Sequence()
+ suitPos, suitHpr = battle.getActorPosHpr(suit)
+ distance = suitPos.getX() - TRAIN_STARTING_X
+ timeToGetHit = distance / TRAIN_SPEED
+
+ suitTrack = Sequence()
+ showDamage = Func(suit.showHpText, -hp, openEnded=0)
+ updateHealthBar = Func(suit.updateHealthBar, hp)
+ anim = 'flatten'
+ suitReact = ActorInterval(suit, anim)
+ cogGettingHit = getSoundTrack('TL_train_cog.mp3', node = toon)
+
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ suitTrack.append(Wait(timeToGetHit + TRAIN_MATERIALIZE_TIME))
+ suitTrack.append(updateHealthBar)
+ suitTrack.append(Parallel(suitReact,cogGettingHit))
+ suitTrack.append(showDamage)
+
+
+ curDuration = suitTrack.getDuration()
+
+ timeTillEnd = TOTAL_TRAIN_TIME - curDuration
+ if timeTillEnd > 0:
+ suitTrack.append(Wait(timeTillEnd))
+
+ retval.append(suitTrack)
+
+ return retval
+
+def createIncomingTrainInterval(battle, suit, hp, lure, trapProp):
+ """
+ create the interval of a train going across the track and flattening the cogs
+ """
+ toon = lure['toon']
+ retval = Parallel()
+
+ #nope we still need to react correctly to it
+ suitTrack = createSuitReactionToTrain(battle, suit,hp,lure,trapProp)
+ retval.append(suitTrack)
+
+ #make sure we only call this once
+ if not trapProp.find('**/train_gag').isEmpty():
+ return retval
+
+
+
+ # Set up a pair of clipping planes to cut off the train before and
+ # after the tunnels.
+ clipper = PlaneNode('clipper')
+ clipper.setPlane(Plane(Vec3(+1, 0, 0), Point3(TRAIN_STARTING_X, 0, 0)))
+ clipNP = trapProp.attachNewNode(clipper)
+ trapProp.setClipPlane(clipNP)
+
+ clipper2 = PlaneNode('clipper2')
+ clipper2.setPlane(Plane(Vec3(-1, 0, 0), Point3(TRAIN_TUNNEL_END_X, 0, 0)))
+ clipNP2 = trapProp.attachNewNode(clipper2)
+ trapProp.setClipPlane(clipNP2)
+
+ train = globalPropPool.getProp('train')
+ train.hide()
+ train.reparentTo(trapProp)
+ #train.setBin('')
+ tempScale = trapProp.getScale()
+ trainScale = Vec3(1.0/tempScale[0], 1.0/tempScale[1], 1.0 / tempScale[2])
+ #import pdb; pdb.set_trace()
+
+ trainIval = Sequence()
+ trainIval.append(Func(train.setScale, trainScale))
+ trainIval.append(Func(train.setH, 90))
+ trainIval.append(Func(train.setX, TRAIN_STARTING_X))
+ trainIval.append(Func(train.setTransparency,1))
+ trainIval.append(Func(train.setColorScale, Point4(1,1,1,0)))
+ trainIval.append(Func(train.show))
+
+ tunnel2 = trapProp.find('**/tunnel3')
+ tunnel3 = trapProp.find('**/tunnel2')
+ tunnels = [tunnel2, tunnel3]
+ #import pdb; pdb.set_trace()
+ for tunnel in tunnels:
+ trainIval.append(Func(tunnel.setTransparency,1))
+ trainIval.append(Func(tunnel.setColorScale, Point4(1,1,1,0)))
+ trainIval.append(Func(tunnel.setScale, Point3(1.0,0.01,0.01)))
+ trainIval.append(Func(tunnel.show))
+
+ materializeIval = Parallel()
+ materializeIval.append(LerpColorScaleInterval(train, TRAIN_MATERIALIZE_TIME, Point4(1,1,1,1)))
+ for tunnel in tunnels:
+ materializeIval.append(LerpColorScaleInterval(tunnel, TRAIN_MATERIALIZE_TIME, Point4(1,1,1,1)))
+
+
+ for tunnel in tunnels:
+ tunnelScaleIval = Sequence()
+ tunnelScaleIval.append(LerpScaleInterval(tunnel, TRAIN_MATERIALIZE_TIME - 1.0, Point3(1.0, 2.0, 2.5)))
+ tunnelScaleIval.append(LerpScaleInterval(tunnel, 0.5, Point3(1.0, 3.0, 1.5)))
+ tunnelScaleIval.append(LerpScaleInterval(tunnel, 0.5, Point3(1.0, 2.5, 2.0)))
+ materializeIval.append(tunnelScaleIval)
+
+ trainIval.append(materializeIval)
+
+ endingX = TRAIN_STARTING_X + TRAIN_TRAVEL_DISTANCE
+ trainIval.append( LerpPosInterval(train, TRAIN_DURATION, Point3(endingX,0,0), other=battle ))
+ trainIval.append(LerpColorScaleInterval(train, TRAIN_MATERIALIZE_TIME, Point4(1,1,1,0)))
+
+ retval.append(trainIval)
+
+ #incoming train sound effect
+ trainSoundTrack = getSoundTrack('TL_train.mp3',node=toon)
+ retval.append(trainSoundTrack)
+ return retval
+
+
+
+def __createSlideshowMultiTrack(lure, npcs = []):
+ toon = lure['toon']
+ battle = lure['battle']
+ sidestep = lure['sidestep']
+ origHpr = toon.getHpr(battle)
+ slideshowDelay = 2.5
+ hands = toon.getLeftHands()
+ endPos = toon.getPos(battle)
+ endPos.setY( endPos.getY() + 4)
+
+ button = globalPropPool.getProp('button')
+ button2 = MovieUtil.copyProp(button)
+ buttons = [button, button2]
+
+ #do the toon track
+ toonTrack = Sequence()
+ toonTrack.append(Func(MovieUtil.showProps, buttons, hands))
+ toonTrack.append(Func(toon.headsUp, battle, endPos))
+ toonTrack.append(ActorInterval(toon, 'pushbutton'))
+ toonTrack.append(Func(MovieUtil.removeProps, buttons))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.setHpr, battle, origHpr))
+
+ # do the slideshow popping up
+
+ slideShowProp = globalPropPool.getProp('slideshow')
+ propTrack = Sequence()
+ propTrack.append(Wait(slideshowDelay))
+ propTrack.append(Func(slideShowProp.show))
+ propTrack.append(Func(slideShowProp.setScale, Point3(0.1, 0.1, 0.1)))
+ propTrack.append(Func(slideShowProp.reparentTo, battle))
+ propTrack.append(Func(slideShowProp.setPos, endPos))
+ propTrack.append(LerpScaleInterval(slideShowProp, 1.2, Point3(1.0, 1.0, 1.0)))
+ # shrink it to nothing
+ shrinkDuration = 0.4
+ totalDuration = 7.1
+ propTrackDurationAtThisPoint = propTrack.getDuration()
+ waitTime = totalDuration - propTrackDurationAtThisPoint - shrinkDuration
+ if waitTime > 0:
+ propTrack.append(Wait(waitTime))
+ propTrack.append( LerpScaleInterval(nodePath = slideShowProp, scale = Point3(1.0,1.0,0.1),
+ duration = shrinkDuration, ))
+ propTrack.append(Func(MovieUtil.removeProp, slideShowProp))
+
+ tracks = Parallel(propTrack, toonTrack)
+
+ targets = lure['target']
+ for target in targets:
+ suit = target['suit']
+ trapProp = suit.battleTrapProp
+ # See if lure succeeds
+ if (sidestep == 0):
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ # See if suit animates to this particular lure (or another one that
+ # also succeeds on the same suit)
+ if (kbbonus == 1 or hp > 0):
+ suitTrack = Sequence()
+ suitDelay = 3.8
+ suitAnimDuration = 1.5
+ opos, ohpr = battle.getActorPosHpr(suit)
+ reachDist = MovieUtil.SUIT_LURE_DISTANCE
+ reachPos = Point3(opos[0], opos[1]-reachDist, opos[2])
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ # Wait before being hypnotized
+ suitTrack.append(Wait(suitDelay))
+ suitTrack.append(ActorInterval(suit, 'hypnotized', duration=3.1))
+ suitTrack.append(Func(suit.setPos, battle, reachPos))
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ suitTrack.append(Func(battle.lureSuit, suit))
+ if (hp > 0):
+ suitTrack.append(__createSuitDamageTrack(battle, suit,
+ hp, lure, trapProp))
+ if (revived != 0):
+ suitTrack.append(MovieUtil.createSuitReviveTrack(suit, toon, battle, npcs))
+ elif (died != 0):
+ suitTrack.append(MovieUtil.createSuitDeathTrack(suit, toon, battle, npcs))
+
+ tracks.append(suitTrack)
+ # Must lerp the suit during the hypnotize animation
+ tracks.append(lerpSuit(suit, suitDelay+1.7, 0.7, reachPos,
+ battle, trapProp))
+ else: # If lure fails
+ tracks.append(Sequence(Wait(2.3),
+ Func(MovieUtil.indicateMissed, suit, 1.1)))
+ #TODO sound effects
+ tracks.append(getSoundTrack('TL_presentation.mp3', delay=2.3, node=toon))
+ tracks.append(getSoundTrack('AA_drop_trigger_box.mp3', delay=slideshowDelay, node=toon))
+
+ return tracks
diff --git a/toontown/src/battle/MovieNPCSOS.py b/toontown/src/battle/MovieNPCSOS.py
new file mode 100644
index 0000000..1c9daed
--- /dev/null
+++ b/toontown/src/battle/MovieNPCSOS.py
@@ -0,0 +1,339 @@
+from direct.interval.IntervalGlobal import *
+from BattleProps import *
+from BattleSounds import *
+
+from direct.directnotify import DirectNotifyGlobal
+import MovieCamera
+import random
+import MovieUtil
+import BattleParticles
+import HealJokes
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownBattleGlobals
+from toontown.toon import NPCToons
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieNPCSOS')
+
+soundFiles = ('AA_heal_tickle.mp3',
+ 'AA_heal_telljoke.mp3',
+ 'AA_heal_smooch.mp3',
+ 'AA_heal_happydance.mp3',
+ 'AA_heal_pixiedust.mp3',
+ 'AA_heal_juggle.mp3')
+
+offset = Point3(0, 4.0, 0)
+
+def __cogsMiss(attack, level, hp):
+ return __doCogsMiss(attack, level, hp)
+
+def __toonsHit(attack, level, hp):
+ return __doToonsHit(attack, level, hp)
+
+def __restockGags(attack, level, hp):
+ return __doRestockGags(attack, level, hp)
+
+NPCSOSfn_dict = {
+ ToontownBattleGlobals.NPC_COGS_MISS: __cogsMiss,
+ ToontownBattleGlobals.NPC_TOONS_HIT: __toonsHit,
+ ToontownBattleGlobals.NPC_RESTOCK_GAGS: __restockGags,
+ }
+
+
+def doNPCSOSs(NPCSOSs):
+ """ doNPCSOSs()
+ """
+ if (len(NPCSOSs) == 0):
+ return (None, None)
+ track = Sequence()
+ textTrack = Sequence()
+ for n in NPCSOSs:
+ ival, textIval = __doNPCSOS(n)
+ if (ival):
+ track.append(ival)
+ textTrack.append(textIval)
+
+ camDuration = track.getDuration()
+ if (camDuration > 0.0):
+ camTrack = MovieCamera.chooseHealShot(NPCSOSs, camDuration)
+ else:
+ camTrack = Sequence()
+ return (track, Parallel(camTrack, textTrack))
+
+def __doNPCSOS(sos):
+ """ __doNPCSOS(sos)
+ """
+ npcId = sos['npcId']
+ track, level, hp = NPCToons.getNPCTrackLevelHp(npcId)
+ if (track != None):
+ return NPCSOSfn_dict[track](sos, level, hp)
+ else:
+ return __cogsMiss(sos, 0, 0)
+
+def __healToon(toon, hp, ineffective = 0):
+ notify.debug('healToon() - toon: %d hp: %d ineffective: %d' % \
+ (toon.doId, hp, ineffective))
+ if (ineffective == 1):
+ laughter = random.choice(TTLocalizer.MovieHealLaughterMisses)
+ else:
+ maxDam=ToontownBattleGlobals.AvPropDamage[0][1][0][1]
+ if (hp >= maxDam-1):
+ laughter = random.choice(TTLocalizer.MovieHealLaughterHits2)
+ else:
+ laughter = random.choice(TTLocalizer.MovieHealLaughterHits1)
+ toon.setChatAbsolute(laughter, CFSpeech | CFTimeout)
+
+def __getSoundTrack(level, delay, duration=None, node=None):
+ #level: the level of attack, int 0-5
+ #delay: time delay before playing sound
+
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+
+ soundIntervals = Sequence()
+
+ if soundEffect:
+ if duration:
+ playSound = SoundInterval(soundEffect, duration=duration, node=node)
+ else:
+ playSound = SoundInterval(soundEffect, node=node)
+ soundIntervals.append(Wait(delay))
+ soundIntervals.append(playSound)
+
+ return soundIntervals
+
+def teleportIn(attack, npc, pos = Point3(0, 0, 0), hpr = Vec3(180.0, 0.0, 0.0)):
+ a = Func(npc.reparentTo, attack['battle'])
+ b = Func(npc.setPos, pos)
+ c = Func(npc.setHpr, hpr)
+ d = Func(npc.pose, "teleport", npc.getNumFrames("teleport") - 1)
+ e = npc.getTeleportInTrack()
+ ee = Func(npc.addActive)
+ f = Func(npc.setChatAbsolute, TTLocalizer.MovieNPCSOSGreeting % attack['toon'].getName(), CFSpeech | CFTimeout)
+ g = ActorInterval(npc, 'wave')
+ h = Func(npc.loop, 'neutral')
+ i = Func(npc.clearChat)
+ return Sequence(a, b, c, d, e, ee, f, g, h, i)
+
+def teleportOut(attack, npc):
+ if (npc.style.getGender() == 'm'):
+ a = ActorInterval(npc, 'bow')
+ else:
+ a = ActorInterval(npc, 'curtsy')
+ b = Func(npc.setChatAbsolute, TTLocalizer.MovieNPCSOSGoodbye, CFSpeech | CFTimeout)
+ c = npc.getTeleportOutTrack()
+ d = Func(npc.removeActive)
+ e = Func(npc.detachNode)
+ f = Func(npc.delete)
+ return Sequence(a, b, c, d, e, f)
+
+def __getPartTrack(particleEffect, startDelay, durationDelay, partExtraArgs):
+ """ This function returns the default particle track for a suit attack
+ animation. Arguments:
+ startDelay = time delay before particle effect begins
+ durationDelay = time delay before particles are cleaned up
+ partExtraArgs = extraArgs for startParticleEffect function, the first
+ element of which is always the particle effect (function relies on this)
+ """
+ pEffect = partExtraArgs[0]
+ parent = partExtraArgs[1]
+ if (len(partExtraArgs) == 3):
+ worldRelative = partExtraArgs[2]
+ else:
+ worldRelative = 1
+ return Sequence(
+ Wait(startDelay),
+ ParticleInterval(pEffect, parent, worldRelative,
+ duration=durationDelay, cleanup = True),
+ )
+
+def __doSprinkle(attack, recipients, hp = 0):
+
+ toon = NPCToons.createLocalNPC(attack['npcId'])
+ if (toon == None):
+ return None
+ targets = attack[recipients]
+ level = 4 # Sprinkle
+ battle = attack['battle']
+
+ # Make a 'sandwich' around the track specific interval
+ track = Sequence(teleportIn(attack, toon))
+
+ def face90(target, toon, battle):
+ # turn the toon so that the right side
+ # of his body faces his target
+ vec = Point3(target.getPos(battle) - toon.getPos(battle))
+ vec.setZ(0)
+ temp = vec[0]
+ vec.setX(-vec[1])
+ vec.setY(temp)
+ targetPoint = Point3(toon.getPos(battle) + vec)
+ toon.headsUp(battle, targetPoint)
+
+ delay = 2.5
+ effectTrack = Sequence()
+ for target in targets:
+
+ sprayEffect = BattleParticles.createParticleEffect(file='pixieSpray')
+ dropEffect = BattleParticles.createParticleEffect(file='pixieDrop')
+ explodeEffect = BattleParticles.createParticleEffect(file='pixieExplode')
+ poofEffect = BattleParticles.createParticleEffect(file='pixiePoof')
+ wallEffect = BattleParticles.createParticleEffect(file='pixieWall')
+
+ mtrack = Parallel(
+ __getPartTrack(sprayEffect, 1.5, 0.5, [sprayEffect, toon, 0]),
+ __getPartTrack(dropEffect, 1.9, 2., [dropEffect, target, 0]),
+ __getPartTrack(explodeEffect, 2.7, 1.0, [explodeEffect, toon, 0]),
+ __getPartTrack(poofEffect, 3.4, 1.0, [poofEffect, target, 0]),
+ __getPartTrack(wallEffect, 4.05, 1.2, [wallEffect, toon, 0]),
+ #__getSoundTrack(level, 2, duration=4.1, node=toon),
+ __getSoundTrack(level, 2, duration=3.1, node=toon),
+ Sequence(Func(face90, target, toon, battle),
+ ActorInterval(toon, 'sprinkle-dust')),
+ Sequence(Wait(delay),
+ Func(__healToon, target, hp)),
+ )
+ effectTrack.append(mtrack)
+ track.append(effectTrack)
+ track.append(Func(toon.setHpr, Vec3(180.0, 0.0, 0.0)))
+ track.append(teleportOut(attack, toon))
+ return track
+
+def __doSmooch(attack, hp = 0):
+ toon = NPCToons.createLocalNPC(attack['npcId'])
+ if (toon == None):
+ return None
+ targets = attack['toons']
+ level = 2 # Smooch
+ battle = attack['battle']
+
+ # Make a 'sandwich' around the track specific interval
+ track = Sequence(teleportIn(attack, toon))
+
+ lipstick = globalPropPool.getProp('lipstick')
+ lipstick2 = MovieUtil.copyProp(lipstick)
+ lipsticks = [lipstick, lipstick2]
+ rightHands = toon.getRightHands()
+ dScale = 0.5
+ lipstickTrack = Sequence(
+ Func(MovieUtil.showProps,
+ lipsticks, rightHands,
+ Point3(-0.27, -0.24, -0.95),
+ Point3(-118, -10.6, -25.9)),
+ MovieUtil.getScaleIntervals(lipsticks, dScale,
+ MovieUtil.PNT3_NEARZERO, MovieUtil.PNT3_ONE),
+ Wait(toon.getDuration('smooch') - (2.*dScale)),
+ MovieUtil.getScaleIntervals(lipsticks, dScale,
+ MovieUtil.PNT3_ONE, MovieUtil.PNT3_NEARZERO),
+ #Func(MovieUtil.removeProps, lipsticks),
+ )
+
+ lips = globalPropPool.getProp('lips')
+ dScale = 0.5
+ tLips = 2.5
+ tThrow = 115. / toon.getFrameRate('smooch')
+ dThrow = 0.5
+
+ def getLipPos(toon=toon):
+ toon.pose('smooch', 57)
+ toon.update(0)
+ hand = toon.getRightHands()[0]
+ return hand.getPos(render)
+
+ effectTrack = Sequence()
+ for target in targets:
+ lipcopy = MovieUtil.copyProp(lips)
+ lipsTrack = Sequence(
+ Wait(tLips),
+ Func(MovieUtil.showProp, lipcopy, render, getLipPos),
+ Func(lipcopy.setBillboardPointWorld),
+ LerpScaleInterval(lipcopy, dScale, Point3(3, 3, 3),
+ startScale=MovieUtil.PNT3_NEARZERO),
+ Wait((tThrow - tLips) - dScale),
+ LerpPosInterval(lipcopy, dThrow, Point3(target.getPos() +
+ Point3(0, 0, target.getHeight()))),
+ Func(MovieUtil.removeProp, lipcopy),
+ )
+
+ delay = tThrow + dThrow
+ mtrack = Parallel(lipstickTrack,
+ lipsTrack,
+ __getSoundTrack(level, 2, node=toon),
+ Sequence(ActorInterval(toon, 'smooch')),
+ Sequence(Wait(delay), ActorInterval(target, 'conked')),
+ Sequence(Wait(delay), Func(__healToon, target, hp)),
+ )
+ effectTrack.append(mtrack)
+ effectTrack.append(Func(MovieUtil.removeProps, lipsticks))
+
+ track.append(effectTrack)
+ track.append(teleportOut(attack, toon))
+ track.append(Func(target.clearChat))
+ return track
+
+def __doToonsHit(attack, level, hp):
+ track = __doSprinkle(attack, 'toons', hp)
+ pbpText = attack['playByPlayText']
+ pbpTrack = pbpText.getShowInterval(TTLocalizer.MovieNPCSOSToonsHit,
+ track.getDuration())
+ return track, pbpTrack
+
+def __doCogsMiss(attack, level, hp):
+ track = __doSprinkle(attack, 'suits', hp)
+ pbpText = attack['playByPlayText']
+ pbpTrack = pbpText.getShowInterval(TTLocalizer.MovieNPCSOSCogsMiss,
+ track.getDuration())
+ return track, pbpTrack
+
+def __doRestockGags(attack, level, hp):
+ track = __doSmooch(attack, hp)
+ pbpText = attack['playByPlayText']
+ if (level == ToontownBattleGlobals.HEAL_TRACK):
+ text = TTLocalizer.MovieNPCSOSHeal
+ elif (level == ToontownBattleGlobals.TRAP_TRACK):
+ text = TTLocalizer.MovieNPCSOSTrap
+ elif (level == ToontownBattleGlobals.LURE_TRACK):
+ text = TTLocalizer.MovieNPCSOSLure
+ elif (level == ToontownBattleGlobals.SOUND_TRACK):
+ text = TTLocalizer.MovieNPCSOSSound
+ elif (level == ToontownBattleGlobals.THROW_TRACK):
+ text = TTLocalizer.MovieNPCSOSThrow
+ elif (level == ToontownBattleGlobals.SQUIRT_TRACK):
+ text = TTLocalizer.MovieNPCSOSSquirt
+ elif (level == ToontownBattleGlobals.DROP_TRACK):
+ text = TTLocalizer.MovieNPCSOSDrop
+ elif (level == -1):
+ text = TTLocalizer.MovieNPCSOSAll
+ pbpTrack = pbpText.getShowInterval(TTLocalizer.MovieNPCSOSRestockGags % text,
+ track.getDuration())
+ return track, pbpTrack
+
+def doNPCTeleports(attacks):
+ npcs = []
+ npcDatas = []
+ arrivals = Sequence()
+ departures = Parallel()
+ for attack in attacks:
+ if (attack.has_key('npcId')):
+ npcId = attack['npcId']
+ npc = NPCToons.createLocalNPC(npcId)
+ if (npc != None):
+ npcs.append(npc)
+ attack['npc'] = npc
+ toon = attack['toon']
+ battle = attack['battle']
+ pos = toon.getPos(battle) + offset
+ hpr = toon.getHpr(battle)
+ npcDatas.append((npc, battle, hpr))
+ arrival = teleportIn(attack, npc, pos = pos)
+ arrivals.append(arrival)
+ departure = teleportOut(attack, npc)
+ departures.append(departure)
+ turns = Parallel()
+ unturns = Parallel()
+ hpr = Vec3(180.0, 0, 0)
+ for npc in npcDatas:
+ turns.append(Func(npc[0].setHpr, npc[1], npc[2]))
+ unturns.append(Func(npc[0].setHpr, npc[1], hpr))
+ arrivals.append(turns)
+ unturns.append(departures)
+
+ return arrivals, unturns, npcs
diff --git a/toontown/src/battle/MoviePetSOS.py b/toontown/src/battle/MoviePetSOS.py
new file mode 100644
index 0000000..85ca41d
--- /dev/null
+++ b/toontown/src/battle/MoviePetSOS.py
@@ -0,0 +1,159 @@
+from direct.interval.IntervalGlobal import *
+from BattleProps import *
+from BattleSounds import *
+
+from direct.directnotify import DirectNotifyGlobal
+import MovieCamera
+import random
+import MovieUtil
+import BattleParticles
+import HealJokes
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownBattleGlobals
+from toontown.pets import Pet, PetTricks
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MoviePetSOS')
+
+soundFiles = ('AA_heal_tickle.mp3',
+ 'AA_heal_telljoke.mp3',
+ 'AA_heal_smooch.mp3',
+ 'AA_heal_happydance.mp3',
+ 'AA_heal_pixiedust.mp3',
+ 'AA_heal_juggle.mp3')
+
+offset = Point3(0, 4.0, 0)
+
+def doPetSOSs(PetSOSs):
+ """ doPetSOSs()
+ """
+ if (len(PetSOSs) == 0):
+ return (None, None)
+ track = Sequence()
+ textTrack = Sequence()
+ for p in PetSOSs:
+ ival = __doPetSOS(p)
+ if (ival):
+ track.append(ival)
+
+ camDuration = track.getDuration()
+ camTrack = MovieCamera.chooseHealShot(PetSOSs, camDuration)
+ return (track, camTrack)
+
+def __doPetSOS(sos):
+ """ __doPetSOS(sos)
+ """
+ # MPG need to have levels and HPs
+ return __healJuggle(sos)
+
+def __healToon(toon, hp, gender, callerToonId, ineffective = 0):
+ notify.debug('healToon() - toon: %d hp: %d ineffective: %d' % \
+ (toon.doId, hp, ineffective))
+ nolaughter = 0
+ if ineffective == 1:
+ if callerToonId == toon.doId:
+ laughter = TTLocalizer.MoviePetSOSTrickFail
+ else:
+ nolaughter = 1
+ else:
+ maxDam=ToontownBattleGlobals.AvPropDamage[0][1][0][1]
+ if callerToonId == toon.doId:
+ if gender == 1:
+ laughter = TTLocalizer.MoviePetSOSTrickSucceedBoy
+ else:
+ laughter = TTLocalizer.MoviePetSOSTrickSucceedGirl
+ else:
+ if (hp >= maxDam-1):
+ laughter = random.choice(TTLocalizer.MovieHealLaughterHits2)
+ else:
+ laughter = random.choice(TTLocalizer.MovieHealLaughterHits1)
+ if nolaughter == 0:
+ toon.setChatAbsolute(laughter, CFSpeech | CFTimeout)
+ if (hp > 0 and toon.hp != None):
+ toon.toonUp(hp)
+ else:
+ notify.debug('__healToon() - toon: %d hp: %d' % (toon.doId, hp))
+
+def __teleportIn(attack, pet, pos = Point3(0, 0, 0), hpr = Vec3(180.0, 0.0, 0.0)):
+ a = Func(pet.reparentTo, attack['battle'])
+ b = Func(pet.setPos, pos)
+ c = Func(pet.setHpr, hpr)
+ d = Func(pet.pose, "reappear", 0)
+ e = pet.getTeleportInTrack()
+ #f = Func(pet.addActive)
+ g = Func(pet.loop, 'neutral')
+ #return Sequence(a, b, c, d, e, f, g)
+ return Sequence(a, b, c, d, e, g)
+
+def __teleportOut(attack, pet):
+ a = pet.getTeleportOutTrack()
+ #b = Func(pet.removeActive)
+ c = Func(pet.detachNode)
+ d = Func(pet.delete)
+ #return Sequence(a, b, c)
+ return Sequence(a, c)
+
+def __doPet(attack, level, hp):
+ track = __doSprinkle(attack, 'suits', hp)
+ pbpText = attack['playByPlayText']
+ pbpTrack = pbpText.getShowInterval(TTLocalizer.MovieNPCSOSCogsMiss,
+ track.getDuration())
+ return track, pbpTrack
+
+def __healJuggle(heal):
+ """ __healJuggle(heal)
+ """
+ petProxyId = heal['petId']
+ pet = Pet.Pet()
+ gender = 0
+ if base.cr.doId2do.has_key(petProxyId):
+ petProxy = base.cr.doId2do[petProxyId]
+ if (petProxy == None):
+ return
+ pet.setDNA(petProxy.style)
+ pet.setName(petProxy.petName)
+ gender = petProxy.gender
+ else:
+ pet.setDNA([-1, 0, 0, -1, 2, 0, 4, 0, 1])
+ pet.setName('Smiley')
+ targets = heal['target']
+ ineffective = heal['sidestep']
+ level = heal['level']
+
+ # Make a 'sandwich' around the track specific interval
+ track = Sequence(__teleportIn(heal, pet))
+
+ # Do the trick (or not)
+ if ineffective:
+ trickTrack = Parallel(Wait(1.0),
+ Func(pet.loop, 'neutralSad'),
+ Func(pet.showMood, 'confusion')
+ )
+ else:
+ trickTrack = PetTricks.getTrickIval(pet, level)
+ track.append(trickTrack)
+
+ delay = 4.0
+ first = 1
+ targetTrack = Sequence()
+ for target in targets:
+ targetToon = target['toon']
+ hp = target['hp']
+ callerToonId = heal['toonId']
+ reactIval = Func(__healToon, targetToon, hp, gender, callerToonId, ineffective)
+ if (first == 1):
+ #targetTrack.append(Wait(delay))
+ first = 0
+
+ targetTrack.append(reactIval)
+
+ mtrack = Parallel(Wait(2.0),
+ targetTrack)
+ track.append(mtrack)
+ track.append(Sequence(Func(pet.clearMood)))
+ track.append(__teleportOut(heal, pet))
+ for target in targets:
+ targetToon = target['toon']
+ track.append(Func(targetToon.clearChat))
+ track.append(Func(pet.delete))
+ return track
+
diff --git a/toontown/src/battle/MovieSOS.py b/toontown/src/battle/MovieSOS.py
new file mode 100644
index 0000000..8643776
--- /dev/null
+++ b/toontown/src/battle/MovieSOS.py
@@ -0,0 +1,49 @@
+from direct.interval.IntervalGlobal import *
+
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import TTLocalizer
+from pandac.PandaModules import *
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieSOS')
+
+def doSOSs(calls):
+ """ doSOSs(calls)
+ Calls for help occur in the following order:
+ right to left, one at a time
+ """
+ if (len(calls) == 0):
+ return (None, None)
+ # setChatAbsolute gets around the speedchat
+ def callerFunc(toon, handle):
+ toon.setChatAbsolute(TTLocalizer.MovieSOSCallHelp %
+ handle.getName(), CFSpeech | CFTimeout)
+ handle.d_battleSOS(base.localAvatar.doId)
+ def calleeFunc(toon, handle):
+ toon.setChatAbsolute(TTLocalizer.MovieSOSCallHelp %
+ handle.getName(), CFSpeech | CFTimeout)
+ def observerFunc(toon):
+ toon.setChatAbsolute(TTLocalizer.MovieSOSObserverHelp,
+ CFSpeech | CFTimeout)
+ mtrack = Sequence()
+ for c in calls:
+ toon = c['toon']
+ targetType = c['targetType']
+ handle = c['target']
+ mtrack.append(Wait(0.5))
+ if (targetType == 'observer'):
+ ival = Func(observerFunc, toon)
+ elif (targetType == 'caller'):
+ ival = Func(callerFunc, toon, handle)
+ elif (targetType == 'callee'):
+ ival = Func(calleeFunc, toon, handle)
+ else:
+ notify.error('invalid target type: %s' % targetType)
+ mtrack.append(ival)
+ # Hold on the word balloon for 2 seconds
+ mtrack.append(Wait(2.0))
+ notify.debug('toon: %s calls for help' % toon.getName())
+
+ camDuration = mtrack.getDuration()
+ camTrack = MovieCamera.chooseSOSShot(toon, camDuration)
+ return (mtrack, camTrack)
diff --git a/toontown/src/battle/MovieSound.py b/toontown/src/battle/MovieSound.py
new file mode 100644
index 0000000..2573a4d
--- /dev/null
+++ b/toontown/src/battle/MovieSound.py
@@ -0,0 +1,1079 @@
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from BattleSounds import *
+import BattleParticles
+
+from RewardPanel import *
+
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+import MovieUtil
+import MovieNPCSOS
+from toontown.toonbase import ToontownBattleGlobals
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieSound')
+
+# sound file that plays when an instrument is used
+soundFiles = ('AA_sound_bikehorn.mp3',
+ 'AA_sound_whistle.mp3',
+ 'AA_sound_bugle.mp3',
+ 'AA_sound_aoogah.mp3',
+ 'AA_sound_elephant.mp3',
+ 'SZ_DD_foghorn.mp3',
+ 'AA_sound_Opera_Singer.mp3') #UBER
+
+# sound file that plays when an instrument first appears
+appearSoundFiles = ('MG_tag_1.mp3',
+ 'LB_receive_evidence.mp3',
+ 'm_match_trumpet.mp3',
+ 'TL_step_on_rake.mp3',
+ 'toonbldg_grow.mp3',
+ 'mailbox_full_wobble.mp3',
+ 'mailbox_full_wobble.mp3') #UBER
+
+hitSoundFiles = ('AA_sound_Opera_Singer_Cog_Glass.mp3',)
+
+
+tSound = 2.45
+tSuitReact = 2.8
+
+DISTANCE_TO_WALK_BACK = MovieUtil.SUIT_LURE_DISTANCE * 0.75 #set this to zero if we don't want to move back duriing sound gags
+TIME_TO_WALK_BACK = .5 #in seconds
+if DISTANCE_TO_WALK_BACK == 0:
+ TIME_TO_WALK_BACK = 0
+INSTRUMENT_SCALE_MODIFIER = 0.5 #multiply all instrument scales by this amount
+
+BEFORE_STARS = 0.5
+AFTER_STARS = 1.75
+
+def doSounds(sounds):
+ """ doSounds(sounds)
+ Sounds occur in the following order:
+ 1) level 1 sounds, simultaneously
+ 2) level 2 sounds, simultaneously, (TOON_SOUND_DELAY later)
+ 3) level 3 sounds, simultaneously, (TOON_SOUND_DELAY later)
+ etc.
+ Note: simultaneous sounds should be louder
+ """
+ if (len(sounds) == 0):
+ return (None, None)
+
+ npcArrivals, npcDepartures, npcs = MovieNPCSOS.doNPCTeleports(sounds)
+
+ mtrack = Parallel()
+ # Keep track of how many successful toon sound attacks there were
+ hitCount = 0
+ # Group sounds by level and count how many sounds hit target
+ prevLevel = 0
+ prevSounds = [[], [], [], [], [], [], []] #UBER
+ for sound in sounds:
+ level = sound['level']
+ prevSounds[level].append(sound)
+ # See if this attack hit the suits, if so, increment counter
+ for target in sound['target']:
+ if target['hp'] > 0:
+ hitCount += 1
+ break
+ delay = 0.0
+ for soundList in prevSounds:
+ if (len(soundList) > 0):
+ mtrack.append(__doSoundsLevel(soundList, delay, hitCount, npcs))
+ delay += TOON_SOUND_DELAY
+
+ soundTrack = Sequence(npcArrivals, mtrack, npcDepartures)
+
+ targets = sounds[0]['target']
+ camDuration = mtrack.getDuration()
+ enterDuration = npcArrivals.getDuration()
+ exitDuration = npcDepartures.getDuration()
+ camTrack = MovieCamera.chooseSoundShot(sounds, targets, camDuration,
+ enterDuration, exitDuration)
+ return (soundTrack, camTrack)
+
+def __getSuitTrack(sound,lastSoundThatHit,delay,hitCount,targets,totalDamage,
+ hpbonus,toon,npcs):
+ #import pdb; pdb.set_trace()
+ tracks = Parallel()
+ attacks = 0
+ uberDelay = 0.0
+ isUber = 0
+ if sound["level"] >= ToontownBattleGlobals.UBER_GAG_LEVEL_INDEX:
+ uberDelay = 3.0
+ isUber = 1
+ for target in targets:
+ suit = target['suit']
+ if (totalDamage > 0 and sound == lastSoundThatHit):
+ hp = target['hp']
+ died = target['died']
+ battle = sound['battle']
+ # This kick back bonus (kbbonus) is not actually used as
+ # as it is with throws and squirts. We will simply use it
+ # to detect if the suit
+ # has been attacked with a sound while lured (will have a
+ # value of 0, not -1)
+ kbbonus = target['kbbonus']
+ suitTrack = Sequence()
+ showDamage = Func(suit.showHpText,
+ -totalDamage, openEnded=0)
+ updateHealthBar = Func(suit.updateHealthBar, totalDamage)
+
+ if isUber:
+ breakEffect = BattleParticles.createParticleEffect(file='soundBreak')
+ breakEffect.setDepthWrite(0)
+ breakEffect.setDepthTest(0)
+ breakEffect.setTwoSided(1)
+
+ soundEffect = globalBattleSoundCache.getSound(hitSoundFiles[0])
+
+ #if attacks == 0:
+ #attacks = attacks + 1
+ #suitTrack.append(Wait(delay + tSuitReact))
+ #else:
+ suitTrack.append(Wait(delay + tSuitReact))
+ #suitTrack.append(Wait(delay + uberDelay))
+
+
+
+ if isUber:
+ delayTime = random.random()
+ suitTrack.append(Wait(delayTime + 2.0))
+ suitTrack.append(Func(setPosFromOther, breakEffect, suit, Point3(0,0.0,suit.getHeight() - 1.0)))
+ suitTrack.append(
+ Parallel(
+ showDamage,
+ updateHealthBar,
+ SoundInterval(soundEffect, node=suit),
+ __getPartTrack(breakEffect, 0.0, 1.0, [breakEffect, suit, 0], softStop = -0.5),
+ )
+ )
+
+ else:
+
+ suitTrack.append(showDamage)
+ suitTrack.append(updateHealthBar)
+
+
+ # Show stun if the suits are only hit once
+ if hitCount == 1:
+ suitTrack.append(
+ Parallel(
+ ActorInterval(suit, 'squirt-small-react'),
+ MovieUtil.createSuitStunInterval(suit, 0.5, 1.8),
+ ))
+ else:
+ suitTrack.append(
+ ActorInterval(suit, 'squirt-small-react'))
+ if (kbbonus == 0):
+ suitTrack.append(__createSuitResetPosTrack(suit,
+ battle))
+ suitTrack.append(Func(battle.unlureSuit, suit))
+ # Create a bonus track if there is a bonus
+ bonusTrack = None
+ if (hpbonus > 0):
+ bonusTrack = Sequence(Wait(delay + tSuitReact + delay + 0.75 + uberDelay),
+ Func(suit.showHpText,
+ -hpbonus, 1, openEnded=0))
+ #if (died != 0):
+ # suitTrack.append(MovieUtil.createSuitDeathTrack(suit,
+ # toon, battle, npcs))
+ #else:
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ if (bonusTrack == None):
+ tracks.append(suitTrack)
+ else:
+ tracks.append(Parallel(suitTrack,
+ bonusTrack))
+ elif (totalDamage <= 0): # sound missed, so indicate missed sign
+ tracks.append(Sequence(Wait(2.9),
+ Func(MovieUtil.indicateMissed,
+ suit, 1.0)))
+ return tracks
+
+
+
+def __doSoundsLevel(sounds, delay, hitCount, npcs):
+ # Determine if there are any simultaneous sounds that hit (sound attacks of
+ # the same level in the same round happend simultaneously). If there
+ # are any simultaneous sounds, determine which is the last one that hits so
+ # that suits only flinch once for the group of simultaneous sounds that hit.
+ lastSoundThatHit = None
+ totalDamage = 0
+ for sound in sounds:
+ # Sounds either hit all targets or miss all targets
+ for target in sound['target']:
+ if (target['hp'] > 0):
+ lastSoundThatHit = sound
+ totalDamage += target['hp']
+ break
+ mainTrack = Sequence()
+ tracks = Parallel()
+ deathTracks = Parallel()
+
+ for sound in sounds:
+ #soundfn_array(
+ toon = sound['toon']
+ if (sound.has_key('npc')):
+ toon = sound['npc']
+ level = sound['level']
+ targets = sound['target']
+ hpbonus = sound['hpbonus']
+
+ attackMTrack = soundfn_array[sound['level']](sound,delay,toon,targets,level)
+ #attackMTrack = squirtfn_array[squirt['level']](squirt, delay, fShowStun)
+ tracks.append(Sequence(Wait(delay), attackMTrack))
+ tracks.append(__getSuitTrack(sound,lastSoundThatHit,delay,hitCount,
+ targets,totalDamage,hpbonus,toon,npcs))
+
+ # get suit track no longer creates the deathTrack append it after creating all attack tracks
+ for target in targets:
+ battle = sound['battle']
+ suit = target['suit']
+ died = target['died']
+ revived = target['revived']
+ if (revived):
+ deathTracks.append(MovieUtil.createSuitReviveTrack(suit,
+ toon, battle, npcs))
+ elif (died):
+ deathTracks.append(MovieUtil.createSuitDeathTrack(suit,
+ toon, battle, npcs))
+
+ mainTrack.append(tracks)
+ mainTrack.append(deathTracks)
+
+ return mainTrack
+
+def __createSuitResetPosTrack(suit, battle):
+ resetPos, resetHpr = battle.getActorPosHpr(suit)
+ moveDist = Vec3(suit.getPos(battle) - resetPos).length()
+ moveDuration = 0.5
+ walkTrack = Sequence(
+ # First face the right direction, then walk backwards
+ Func(suit.setHpr, battle, resetHpr),
+ ActorInterval(suit, 'walk', startTime=1, duration=moveDuration, endTime=0.0001),
+ Func(suit.loop, 'neutral')
+ )
+ # Actually move the suit
+ moveTrack = LerpPosInterval(suit, moveDuration, resetPos, other=battle)
+ return Parallel(walkTrack, moveTrack)
+
+
+def createSuitResetPosTrack(suit,battle):
+ """
+ MovieTrap will use this when a train trap appears under an already lured suit
+ """
+ return __createSuitResetPosTrack(suit,battle)
+
+def __createToonInterval(sound, delay, toon, operaInstrument = None):
+ """
+ back up the toon, do the megaphone, then go back to original position
+ """
+ isNPC = 0
+ if sound.get('npc'):
+ isNPC = 1
+
+ battle = sound['battle']
+ hasLuredSuits = __hasLuredSuits(sound)
+
+ if not isNPC:
+ oldPos,oldHpr = battle.getActorPosHpr(toon)
+ newPos = Point3(oldPos)
+ newPos.setY( newPos.getY() - DISTANCE_TO_WALK_BACK)
+
+ retval = Sequence(
+ Wait(delay),
+ )
+
+ if DISTANCE_TO_WALK_BACK and hasLuredSuits and not isNPC:
+ retval.append(
+ Parallel( ActorInterval(toon, 'walk', startTime = 1, duration = TIME_TO_WALK_BACK, endTime = 0.0001),
+ LerpPosInterval(toon, TIME_TO_WALK_BACK, newPos, other=battle)))
+ if operaInstrument:
+ sprayEffect = BattleParticles.createParticleEffect(file='soundWave')
+ sprayEffect.setDepthWrite(0)
+ sprayEffect.setDepthTest(0)
+ sprayEffect.setTwoSided(1)
+
+ I1 = 2.8
+ retval.append(ActorInterval(toon, 'sound', playRate = 1.0, startTime=0.0,endTime=I1))
+ #Wait(3.0),
+ retval.append(Func(setPosFromOther, sprayEffect, operaInstrument, Point3(0,1.6,-0.18)))
+ retval.append( __getPartTrack(sprayEffect, 0.0, 6.0, [sprayEffect, toon, 0], softStop = -3.5))
+ retval.append(ActorInterval(toon, 'sound', playRate = 1.0, startTime=I1))
+ else:
+ retval.append(ActorInterval(toon, 'sound'))
+
+ if DISTANCE_TO_WALK_BACK and hasLuredSuits and not isNPC:
+ retval.append(
+ Parallel( ActorInterval(toon, 'walk', startTime = 0.0001, duration = TIME_TO_WALK_BACK, endTime = 1),
+ LerpPosInterval(toon, TIME_TO_WALK_BACK, oldPos, other=battle)))
+
+ retval.append(Func(toon.loop, 'neutral'))
+
+ return retval
+
+def __hasLuredSuits(sound):
+ """
+ return True if any suits are lured
+ """
+ retval = False
+ targets = sound['target']
+ for target in targets:
+ # This kick back bonus (kbbonus) is not actually used as
+ # as it is with throws and squirts. We will simply use it
+ # to detect if the suit
+ # has been attacked with a sound while lured (will have a
+ # value of 0, not -1)
+ kbbonus = target['kbbonus']
+ if kbbonus == 0:
+ retval = True
+ break
+ return retval
+
+def __doBikehorn(sound, delay, toon, targets, level):
+ tracks = Parallel()
+
+ instrMin = Vec3(.001,.001,.001)
+ instrMax = Vec3(.65,.65,.65)
+ instrMax *= INSTRUMENT_SCALE_MODIFIER
+ instrStretch = Vec3(.6,1.1,.6)
+ instrStretch *= INSTRUMENT_SCALE_MODIFIER
+
+ # loading of the megaphone
+ megaphone = globalPropPool.getProp('megaphone')
+ megaphone2 = MovieUtil.copyProp(megaphone)
+ megaphones = [megaphone, megaphone2]
+
+ # loading of the bikehorn
+ instrument = globalPropPool.getProp('bikehorn')
+ instrument2 = MovieUtil.copyProp(instrument)
+ instruments = [instrument,instrument2]
+
+ # default values
+ def setInstrumentStats(instrument=instrument, instrument2=instrument2):
+ #instrument.setPos(-.5,-.6,.1)
+ instrument.setPos(-1.1,-1.4,.1)
+ instrument.setHpr(145,0,0)
+ instrument.setScale(instrMin)
+
+ #instrument2.setPos(-.5,-.6,.1)
+ instrument2.setPos(-1.1,-1.4,.1)
+ instrument2.setHpr(145,0,0)
+ instrument2.setScale(instrMin)
+
+ hands = toon.getRightHands()
+
+ megaphoneShow = Sequence(Func(MovieUtil.showProps, megaphones, hands),
+ Func(MovieUtil.showProps, instruments, hands),
+ Func(setInstrumentStats))
+ megaphoneHide = Sequence(Func(MovieUtil.removeProps, megaphones),
+ Func(MovieUtil.removeProps, instruments))
+
+ # have the instrument appear
+ instrumentAppearSfx = globalBattleSoundCache.getSound(appearSoundFiles[level])
+ grow = getScaleIntervals(instruments, duration=.2, startScale=instrMin,
+ endScale=instrMax)
+ instrumentAppear = Parallel(grow, Sequence(Wait(.15),
+ SoundInterval(instrumentAppearSfx,node=toon)))
+
+ # instruments stretches with use, as does megaphone
+ stretchInstr = getScaleBlendIntervals(instruments, duration=.2, startScale=instrMax,
+ endScale=instrStretch, blendType='easeOut')
+ backInstr = getScaleBlendIntervals(instruments, duration=.2, startScale=instrStretch,
+ endScale=instrMax, blendType='easeIn')
+ stretchMega = getScaleBlendIntervals(megaphones, duration=.2,
+ startScale=megaphone.getScale(),
+ endScale=.9, blendType='easeOut')
+ backMega = getScaleBlendIntervals(megaphones, duration=.2, startScale=.9,
+ endScale=megaphone.getScale(), blendType='easeIn')
+ attackTrack = Parallel(Sequence(stretchInstr,backInstr),Sequence(stretchMega,backMega))
+
+ hasLuredSuits =__hasLuredSuits(sound)
+ delayTime = delay
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+
+ # instrument and megaphone track
+ megaphoneTrack = Sequence(
+ Wait(delayTime),
+ megaphoneShow,
+ Wait(1.0),
+ instrumentAppear,
+ Wait(3.0),
+ megaphoneHide,
+ )
+
+ tracks.append(megaphoneTrack)
+
+ # create the toon track
+ toonTrack = __createToonInterval(sound,delay,toon)
+ tracks.append(toonTrack)
+
+ # play the sound and shrink the instrument
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+ instrumentshrink = getScaleIntervals(instruments,duration=.1,
+ startScale=instrMax,endScale=instrMin)
+
+ if soundEffect:
+ delayTime = delay + tSound
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ soundTrack = Sequence(Wait(delayTime),
+ Parallel(attackTrack,SoundInterval(soundEffect, node=toon)),
+ Wait(.2), instrumentshrink)
+ tracks.append(soundTrack)
+
+ return tracks
+
+
+def __doWhistle(sound, delay, toon, targets, level):
+ tracks = Parallel()
+
+ instrMin = Vec3(.001,.001,.001)
+ instrMax = Vec3(.2,.2,.2)
+ instrMax *= INSTRUMENT_SCALE_MODIFIER
+ instrStretch = Vec3(.25,.25,.25)
+ instrStretch *= INSTRUMENT_SCALE_MODIFIER
+
+ # create the megaphone
+ megaphone = globalPropPool.getProp('megaphone')
+ megaphone2 = MovieUtil.copyProp(megaphone)
+ megaphones = [megaphone, megaphone2]
+
+ # create the whistle instrument
+ instrument = globalPropPool.getProp('whistle')
+ instrument2 = MovieUtil.copyProp(instrument)
+ instruments = [instrument, instrument2]
+
+ # sets default values
+ def setInstrumentStats(instrument=instrument, instrument2=instrument2):
+ #instrument.setPos(-.8,-.9,.1)
+ instrument.setPos(-1.2,-1.3,.1)
+ instrument.setHpr(145,0,85)
+ instrument.setScale(instrMin)
+
+ #instrument2.setPos(-.8,-.9,.1)
+ instrument2.setPos(-1.2,-1.3,.1)
+ instrument2.setHpr(145,0,85)
+ instrument2.setScale(instrMin)
+
+ hands = toon.getRightHands()
+
+ megaphoneShow = Sequence(Func(MovieUtil.showProps, megaphones, hands),
+ Func(MovieUtil.showProps, instruments, hands),
+ Func(setInstrumentStats))
+ megaphoneHide = Sequence(Func(MovieUtil.removeProps, megaphones),
+ Func(MovieUtil.removeProps, instruments))
+
+ # instrument appears
+ instrumentAppearSfx = globalBattleSoundCache.getSound(appearSoundFiles[level])
+ grow = getScaleIntervals(instruments, duration=.2, startScale=instrMin,
+ endScale=instrMax)
+ instrumentAppear = Parallel(grow, Sequence(Wait(.05),
+ SoundInterval(instrumentAppearSfx,node=toon)))
+
+ # instrument stretches when used
+ stretchInstr = getScaleBlendIntervals(instruments, duration=.2,startScale=instrMax,
+ endScale=instrStretch,blendType='easeOut')
+ backInstr = getScaleBlendIntervals(instruments,duration=.2, startScale=instrStretch,
+ endScale=instrMax,blendType='easeIn')
+ attackTrack = Sequence(stretchInstr,backInstr)
+
+ hasLuredSuits =__hasLuredSuits(sound)
+ delayTime = delay
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+
+ megaphoneTrack = Sequence(
+ Wait(delayTime),
+ megaphoneShow,
+ Wait(1.0),
+ instrumentAppear,
+ Wait(3.0),
+ megaphoneHide,
+ )
+
+ tracks.append(megaphoneTrack)
+
+ # create the toon track
+ toonTrack = __createToonInterval(sound,delay,toon)
+
+ tracks.append(toonTrack)
+
+ # play the sound and shrink the instrument
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+ instrumentshrink = getScaleIntervals(instruments,duration=.1,startScale=instrMax,
+ endScale=instrMin)
+
+ if soundEffect:
+ delayTime = delay + tSound
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ soundTrack = Sequence(Wait(delayTime),
+ Parallel(attackTrack,SoundInterval(soundEffect, node=toon)),
+ Wait(.2), instrumentshrink)
+ tracks.append(soundTrack)
+
+ return tracks
+
+def __doBugle(sound, delay, toon, targets, level):
+ tracks = Parallel()
+
+ instrMin = Vec3(.001,.001,.001)
+ instrMax = Vec3(.4,.4,.4)
+ instrMax *= INSTRUMENT_SCALE_MODIFIER
+ instrStretch = Vec3(.5,.5,.5)
+ instrStretch *= INSTRUMENT_SCALE_MODIFIER
+
+ # creates the megaphone
+ megaphone = globalPropPool.getProp('megaphone')
+ megaphone2 = MovieUtil.copyProp(megaphone)
+ megaphones = [megaphone, megaphone2]
+
+ # creates the bugle
+ instrument = globalPropPool.getProp('bugle')
+ instrument2 = MovieUtil.copyProp(instrument)
+ instruments = [instrument, instrument2]
+
+ # default values
+ def setInstrumentStats(instrument=instrument, instrument2=instrument2):
+ #instrument.setPos(-.8,-.9,.1)
+ #instrument.setPos(-1.2,-1.3,.1)
+ instrument.setPos(-1.3,-1.4,.1)
+ instrument.setHpr(145,0,85)
+ instrument.setScale(instrMin)
+
+ #instrument2.setPos(-.8,-.9,.1)
+ #instrument2.setPos(-1.2,-1.3,.1)
+ instrument2.setPos(-1.3,-1.4,.1)
+ instrument2.setHpr(145,0,85)
+ instrument2.setScale(instrMin)
+
+ # used for the trumpet playing, causes it to shrink and grow
+ def longshake(models,num):
+ inShake = getScaleBlendIntervals(models,duration=.2,startScale=instrMax,
+ endScale=instrStretch,blendType='easeInOut')
+ outShake = getScaleBlendIntervals(models,duration=.2,startScale=instrStretch,
+ endScale=instrMax,blendType='easeInOut')
+ i = 1
+ seq = Sequence()
+ while i < num:
+ if i % 2 == 0:
+ seq.append(inShake)
+ else:
+ seq.append(outShake)
+ i+=1
+ seq.start()
+
+ hands = toon.getRightHands()
+
+ megaphoneShow = Sequence(Func(MovieUtil.showProps, megaphones, hands),
+ Func(MovieUtil.showProps, instruments, hands),
+ Func(setInstrumentStats))
+ megaphoneHide = Sequence(Func(MovieUtil.removeProps, megaphones),
+ Func(MovieUtil.removeProps, instruments))
+
+ # instrument appears
+ instrumentAppearSfx = globalBattleSoundCache.getSound(appearSoundFiles[level])
+ grow = getScaleBlendIntervals(instruments, duration=1, startScale=instrMin, endScale=instrMax,blendType='easeInOut')
+ #instrumentAppear = Parallel(grow, Sequence(Wait(.05),SoundInterval(instrumentAppearSfx,node=toon)))
+
+ instrumentshrink = getScaleIntervals(instruments, duration=.1,
+ startScale=instrMax, endScale=instrMin)
+ instrumentAppear = Sequence(grow,Wait(0),Func(longshake,instruments,5))
+
+ # instrument shakes when used
+ hasLuredSuits =__hasLuredSuits(sound)
+ delayTime = delay
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+ megaphoneTrack = Parallel(Sequence(Wait(delay+1.7), SoundInterval(soundEffect, node=toon)),
+ Sequence(
+ Wait(delayTime),
+ megaphoneShow,
+ Wait(1.7),
+ #Func(soundEffect.play),
+ #Wait(0),
+ instrumentAppear,
+ Wait(1),
+ instrumentshrink,
+ Wait(1.5),
+ megaphoneHide,
+ )
+ )
+ tracks.append(megaphoneTrack)
+
+ # create the toon track
+ toonTrack = __createToonInterval(sound,delay,toon)
+
+ tracks.append(toonTrack)
+
+ # play the sound and shrink the instrument
+ #soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+ #SoundInterval(soundEffect, node=toon)
+
+ if soundEffect:
+ delayTime = delay + tSound
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ soundTrack = Wait(delayTime)
+ tracks.append(soundTrack)
+
+ return tracks
+
+def __doAoogah(sound, delay, toon, targets, level):
+ tracks = Parallel()
+
+ instrMin = Vec3(.001,.001,.001)
+ instrMax = Vec3(.5,.5,.5)
+ instrMax *= INSTRUMENT_SCALE_MODIFIER
+ instrStretch = Vec3(1.1,.9,.4)
+ instrStretch *= INSTRUMENT_SCALE_MODIFIER
+
+ # create the megaphone
+ megaphone = globalPropPool.getProp('megaphone')
+ megaphone2 = MovieUtil.copyProp(megaphone)
+ megaphones = [megaphone, megaphone2]
+
+ # creates the aoogah instrument
+ instrument = globalPropPool.getProp('aoogah')
+ instrument2 = MovieUtil.copyProp(instrument)
+ instruments = [instrument, instrument2]
+
+ # sets the default values
+ def setInstrumentStats(instrument=instrument, instrument2=instrument2):
+ instrument.setPos(-1.0,-1.5,.2)
+ instrument.setHpr(145,0,85)
+ instrument.setScale(instrMin)
+
+ #instrument2.setPos(-.5,-1,.2)
+ instrument2.setPos(-1.0,-1.5,.2)
+ instrument2.setHpr(145,0,85)
+ instrument2.setScale(instrMin)
+
+ hands = toon.getRightHands()
+
+ megaphoneShow = Sequence(Func(MovieUtil.showProps, megaphones, hands),
+ Func(MovieUtil.showProps, instruments, hands),
+ Func(setInstrumentStats))
+ megaphoneHide = Sequence(Func(MovieUtil.removeProps, megaphones),
+ Func(MovieUtil.removeProps, instruments))
+
+ # instrument appears
+ instrumentAppearSfx = globalBattleSoundCache.getSound(appearSoundFiles[level])
+ grow = getScaleIntervals(instruments, duration=.2, startScale=instrMin,
+ endScale=instrMax)
+ instrumentAppear = Parallel(grow, Sequence(Wait(.05),
+ SoundInterval(instrumentAppearSfx, node=toon)))
+
+ # instrument stretches when used
+ stretchInstr = getScaleBlendIntervals(instruments, duration=.2, startScale=instrMax,
+ endScale=instrStretch, blendType='easeOut')
+ backInstr = getScaleBlendIntervals(instruments, duration=.2, startScale=instrStretch,
+ endScale=instrMax, blendType='easeInOut')
+ attackTrack = Sequence(stretchInstr, Wait(1), backInstr)
+
+ hasLuredSuits =__hasLuredSuits(sound)
+ delayTime = delay
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ megaphoneTrack = Sequence(
+ Wait(delayTime),
+ megaphoneShow,
+ Wait(1.0),
+ instrumentAppear,
+ Wait(3.0),
+ megaphoneHide,
+ )
+
+ tracks.append(megaphoneTrack)
+
+ # create the toon track
+ toonTrack = __createToonInterval(sound, delay, toon)
+
+ tracks.append(toonTrack)
+
+ # play the sound and shrink the instrument
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+ instrumentshrink = getScaleIntervals(instruments, duration=.1, startScale=instrMax,
+ endScale=instrMin)
+
+ if soundEffect:
+ delayTime = delay + tSound
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ soundTrack = Sequence(Wait(delayTime),
+ Parallel(attackTrack,SoundInterval(soundEffect, node=toon),
+ Sequence(Wait(1.5), instrumentshrink)))
+ tracks.append(soundTrack)
+ return tracks
+
+def __doElephant(sound, delay, toon, targets, level):
+ tracks = Parallel()
+
+ instrMin = Vec3(.001,.001,.001)
+ instrMax1 = Vec3(.3,.4,.2)
+ instrMax1 *= INSTRUMENT_SCALE_MODIFIER
+ instrMax2 = Vec3(.3,.3,.3)
+ instrMax2 *= INSTRUMENT_SCALE_MODIFIER
+ instrStretch1 = Vec3(.3,.5,.25)
+ instrStretch1 *= INSTRUMENT_SCALE_MODIFIER
+ instrStretch2 = Vec3(.3,.7,.3)
+ instrStretch2 *= INSTRUMENT_SCALE_MODIFIER
+
+ # create the megaphone track
+ megaphone = globalPropPool.getProp('megaphone')
+ megaphone2 = MovieUtil.copyProp(megaphone)
+ megaphones = [megaphone, megaphone2]
+
+ # create the elephant instrument
+ instrument = globalPropPool.getProp('elephant')
+ instrument2 = MovieUtil.copyProp(instrument)
+ instruments = [instrument, instrument2]
+
+ # sets default values
+ def setInstrumentStats(instrument=instrument, instrument2=instrument2):
+ instrument.setPos(-.6,-.9,.15)
+ instrument.setHpr(145,0,85)
+ instrument.setScale(instrMin)
+
+ instrument2.setPos(-.6,-.9,.15)
+ instrument2.setHpr(145,0,85)
+ instrument2.setScale(instrMin)
+
+ hands = toon.getRightHands()
+
+ megaphoneShow = Sequence(Func(MovieUtil.showProps, megaphones, hands),
+ Func(MovieUtil.showProps, instruments, hands),
+ Func(setInstrumentStats))
+ megaphoneHide = Sequence(Func(MovieUtil.removeProps, megaphones),
+ Func(MovieUtil.removeProps, instruments))
+
+ # instrument appears and stretches twice to give a 'kind of' animation
+ instrumentAppearSfx = globalBattleSoundCache.getSound(appearSoundFiles[level])
+ grow1 = getScaleIntervals(instruments, duration=.3, startScale=instrMin,
+ endScale=instrMax1)
+ grow2 = getScaleIntervals(instruments, duration=.3, startScale=instrMax1,
+ endScale=instrMax2)
+ instrumentAppear = Parallel(Sequence(grow1,grow2),
+ Sequence(Wait(.05),
+ SoundInterval(instrumentAppearSfx, node=toon)))
+
+ # elephant trunk stretches when used to give illusion of animation
+ stretchInstr1 = getScaleBlendIntervals(instruments, duration=.1,
+ startScale=instrMax2,
+ endScale=instrStretch1,
+ blendType='easeOut')
+ stretchInstr2 = getScaleBlendIntervals(instruments, duration=.1,
+ startScale=instrStretch1,
+ endScale=instrStretch2,
+ blendType='easeOut')
+ stretchInstr = Sequence(stretchInstr1, stretchInstr2)
+ backInstr = getScaleBlendIntervals(instruments, duration=.1,
+ startScale=instrStretch2,
+ endScale=instrMax2,
+ blendType='easeOut')
+ attackTrack = Sequence(stretchInstr, Wait(1), backInstr)
+
+ hasLuredSuits =__hasLuredSuits(sound)
+ delayTime = delay
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ megaphoneTrack = Sequence(
+ Wait(delayTime),
+ megaphoneShow,
+ Wait(1.0),
+ instrumentAppear,
+ Wait(3.0),
+ megaphoneHide,
+ )
+
+ tracks.append(megaphoneTrack)
+
+ # create the toon track
+ toonTrack = __createToonInterval(sound,delay,toon)
+
+ tracks.append(toonTrack)
+
+ # play the sound and shrink the instrument
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+ instrumentshrink = getScaleIntervals(instruments, duration=.1,
+ startScale=instrMax2,
+ endScale=instrMin)
+
+ if soundEffect:
+ delayTime = delay + tSound
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ soundTrack = Sequence(Wait(delayTime),
+ Parallel(attackTrack,SoundInterval(soundEffect, node=toon),
+ Sequence(Wait(1.5), instrumentshrink)))
+ tracks.append(soundTrack)
+ return tracks
+
+def __doFoghorn(sound, delay, toon, targets, level):
+ tracks = Parallel()
+
+ instrMin = Vec3(.001,.001,.001)
+ instrMax1 = Vec3(.1,.1,.1)
+ instrMax1 *= INSTRUMENT_SCALE_MODIFIER
+ instrMax2 = Vec3(.3,.3,.3)
+ instrMax2 *= INSTRUMENT_SCALE_MODIFIER
+ instrStretch = Vec3(.4,.4,.4)
+ instrStretch *= INSTRUMENT_SCALE_MODIFIER
+
+ # create the megaphone
+ megaphone = globalPropPool.getProp('megaphone')
+ megaphone2 = MovieUtil.copyProp(megaphone)
+ megaphones = [megaphone, megaphone2]
+
+ # create the fog horn instrument
+ instrument = globalPropPool.getProp('fog_horn')
+ instrument2 = MovieUtil.copyProp(instrument)
+ instruments = [instrument, instrument2]
+
+ # sets default values
+ def setInstrumentStats(instrument=instrument, instrument2=instrument2):
+ instrument.setPos(-.8,-.9,.2)
+ instrument.setHpr(145,0,0)
+ instrument.setScale(instrMin)
+
+ instrument2.setPos(-.8,-.9,.2)
+ instrument2.setHpr(145,0,0)
+ instrument2.setScale(instrMin)
+
+
+ hands = toon.getRightHands()
+
+ megaphoneShow = Sequence(Func(MovieUtil.showProps, megaphones, hands),
+ Func(MovieUtil.showProps, instruments, hands),
+ Func(setInstrumentStats))
+ megaphoneHide = Sequence(Func(MovieUtil.removeProps, megaphones),
+ Func(MovieUtil.removeProps, instruments))
+
+ # instrument appears via a slow stretch and then a boing for humor
+ instrumentAppearSfx = globalBattleSoundCache.getSound(appearSoundFiles[level])
+ grow1 = getScaleIntervals(instruments, duration=1, startScale=instrMin,
+ endScale=instrMax1)
+ grow2 = getScaleIntervals(instruments, duration=.1, startScale=instrMax1,
+ endScale=instrMax2)
+ instrumentAppear = Parallel(Sequence(grow1,grow2),
+ Sequence(Wait(.05),
+ SoundInterval(instrumentAppearSfx, node=toon)))
+
+ # instrument stretches when used, and spins a little bit. As it is a
+ # longer sound, when it's finished it merely vanishes
+ stretchInstr = getScaleBlendIntervals(instruments, duration=.3, startScale=instrMax2,
+ endScale=instrStretch, blendType='easeOut')
+ backInstr = getScaleBlendIntervals(instruments, duration=1.0, startScale=instrStretch,
+ endScale=instrMin, blendType='easeIn')
+ spinInstr1 = LerpHprInterval(instrument,duration=1.5, startHpr=Vec3(145,0,0),
+ hpr=Vec3(145,0,90), blendType='easeInOut')
+ spinInstr2 = LerpHprInterval(instrument2,duration=1.5, startHpr=Vec3(145,0,0),
+ hpr=Vec3(145,0,90), blendType='easeInOut')
+ spinInstr = Parallel(spinInstr1,spinInstr2)
+ attackTrack = Parallel(Sequence(Wait(.2),spinInstr),
+ Sequence(stretchInstr, Wait(.5), backInstr))
+
+ hasLuredSuits =__hasLuredSuits(sound)
+ delayTime = delay
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ megaphoneTrack = Sequence(
+ Wait(delayTime),
+ megaphoneShow,
+ Wait(1.0),
+ instrumentAppear,
+ Wait(3.0),
+ megaphoneHide,
+ )
+
+ tracks.append(megaphoneTrack)
+
+ # create the toon track
+ toonTrack = __createToonInterval(sound,delay,toon)
+
+ tracks.append(toonTrack)
+
+ # play the sound, no need to shrink instrument as it's already done
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+ if soundEffect:
+ delayTime = delay + tSound
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ soundTrack = Sequence(Wait(delayTime),
+ Parallel(attackTrack,SoundInterval(soundEffect, node=toon)))
+ tracks.append(soundTrack)
+ return tracks
+
+def __doOpera(sound, delay, toon, targets, level):
+ tracks = Parallel()
+
+
+ delay = delay
+
+ instrMin = Vec3(.001,.001,.001)
+ instrMax1 = Vec3(1.7,1.7,1.7)
+ instrMax1 *= INSTRUMENT_SCALE_MODIFIER
+ instrMax2 = Vec3(2.2,2.2,2.2)
+ instrMax2 *= INSTRUMENT_SCALE_MODIFIER
+ instrStretch = Vec3(.4,.4,.4)
+ instrStretch *= INSTRUMENT_SCALE_MODIFIER
+
+ # create the megaphone
+ megaphone = globalPropPool.getProp('megaphone')
+ megaphone2 = MovieUtil.copyProp(megaphone)
+ megaphones = [megaphone, megaphone2]
+
+ # create the fog horn instrument
+ instrument = globalPropPool.getProp('singing')
+ instrument2 = MovieUtil.copyProp(instrument)
+ instruments = [instrument, instrument2]
+ head = instrument2.find("**/opera_singer")
+ head.setPos(0,0,0)
+
+ # sets default values
+ def setInstrumentStats(instrument=instrument, instrument2=instrument2):
+ notify.debug("setInstrumentStats")
+ newPos = Vec3(-0.8, -0.9, .2)
+ newPos *= 1.3
+
+ instrument.setPos(newPos[0], newPos[1], newPos[2])
+ instrument.setHpr(145,0,90)
+ instrument.setScale(instrMin)
+
+ instrument2.setPos(newPos[0], newPos[1], newPos[2])
+ instrument2.setHpr(145,0,90)
+ instrument2.setScale(instrMin)
+
+
+
+ hands = toon.getRightHands()
+
+ megaphoneShow = Sequence(Func(MovieUtil.showProps, megaphones, hands),
+ Func(MovieUtil.showProps, instruments, hands),
+ Func(setInstrumentStats))
+ megaphoneHide = Sequence(Func(MovieUtil.removeProps, megaphones),
+ Func(MovieUtil.removeProps, instruments))
+
+ # instrument appears via a slow stretch and then a boing for humor
+ instrumentAppearSfx = globalBattleSoundCache.getSound(appearSoundFiles[level])
+ grow1 = getScaleBlendIntervals(instruments, duration=1, startScale=instrMin,
+ endScale=instrMax1, blendType = 'easeOut')
+ grow2 = getScaleBlendIntervals(instruments, duration=1.1, startScale=instrMax1,
+ endScale=instrMax2, blendType = 'easeIn')
+ shrink2 = getScaleIntervals(instruments, duration=.1, startScale=instrMax2,
+ endScale=instrMin)
+ instrumentAppear = Parallel(Sequence(grow1,grow2,Wait(6.0),shrink2),
+ Sequence(Wait(0.00),
+ SoundInterval(instrumentAppearSfx, node=toon)))
+
+ # instrument stretches when used, and spins a little bit. As it is a
+ # longer sound, when it's finished it merely vanishes
+ #stretchInstr = getScaleBlendIntervals(instruments, duration=3.3, startScale=instrMax2,
+ # endScale=instrStretch, blendType='easeOut')
+ #backInstr = getScaleBlendIntervals(instruments, duration=3.0, startScale=instrStretch,
+ # endScale=instrMin, blendType='easeIn')
+ #
+ #attackTrack = Parallel(Sequence(Wait(3.2)),
+ # Sequence(stretchInstr, Wait(1.5), backInstr))
+
+ hasLuredSuits =__hasLuredSuits(sound)
+ delayTime = delay
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ megaphoneTrack = Sequence(
+ Wait(delayTime),
+ megaphoneShow,
+ Wait(1.0),
+ instrumentAppear,
+ Wait(2.0),
+ megaphoneHide,
+ )
+
+ tracks.append(megaphoneTrack)
+
+
+
+ # create the toon track
+ toonTrack = __createToonInterval(sound,delay,toon, operaInstrument = instrument)
+ """
+ toonTrack = Sequence(
+ Wait(delay),
+ ActorInterval(toon, 'sound', playRate = 1.0, startTime=0.0,endTime=I1),
+ #Wait(3.0),
+ Func(setPosFromOther, sprayEffect, instrument, Point3(0,1.6,-0.18)),
+ __getPartTrack(sprayEffect, 0.0, 6.0, [sprayEffect, toon, 0], softStop = -3.5),
+ ActorInterval(toon, 'sound', playRate = 1.0, startTime=I1),
+ #Wait(3.0),
+ Func(toon.loop, 'neutral'),
+ )
+ """
+ tracks.append(toonTrack)
+
+ # play the sound, no need to shrink instrument as it's already done
+ soundEffect = globalBattleSoundCache.getSound(soundFiles[level])
+ #import pdb; pdb.set_trace()
+ if soundEffect:
+ delayTime = delay + tSound - 0.3
+ if hasLuredSuits:
+ delayTime += TIME_TO_WALK_BACK
+ soundTrack = Sequence(Wait(delayTime),
+ SoundInterval(soundEffect, node=toon),
+ #Parallel(attackTrack,SoundInterval(soundEffect, node=toon))
+ )
+ tracks.append(Sequence(Wait(0),))
+ tracks.append(soundTrack)
+ return tracks
+
+def setPosFromOther(dest, source, offset = Point3(0,0,0)):
+ #import pdb; pdb.set_trace()
+ #pos = source.getPos(render)
+ pos = render.getRelativePoint(source, offset)
+ #dest.setPos(render, pos)
+ dest.setPos(pos)
+ dest.reparentTo(render)
+
+# used for the two LOD instruments and to keep code clean
+def getScaleIntervals(props, duration, startScale, endScale):
+ tracks = Parallel()
+ for prop in props:
+ tracks.append(LerpScaleInterval(prop, duration, endScale,
+ startScale=startScale))
+ return tracks
+
+# same as above, but with the blendType added
+def getScaleBlendIntervals(props, duration, startScale, endScale, blendType):
+ tracks = Parallel()
+ for prop in props:
+ tracks.append(LerpScaleInterval(prop, duration, endScale,
+ startScale=startScale, blendType=blendType))
+ return tracks
+
+# array with the methods
+soundfn_array = (__doBikehorn, __doWhistle,
+ __doBugle, __doAoogah,
+ __doElephant, __doFoghorn, __doOpera)#UBER
+
+
+def __getPartTrack(particleEffect, startDelay, durationDelay, partExtraArgs, softStop = 0):
+ """ This function returns the default particle track for a suit attack
+ animation. Arguments:
+ startDelay = time delay before particle effect begins
+ durationDelay = time delay before particles are cleaned up
+ partExtraArgs = extraArgs for startParticleEffect function, the first
+ element of which is always the particle effect (function relies on this)
+ """
+ pEffect = partExtraArgs[0]
+ parent = partExtraArgs[1]
+ if (len(partExtraArgs) == 3):
+ worldRelative = partExtraArgs[2]
+ else:
+ worldRelative = 1
+ return Sequence(
+ Wait(startDelay),
+ ParticleInterval(pEffect, parent, worldRelative,
+ duration=durationDelay, cleanup = True, softStopT = softStop),
+ )
diff --git a/toontown/src/battle/MovieSquirt.py b/toontown/src/battle/MovieSquirt.py
new file mode 100644
index 0000000..8f1ebbb
--- /dev/null
+++ b/toontown/src/battle/MovieSquirt.py
@@ -0,0 +1,1234 @@
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from BattleSounds import *
+from toontown.toon.ToonDNA import *
+from toontown.suit.SuitDNA import *
+
+
+import MovieUtil
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+import BattleParticles
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import ToontownBattleGlobals
+import random
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieSquirt')
+
+hitSoundFiles = ('AA_squirt_flowersquirt.mp3',
+ 'AA_squirt_glasswater.mp3',
+ 'AA_squirt_neonwatergun.mp3',
+ 'AA_squirt_seltzer.mp3',
+ 'firehose_spray.mp3',
+ 'AA_throw_stormcloud.mp3',
+ 'AA_squirt_Geyser.mp3') #UBER
+
+missSoundFiles = ('AA_squirt_flowersquirt_miss.mp3',
+ 'AA_squirt_glasswater_miss.mp3',
+ 'AA_squirt_neonwatergun_miss.mp3',
+ 'AA_squirt_seltzer_miss.mp3',
+ 'firehose_spray.mp3',
+ 'AA_throw_stormcloud_miss.mp3',
+ 'AA_squirt_Geyser.mp3') #UBER
+
+sprayScales = [0.2, 0.3, 0.1, 0.6, 0.8, 1.0, 2.0] #UBER
+
+WaterSprayColor = Point4(0.75, 0.75, 1.0, 0.8)
+
+def doSquirts(squirts):
+ """ Squirts occur in the following order:
+ a) by suit, in order of increasing number of squirts per suit
+ 1) level 1 squirts, right to left, (TOON_SQUIRT_DELAY later)
+ 2) level 2 squirts, right to left, (TOON_SQUIRT_DELAY later)
+ 3) level 3 squirts, right to left, (TOON_SQUIRT_DELAY later)
+ etc.
+ b) next suit, (TOON_SQUIRT_SUIT_DELAY later)
+ """
+ if (len(squirts) == 0):
+ return (None, None)
+
+ # Group the squirts by targeted suit
+ suitSquirtsDict = {}
+ doneUber = 0
+ skip = 0
+
+ for squirt in squirts:
+ skip = 0
+ #if squirt["level"] >= ToontownBattleGlobals.UBER_GAG_LEVEL_INDEX:
+ # if doneUber:
+ # skip = 1
+ # doneUber = 1
+ if skip:
+ pass
+ else:
+ #print("squirt in squirts")
+ if type(squirt['target']) == type([]): #if target is a list
+ #for target in squirt['target']:
+ if 1:
+ target = squirt['target'][0]
+ suitId = target['suit'].doId
+ if (suitSquirtsDict.has_key(suitId)):
+ suitSquirtsDict[suitId].append(squirt)
+ else:
+ suitSquirtsDict[suitId] = [squirt]
+ else:
+ suitId = squirt['target']['suit'].doId
+ if (suitSquirtsDict.has_key(suitId)):
+ suitSquirtsDict[suitId].append(squirt)
+ else:
+ suitSquirtsDict[suitId] = [squirt]
+ suitSquirts = suitSquirtsDict.values()
+
+ # Sort the suits based on the number of squirts per suit
+ def compFunc(a, b):
+ if (len(a) > len(b)):
+ return 1
+ elif (len(a) < len(b)):
+ return -1
+ return 0
+ suitSquirts.sort(compFunc)
+ delay = 0.0
+ mtrack = Parallel()
+ for st in suitSquirts:
+ #print("st in suitSquirts")
+ if (len(st) > 0):
+ ival = __doSuitSquirts(st)
+ if (ival):
+ mtrack.append(Sequence(Wait(delay), ival))
+ delay = delay + TOON_SQUIRT_SUIT_DELAY
+
+ camDuration = mtrack.getDuration()
+ camTrack = MovieCamera.chooseSquirtShot(squirts, suitSquirtsDict,
+ camDuration)
+
+ return (mtrack, camTrack)
+
+def __doSuitSquirts(squirts):
+ """ __doSuitSquirts(squirts)
+ Create the intervals for the attacks on one suit.
+ 1 or more toons can squirt at the same target suit
+ Note: attacks are sorted by increasing level (as are toons)
+ Returns a multitrack with toon squirts in the following order:
+ 1) level 1 squirts, right to left, (TOON_SQUIRT_DELAY later)
+ 2) level 2 squirts, right to left, (TOON_SQUIRT_DELAY later)
+ etc.
+ """
+ uberClone = 0
+ toonTracks = Parallel()
+ delay = 0.0
+
+ # Determine how many times the suit is hit, if only once, play stun effect
+ if type(squirts[0]['target']) == type([]): #is target is a list?
+ for target in squirts[0]['target']:
+ if ((len(squirts) == 1) and (target['hp'] > 0)):
+ fShowStun = 1
+ else:
+ fShowStun = 0
+ else:
+ if ((len(squirts) == 1) and (squirts[0]['target']['hp'] > 0)):
+ fShowStun = 1
+ else:
+ fShowStun = 0
+ #import pdb; pdb.set_trace()
+ for s in squirts:
+ #print("s in squirts")
+ tracks = __doSquirt(s, delay, fShowStun, uberClone)
+ if s["level"] >= ToontownBattleGlobals.UBER_GAG_LEVEL_INDEX:
+ uberClone = 1
+ if tracks:
+ for track in tracks:
+ toonTracks.append(track)
+ delay = delay + TOON_SQUIRT_DELAY
+ return toonTracks
+
+def __doSquirt(squirt, delay, fShowStun, uberClone = 0):
+ #print("__doSquirt")
+ squirtSequence = Sequence(Wait(delay))
+ if type(squirt['target']) == type([]): #is target is a list?
+ for target in squirt['target']:
+ notify.debug('toon: %s squirts prop: %d at suit: %d for hp: %d' % \
+ (squirt['toon'].getName(), squirt['level'],
+ target['suit'].doId, target['hp']))
+ else:
+ notify.debug('toon: %s squirts prop: %d at suit: %d for hp: %d' % \
+ (squirt['toon'].getName(), squirt['level'],
+ squirt['target']['suit'].doId, squirt['target']['hp']))
+ if uberClone:
+ ival = squirtfn_array[squirt['level']](squirt, delay, fShowStun, uberClone)
+ if ival:
+ squirtSequence.append(ival)
+ else:
+ ival = squirtfn_array[squirt['level']](squirt, delay, fShowStun)
+ if ival:
+ squirtSequence.append(ival)
+ return [squirtSequence]
+
+def __suitTargetPoint(suit):
+ pnt = suit.getPos(render)
+ pnt.setZ(pnt[2] + suit.getHeight()*0.66)
+ return Point3(pnt)
+
+def __getSplashTrack(point, scale, delay, battle, splashHold=0.01):
+ # TODO: we should be using the real splash animated texture
+ # as soon as the visibility is fixed
+
+ def prepSplash(splash, point):
+ if callable(point):
+ point = point()
+ splash.reparentTo(render)
+ splash.setPos(point)
+ scale = splash.getScale()
+ splash.setBillboardPointWorld()
+ splash.setScale(scale)
+ splash = globalPropPool.getProp('splash-from-splat')
+ splash.setScale(scale)
+ return Sequence(
+ Func(battle.movie.needRestoreRenderProp, splash),
+ Wait(delay),
+ Func(prepSplash, splash, point),
+ ActorInterval(splash, 'splash-from-splat'),
+ Wait(splashHold),
+ Func(MovieUtil.removeProp, splash),
+ Func(battle.movie.clearRenderProp, splash))
+
+def __getSuitTrack(suit, tContact, tDodge, hp, hpbonus, kbbonus, anim,
+ died, leftSuits, rightSuits, battle, toon, fShowStun,
+ beforeStun = 0.5, afterStun = 1.8, geyser = 0, uberRepeat = 0, revived = 0):
+ if (hp > 0):
+ suitTrack = Sequence()
+ sival = ActorInterval(suit, anim)
+ sival = [] # Suit interval of its animation
+ if (kbbonus > 0) and not geyser: # If there's a knockback, then animate the suit falling back
+ suitPos, suitHpr = battle.getActorPosHpr(suit)
+ suitType = getSuitBodyType(suit.getStyleName())
+ animTrack = Sequence()
+ animTrack.append(ActorInterval(suit, anim, duration=0.2))
+ if (suitType == 'a'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=2.43))
+ elif (suitType == 'b'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=1.94))
+ elif (suitType == 'c'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=2.58))
+ # Be sure to unlure the suit so it doesn't walk back (already knocked back)
+ animTrack.append(Func(battle.unlureSuit, suit))
+ moveTrack = Sequence(
+ Wait(0.2),
+ LerpPosInterval(suit, 0.6, pos=suitPos, other=battle),
+ )
+ sival = Parallel(animTrack, moveTrack)
+ elif geyser:
+ #print("getting suit geyser track")
+ suitStartPos = suit.getPos()
+ suitFloat = Point3(0,0,14)
+ suitEndPos = Point3(suitStartPos[0] + suitFloat[0],
+ suitStartPos[1] + suitFloat[1],
+ suitStartPos[2] + suitFloat[2])
+ #sival = ActorInterval(suit, 'slip-backward', playRate = 0.50)
+ #sival = ActorInterval(suit, 'flail', playRate = 0.50)
+ suitType = getSuitBodyType(suit.getStyleName())
+ if suitType == 'a':
+ startFlailFrame = 16
+ endFlailFrame = 16#27
+
+ elif suitType == 'b':
+ startFlailFrame = 15
+ endFlailFrame = 15#22
+
+ else:
+ startFlailFrame = 15
+ endFlailFrame = 15#20
+
+
+ sival = Sequence(
+ ActorInterval(suit, 'slip-backward', playRate = 0.5, startFrame=0, endFrame=startFlailFrame-1),
+ Func(suit.pingpong, 'slip-backward', fromFrame=startFlailFrame, toFrame=endFlailFrame),
+ Wait(0.5),
+ ActorInterval(suit, 'slip-backward', playRate = 1.00, startFrame = endFlailFrame),
+ )
+
+ sUp = LerpPosInterval(suit, 1.1,
+ suitEndPos,
+ startPos=suitStartPos,
+ fluid = 1)
+ sDown = LerpPosInterval(suit, 0.6,
+ suitStartPos,
+ startPos=suitEndPos,
+ fluid = 1)
+
+ else:
+ if fShowStun == 1:
+ sival = Parallel(
+ ActorInterval(suit, anim),
+ MovieUtil.createSuitStunInterval(
+ suit, beforeStun, afterStun),
+ )
+ else:
+ sival = ActorInterval(suit, anim)
+ showDamage = Func(suit.showHpText, -hp, openEnded=0, attackTrack=SQUIRT_TRACK)
+ updateHealthBar = Func(suit.updateHealthBar, hp)
+ suitTrack.append(Wait(tContact))
+ suitTrack.append(showDamage)
+ suitTrack.append(updateHealthBar)
+ if not geyser:
+ suitTrack.append(sival)
+ else:
+ if not uberRepeat:
+ geyserMotion = Sequence(sUp, Wait(0.0), sDown)
+ suitLaunch = Parallel(sival, geyserMotion)
+ suitTrack.append(suitLaunch)
+ else:
+ suitTrack.append(Wait(5.5))
+
+ # Make a bonus track for any hp bonus
+ bonusTrack = Sequence(Wait(tContact))
+ if (kbbonus > 0):
+ bonusTrack.append(Wait(0.75))
+ bonusTrack.append(Func(suit.showHpText, -kbbonus, 2, openEnded=0, attackTrack=SQUIRT_TRACK))
+ if (hpbonus > 0):
+ bonusTrack.append(Wait(0.75))
+ bonusTrack.append(Func(suit.showHpText, -hpbonus, 1, openEnded=0, attackTrack=SQUIRT_TRACK))
+ if (died != 0):
+ suitTrack.append(MovieUtil.createSuitDeathTrack(suit, toon, battle))
+ else:
+ suitTrack.append(Func(suit.loop, 'neutral'))
+ if (revived != 0):
+ suitTrack.append(MovieUtil.createSuitReviveTrack(suit, toon, battle))
+
+ return Parallel(suitTrack, bonusTrack)
+
+ else:
+ # suit dodges
+ # other suits may need to dodge as well
+ return MovieUtil.createSuitDodgeMultitrack(tDodge, suit,
+ leftSuits, rightSuits)
+
+def say(statement):
+ print(statement)
+
+def __getSoundTrack(level, hitSuit, delay, node=None):
+ #level: the level of attack, int 0-5
+ #hitSuit: does the attack hit toon, bool
+ #delay: time delay before playing sound
+
+ if hitSuit:
+ soundEffect = globalBattleSoundCache.getSound(hitSoundFiles[level])
+ else:
+ soundEffect = globalBattleSoundCache.getSound(missSoundFiles[level])
+
+ soundTrack = Sequence()
+ if soundEffect:
+ soundTrack.append(Wait(delay))
+ soundTrack.append(SoundInterval(soundEffect, node=node))
+
+ return soundTrack
+
+def __doFlower(squirt, delay, fShowStun):
+ toon = squirt['toon']
+ level = squirt['level']
+ hpbonus = squirt['hpbonus']
+ target = squirt['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ battle = squirt['battle']
+ suitPos = suit.getPos(battle)
+ origHpr = toon.getHpr(battle)
+ hitSuit = (hp > 0)
+
+ scale = sprayScales[level]
+
+ tTotalFlowerToonAnimationTime = 2.5 # hardcoded by the animation
+ tFlowerFirstAppears = 1.0 # set the flower to appear after this time
+ dFlowerScaleTime = 0.5 # total time it will take flower to scale up/dwn
+
+ tSprayStarts = tTotalFlowerToonAnimationTime
+ dSprayScale = 0.2
+ dSprayHold = 0.1
+ tContact = tSprayStarts + dSprayScale
+ tSuitDodges = tTotalFlowerToonAnimationTime
+
+ tracks = Parallel()
+
+ button = globalPropPool.getProp('button')
+ button2 = MovieUtil.copyProp(button)
+ buttons = [button, button2]
+ hands = toon.getLeftHands()
+
+ toonTrack = Sequence(
+ Func(MovieUtil.showProps, buttons, hands),
+ Func(toon.headsUp, battle, suitPos),
+ ActorInterval(toon, 'pushbutton'),
+ Func(MovieUtil.removeProps, buttons),
+ Func(toon.loop, 'neutral'),
+ Func(toon.setHpr, battle, origHpr),
+ )
+
+ tracks.append(toonTrack)
+
+ # create sound track
+ tracks.append(__getSoundTrack(level, hitSuit, tTotalFlowerToonAnimationTime-0.4, toon))
+
+ # show the flower
+ flower = globalPropPool.getProp('squirting-flower')
+ flower.setScale(1.5, 1.5, 1.5)
+
+ # prepare the spray
+ targetPoint = lambda suit=suit: __suitTargetPoint(suit)
+
+ def getSprayStartPos(flower=flower):
+ # flower is parented to LOD 0 path, so make sure this LOD is being animated
+ # before we get the flower position
+ # should I parent to LOD 2 instead to reduce expense of update()?
+
+ #note: dont have to call toon.pose() since getSprayStartPos isnt called
+ # until the exact frame we need the flower pos
+ toon.update(0) # force update of LOD 0 to current animation frame (dont know if it is being displayed or not)
+ return flower.getPos(render)
+
+ sprayTrack = MovieUtil.getSprayTrack(
+ battle, WaterSprayColor,
+ getSprayStartPos, targetPoint,
+ dSprayScale, dSprayHold, dSprayScale,
+ horizScale = scale, vertScale = scale)
+ lodnames = toon.getLODNames()
+
+ # dont bother with LOD 2 intervals for now since viewer will be very far away flower will be too small to see
+ toonlod0 = toon.getLOD(lodnames[0])
+ toonlod1 = toon.getLOD(lodnames[1])
+ if base.config.GetBool('want-new-anims', 1):
+ if not toonlod0.find('**/def_joint_attachFlower').isEmpty():
+ flower_joint0 = toonlod0.find('**/def_joint_attachFlower')
+ else:
+ flower_joint0 = toonlod0.find('**/joint_attachFlower')
+
+ if base.config.GetBool('want-new-anims', 1):
+ if not toonlod1.find('**/def_joint_attachFlower').isEmpty():
+ flower_joint1 = toonlod1.find('**/def_joint_attachFlower')
+ else:
+ flower_joint1 = toonlod1.find('**/joint_attachFlower')
+
+ # scale flower only once, use instances to create nodepaths attaching
+ # all LOD joints to the 1 flower
+ flower_jointpath0 = flower_joint0.attachNewNode('attachFlower-InstanceNode')
+ flower_jointpath1 = flower_jointpath0.instanceTo(flower_joint1)
+
+ # show the flower
+ flowerTrack = Sequence( # this series of intervals will take tTotalFlowerToonAnimationTime
+ # wait
+ Wait(tFlowerFirstAppears),
+
+ # parent the flower to the toon joint LOD 0 only (this is used by getSprayStartPos)
+ Func(flower.reparentTo, flower_jointpath0),
+ # scale the flower up
+ LerpScaleInterval(flower, dFlowerScaleTime,
+ flower.getScale(), startScale=MovieUtil.PNT3_NEARZERO),
+ # wait until toon presses the button
+ Wait(tTotalFlowerToonAnimationTime-dFlowerScaleTime-tFlowerFirstAppears),
+ )
+
+ if hp <= 0:
+ # If we're going to miss, give the suit a chance to dodge.
+ flowerTrack.append(Wait(0.5))
+ flowerTrack.append(sprayTrack) # Add in the spray
+ # Now scale down and delete the flower
+ flowerTrack.append(LerpScaleInterval(flower, dFlowerScaleTime, MovieUtil.PNT3_NEARZERO))
+
+ # must get rid of stuff created by attachNewNode and instanceTo
+ # eventually would like to just create these once and parent to joint at load time
+ flowerTrack.append(Func(flower_jointpath1.removeNode))
+ flowerTrack.append(Func(flower_jointpath0.removeNode))
+ flowerTrack.append(Func(MovieUtil.removeProp, flower))
+
+ tracks.append(flowerTrack)
+
+ # do the splash
+ if hp > 0:
+ tracks.append(__getSplashTrack(targetPoint, scale,
+ tSprayStarts + dSprayScale, battle))
+
+ # Do the suit reaction
+ # If the suit takes damage (hp > 0) or this squirt is the first one to strike (delay = 0)
+ # then we naturally add a suit track, otherwise we don't so that a suit won't dodge
+ # multiple times on sequential squirts in the same attack
+ if hp > 0 or delay <= 0:
+ tracks.append(__getSuitTrack(suit, tContact, tSuitDodges, hp,
+ hpbonus, kbbonus, 'squirt-small-react',
+ died, leftSuits, rightSuits, battle,
+ toon, fShowStun, revived = revived))
+
+ return tracks
+
+def __doWaterGlass(squirt, delay, fShowStun):
+ toon = squirt['toon']
+ level = squirt['level']
+ hpbonus = squirt['hpbonus']
+ target = squirt['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ battle = squirt['battle']
+ suitPos = suit.getPos(battle)
+ origHpr = toon.getHpr(battle)
+ hitSuit = (hp > 0)
+
+ scale = sprayScales[level]
+
+ dGlassHold = 5.
+ dGlassScale = 0.5
+ tSpray = (82. / toon.getFrameRate('spit'))
+ sprayPoseFrame = 88
+ dSprayScale = 0.1
+ dSprayHold = 0.1
+ tContact = tSpray + dSprayScale
+ tSuitDodges = max(tSpray - 0.5, 0.)
+
+ tracks = Parallel()
+
+ # toon
+ tracks.append(ActorInterval(toon, 'spit'))
+
+ # sound
+ soundTrack = __getSoundTrack(level, hitSuit, 1.7, toon)
+ tracks.append(soundTrack)
+
+ glass = globalPropPool.getProp('glass')
+ hands = toon.getRightHands()
+
+ # scale glass only once, use instances to create nodepaths attaching
+ # all LOD hand joints to the 1 glass. would like to do this once at init time eventually
+ hand_jointpath0 = hands[0].attachNewNode('handJoint0-path')
+ hand_jointpath1 = hand_jointpath0.instanceTo(hands[1])
+
+ glassTrack = Sequence(
+ Func(MovieUtil.showProp, glass, hand_jointpath0),
+ ActorInterval(glass,'glass'),
+ # must get rid of stuff created by attachNewNode and instanceTo
+ # eventually would like to just create these once and parent to joint at load time
+ Func(hand_jointpath1.removeNode),
+ Func(hand_jointpath0.removeNode),
+ Func(MovieUtil.removeProp, glass),
+ )
+ tracks.append(glassTrack)
+
+ # do the spray
+ targetPoint = lambda suit=suit: __suitTargetPoint(suit)
+ def getSprayStartPos(toon=toon):
+ toon.update(0) # force update of LOD 0 to current animation frame (dont know if it is being displayed or not)
+ lod0 = toon.getLOD(toon.getLODNames()[0])
+
+ if base.config.GetBool('want-new-anims', 1):
+ if not lod0.find("**/def_head").isEmpty():
+ joint = lod0.find("**/def_head")
+ else:
+ joint = lod0.find("**/joint_head")
+ else:
+ joint = lod0.find("**/joint_head")
+
+ n = hidden.attachNewNode('pointInFrontOfHead')
+ n.reparentTo(toon)
+ n.setPos(joint.getPos(toon) + Point3(0, 0.3, -0.2)) # TODO: adjust this based on snout length
+ p = n.getPos(render)
+ n.removeNode()
+ del n
+ return p
+
+ sprayTrack = MovieUtil.getSprayTrack(
+ battle, WaterSprayColor,
+ getSprayStartPos, targetPoint,
+ dSprayScale, dSprayHold, dSprayScale,
+ horizScale = scale, vertScale = scale)
+
+ tracks.append(Sequence(Wait(tSpray), sprayTrack))
+
+ # do the splash
+ if hp > 0:
+ tracks.append(__getSplashTrack(targetPoint, scale,
+ tSpray + dSprayScale, battle))
+
+ # Do the suit reaction
+ # If the suit takes damage (hp > 0) or this squirt is the first one to strike (delay = 0)
+ # then we naturally add a suit track, otherwise we don't so that a suit won't dodge
+ # multiple times on sequential squirts in the same attack
+ if hp > 0 or delay <= 0:
+ tracks.append(__getSuitTrack(suit, tContact, tSuitDodges, hp,
+ hpbonus, kbbonus, 'squirt-small-react',
+ died, leftSuits, rightSuits, battle,
+ toon, fShowStun, revived = revived))
+
+ return tracks
+
+def __doWaterGun(squirt, delay, fShowStun):
+ toon = squirt['toon']
+ level = squirt['level']
+ hpbonus = squirt['hpbonus']
+ target = squirt['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ battle = squirt['battle']
+ suitPos = suit.getPos(battle)
+ origHpr = toon.getHpr(battle)
+ hitSuit = (hp > 0)
+
+ scale = sprayScales[level]
+
+ tPistol = 0.
+ dPistolScale = 0.5
+ dPistolHold = 1.8
+ tSpray = 48.0 / toon.getFrameRate('water-gun')
+ sprayPoseFrame = 63
+ dSprayScale = 0.1
+ dSprayHold = 0.3
+ tContact = tSpray + dSprayScale
+ tSuitDodges = 1.1
+
+ tracks = Parallel()
+
+ # animate the toon
+ toonTrack = Sequence(
+ Func(toon.headsUp, battle, suitPos),
+ ActorInterval(toon, 'water-gun'),
+ Func(toon.loop, 'neutral'),
+ Func(toon.setHpr, battle, origHpr),
+ )
+ tracks.append(toonTrack)
+
+ # add the sound
+ soundTrack = __getSoundTrack(level, hitSuit, 1.8, toon)
+ tracks.append(soundTrack)
+
+ # prepare the water pistol
+ pistol = globalPropPool.getProp('water-gun')
+ hands = toon.getRightHands()
+
+ # scale gun only once, use instances to create nodepaths attaching
+ # all LOD hand joints to the 1 gun
+ hand_jointpath0 = hands[0].attachNewNode('handJoint0-path')
+ hand_jointpath1 = hand_jointpath0.instanceTo(hands[1])
+
+ # prepare the spray
+ targetPoint = lambda suit=suit: __suitTargetPoint(suit)
+
+ def getSprayStartPos(pistol=pistol, toon=toon):
+ #note: dont have to call toon.pose() since getSprayStartPos isnt called
+ # until the exact frame we need the posn, so the animation has already been updated
+ toon.update(0) # force update of LOD 0 to current animation frame (dont know if it is being displayed or not)
+ joint = pistol.find("**/joint_nozzle")
+ p = joint.getPos(render)
+ return p
+
+ sprayTrack = MovieUtil.getSprayTrack(
+ battle, WaterSprayColor,
+ getSprayStartPos, targetPoint,
+ dSprayScale, dSprayHold, dSprayScale,
+ horizScale = scale, vertScale = scale)
+
+ # Must include the spray intervals with the gun
+ # to avoid parenting to a null node
+ pistolPos = Point3(0.28, 0.10, 0.08)
+ pistolHpr = VBase3(85.60, -4.44, 94.43)
+
+ pistolTrack = Sequence(
+ Func(MovieUtil.showProp,
+ pistol, hand_jointpath0, pistolPos, pistolHpr),
+ LerpScaleInterval(pistol, dPistolScale, pistol.getScale(),
+ startScale=MovieUtil.PNT3_NEARZERO),
+ Wait(tSpray-dPistolScale), # Wait before spraying
+ )
+
+ pistolTrack.append(sprayTrack)
+ pistolTrack.append(Wait(dPistolHold))# Wait before losing the pistol
+ pistolTrack.append(LerpScaleInterval(pistol, dPistolScale, MovieUtil.PNT3_NEARZERO))
+
+ # must get rid of stuff created by attachNewNode and instanceTo
+ # eventually would like to just create these once and parent to joint at load time
+ pistolTrack.append(Func(hand_jointpath1.removeNode))
+ pistolTrack.append(Func(hand_jointpath0.removeNode))
+ pistolTrack.append(Func(MovieUtil.removeProp, pistol))
+
+ tracks.append(pistolTrack)
+
+ if hp > 0: # do the splash
+ tracks.append(__getSplashTrack(targetPoint, 0.3,
+ tSpray + dSprayScale, battle))
+
+ # Do the suit reaction
+ # If the suit takes damage (hp > 0) or this squirt is the first one to strike (delay = 0)
+ # then we naturally add a suit track, otherwise we don't so that a suit won't dodge
+ # multiple times on sequential squirts in the same attack
+ if hp > 0 or delay <= 0:
+ tracks.append(__getSuitTrack(suit, tContact, tSuitDodges, hp,
+ hpbonus, kbbonus, 'squirt-small-react',
+ died, leftSuits, rightSuits, battle,
+ toon, fShowStun, revived = revived))
+
+ return tracks
+
+def __doSeltzerBottle(squirt, delay, fShowStun):
+ toon = squirt['toon']
+ level = squirt['level']
+ hpbonus = squirt['hpbonus']
+ target = squirt['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ battle = squirt['battle']
+ suitPos = suit.getPos(battle)
+ origHpr = toon.getHpr(battle)
+ hitSuit = (hp > 0)
+
+ scale = sprayScales[level]
+
+ tBottle = 0.
+ dBottleScale = 0.5
+ dBottleHold = 3.
+ tSpray = (53.0 / toon.getFrameRate('hold-bottle')) + 0.05
+ dSprayScale = 0.2
+ dSprayHold = 0.1
+ tContact = tSpray + dSprayScale
+ tSuitDodges = max(tContact - 0.7, 0.)
+
+ tracks = Parallel()
+
+ # animate the toon
+ toonTrack = Sequence(
+ Func(toon.headsUp, battle, suitPos),
+ ActorInterval(toon, 'hold-bottle'),
+ Func(toon.loop, 'neutral'),
+ Func(toon.setHpr, battle, origHpr),
+ )
+ tracks.append(toonTrack)
+
+ # add sound
+ soundTrack = __getSoundTrack(level, hitSuit, tSpray - 0.1, toon)
+ tracks.append(soundTrack)
+
+ # do the seltzer bottle
+ bottle = globalPropPool.getProp('bottle')
+ hands = toon.getRightHands()
+
+ # make the spray interval
+ targetPoint = lambda suit=suit: __suitTargetPoint(suit)
+ def getSprayStartPos(bottle=bottle, toon=toon):
+ #note: dont have to call toon.pose() since getSprayStartPos isnt called
+ # until the exact frame we need the posn, so the animation has already been updated
+ toon.update(0) # force update of LOD 0 to current animation frame (dont know if it is being displayed or not)
+ joint = bottle.find("**/joint_toSpray")
+ n = hidden.attachNewNode('pointBehindSprayProp')
+ n.reparentTo(toon)
+ n.setPos(joint.getPos(toon) + Point3(0, -0.4, 0))
+ p = n.getPos(render)
+ n.removeNode()
+ del n
+ return p
+
+ sprayTrack = MovieUtil.getSprayTrack(
+ battle, WaterSprayColor,
+ getSprayStartPos, targetPoint,
+ dSprayScale, dSprayHold, dSprayScale,
+ horizScale = scale, vertScale = scale)
+
+ # scale bottle only once, use instances to create nodepaths attaching
+ # all LOD hand joints to the 1 bottle
+ hand_jointpath0 = hands[0].attachNewNode('handJoint0-path')
+ hand_jointpath1 = hand_jointpath0.instanceTo(hands[1])
+
+ bottleTrack = Sequence(
+ Func(MovieUtil.showProp, bottle, hand_jointpath0),
+ LerpScaleInterval(bottle, dBottleScale,
+ bottle.getScale(), startScale=MovieUtil.PNT3_NEARZERO),
+ Wait(tSpray-dBottleScale), # Wait before spraying
+ )
+
+ bottleTrack.append(sprayTrack)
+ bottleTrack.append(Wait(dBottleHold)) # Wait before losing the bottle
+ bottleTrack.append(LerpScaleInterval(bottle, dBottleScale, MovieUtil.PNT3_NEARZERO))
+
+ # must get rid of stuff created by attachNewNode and instanceTo
+ # eventually would like to just create these once and parent to joint at load time
+ bottleTrack.append(Func(hand_jointpath1.removeNode))
+ bottleTrack.append(Func(hand_jointpath0.removeNode))
+ bottleTrack.append(Func(MovieUtil.removeProp, bottle))
+
+ tracks.append(bottleTrack)
+
+ # do the splash
+ if hp > 0:
+ tracks.append(__getSplashTrack(targetPoint, scale,
+ tSpray + dSprayScale, battle))
+
+ # Do the suit reaction
+ # If the suit takes damage (hp > 0) or this squirt is the first one to strike (delay = 0)
+ # then we naturally add a suit track, otherwise we don't so that a suit won't dodge
+ # multiple times on sequential squirts in the same attack
+ if (hp > 0 or delay <= 0) and suit:
+ tracks.append(__getSuitTrack(suit, tContact, tSuitDodges, hp,
+ hpbonus, kbbonus, 'squirt-small-react',
+ died, leftSuits, rightSuits, battle,
+ toon, fShowStun, revived = revived))
+
+ return tracks
+
+def __doFireHose(squirt, delay, fShowStun):
+ toon = squirt['toon']
+ level = squirt['level']
+ hpbonus = squirt['hpbonus']
+ target = squirt['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ battle = squirt['battle']
+ suitPos = suit.getPos(battle)
+ origHpr = toon.getHpr(battle)
+ hitSuit = (hp > 0)
+ scale = 0.3
+
+ tAppearDelay = 0.7
+ dHoseHold = 0.7
+ dAnimHold = 5.1
+ tSprayDelay = 2.8
+ tSpray = 0.2
+ dSprayScale = 0.1
+ dSprayHold = 1.8
+ tContact = 2.9
+ tSuitDodges = 2.1
+
+ tracks = Parallel()
+
+ # animate the toon
+ toonTrack = Sequence(
+ Wait(tAppearDelay), # Wait a bit for the hydrant and hose to appear
+ Func(toon.headsUp, battle, suitPos),
+ ActorInterval(toon, 'firehose'),
+ Func(toon.loop, 'neutral'),
+ Func(toon.setHpr, battle, origHpr),
+ )
+ tracks.append(toonTrack)
+
+ # add sound
+ soundTrack = __getSoundTrack(level, hitSuit, tSprayDelay, toon)
+ tracks.append(soundTrack)
+
+ # prepare the fire hose and hydrant.
+ hose = globalPropPool.getProp('firehose')
+ hydrant = globalPropPool.getProp('hydrant')
+ hose.reparentTo(hydrant)
+ hose.pose('firehose', 2),
+
+ # Create a node to inherit any animal scale and cheesy effect
+ # scale.
+ hydrantNode = toon.attachNewNode('hydrantNode')
+ hydrantNode.clearTransform(toon.getGeomNode().getChild(0))
+
+ # And another node to animate the scale.
+ hydrantScale = hydrantNode.attachNewNode('hydrantScale')
+ hydrant.reparentTo(hydrantScale)
+
+ # Temporarily pose the toon to the action frame so we can get the
+ # transform from his legs. This allows us to account for leg
+ # style and cheesy effect legs.
+ toon.pose('firehose', 30)
+ toon.update(0)
+ torso = toon.getPart('torso', '1000')
+
+ # The destination Z value of the hydrant depends on the torso.
+ if toon.style.torso[0] == 'm':
+ # Medium torso, down here
+ hydrant.setPos(torso, 0, 0, -1.85)
+ else:
+ # Small or large torso, up here
+ hydrant.setPos(torso, 0, 0, -1.45)
+
+ hydrant.setPos(0, 0, hydrant.getZ())
+
+ base = hydrant.find('**/base')
+ base.setColor(1, 1, 1, 0.5)
+ base.setPos(toon, 0, 0, 0)
+
+ toon.loop('neutral')
+
+ # prepare the spray and create its intervals
+ targetPoint = lambda suit=suit: __suitTargetPoint(suit)
+ def getSprayStartPos(hose=hose, toon=toon, targetPoint=targetPoint):
+ toon.update(0) # force update of LOD 0 to current animation frame (dont know if it is being displayed or not)
+ # If the hose is not present for the spray, effectively hide the spray by
+ # making the origin the same as the target
+ if (hose.isEmpty() == 1):
+ if callable(targetPoint):
+ return targetPoint()
+ else:
+ return targetPoint
+
+ joint = hose.find("**/joint_water_stream")
+ n = hidden.attachNewNode('pointBehindSprayProp')
+ n.reparentTo(toon)
+ # Now push the point back behind the nozzle for when the gun moves back
+ n.setPos(joint.getPos(toon) + Point3(0, -0.55, 0))
+ p = n.getPos(render)
+ n.removeNode()
+ del n
+ return p
+
+ sprayTrack = Sequence()
+ sprayTrack.append(Wait(tSprayDelay))
+ sprayTrack.append(MovieUtil.getSprayTrack(
+ battle, WaterSprayColor,
+ getSprayStartPos, targetPoint, dSprayScale, dSprayHold,
+ dSprayScale, horizScale = scale, vertScale = scale))
+ tracks.append(sprayTrack)
+
+ # Unparent the hydrant for now; we'll put it back in when the
+ # movie starts.
+ hydrantNode.detachNode()
+
+ propTrack = Sequence(# Use firehose, hydrant, and spray in the same track
+ Func(battle.movie.needRestoreRenderProp, hydrantNode),
+ Func(hydrantNode.reparentTo, toon),
+
+ LerpScaleInterval(hydrantScale, tAppearDelay*0.5, Point3(1, 1, 1.4),
+ startScale=Point3(1, 1, 0.01),
+ ),
+ LerpScaleInterval(hydrantScale, tAppearDelay*0.3, Point3(1, 1, 0.8),
+ startScale=Point3(1, 1, 1.4),
+ ),
+ LerpScaleInterval(hydrantScale, tAppearDelay*0.1, Point3(1, 1, 1.2),
+ startScale=Point3(1, 1, 0.8),
+ ),
+ LerpScaleInterval(hydrantScale, tAppearDelay*0.1, Point3(1, 1, 1),
+ startScale=Point3(1, 1, 1.2),
+ ),
+
+ ActorInterval(hose, 'firehose', duration=dAnimHold), # Animate hose
+ Wait(dHoseHold-0.2), # Wait before losing the hose
+ LerpScaleInterval(hydrantScale, 0.2, Point3(1, 1, 0.01),
+ startScale=Point3(1, 1, 1),
+ ),
+
+ Func(MovieUtil.removeProps, [hydrantNode, hose]),
+ Func(battle.movie.clearRenderProp, hydrantNode),
+ )
+ tracks.append(propTrack)
+
+ if hp > 0:
+ tracks.append(__getSplashTrack(targetPoint, 0.4, 2.7, battle, splashHold=1.5))
+
+ # Do the suit reaction
+ # If the suit takes damage (hp > 0) or this squirt is the first one to strike (delay = 0)
+ # then we naturally add a suit track, otherwise we don't so that a suit won't dodge
+ # multiple times on sequential squirts in the same attack
+ if hp > 0 or delay <= 0:
+ tracks.append(__getSuitTrack(suit, tContact, tSuitDodges, hp,
+ hpbonus, kbbonus, 'squirt-small-react',
+ died, leftSuits, rightSuits, battle,
+ toon, fShowStun, revived = revived))
+
+
+ return tracks
+
+def __doStormCloud(squirt, delay, fShowStun):
+ toon = squirt['toon']
+ level = squirt['level']
+ hpbonus = squirt['hpbonus']
+ target = squirt['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ battle = squirt['battle']
+ suitPos = suit.getPos(battle)
+ origHpr = toon.getHpr(battle)
+ hitSuit = (hp > 0)
+ scale = sprayScales[level]
+
+ tButton = 0.
+ dButtonScale = 0.5
+ dButtonHold = 3.
+ tContact = 2.9
+ tSpray = 1
+ tSuitDodges = 1.8
+
+ tracks = Parallel()
+
+ # add sound
+ soundTrack = __getSoundTrack(level, hitSuit, 2.3, toon)
+ soundTrack2 = __getSoundTrack(level, hitSuit, 4.6, toon)
+ tracks.append(soundTrack)
+ tracks.append(soundTrack2)
+
+ # do the button
+ button = globalPropPool.getProp('button')
+ button2 = MovieUtil.copyProp(button)
+ buttons = [button, button2]
+ hands = toon.getLeftHands()
+
+ toonTrack = Sequence(
+ Func(MovieUtil.showProps, buttons, hands),
+ Func(toon.headsUp, battle, suitPos),
+ ActorInterval(toon, 'pushbutton'),
+ Func(MovieUtil.removeProps, buttons),
+ Func(toon.loop, 'neutral'),
+ Func(toon.setHpr, battle, origHpr),
+ )
+ tracks.append(toonTrack)
+
+ # do the storm cloud and raining effect
+ cloud = globalPropPool.getProp('stormcloud')
+ cloud2 = MovieUtil.copyProp(cloud)
+ # cloud = MovieUtil.copyProp(toon.cloudActors[0])
+ # cloud2 = MovieUtil.copyProp(toon.cloudActors[1])
+ BattleParticles.loadParticles()
+ trickleEffect = BattleParticles.createParticleEffect(file='trickleLiquidate')
+ rainEffect = BattleParticles.createParticleEffect(file='liquidate')
+ rainEffect2 = BattleParticles.createParticleEffect(file='liquidate')
+ rainEffect3 = BattleParticles.createParticleEffect(file='liquidate')
+ cloudHeight = suit.height + 3
+ cloudPosPoint = Point3(0, 0, cloudHeight)
+ scaleUpPoint = Point3(3, 3, 3)
+ rainEffects = [rainEffect, rainEffect2, rainEffect3]
+ rainDelay = 1
+ effectDelay = 0.3
+
+ # The cloud rains for much longer when it actually hits
+ if hp > 0:
+ cloudHold = 4.7
+ else:
+ cloudHold = 1.7
+
+ def getCloudTrack(cloud, suit, cloudPosPoint, scaleUpPoint, rainEffects, rainDelay,
+ effectDelay, cloudHold, useEffect, battle=battle,
+ trickleEffect=trickleEffect):
+ track = Sequence(
+ Func(MovieUtil.showProp, cloud, suit, cloudPosPoint),
+ Func(cloud.pose, 'stormcloud', 0),
+ LerpScaleInterval(cloud, 1.5, scaleUpPoint,
+ startScale=MovieUtil.PNT3_NEARZERO),
+ Wait(rainDelay), # Wait until button is pushed to rain
+ )
+
+ if (useEffect == 1):
+ ptrack = Parallel()
+ delay = trickleDuration = cloudHold * 0.25
+ trickleTrack = Sequence(
+ Func(battle.movie.needRestoreParticleEffect, trickleEffect),
+ ParticleInterval(trickleEffect, cloud, worldRelative=0,
+ duration=trickleDuration, cleanup = True),
+ Func(battle.movie.clearRestoreParticleEffect, trickleEffect)
+ )
+ track.append(trickleTrack)
+ for i in range(0, 3):
+ dur = cloudHold - 2*trickleDuration
+ ptrack.append(Sequence(
+ Func(battle.movie.needRestoreParticleEffect,
+ rainEffects[i]),
+ Wait(delay),
+ ParticleInterval(rainEffects[i],
+ cloud, worldRelative=0, duration=dur, cleanup = True),
+ Func(battle.movie.clearRestoreParticleEffect,
+ rainEffects[i]),
+ ))
+ delay += effectDelay
+ ptrack.append(Sequence(Wait(3 * effectDelay),
+ ActorInterval(cloud, 'stormcloud',
+ startTime=1, duration=cloudHold)))
+ track.append(ptrack)
+ else:
+ track.append(ActorInterval(cloud, 'stormcloud', startTime=1,
+ duration=cloudHold))
+
+ track.append(LerpScaleInterval(cloud, 0.5, MovieUtil.PNT3_NEARZERO))
+ track.append(Func(MovieUtil.removeProp, cloud))
+ return track
+
+ tracks.append(getCloudTrack(cloud, suit, cloudPosPoint,
+ scaleUpPoint, rainEffects, rainDelay,
+ effectDelay, cloudHold, useEffect=1))
+ tracks.append(getCloudTrack(cloud2, suit, cloudPosPoint,
+ scaleUpPoint, rainEffects, rainDelay,
+ effectDelay, cloudHold, useEffect=0))
+
+ # Do the suit reaction
+ # If the suit takes damage (hp > 0) or this squirt is the first one to strike (delay = 0)
+ # then we naturally add a suit track, otherwise we don't so that a suit won't dodge
+ # multiple times on sequential squirts in the same attack
+ if hp > 0 or delay <= 0:
+ tracks.append(__getSuitTrack(suit, tContact, tSuitDodges, hp,
+ hpbonus, kbbonus, 'soak', died,
+ leftSuits, rightSuits, battle, toon,
+ fShowStun, beforeStun = 2.6,
+ afterStun = 2.3, revived = revived))
+
+ return tracks
+
+def __doGeyser(squirt, delay, fShowStun, uberClone = 0):
+ #print("__doGeyser %s" % (uberClone))
+ toon = squirt['toon']
+ level = squirt['level']
+ hpbonus = squirt['hpbonus']
+ tracks = Parallel()
+ tButton = 0.
+ dButtonScale = 0.5
+ dButtonHold = 3.
+ tContact = 2.9
+ tSpray = 1
+ tSuitDodges = 1.8
+
+ # do the button
+ button = globalPropPool.getProp('button')
+ button2 = MovieUtil.copyProp(button)
+ buttons = [button, button2]
+ hands = toon.getLeftHands()
+ battle = squirt['battle']
+ origHpr = toon.getHpr(battle)
+ suit = squirt['target'][0]['suit']
+ suitPos = suit.getPos(battle)
+
+ toonTrack = Sequence(
+ Func(MovieUtil.showProps, buttons, hands),
+ Func(toon.headsUp, battle, suitPos),
+ ActorInterval(toon, 'pushbutton'),
+ Func(MovieUtil.removeProps, buttons),
+ Func(toon.loop, 'neutral'),
+ Func(toon.setHpr, battle, origHpr),
+ )
+ tracks.append(toonTrack)
+
+ for target in squirt['target']:
+ #target = squirt['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ suitPos = suit.getPos(battle)
+ hitSuit = (hp > 0)
+ scale = sprayScales[level]
+
+ # add sound
+ soundTrack = __getSoundTrack(level, hitSuit, 1.8, toon)
+ #soundTrack2 = __getSoundTrack(level, hitSuit, 4.6, toon)
+ delayTime = random.random()
+ tracks.append(Wait(delayTime))
+ tracks.append(soundTrack)
+ #tracks.append(soundTrack2)
+
+ # do the storm cloud and raining effect
+ cloud = globalPropPool.getProp('geyser')
+ cloud2 = MovieUtil.copyProp(cloud)
+ # cloud = MovieUtil.copyProp(toon.cloudActors[0])
+ # cloud2 = MovieUtil.copyProp(toon.cloudActors[1])
+ BattleParticles.loadParticles()
+ #trickleEffect = BattleParticles.createParticleEffect(file='trickleLiquidate')
+ #rainEffect = BattleParticles.createParticleEffect(file='liquidate')
+ #rainEffect2 = BattleParticles.createParticleEffect(file='liquidate')
+ #rainEffect3 = BattleParticles.createParticleEffect(file='liquidate')
+ #geyserHeight = 0.0 #suit.height + 3
+ geyserHeight = battle.getH()
+ geyserPosPoint = Point3(0, 0, geyserHeight)
+ scaleUpPoint = Point3(1.8, 1.8, 1.8)
+ rainEffects = []#rainEffect, rainEffect2, rainEffect3]
+ rainDelay = 2.5
+ effectDelay = 0.3
+
+ # The geyser goes for much longer when it actually hits
+ if hp > 0:
+ geyserHold = 1.5
+ else:
+ geyserHold = 0.5
+
+ def getGeyserTrack(geyser, suit, geyserPosPoint, scaleUpPoint, rainEffects, rainDelay,
+ effectDelay, geyserHold, useEffect, battle=battle):
+ #,trickleEffect=trickleEffect):
+
+ geyserMound = MovieUtil.copyProp(geyser)
+ geyserRemoveM = geyserMound.findAllMatches("**/Splash*")
+ geyserRemoveM.addPathsFrom(geyserMound.findAllMatches("**/spout"))
+ for i in range(geyserRemoveM.getNumPaths()):
+ geyserRemoveM[i].removeNode()
+ #geyserMound.wrtReparentTo(render)
+
+
+ geyserWater = MovieUtil.copyProp(geyser)
+ geyserRemoveW = geyserWater.findAllMatches("**/hole")
+ geyserRemoveW.addPathsFrom(geyserWater.findAllMatches("**/shadow"))
+ #import pdb; pdb.set_trace()
+ for i in range(geyserRemoveW.getNumPaths()):
+ geyserRemoveW[i].removeNode()
+ #geyserWater.wrtReparentTo(render)
+
+ #import pdb; pdb.set_trace()
+ track = Sequence(
+ Wait(rainDelay), # Wait until button is pushed to rain
+ #Wait(delayTime),
+ #Func(MovieUtil.showProp, geyserMound, suit, geyserPosPoint),
+ #Func(MovieUtil.showProp, geyserWater, suit, geyserPosPoint),
+ Func(MovieUtil.showProp, geyserMound, battle, suit.getPos(battle)),
+ Func(MovieUtil.showProp, geyserWater, battle, suit.getPos(battle)),
+ LerpScaleInterval(geyserWater, 1.0, scaleUpPoint,
+ startScale=MovieUtil.PNT3_NEARZERO),
+ Wait(geyserHold * 0.5),
+ LerpScaleInterval(geyserWater, 0.5, MovieUtil.PNT3_NEARZERO,
+ startScale=scaleUpPoint),
+ )
+
+ track.append(LerpScaleInterval(geyserMound, 0.5, MovieUtil.PNT3_NEARZERO))
+ track.append(Func(MovieUtil.removeProp, geyserMound))
+ track.append(Func(MovieUtil.removeProp, geyserWater))
+ track.append(Func(MovieUtil.removeProp, geyser))
+ return track
+
+ if not uberClone:
+ tracks.append(Sequence(Wait(delayTime),
+ getGeyserTrack(cloud, suit, geyserPosPoint,
+ scaleUpPoint, rainEffects, rainDelay,
+ effectDelay, geyserHold, useEffect=1),
+ ))
+ # tracks.append(getGeyserTrack(cloud2, suit, cloudPosPoint,
+ # scaleUpPoint, rainEffects, rainDelay,
+ # effectDelay, cloudHold, useEffect=0))
+
+ # Do the suit reaction
+ # If the suit takes damage (hp > 0) or this squirt is the first one to strike (delay = 0)
+ # then we naturally add a suit track, otherwise we don't so that a suit won't dodge
+ # multiple times on sequential squirts in the same attack
+ if hp > 0 or delay <= 0:
+ tracks.append(Sequence(Wait(delayTime),
+ __getSuitTrack(suit, tContact, tSuitDodges, hp,
+ hpbonus, kbbonus, 'soak', died,
+ leftSuits, rightSuits, battle, toon,
+ fShowStun, beforeStun = 2.6,
+ afterStun = 2.3, geyser = 1, uberRepeat = uberClone, revived = revived),))
+
+ return tracks
+
+squirtfn_array = (__doFlower, __doWaterGlass,
+ __doWaterGun, __doSeltzerBottle,
+ __doFireHose, __doStormCloud,
+ __doGeyser) #UBER
+
+
diff --git a/toontown/src/battle/MovieSuitAttacks.py b/toontown/src/battle/MovieSuitAttacks.py
new file mode 100644
index 0000000..37bcad4
--- /dev/null
+++ b/toontown/src/battle/MovieSuitAttacks.py
@@ -0,0 +1,4891 @@
+from toontown.toonbase.ToontownGlobals import *
+from SuitBattleGlobals import *
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from toontown.suit.SuitDNA import *
+from BattleBase import *
+from BattleSounds import *
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+import MovieUtil
+from direct.particles import ParticleEffect
+import BattleParticles
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieSuitAttacks')
+
+def __doDamage(toon, dmg, died):
+ assert(notify.debug('__doDamage(toon: %s damage: %d died: %d' % (toon.getName(), dmg, died)))
+
+ if (dmg > 0 and toon.hp != None):
+ toon.takeDamage(dmg)
+
+def __showProp(prop, parent, pos, hpr=None, scale=None):
+ prop.reparentTo(parent)
+ prop.setPos(pos)
+ if hpr:
+ prop.setHpr(hpr)
+ if scale:
+ prop.setScale(scale)
+
+def __animProp(prop, propName, propType='actor'):
+ if 'actor' == propType:
+ prop.play(propName)
+ elif 'model' == propType:
+ pass
+ else:
+ self.notify.error("No such propType as: %s" % propType)
+
+def __suitFacePoint(suit, zOffset = 0):
+ pnt = suit.getPos()
+ pnt.setZ(pnt[2] + suit.shoulderHeight + 0.3 + zOffset)
+ return Point3(pnt)
+
+def __toonFacePoint(toon, zOffset = 0, parent=render):
+ pnt = toon.getPos(parent)
+ pnt.setZ(pnt[2] + toon.shoulderHeight + 0.3 + zOffset)
+ return Point3(pnt)
+
+def __toonTorsoPoint(toon, zOffset = 0):
+ pnt = toon.getPos()
+ pnt.setZ(pnt[2] + toon.shoulderHeight - 0.2)
+ return Point3(pnt)
+
+def __toonGroundPoint(attack, toon, zOffset = 0, parent=render):
+ pnt = toon.getPos(parent)
+ battle = attack['battle']
+ pnt.setZ(battle.getZ(parent) + zOffset)
+ return Point3(pnt)
+
+def __toonGroundMissPoint(attack, prop, toon, zOffset = 0):
+ # The ground miss point is 5 feet beyond the toon, on the ground
+ point = __toonMissPoint(prop, toon)
+ battle = attack['battle']
+ point.setZ(battle.getZ() + zOffset)
+ return Point3(point)
+
+def __toonMissPoint(prop, toon, yOffset = 0, parent=None):
+ # The miss point is 5 feet beyond the toon.
+ if parent:
+ p = __toonFacePoint(toon) - prop.getPos(parent)
+ else:
+ p = __toonFacePoint(toon) - prop.getPos()
+
+ v = Vec3(p)
+ baseDistance = v.length()
+ v.normalize()
+
+ if parent:
+ endPos = prop.getPos(parent) + (v * (baseDistance + 5 + yOffset))
+ else:
+ endPos = prop.getPos() + (v * (baseDistance + 5 + yOffset))
+ return Point3(endPos)
+
+def __toonMissBehindPoint(toon, parent=render, offset=0):
+ point = toon.getPos(parent)
+ point.setY(point.getY() - 5 + offset)
+ return point
+
+def __throwBounceHitPoint(prop, toon):
+ startPoint = prop.getPos()
+ endPoint = __toonFacePoint(toon)
+ return __throwBouncePoint(startPoint, endPoint)
+
+def __throwBounceMissPoint(prop, toon):
+ startPoint = prop.getPos()
+ endPoint = __toonFacePoint(toon)
+ return __throwBouncePoint(startPoint, endPoint)
+
+def __throwBouncePoint(startPoint, endPoint):
+ midPoint = startPoint + ((endPoint - startPoint) / 2.0)
+ # The bounce point is on the ground
+ midPoint.setZ(0)
+ return Point3(midPoint)
+
+
+def doSuitAttack(attack):
+ """ doSuitAttack(attack)
+ """
+ notify.debug('building suit attack in doSuitAttack: %s' % attack['name'])
+ name = attack['id']
+ if (name == AUDIT):
+ suitTrack = doAudit(attack)
+ elif (name == BITE):
+ suitTrack = doBite(attack)
+ elif (name == BOUNCE_CHECK):
+ suitTrack = doBounceCheck(attack)
+ elif (name == BRAIN_STORM):
+ suitTrack = doBrainStorm(attack)
+ elif (name == BUZZ_WORD):
+ suitTrack = doBuzzWord(attack)
+ elif (name == CALCULATE):
+ suitTrack = doCalculate(attack)
+ elif (name == CANNED):
+ suitTrack = doCanned(attack)
+ elif (name == CHOMP):
+ suitTrack = doChomp(attack)
+ elif (name == CIGAR_SMOKE):
+ suitTrack = doDefault(attack)
+# suitTrack = doCigarSmoke(attack)
+ elif (name == CLIPON_TIE):
+ suitTrack = doClipOnTie(attack)
+ elif (name == CRUNCH):
+ suitTrack = doCrunch(attack)
+ elif (name == DEMOTION):
+ suitTrack = doDemotion(attack)
+ elif (name == DOUBLE_TALK):
+ suitTrack = doDoubleTalk(attack)
+ elif (name == DOWNSIZE):
+ suitTrack = doDownsize(attack)
+ elif (name == EVICTION_NOTICE):
+ suitTrack = doEvictionNotice(attack)
+ elif (name == EVIL_EYE):
+ suitTrack = doEvilEye(attack)
+ elif (name == FILIBUSTER):
+ suitTrack = doFilibuster(attack)
+ elif (name == FILL_WITH_LEAD):
+ suitTrack = doFillWithLead(attack)
+ elif (name == FINGER_WAG):
+ suitTrack = doFingerWag(attack)
+ elif (name == FIRED):
+ suitTrack = doFired(attack)
+ elif (name == FIVE_O_CLOCK_SHADOW):
+ suitTrack = doDefault(attack)
+# suitTrack = doFiveOClockShadow(attack)
+ elif (name == FLOOD_THE_MARKET):
+ suitTrack = doDefault(attack)
+# suitTrack = doFloodTheMarket(attack)
+ elif (name == FOUNTAIN_PEN):
+ suitTrack = doFountainPen(attack)
+ elif (name == FREEZE_ASSETS):
+ suitTrack = doFreezeAssets(attack)
+ elif (name == GAVEL):
+ suitTrack = doDefault(attack)
+# suitTrack = doGavel(attack)
+ elif (name == GLOWER_POWER):
+ suitTrack = doGlowerPower(attack)
+ elif (name == GUILT_TRIP):
+ suitTrack = doGuiltTrip(attack)
+ elif (name == HALF_WINDSOR):
+ suitTrack = doHalfWindsor(attack)
+ elif (name == HANG_UP):
+ suitTrack = doHangUp(attack)
+ elif (name == HEAD_SHRINK):
+ suitTrack = doHeadShrink(attack)
+ elif (name == HOT_AIR):
+ suitTrack = doHotAir(attack)
+ elif (name == JARGON):
+ suitTrack = doJargon(attack)
+ elif (name == LEGALESE):
+ suitTrack = doLegalese(attack)
+ elif (name == LIQUIDATE):
+ suitTrack = doLiquidate(attack)
+ elif (name == MARKET_CRASH):
+ suitTrack = doMarketCrash(attack)
+ elif (name == MUMBO_JUMBO):
+ suitTrack = doMumboJumbo(attack)
+ elif (name == PARADIGM_SHIFT):
+ suitTrack = doParadigmShift(attack)
+ elif (name == PECKING_ORDER):
+ suitTrack = doPeckingOrder(attack)
+ elif (name == PICK_POCKET):
+ suitTrack = doPickPocket(attack)
+ elif (name == PINK_SLIP):
+ suitTrack = doPinkSlip(attack)
+ elif (name == PLAY_HARDBALL):
+ suitTrack = doPlayHardball(attack)
+ elif (name == POUND_KEY):
+ suitTrack = doPoundKey(attack)
+ elif (name == POWER_TIE):
+ suitTrack = doPowerTie(attack)
+ elif (name == POWER_TRIP):
+ suitTrack = doPowerTrip(attack)
+ elif (name == QUAKE):
+ suitTrack = doQuake(attack)
+ elif (name == RAZZLE_DAZZLE):
+ suitTrack = doRazzleDazzle(attack)
+ elif (name == RED_TAPE):
+ suitTrack = doRedTape(attack)
+ elif (name == RE_ORG):
+ suitTrack = doReOrg(attack)
+ elif (name == RESTRAINING_ORDER):
+ suitTrack = doRestrainingOrder(attack)
+ elif (name == ROLODEX):
+ suitTrack = doRolodex(attack)
+ elif (name == RUBBER_STAMP):
+ suitTrack = doRubberStamp(attack)
+ elif (name == RUB_OUT):
+ suitTrack = doRubOut(attack)
+ elif (name == SACKED):
+ suitTrack = doSacked(attack)
+ elif (name == SANDTRAP):
+ suitTrack = doDefault(attack)
+# suitTrack = doSandtrap(attack)
+ elif (name == SCHMOOZE):
+ suitTrack = doSchmooze(attack)
+ elif (name == SHAKE):
+ suitTrack = doShake(attack)
+ elif (name == SHRED):
+ suitTrack = doShred(attack)
+ elif (name == SONG_AND_DANCE):
+ suitTrack = doDefault(attack)
+# suitTrack = doSongAndDance(attack)
+ elif (name == SPIN):
+ suitTrack = doSpin(attack)
+ elif (name == SYNERGY):
+ suitTrack = doSynergy(attack)
+ elif (name == TABULATE):
+ suitTrack = doTabulate(attack)
+ elif (name == TEE_OFF):
+ suitTrack = doTeeOff(attack)
+ elif (name == THROW_BOOK):
+ suitTrack = doDefault(attack)
+# suitTrack = doThrowBook(attack)
+ elif (name == TREMOR):
+ suitTrack = doTremor(attack)
+ elif (name == WATERCOOLER):
+ suitTrack = doWatercooler(attack)
+ elif (name == WITHDRAWAL):
+ suitTrack = doWithdrawal(attack)
+ elif (name == WRITE_OFF):
+ suitTrack = doWriteOff(attack)
+ else:
+ notify.warning('unknown attack: %d substituting Finger Wag' % name)
+ suitTrack = doDefault(attack)
+
+ assert(suitTrack != None)
+ camTrack = MovieCamera.chooseSuitShot(attack, suitTrack.getDuration())
+
+ # The toons should face the center of the battle after each attack
+ battle = attack['battle']
+ target = attack['target']
+ groupStatus = attack['group']
+ if groupStatus == ATK_TGT_SINGLE:
+ toon = target['toon']
+ toonHprTrack = Sequence(
+ Func(toon.headsUp, battle, MovieUtil.PNT3_ZERO),
+ Func(toon.loop, 'neutral'),
+ )
+ else:
+ toonHprTrack = Parallel()
+ for t in target:
+ toon = t['toon']
+ toonHprTrack.append(Sequence(
+ Func(toon.headsUp, battle, MovieUtil.PNT3_ZERO),
+ Func(toon.loop, 'neutral'),
+ ))
+
+ suit = attack['suit']
+ neutralIval = Func(suit.loop, 'neutral')
+ suitTrack = Sequence(suitTrack, neutralIval, toonHprTrack)
+
+ # !!! If the suit is currently in a lured position (slightly forward), then we must
+ # first reset the position before performing the attack
+ suitPos = suit.getPos(battle)
+ resetPos, resetHpr = battle.getActorPosHpr(suit)
+
+ if (battle.isSuitLured(suit)): # If suit is lured, reset its pos
+ resetTrack = getResetTrack(suit, battle)
+ resetSuitTrack = Sequence(resetTrack, suitTrack)
+ # Must incorporate a wait in the camTrack to account for the resetting time
+ # and also explicitly unlure the suit
+ waitTrack = Sequence(
+ Wait(resetTrack.getDuration()),
+ Func(battle.unlureSuit, suit),
+ )
+ resetCamTrack = Sequence(waitTrack, camTrack)
+ return (resetSuitTrack, resetCamTrack)
+ else:
+ return (suitTrack, camTrack)
+
+def getResetTrack(suit, battle):
+ resetPos, resetHpr = battle.getActorPosHpr(suit)
+ moveDist = Vec3(suit.getPos(battle) - resetPos).length()
+ moveDuration = 0.5
+# moveDuration = (moveDist / BattleBase.suitSpeed) # Too short a time interval to see
+ walkTrack = Sequence(
+ # First face the right direction, then walk backwards
+ Func(suit.setHpr, battle, resetHpr),
+ ActorInterval(suit, 'walk', startTime=1, duration=moveDuration,
+ endTime=0.00001),
+ Func(suit.loop, 'neutral')
+ )
+ # Actually move the suit
+ moveTrack = LerpPosInterval(suit, moveDuration, resetPos, other=battle)
+ return Parallel(walkTrack, moveTrack)
+
+def __makeCancelledNodePath():
+ # Make the text node
+ tn = TextNode("CANCELLED")
+ tn.setFont(getSuitFont())
+ tn.setText(TTLocalizer.MovieSuitCancelled)
+ tn.setAlign(TextNode.ACenter)
+
+ # Make the top node path
+ tntop = hidden.attachNewNode("CancelledTop")
+ # Make a node path for the text node
+ tnpath = tntop.attachNewNode(tn)
+ tnpath.setPosHpr(0, 0, 0, 0, 0, 0)
+ tnpath.setScale(1)
+ tnpath.setColor(0.7, 0, 0, 1)
+ # Make a backside node path for the text node
+ tnpathback = tnpath.instanceUnderNode(tntop, "backside")
+ tnpathback.setPosHpr(0, 0, 0, 180, 0, 0)
+ tnpath.setScale(1)
+ return tntop
+
+def doDefault(attack):
+ """ doDefault(attack)
+ """
+ notify.debug('building suit attack in doDefault')
+
+ suitName = attack['suitName']
+ if (suitName == 'f'):
+ attack['id'] = POUND_KEY
+ attack['name'] = 'PoundKey'
+ attack['animName'] = 'phone'
+ return doPoundKey(attack)
+ elif (suitName == 'p'):
+ attack['id'] = FOUNTAIN_PEN
+ attack['name'] = 'FountainPen'
+ attack['animName'] = 'pen-squirt'
+ return doFountainPen(attack)
+ elif (suitName == 'ym'):
+ attack['id'] = RUBBER_STAMP
+ attack['name'] = 'RubberStamp'
+ attack['animName'] = 'rubber-stamp'
+ return doRubberStamp(attack)
+ elif (suitName == 'mm'):
+ attack['id'] = FINGER_WAG
+ attack['name'] = 'FingerWag'
+ attack['animName'] = 'finger-wag'
+ return doFingerWag(attack)
+ elif (suitName == 'ds'):
+ attack['id'] = DEMOTION
+ attack['name'] = 'Demotion'
+ attack['animName'] = 'magic1'
+ return doDemotion(attack)
+ elif (suitName == 'hh'):
+ attack['id'] = GLOWER_POWER
+ attack['name'] = 'GlowerPower'
+ attack['animName'] = 'glower'
+ return doGlowerPower(attack)
+ elif (suitName == 'cr'):
+ attack['id'] = PICK_POCKET
+ attack['name'] = 'PickPocket'
+ attack['animName'] = 'pickpocket'
+ return doPickPocket(attack)
+ elif (suitName == 'tbc'):
+ attack['id'] = GLOWER_POWER
+ attack['name'] = 'GlowerPower'
+ attack['animName'] = 'glower'
+ return doGlowerPower(attack)
+ elif (suitName == 'cc'):
+ attack['id'] = POUND_KEY
+ attack['name'] = 'PoundKey'
+ attack['animName'] = 'phone'
+ return doPoundKey(attack)
+ elif (suitName == 'tm'):
+ attack['id'] = CLIPON_TIE
+ attack['name'] = 'ClipOnTie'
+ attack['animName'] = 'throw-paper'
+ return doClipOnTie(attack)
+ elif (suitName == 'nd'):
+ attack['id'] = PICK_POCKET
+ attack['name'] = 'PickPocket'
+ attack['animName'] = 'pickpocket'
+ return doPickPocket(attack)
+ elif (suitName == 'gh'):
+ attack['id'] = FOUNTAIN_PEN
+ attack['name'] = 'FountainPen'
+ attack['animName'] = 'pen-squirt'
+ return doFountainPen(attack)
+ elif (suitName == 'ms'):
+ attack['id'] = BRAIN_STORM
+ attack['name'] = 'BrainStorm'
+ attack['animName'] = 'effort'
+ return doBrainStorm(attack)
+ elif (suitName == 'tf'):
+ attack['id'] = RED_TAPE
+ attack['name'] = 'RedTape'
+ attack['animName'] = 'throw-object'
+ return doRedTape(attack)
+ elif (suitName == 'm'):
+ attack['id'] = BUZZ_WORD
+ attack['name'] = 'BuzzWord'
+ attack['animName'] = 'speak'
+ return doBuzzWord(attack)
+ elif (suitName == 'mh'):
+ attack['id'] = RAZZLE_DAZZLE
+ attack['name'] = 'RazzleDazzle'
+ attack['animName'] = 'smile'
+ return doRazzleDazzle(attack)
+ elif (suitName == 'sc'):
+ attack['id'] = WATERCOOLER
+ attack['name'] = 'Watercooler'
+ attack['animName'] = 'water-cooler'
+ return doWatercooler(attack)
+ elif (suitName == 'pp'):
+ attack['id'] = BOUNCE_CHECK
+ attack['name'] = 'BounceCheck'
+ attack['animName'] = 'throw-paper'
+ return doBounceCheck(attack)
+ elif (suitName == 'tw'):
+ attack['id'] = GLOWER_POWER
+ attack['name'] = 'GlowerPower'
+ attack['animName'] = 'glower'
+ return doGlowerPower(attack)
+ elif (suitName == 'bc'):
+ attack['id'] = AUDIT
+ attack['name'] = 'Audit'
+ attack['animName'] = 'phone'
+ return doAudit(attack)
+ elif (suitName == 'nc'):
+ attack['id'] = RED_TAPE
+ attack['name'] = 'RedTape'
+ attack['animName'] = 'throw-object'
+ return doRedTape(attack)
+ elif (suitName == 'mb'):
+ attack['id'] = LIQUIDATE
+ attack['name'] = 'Liquidate'
+ attack['animName'] = 'magic1'
+ return doLiquidate(attack)
+ elif (suitName == 'ls'):
+ attack['id'] = WRITE_OFF
+ attack['name'] = 'WriteOff'
+ attack['animName'] = 'hold-pencil'
+ return doWriteOff(attack)
+ elif (suitName == 'rb'):
+ attack['id'] = TEE_OFF
+ attack['name'] = 'TeeOff'
+ attack['animName'] = 'golf-club-swing'
+ return doTeeOff(attack)
+ elif (suitName == 'bf'):
+ attack['id'] = RUBBER_STAMP
+ attack['name'] = 'RubberStamp'
+ attack['animName'] = 'rubber-stamp'
+ return doRubberStamp(attack)
+ elif (suitName == 'b'):
+ attack['id'] = EVICTION_NOTICE
+ attack['name'] = 'EvictionNotice'
+ attack['animName'] = 'throw-paper'
+ return doEvictionNotice(attack)
+ elif (suitName == 'dt'):
+ attack['id'] = RUBBER_STAMP
+ attack['name'] = 'RubberStamp'
+ attack['animName'] = 'rubber-stamp'
+ return doRubberStamp(attack)
+ elif (suitName == 'ac'):
+ attack['id'] = RED_TAPE
+ attack['name'] = 'RedTape'
+ attack['animName'] = 'throw-object'
+ return doRedTape(attack)
+ elif (suitName == 'bs'):
+ attack['id'] = FINGER_WAG
+ attack['name'] = 'FingerWag'
+ attack['animName'] = 'finger-wag'
+ return doFingerWag(attack)
+ elif (suitName == 'sd'):
+ attack['id'] = WRITE_OFF
+ attack['name'] = 'WriteOff'
+ attack['animName'] = 'hold-pencil'
+ return doWriteOff(attack)
+ elif (suitName == 'le'):
+ attack['id'] = JARGON
+ attack['name'] = 'Jargon'
+ attack['animName'] = 'speak'
+ return doJargon(attack)
+ elif (suitName == 'bw'):
+ attack['id'] = FINGER_WAG
+ attack['name'] = 'FingerWag'
+ attack['animName'] = 'finger-wag'
+ return doFingerWag(attack)
+ else:
+ self.notify.error('doDefault() - unsupported suit type: %s' % \
+ suitName)
+ return None
+
+#######
+# Begin encapsulating sub functions for suit attack functions
+#######
+
+def getSuitTrack(attack, delay=0.000001, splicedAnims=None):
+ """ This function returns the standard suit Track of face up, animate, face up."""
+ suit = attack['suit']
+ battle = attack['battle']
+ tauntIndex = attack['taunt']
+ target = attack['target']
+ toon = target['toon']
+ targetPos = toon.getPos(battle)
+ taunt = getAttackTaunt(attack['name'], tauntIndex)
+ trapStorage = {}
+ trapStorage['trap'] = None
+
+ track = Sequence(# Start building the intervals for the suit track
+ Wait(delay), # Optionally wait to start animation
+ Func(suit.setChatAbsolute, taunt, CFSpeech | CFTimeout), # Use taunt text
+ )
+
+ # If suit has a trap prop, be sure to reparent it to the battle and not the
+ # suit so it doesn't turn with him when he attacks
+ def reparentTrap(suit=suit, battle=battle, trapStorage=trapStorage):
+ trapProp = suit.battleTrapProp
+ if (trapProp != None):
+ trapProp.wrtReparentTo(battle)
+ trapStorage['trap'] = trapProp
+
+ track.append(Func(reparentTrap))
+ track.append(Func(suit.headsUp, battle, targetPos))
+
+ # Some attacks used spliced animations instead of just the default animation
+ if splicedAnims:
+ track.append(getSplicedAnimsTrack(splicedAnims, actor=suit))
+ else:
+ track.append(ActorInterval(suit, attack['animName'])) # Attack animation
+
+ origPos, origHpr = battle.getActorPosHpr(suit) # To restore original hpr
+ track.append(Func(suit.setHpr, battle, origHpr))
+
+ # Now return the trapProp to the suit
+ def returnTrapToSuit(suit=suit, trapStorage=trapStorage):
+ trapProp = trapStorage['trap']
+ if (trapProp != None):
+ if trapProp.getName() == 'traintrack':
+ notify.debug('deliberately not parenting traintrack to suit')
+ else:
+ trapProp.wrtReparentTo(suit)
+ suit.battleTrapProp = trapProp
+ track.append(Func(returnTrapToSuit))
+
+ track.append(Func(suit.clearChat))
+ return track
+
+
+def getSuitAnimTrack(attack, delay=0):
+ """ This function returns just the attack animation track for a suit """
+ suit = attack['suit']
+ tauntIndex = attack['taunt']
+ taunt = getAttackTaunt(attack['name'], tauntIndex)
+
+ return Sequence(
+ Wait(delay),
+ # Note: we needn't try to reparent a suit's trap to the battle with this
+ # function (as seen in getSuitTrack) since here we never face the target
+ # toon (used for group attacks where the suit addresses everyone
+ Func(suit.setChatAbsolute, taunt, CFSpeech | CFTimeout),
+ ActorInterval(attack['suit'], attack['animName']),
+ Func(suit.clearChat),
+ )
+
+
+def getPartTrack(particleEffect, startDelay, durationDelay, partExtraArgs):
+ """ This function returns the default particle track for a suit attack
+ animation. Arguments:
+ startDelay = time delay before particle effect begins
+ durationDelay = time delay before particles are cleaned up
+ partExtraArgs = extraArgs for startParticleEffect function, the first
+ element of which is always the particle effect (function relies on this)
+ """
+ particleEffect = partExtraArgs[0]
+ parent = partExtraArgs[1]
+ if (len(partExtraArgs) > 2):
+ worldRelative = partExtraArgs[2]
+ else:
+ worldRelative=1
+ return Sequence (
+ Wait(startDelay),
+ ParticleInterval(particleEffect, parent, worldRelative,
+ duration=durationDelay, cleanup = True),
+ )
+
+
+def getToonTrack(attack, damageDelay=0.000001, damageAnimNames=None, dodgeDelay=0.0001,
+ dodgeAnimNames=None, splicedDamageAnims=None, splicedDodgeAnims=None,
+ target=None, showDamageExtraTime=0.01, showMissedExtraTime=0.5):
+ """ This function returns the default Track for a toon portraying whether it is
+ hit or missed. Arguments:
+ attack = suit attack against the toon(s)
+ damageDelay = time delay before damage animation occurs
+ damageAnimNames = names of damage animations to perform in order
+ dodgeDelay = time delay before dodge animation occurs
+ dodgeAnimNames = names of dodge animations to perform in order
+ splicedDamageAnims = can use spliced actor intervals from getSplicedAnimsTrack
+ splicedDodgeAnims = can use spliced actor intervals from getSplieedAnims
+ target = can specify a target for the toon track
+ showMissedExtraTime = can add extra time onto the dodge delay to wait before
+ showing a missed indicator if applicable
+ Note: This function uses damageAnimNames and dodgeAnimNames if they exist. So do
+ not provide these arguments if you want to use splicedDamageAnims and
+ splicedDodgeAnims. Must have one or the other of these two lists.
+ """
+ if not target: # if a target is not provided, use default target in attack
+ target = attack['target']
+ toon = target['toon']
+ battle = attack['battle']
+ suit = attack['suit']
+ suitPos = suit.getPos(battle)
+ dmg = target['hp']
+
+ animTrack = Sequence()
+ animTrack.append(Func(toon.headsUp, battle, suitPos))
+
+ if (dmg > 0): # aka (dmg > 0), or if hitToon
+ animTrack.append(getToonTakeDamageTrack(toon, target['died'], dmg, damageDelay,
+ damageAnimNames, splicedDamageAnims, showDamageExtraTime))
+ return animTrack
+ else:
+ animTrack.append(getToonDodgeTrack(target, dodgeDelay, dodgeAnimNames,
+ splicedDodgeAnims, showMissedExtraTime))
+ indicatorTrack = Sequence(
+ Wait(dodgeDelay + showMissedExtraTime),
+ Func(MovieUtil.indicateMissed, toon),
+ )
+ return Parallel(animTrack, indicatorTrack)
+
+def getToonTracks(attack, damageDelay=0.000001, damageAnimNames=None, dodgeDelay=0.000001,
+ dodgeAnimNames=None, splicedDamageAnims=None, splicedDodgeAnims=None,
+ showDamageExtraTime=0.01, showMissedExtraTime=0.5):
+ """ This function returns the default Tracks for a group of toons portraying if
+ hit or missed. Arguments identical to getToonTrack(), except that this function
+ intentionally uses the optional 'target' keyword argument to cycle through multiple
+ targets for a group attack. Note: The target variable in the attack dictionary
+ will (and should) be a list if using a group attack. Therefore this function
+ should never be used for single attacks (use getToonTrack)."""
+ toonTracks = Parallel()
+ targets = attack['target']
+ for i in range(len(targets)):
+ tgt = targets[i]
+ # slevel = a['suit'].getActualLevel()
+ # print "ZZZZ processing attack ", i," damage=", tgt['hp']," toondied=", tgt['died']," suitlevel=", slevel
+ toonTracks.append(getToonTrack(attack, damageDelay, damageAnimNames,
+ dodgeDelay, dodgeAnimNames, splicedDamageAnims, splicedDodgeAnims,
+ target=tgt, showDamageExtraTime=showDamageExtraTime,
+ showMissedExtraTime=showMissedExtraTime))
+ return toonTracks
+
+def getToonDodgeTrack(target, dodgeDelay, dodgeAnimNames,
+ splicedDodgeAnims, showMissedExtraTime):
+ """ This function returns a track for a toon dodging an attack """
+
+ toon = target['toon']
+ toonTrack = Sequence()
+ toonTrack.append(Wait(dodgeDelay))
+ if dodgeAnimNames: # If exists, use these animations
+ for d in dodgeAnimNames: # Use each dodge animation in order
+ # If the dodge is a sidestep, other toons may need to get out of the way
+ if (d == 'sidestep'):
+ toonTrack.append(getAllyToonsDodgeParallel(target))
+ else:
+ toonTrack.append(ActorInterval(toon, d))
+ else:
+ toonTrack.append(getSplicedAnimsTrack(splicedDodgeAnims, actor=toon))
+
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ return toonTrack
+
+
+def getAllyToonsDodgeParallel(target):
+ """ This function creates a multitrack to portray both a target toon dodging and the
+ ally toons of a target toon dodging out of the way if neccessary """
+ toon = target['toon']
+ leftToons = target['leftToons']
+ rightToons = target['rightToons']
+
+ # If there are toons on the left, we then decide which side to dodge towards based on
+ # least resistance, the same formula used by the suits
+ if len(leftToons) > len(rightToons):
+ # Path of Least/Most Resistance
+ PoLR = rightToons
+ PoMR = leftToons
+ else:
+ PoLR = leftToons
+ PoMR = rightToons
+
+ # most of the time, choose the side with the least avatars
+ # base the random choice on the difference between the
+ # number of avatars on the left versus the right
+ upper = 1 + (4 * abs(len(leftToons) - len(rightToons)))
+ if (random.randint(0, upper) > 0):
+ toonDodgeList = PoLR
+ else:
+ toonDodgeList = PoMR
+ if toonDodgeList is leftToons:
+ sidestepAnim = 'sidestep-left'
+ soundEffect = globalBattleSoundCache.getSound('AV_side_step.mp3')
+ else:
+ sidestepAnim = 'sidestep-right'
+ soundEffect = globalBattleSoundCache.getSound('AV_jump_to_side.mp3')
+
+ # Make the other toons dodge
+ toonTracks = Parallel()
+ for t in toonDodgeList:
+ toonTracks.append(Sequence(ActorInterval(t, sidestepAnim),
+ Func(t.loop, 'neutral'),
+ ))
+ # finally, make the target toon dodge
+ toonTracks.append(Sequence(ActorInterval(toon, sidestepAnim),
+ Func(toon.loop, 'neutral'),
+ ))
+
+ # Include a sound track in with the toon tracks
+ toonTracks.append(Sequence(Wait(0.5),
+ SoundInterval(soundEffect, node=toon),
+ ))
+
+ return toonTracks
+
+
+def getPropTrack(prop, parent, posPoints, appearDelay, remainDelay,
+ scaleUpPoint=Point3(1), scaleUpTime=0.5, scaleDownTime=0.5,
+ startScale=Point3(0.01), anim=0, propName='none',
+ animDuration=0.0, animStartTime=0.0):
+ """ This function returns a Track portraying the appearance of a prop,
+ a delay time for the suit to act with the prop and the disappearancen
+ of the prop. Arguments:
+ propName = the prop object itself
+ parent = the parent for the prop to be a child of
+ posPoints = pos, hpr, scale arguments to __showProp, only requires pos
+ appearDelay = the time to delay before causing the prop to appear
+ remainDelay = the time to leave the object visible before scaling away
+ scaleUpPoint = the point to scale up to
+ scaleUpTime = the duration of time to scale up the prop
+ scaleDownTime = the duration of time to scale down the prop
+ startScale = beginning scale of prop (set to non zero to avoid
+ wiping out the HPR)
+ anim = whether the prop has an animation to play
+ propName = name of the prop (required if calling an animation
+ animDuration = the duration for the animation
+ animStartTime = the start time for the animation
+ """
+
+ if (anim == 1):
+ track = Sequence(
+ Wait(appearDelay), # Wait the duration of appearDelay
+ Func(__showProp, prop, parent, *posPoints),
+ LerpScaleInterval(prop, scaleUpTime, scaleUpPoint,
+ startScale=startScale),
+ ActorInterval(prop, propName, duration=animDuration,
+ startTime=animStartTime),
+ Wait(remainDelay),
+ Func(MovieUtil.removeProp, prop)
+ )
+ else:
+ track = Sequence(
+ Wait(appearDelay), # Wait the duration of appearDelay
+ Func(__showProp, prop, parent, *posPoints),
+ LerpScaleInterval(prop, scaleUpTime, scaleUpPoint, startScale=startScale),
+ Wait(remainDelay), # Wait while suit animation continues
+ LerpScaleInterval(prop, scaleDownTime, MovieUtil.PNT3_NEARZERO), # Scale down the prop
+ Func(MovieUtil.removeProp, prop)
+ )
+
+ return track
+
+def getPropAppearTrack(prop, parent, posPoints, appearDelay,
+ scaleUpPoint=Point3(1), scaleUpTime=0.5,
+ startScale=Point3(0.01), poseExtraArgs=None):
+ """ This function returns the track portraying a prop appearing. Arguments:
+ propName = name of the prop object
+ parent = the parent for the prop to be a child of
+ posPoints = pos, hpr, scale arguments to __showProp, only pos required
+ appearDelay = the time to delay before causing the prop to appear
+ scaleUpPoint = the point to scale up to
+ scaleUpTime = the duration of time to scale up the prop (normally 0.5)
+ startScale = beginning scale of prop (set to non zero to avoid
+ wiping out the HPR)
+ """
+
+ propTrack = Sequence(
+ Wait(appearDelay), # Wait the duration of appearDelay
+ Func(__showProp, prop, parent, *posPoints) # Use showProp
+ )
+ if poseExtraArgs: # Add pose for prop if provided
+ propTrack.append(Func(prop.pose, *poseExtraArgs))
+ propTrack.append(LerpScaleInterval(prop, scaleUpTime, scaleUpPoint,
+ startScale=startScale))
+ return propTrack
+
+
+def getPropThrowTrack(attack, prop, hitPoints=[], missPoints=[], hitDuration=0.5,
+ missDuration=0.5, hitPointNames='none', missPointNames='none',
+ lookAt='none', groundPointOffSet=0, missScaleDown=None, parent=render):
+ """ This function returns the track portraying a prop flying at the target
+ toon. Arguments:
+ attack = attack from the suits to the toons
+ prop = prop to be flown at target toon
+ hitPoints = points to traverse if toon is hit
+ missPoints = points to traverse if toon is missed
+ hitDuration = how long it takes for prop to fly and hit toon
+ missDuration = how long it takes for prop to fly and miss toon
+ hitPointNames = names of throw points to traverse if toon is hit
+ missPointNames = names of miss points to traverse if toon is missed
+ lookAt = cause the prop to lookAt a point if provided
+ """
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ battle = attack['battle']
+
+ def getLambdas(list, prop, toon):
+ for i in range(len(list)): # Create lambda functions for throw points
+ if (list[i] == 'face'):
+ list[i] = lambda toon=toon: __toonFacePoint(toon)
+ elif (list[i] == 'miss'):
+ list[i] = lambda prop=prop, toon=toon: __toonMissPoint(prop, toon)
+ elif (list[i] == 'bounceHit'):
+ list[i] = lambda prop=prop, toon=toon:__throwBounceHitPoint(prop, toon)
+ elif (list[i] == 'bounceMiss'):
+ list[i] = lambda prop=prop, toon=toon:__throwBounceMissPoint(prop, toon)
+ return list
+
+ if (hitPointNames != 'none'):
+ hitPoints = getLambdas(hitPointNames, prop, toon)
+ if (missPointNames != 'none'):
+ missPoints = getLambdas(missPointNames, prop, toon)
+
+ propTrack = Sequence()
+ propTrack.append(Func(battle.movie.needRestoreRenderProp, prop))
+ propTrack.append(Func(prop.wrtReparentTo, parent))
+ if (lookAt != 'none'):
+ propTrack.append(Func(prop.lookAt, lookAt))
+
+ if (dmg > 0): # Fly prop through the hitPoints
+ for i in range(len(hitPoints)):
+ pos = hitPoints[i]
+ propTrack.append(LerpPosInterval(prop, hitDuration, pos=pos))
+ else: # Fly prop through the missPoints
+ for i in range(len(missPoints)):
+ pos = missPoints[i]
+ propTrack.append(LerpPosInterval(prop, missDuration, pos=pos))
+
+ # if provide a scale down time for when the prop misses, scale it down
+ if missScaleDown:
+ propTrack.append(LerpScaleInterval(prop, missScaleDown, MovieUtil.PNT3_NEARZERO))
+
+ propTrack.append(Func(MovieUtil.removeProp, prop))
+ propTrack.append(Func(battle.movie.clearRenderProp, prop))
+
+ return propTrack
+
+def getThrowTrack(object, target, duration=1.0, parent=render, gravity=-32.144):
+
+
+ values = {}
+ def calcOriginAndVelocity(object=object, target=target, values=values,
+ duration=duration, parent=parent, gravity=gravity):
+
+ if callable(target):
+ target = target()
+
+ object.wrtReparentTo(parent)
+ values['origin'] = object.getPos(parent)
+ origin = object.getPos(parent)
+ # Calculate the initial velocity required for the object to land at the target
+ values['velocity'] = ((target[2]-origin[2]) - (0.5*gravity*duration*duration)) \
+ / duration
+
+ return Sequence(
+ Func(calcOriginAndVelocity),
+ LerpFunctionInterval(throwPos, fromData=0., toData=1., duration=duration,
+ extraArgs=[object, duration, target, values, gravity])
+ )
+
+def throwPos(t, object, duration, target, values, gravity=-32.144):
+ origin = values['origin']
+ velocity = values['velocity']
+
+ if callable(target):
+ target = target()
+
+ x = origin[0]*(1-t) + target[0]*t
+ y = origin[1]*(1-t) + target[1]*t
+ time = t*duration
+ z = origin[2] + velocity*time + (0.5*gravity*time*time)
+ object.setPos(x, y, z)
+
+def getToonTakeDamageTrack(toon, died, dmg, delay, damageAnimNames=None,
+ splicedDamageAnims=None, showDamageExtraTime=0.01):
+ """ This function returns the intervals portraying a target toon taking damage.
+ Arguments:
+ toon = the toon taking damage
+ died = whether the toon died or not
+ dmg = how much damage the toon is taking
+ delay = time delay before damage animation occurs
+ damageAnimNames = damage animations to be performed in order
+ splicedDamageAnims = spliced animations from getSplicedAnimsTrack
+ Note: This function uses damageAnimNames if it is not null. Provide only
+ splicedDamageAnims to have them played. Must have atleast one or the other.
+ """
+ toonTrack = Sequence()
+ toonTrack.append(Wait(delay))
+
+ assert(notify.debug('getToonTakeDamageTrack(toon: %s damage: %d died: %d' % (toon.getName(), dmg, died)))
+
+ if damageAnimNames: # If exists, use these animations
+ for d in damageAnimNames: # Use each damage animation in order
+ toonTrack.append(ActorInterval(toon, d)) # Add animation
+ indicatorTrack = Sequence(
+ Wait(delay + showDamageExtraTime),
+ Func(__doDamage, toon, dmg, died),
+ )
+ else:
+ splicedAnims = getSplicedAnimsTrack(splicedDamageAnims, actor=toon)
+ toonTrack.append(splicedAnims)
+
+ indicatorTrack = Sequence(
+ Wait(delay + showDamageExtraTime),
+ Func(__doDamage, toon, dmg, died),
+ )
+
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ if died:
+ # If the battle calculator thinks the toon should have died,
+ # then wait a few seconds here to give the toon's client a
+ # chance to discover that he's died, and start to broadcast
+ # the death animation.
+
+ # The death animation used to be built into the track, but
+ # nowadays we just wait quietly, since there's a chance the
+ # toon might not have died after all (someone may have used a
+ # toon-up ResistanceChat message during the round).
+ toonTrack.append(Wait(5.0))
+
+ return Parallel(toonTrack, indicatorTrack)
+
+
+def getSplicedAnimsTrack(anims, actor=None):
+ """ This function returns a Sequence spliced together from the animations
+ provided as arguments. Arguments:
+ actor = can optional provide an actor to applay all animations to
+ anims = a list of lists. Each list contains an animation with optional arguments.
+ Each argument holds a position in the list and only the first (the animation name)
+ is required. The arguments are:
+ [0] = animation name
+ [1] = delay time before the animation is played (default=0 if not given)
+ [2] = start time within the animation (default=0 if not given)
+ [3] = duration of the animation to play (default=animation's duration if not given)
+ [4] = an actor for the animation (which would overrule the actor keyword argument)
+ The animations will be executed in the order of the anims list.
+ Note: Keyword argument actor must be provided unless each animation list includes it.
+ You can also provide a standard actor and occassionally override it with a specified
+ actor as that fifth argument in the animation list.
+ """
+ track = Sequence()
+ for nextAnim in anims:
+ delay = 0.000001 # Ensure that Wait is not called with a value of zero
+ if (len(nextAnim) >= 2): # If using a delay before the animation
+ if (nextAnim[1] > 0): # Make sure that the given delay is not zero
+ delay = nextAnim[1] # And if not, go ahead and use provided value
+
+ if (len(nextAnim) <= 0): # Shouldn't happen, but make negligible wait
+ track.append(Wait(delay)) # Delay is 0.000001 here
+ elif (len(nextAnim) == 1): # Call animation with default arguments
+ track.append(ActorInterval(actor, nextAnim[0]))
+ elif (len(nextAnim) == 2): # Calls animation with a delay
+ track.append(Wait(delay))
+ track.append(ActorInterval(actor, nextAnim[0]))
+ elif (len(nextAnim) == 3): # Calls animation with a delay and a start time
+ track.append(Wait(delay))
+ track.append(ActorInterval(actor, nextAnim[0], startTime=nextAnim[2]))
+ elif (len(nextAnim) == 4): # Calls animation with a delay, startTime, and duration
+ track.append(Wait(delay))
+ # Allow for reversed animations through negative durations
+ duration = nextAnim[3]
+ if (duration < 0):
+ startTime = nextAnim[2]
+ endTime = startTime+duration
+ if (endTime <= 0): # Ensure endTime is not negative (no animation there)
+ endTime = 0.01
+ track.append(ActorInterval(actor, nextAnim[0], startTime=startTime,
+ endTime=endTime))
+ else:
+ track.append(ActorInterval(actor, nextAnim[0], startTime=nextAnim[2],
+ duration=duration))
+ elif (len(nextAnim) == 5): # Calls animation with all arguements
+ track.append(Wait(delay))
+ track.append(ActorInterval(nextAnim[4], nextAnim[0], startTime=nextAnim[2],
+ duration=nextAnim[3]))
+ return track
+
+
+def getSplicedLerpAnims(animName, origDuration, newDuration, startTime=0, fps=30, reverse=0):
+ """ This function returns a series of intervals splicing together an animation
+ over a modified frame of time. This allows an animation to be shortened or
+ lengthened (if you can tolerate any resulting rushed or choppy animation. This
+ function increases an animation time by inserting a uniform time interval before
+ successive ActorInterval calls. It decreases time by progressing the animation
+ time forward faster than real-time (basically uniformly skipping frames). This
+ function only works in conjunction with getSplicedAnimsTrack, see that function (above)
+ for how animation lists are constructed and manipulated.
+ Arguments:
+ animName = name of the animation to lengthen/shorten
+ origDuration = original time duration the animation should normally play in
+ newDuration = lengthened or shortened time for the new animation
+ startTime = startTime for the animation
+ fps = usually held constant, helps determine number of actor intervals to use
+ """
+ anims = []
+ addition = 0 # Addition will be added to the startTime to move animation forward
+ numAnims = origDuration * fps # Number of actor intervals to use
+ # The timeInterval is what to add before each actor interval to delay time
+ timeInterval = newDuration / numAnims
+ # The animInterval is how much the animation progresses forward each interval
+ animInterval = origDuration / numAnims
+ # If we are reversing the animation, make the animInterval negative
+ if (reverse == 1):
+ animInterval = -animInterval
+ for i in range(0, numAnims):
+ # Constructing the animation list for later use with getSplicedAnimsTrack
+ anims.append([animName, timeInterval, startTime+addition, animInterval])
+ addition += animInterval # Add addition to push the animation forward
+ return anims
+
+
+def getSoundTrack(fileName, delay=0.01, duration=None, node=None):
+ """ This functions returns the standard sound track, which involves one sound
+ effect with a possible delay beforehand and an optional duration specification.
+ """
+
+ soundEffect = globalBattleSoundCache.getSound(fileName)
+
+ if duration:
+ return Sequence(Wait(delay),
+ SoundInterval(soundEffect, duration=duration, node=node))
+ else:
+ return Sequence(Wait(delay),
+ SoundInterval(soundEffect, node=node))
+
+
+#######
+# End encapsulating sub functions for suit attack functions
+#######
+
+
+############
+# Begin Revamped attacks using sub functions defined above.
+############
+
+"""
+def doCarbonCopy(attack): # top f: special = copy suit and double attack; depends; depends
+ pass #later
+"""
+
+def doClipOnTie(attack): # top tm: fixed
+ """ This function returns Tracks portraying the Clip-on-tie attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ tie = globalPropPool.getProp('clip-on-tie') # Get tie from pool
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): # type A not yet used
+ throwDelay = 2.17
+ damageDelay = 3.3
+ dodgeDelay = 3.1
+ elif (suitType == 'b'):
+ throwDelay = 2.17
+ damageDelay = 3.3
+ dodgeDelay = 3.1
+ elif (suitType == 'c'):
+ throwDelay = 1.45
+ damageDelay = 2.61
+ dodgeDelay = 2.34
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(0.66, 0.51, 0.28), VBase3(-69.652, -17.199, 67.960)]
+ # First make the tie appear
+ tiePropTrack = Sequence(
+ getPropAppearTrack(tie, suit.getRightHand(), posPoints, 0.5,
+ MovieUtil.PNT3_ONE, scaleUpTime=0.5, poseExtraArgs=['clip-on-tie', 0]))
+
+ # If tie will hit toon, pose it in its extended position
+ if (dmg > 0): # If damage is greater than zero
+ # Now throw the tie, which travels much faster if thrown "well" (hits toon)
+ tiePropTrack.append(ActorInterval(tie, 'clip-on-tie',
+ duration=throwDelay, startTime=1.1))
+ else:
+ tiePropTrack.append(Wait(throwDelay)) # Wait while suit animates
+
+ tiePropTrack.append(Func(tie.setHpr, Point3(0, -90, 0)))
+ tiePropTrack.append(getPropThrowTrack(attack, tie, [__toonFacePoint(toon)],
+ [__toonGroundPoint(attack, toon, 0.1)], hitDuration=0.4,
+ missDuration=0.8, missScaleDown=1.2))
+
+ toonTrack = getToonTrack(attack, damageDelay, ['conked'], dodgeDelay, ['sidestep'])
+
+ throwSound = getSoundTrack('SA_powertie_throw.mp3', delay=throwDelay+1, node=suit)
+
+ return Parallel(suitTrack, toonTrack, tiePropTrack, throwSound)
+
+
+def doPoundKey(attack): # top f: fixed
+ """ This function returns Tracks portraying the PoundKey attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ phone = globalPropPool.getProp('phone')
+ receiver = globalPropPool.getProp('receiver')
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect('PoundKey')
+ BattleParticles.setEffectTexture(particleEffect, 'poundsign',
+ color=Vec4(0, 0, 0, 1))
+
+ suitTrack = getSuitTrack(attack)
+ partTrack = getPartTrack(particleEffect, 2.1, 1.55, [particleEffect, suit, 0])
+ phonePosPoints = [Point3(0.23, 0.17, -0.11), VBase3(5.939, 2.763, -177.591)]
+ receiverPosPoints = [Point3(0.23, 0.17, -0.11), VBase3(5.939, 2.763, -177.591)]
+
+ propTrack = Sequence(# Single propTrack combines both phone and receiver
+ Wait(0.3), # Wait to show the phone and receiver
+ Func(__showProp, phone, suit.getLeftHand(),
+ phonePosPoints[0], phonePosPoints[1]), # Show phone
+ Func(__showProp, receiver, suit.getLeftHand(),
+ receiverPosPoints[0], receiverPosPoints[1]), # Show receiver
+
+ LerpScaleInterval(phone, 0.5, MovieUtil.PNT3_ONE, MovieUtil.PNT3_NEARZERO), # Scale up phone
+ Wait(0.74), # Wait until suit picks up phone
+ # Now reparent receiver to suit's right hand (he picks it up)
+ Func(receiver.wrtReparentTo, suit.getRightHand()),
+ # Jam receiver into position in case reparenting to right hand takes place early or late.
+ #Note: These coordinates are specific to Type C suits
+ LerpPosHprInterval(receiver, 0.0001, Point3(-0.45, 0.48, -0.62), VBase3(-87.47, -18.21, 7.82)),
+ Wait(3.14), # Wait while suit uses the phone
+ # Now put the receiver back down on the phone
+ Func(receiver.wrtReparentTo, phone),
+ Wait(0.62), # Leave the phone around a bit longer
+ LerpScaleInterval(phone, 0.5, MovieUtil.PNT3_NEARZERO), # Scale down phone & child receiver
+ # Now destroy the receiver and then the phone
+ Func(MovieUtil.removeProps, [receiver, phone]),
+ )
+
+ toonTrack = getToonTrack(attack, 2.7, ['cringe'], 1.9, ['sidestep'])
+
+ soundTrack = getSoundTrack('SA_hangup.mp3', delay=1.3, node=suit)
+
+ return Parallel(suitTrack, toonTrack, propTrack, partTrack, soundTrack)
+
+
+def doShred(attack): # top f: fixed
+ """ This function returns Tracks portraying the Shred attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ paper = globalPropPool.getProp('shredder-paper')
+ shredder = globalPropPool.getProp('shredder')
+ particleEffect = BattleParticles.createParticleEffect('Shred')
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ partTrack = getPartTrack(particleEffect, 3.5, 1.9, [particleEffect, suit, 0])
+ paperPosPoints = [Point3(0.59, -0.31, 0.81), VBase3(79.224, 32.576, -179.449)]
+ paperPropTrack = getPropTrack(paper, suit.getRightHand(), paperPosPoints, 2.4, 0.00001,
+ scaleUpTime=0.2, anim=1, propName='shredder-paper',
+ animDuration=1.5, animStartTime=2.8)
+ shredderPosPoints = [Point3(0, -0.12, -0.34), VBase3(-90.000, -53.770, -0.000)]
+ shredderPropTrack = getPropTrack(shredder, suit.getLeftHand(), shredderPosPoints,
+ 1, 3, scaleUpPoint=Point3(4.81, 4.81, 4.81))
+ toonTrack = getToonTrack(attack, suitTrack.getDuration()-1.1, ['conked'],
+ suitTrack.getDuration()-3.1, ['sidestep'])
+ soundTrack = getSoundTrack('SA_shred.mp3', delay=3.4, node=suit)
+
+ return Parallel(suitTrack, paperPropTrack, shredderPropTrack, partTrack,
+ toonTrack, soundTrack)
+
+
+def doFillWithLead(attack): # top p: particle w/ 2 props; cringe; sidestep
+ """ This function returns Tracks portraying the FillWithLead attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ pencil = globalPropPool.getProp('pencil')
+ sharpener = globalPropPool.getProp('sharpener')
+ BattleParticles.loadParticles()
+ sprayEffect = BattleParticles.createParticleEffect(file='fillWithLeadSpray')
+ headSmotherEffect = BattleParticles.createParticleEffect(file='fillWithLeadSmother')
+ torsoSmotherEffect = BattleParticles.createParticleEffect(file='fillWithLeadSmother')
+ legsSmotherEffect = BattleParticles.createParticleEffect(file='fillWithLeadSmother')
+ BattleParticles.setEffectTexture(sprayEffect, 'roll-o-dex',
+ color=Vec4(0, 0, 0, 1))
+ BattleParticles.setEffectTexture(headSmotherEffect, 'roll-o-dex',
+ color=Vec4(0, 0, 0, 1))
+ BattleParticles.setEffectTexture(torsoSmotherEffect, 'roll-o-dex',
+ color=Vec4(0, 0, 0, 1))
+ BattleParticles.setEffectTexture(legsSmotherEffect, 'roll-o-dex',
+ color=Vec4(0, 0, 0, 1))
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ sprayTrack = getPartTrack(sprayEffect, 2.5, 1.9, [sprayEffect, suit, 0])
+ pencilPosPoints = [Point3(-0.29, -0.33, -0.13), VBase3(160.565, -11.653, -169.244)]
+ pencilPropTrack = getPropTrack(pencil, suit.getRightHand(), pencilPosPoints,
+ 0.7, 3.2, scaleUpTime=0.2)
+ sharpenerPosPoints = [Point3(0.0, 0.0, -0.03), MovieUtil.PNT3_ZERO]
+ sharpenerPropTrack = getPropTrack(sharpener, suit.getLeftHand(), sharpenerPosPoints,
+ 1.3, 2.3, scaleUpPoint=MovieUtil.PNT3_ONE)
+
+ # This damage animation makes the toon remain stunned for much longer (while being
+ # filled with lead) by repeating that portion of the conked animation
+ damageAnims = []
+ damageAnims.append(['conked', suitTrack.getDuration()-1.5, 0.00001, 1.4])
+ damageAnims.append(['conked', 0.00001, 0.7, 0.7])
+ damageAnims.append(['conked', 0.00001, 0.7, 0.7])
+ damageAnims.append(['conked', 0.00001, 1.4])
+ toonTrack = getToonTrack(attack, splicedDamageAnims=damageAnims,
+ dodgeDelay=suitTrack.getDuration()-3.1, dodgeAnimNames=['sidestep'],
+ showDamageExtraTime=4.5, showMissedExtraTime=1.6)
+
+ # Now we'll use a particle effect on each color changing body part, so we must first
+ # calculate the height of the body part to correctly place the particle effects
+ animal = toon.style.getAnimal()
+ bodyScale = ToontownGlobals.toonBodyScales[animal]
+ headEffectHeight = __toonFacePoint(toon).getZ()
+ legsHeight = ToontownGlobals.legHeightDict[toon.style.legs] * bodyScale
+ torsoEffectHeight = ((ToontownGlobals.torsoHeightDict[toon.style.torso]*bodyScale)/2)+legsHeight
+ legsEffectHeight = legsHeight / 2
+ effectX = headSmotherEffect.getX() # This remains the same for each effect
+ effectY = headSmotherEffect.getY() # Same for each effect, adjusted slightly for each
+ headSmotherEffect.setPos(effectX, effectY - 1.5, headEffectHeight)
+ torsoSmotherEffect.setPos(effectX, effectY - 1, torsoEffectHeight)
+ legsSmotherEffect.setPos(effectX, effectY - 0.6, legsEffectHeight)
+ partDelay = 3.5 # Delay for all particle effects
+ partIvalDelay = 0.7 # Delay between each particle effect
+ partDuration = 1.0 # Duration for particle effects
+ headTrack = getPartTrack(headSmotherEffect, partDelay, partDuration,
+ [headSmotherEffect, toon, 0])
+ torsoTrack = getPartTrack(torsoSmotherEffect, partDelay+partIvalDelay, partDuration,
+ [torsoSmotherEffect, toon, 0])
+ legsTrack = getPartTrack(legsSmotherEffect, partDelay+partIvalDelay*2, partDuration,
+ [legsSmotherEffect, toon, 0])
+
+ def colorParts(parts): # Function to color each lod part in list parts
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ track.append(Func(nextPart.setColorScale,
+ Vec4(0, 0, 0, 1)))
+ return track
+
+ def resetParts(parts): # Fucntion to reset color on each lod part in list parts
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ track.append(Func(nextPart.clearColorScale))
+ return track
+
+ if (dmg > 0):
+ colorTrack = Sequence() # Build intervals to change the toon's color to black
+ headParts = toon.getHeadParts()
+ torsoParts = toon.getTorsoParts()
+ legsParts = toon.getLegsParts()
+
+ # Now color each body part in succession, then show each
+ colorTrack.append(Wait(partDelay+0.2))
+ colorTrack.append(Func(battle.movie.needRestoreColor))
+ colorTrack.append(colorParts(headParts))
+ colorTrack.append(Wait(partIvalDelay)) # Wait before next part color change
+ colorTrack.append(colorParts(torsoParts))
+ colorTrack.append(Wait(partIvalDelay)) # Wait before next part color change
+ colorTrack.append(colorParts(legsParts))
+ colorTrack.append(Wait(2.5)) # Wait while color has been changed
+ colorTrack.append(resetParts(headParts))
+ colorTrack.append(resetParts(torsoParts))
+ colorTrack.append(resetParts(legsParts))
+ colorTrack.append(Func(battle.movie.clearRestoreColor))
+ return Parallel(suitTrack, pencilPropTrack, sharpenerPropTrack,
+ sprayTrack, headTrack, torsoTrack, legsTrack,
+ colorTrack, toonTrack)
+ else:
+ return Parallel(suitTrack, pencilPropTrack, sharpenerPropTrack,
+ sprayTrack, toonTrack)
+
+
+def doFountainPen(attack): # top p: fixed
+ """ This function returns Tracks portraying the FountainPen attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ pen = globalPropPool.getProp('pen')
+
+ def getPenTip(pen=pen):
+ tip = pen.find("**/joint_toSpray")
+ return tip.getPos(render)
+ hitPoint = lambda toon=toon: __toonFacePoint(toon)
+ missPoint = lambda prop=pen, toon=toon: __toonMissPoint(prop, toon, 0, parent=render)
+ hitSprayTrack = MovieUtil.getSprayTrack(battle,
+ VBase4(0, 0, 0, 1), getPenTip, hitPoint,
+ 0.2, 0.2, 0.2, horizScale = 0.1, vertScale = 0.1)
+ missSprayTrack = MovieUtil.getSprayTrack(battle,
+ VBase4(0, 0, 0, 1), getPenTip, missPoint,
+ 0.2, 0.2, 0.2, horizScale = 0.1, vertScale = 0.1)
+
+ suitTrack = getSuitTrack(attack)
+ propTrack = Sequence(# Build intervals for foutain pen, including its spray
+ Wait(0.01), # Wait the duration of appearDelay
+ Func(__showProp, pen, suit.getRightHand(), MovieUtil.PNT3_ZERO),
+ LerpScaleInterval(pen, 0.5, Point3(1.5, 1.5, 1.5)),
+ Wait(1.05), # Wait until time to spray
+ # was 1.6
+ )
+
+ if (dmg > 0): # If squirt hits the toon, use the hit spray
+ propTrack.append(hitSprayTrack) # Add in the spray intervals
+ else: # Only use the hitSpray until learn how to extend spray distance
+ propTrack.append(missSprayTrack) # Add in the spray intervals
+
+
+ propTrack += [# Add the rest of the prop intervals
+ LerpScaleInterval(pen, 0.5, MovieUtil.PNT3_NEARZERO), # Scale down the prop
+ Func(MovieUtil.removeProp, pen)
+ ]
+
+ # Now build the splash track for if the toon takes damage
+ splashTrack = Sequence()
+ if (dmg > 0) : # If the target toon takes damage
+ def prepSplash(splash, targetPoint):
+ splash.reparentTo(render)
+ splash.setPos(targetPoint)
+ scale = splash.getScale()
+ splash.setBillboardPointWorld()
+ splash.setScale(scale)
+ splash = globalPropPool.getProp('splash-from-splat')
+ splash.setColor(0, 0, 0, 1)
+ splash.setScale(0.15)
+ splashTrack = Sequence(
+ Func(battle.movie.needRestoreRenderProp,
+ splash),
+ Wait(1.65),
+ Func(prepSplash, splash,
+ __toonFacePoint(toon)),
+ ActorInterval(splash, 'splash-from-splat'),
+ Func(MovieUtil.removeProp,
+ splash),
+ Func(battle.movie.clearRenderProp, splash)
+ )
+
+ # Grab the head parts to turn it to black when hit
+ headParts = toon.getHeadParts()
+ # Now change the toon's head color to black from the pen spray
+ splashTrack.append(Func(battle.movie.needRestoreColor))
+ for partNum in range(0, headParts.getNumPaths()):
+ nextPart = headParts.getPath(partNum)
+ splashTrack.append(Func(nextPart.setColorScale,
+ Vec4(0, 0, 0, 1)))
+ splashTrack.append(Func(MovieUtil.removeProp, splash))
+ splashTrack.append(Wait(2.6)) # Wait while toon has turned black
+ # Now reset the color scale of the head parts
+ for partNum in range(0, headParts.getNumPaths()):
+ nextPart = headParts.getPath(partNum)
+ splashTrack.append(Func(nextPart.clearColorScale))
+ splashTrack.append(Func(battle.movie.clearRestoreColor))
+
+ penSpill = BattleParticles.createParticleEffect(file='penSpill')
+ penSpill.setPos(getPenTip())
+ penSpillTrack = getPartTrack(penSpill, 1.4, 0.7, [penSpill, pen, 0])
+
+ toonTrack = getToonTrack(attack, 1.81, ['conked'], dodgeDelay=0.11,
+ splicedDodgeAnims=[['duck', 0.01, 0.6]],
+ showMissedExtraTime=1.66)
+ soundTrack = getSoundTrack('SA_fountain_pen.mp3', delay=1.6, node=suit)
+
+ return Parallel(suitTrack, toonTrack, propTrack,
+ soundTrack, penSpillTrack, splashTrack)
+
+
+def doRubOut(attack): # top p: fixed
+ """ This function returns Tracks portraying the RubOut attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ pad = globalPropPool.getProp('pad')
+ pencil = globalPropPool.getProp('pencil')
+ headEffect = BattleParticles.createParticleEffect('RubOut',
+ color=toon.style.getHeadColor())
+ torsoEffect = BattleParticles.createParticleEffect('RubOut',
+ color=toon.style.getArmColor())
+ legsEffect = BattleParticles.createParticleEffect('RubOut',
+ color=toon.style.getLegColor())
+
+ suitTrack = getSuitTrack(attack)
+ padPosPoints = [Point3(-0.66, 0.81, -0.06), VBase3(14.930, -2.290, 180.000)]
+ padPropTrack = getPropTrack(pad, suit.getLeftHand(), padPosPoints, 0.5, 2.57)
+ pencilPosPoints = [Point3(0.04, -0.38, -0.10), VBase3(-170.223, -3.762, -62.929)]
+ pencilPropTrack = getPropTrack(pencil, suit.getRightHand(), pencilPosPoints, 0.5, 2.57)
+ toonTrack = getToonTrack(attack, 2.2, ['conked'], 2.0, ['jump'])
+
+ hideTrack = Sequence() # Build intervals to hide the target toon's body parts
+ headParts = toon.getHeadParts()
+ torsoParts = toon.getTorsoParts()
+ legsParts = toon.getLegsParts()
+
+ # Now we'll use a particle effect on each disappearing body part, so we must first
+ # calculate the height of the body part to correctly place the particle effect
+ animal = toon.style.getAnimal()
+ bodyScale = ToontownGlobals.toonBodyScales[animal]
+ headEffectHeight = __toonFacePoint(toon).getZ()
+ legsHeight = ToontownGlobals.legHeightDict[toon.style.legs] * bodyScale
+ torsoEffectHeight = ((ToontownGlobals.torsoHeightDict[toon.style.torso]*bodyScale)/2) + legsHeight
+ legsEffectHeight = legsHeight / 2
+ effectX = headEffect.getX() # This remains the same for each effect
+ effectY = headEffect.getY() # Same for each effect, will be adjusted slightly for each
+ headEffect.setPos(effectX, effectY - 1.5, headEffectHeight)
+ torsoEffect.setPos(effectX, effectY - 1, torsoEffectHeight)
+ legsEffect.setPos(effectX, effectY - 0.6, legsEffectHeight)
+ partDelay = 2.5 # Delay for particle effects
+ headTrack = getPartTrack(headEffect, partDelay+0, 0.5, [headEffect, toon, 0])
+ torsoTrack = getPartTrack(torsoEffect, partDelay+1.1, 0.5, [torsoEffect, toon, 0])
+ legsTrack = getPartTrack(legsEffect, partDelay+2.2, 0.5, [legsEffect, toon, 0])
+
+ def hideParts(parts): # Function to hide each lod part in list parts
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+# hideTrack.append(Func(nextPart.hide))
+ track.append(Func(nextPart.setTransparency, 1))
+ track.append(LerpFunctionInterval(nextPart.setAlphaScale, fromData=1,
+ toData=0, duration=0.2))
+ return track
+
+ def showParts(parts): # Fucntion to show each lod part in list parts
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+# hideTrack.append(Func(nextPart.show))
+ track.append(Func(nextPart.clearColorScale))
+ track.append(Func(nextPart.clearTransparency))
+ return track
+
+ soundTrack = getSoundTrack('SA_rubout.mp3', delay=1.7, node=suit)
+ if (dmg > 0):
+ # Now hide each body part in succession, then show each
+ hideTrack.append(Wait(2.2))
+ hideTrack.append(Func(battle.movie.needRestoreColor))
+ hideTrack.append(hideParts(headParts))
+# hideTrack.append(Wait(0.9))
+ hideTrack.append(Wait(0.4))
+ hideTrack.append(hideParts(torsoParts))
+# hideTrack.append(Wait(0.9))
+ hideTrack.append(Wait(0.4))
+ hideTrack.append(hideParts(legsParts))
+ hideTrack.append(Wait(1))
+ hideTrack.append(showParts(headParts))
+ hideTrack.append(showParts(torsoParts))
+ hideTrack.append(showParts(legsParts))
+ hideTrack.append(Func(battle.movie.clearRestoreColor))
+ return Parallel(suitTrack, toonTrack, padPropTrack, pencilPropTrack, soundTrack,
+ hideTrack, headTrack, torsoTrack, legsTrack)
+ else:
+ return Parallel(suitTrack, toonTrack, padPropTrack, pencilPropTrack, soundTrack)
+
+
+def doFingerWag(attack): # top p: fixed
+ suit = attack['suit']
+ battle = attack['battle']
+
+ #Particle Effect: stream of 'Blahs'
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect('FingerWag')
+ BattleParticles.setEffectTexture(particleEffect, 'blah',
+ color=Vec4(0.55, 0, 0.55, 1))
+
+ #Adjust variables based on suit types - p(b), mm(c), tw(c), pp(a)
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'):
+ partDelay = 1.3
+ damageDelay = 2.7
+ dodgeDelay = 1.7
+ elif (suitType == 'b'):
+ partDelay = 1.3
+ damageDelay = 2.7
+ dodgeDelay = 1.8
+ elif (suitType == 'c'):
+ partDelay = 1.3
+ damageDelay = 2.7
+ dodgeDelay = 2.0
+
+ #Build suit and particle effect tracks
+ suitTrack = getSuitTrack(attack)
+ partTrack = getPartTrack(particleEffect, partDelay, 2, [particleEffect, suit, 0])
+
+ #Adjust particle effect taking suit height into account
+ suitName = attack['suitName']
+ if (suitName == 'mm'):
+ particleEffect.setPos(0.167, 1.5, 2.731)
+ elif (suitName == 'tw'):
+ particleEffect.setPos(0.167, 1.8, 5)
+ particleEffect.setHpr(-90.0, -60.0, 180.0)
+ elif (suitName == 'pp'):
+ particleEffect.setPos(0.167, 1, 4.1)
+ elif (suitName == 'bs'):
+ particleEffect.setPos(0.167, 1, 5.1)
+ elif (suitName == 'bw'):
+ particleEffect.setPos(0.167, 1.9, suit.getHeight()-1.8)
+ particleEffect.setP(-110)
+
+ toonTrack = getToonTrack(attack, damageDelay, ['slip-backward'],
+ dodgeDelay, ['sidestep'])
+ soundTrack = getSoundTrack('SA_finger_wag.mp3', delay=1.3, node=suit)
+
+ return Parallel(suitTrack, toonTrack, partTrack, soundTrack)
+
+
+def doWriteOff(attack): # top bc: fixed
+ """ This function returns Tracks portraying the WriteOff attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ pad = globalPropPool.getProp('pad')
+ pencil = globalPropPool.getProp('pencil')
+ BattleParticles.loadParticles()
+ checkmark = MovieUtil.copyProp(BattleParticles.getParticle('checkmark'))
+ checkmark.setBillboardPointEye()
+
+ suitTrack = getSuitTrack(attack)
+ padPosPoints = [Point3(-0.25, 1.38, -0.08), VBase3(-19.078, -6.603, -171.594)]
+ padPropTrack = getPropTrack(pad, suit.getLeftHand(),
+ padPosPoints, 0.5, 2.57, Point3(1.89, 1.89, 1.89))
+
+ # Calculate missPoint of the check mark with lambda function for combined checkmark, pencil Track
+ missPoint = lambda checkmark = checkmark, toon=toon: __toonMissPoint(checkmark, toon)
+
+ pencilPosPoints = [Point3(-0.47, 1.08, 0.28), VBase3(21.045, 12.702, -176.374)]
+ #pencilPropTrack = getPropTrack(pencil, suit.getRightHand(), pencilPosPoints, #DELETE LATER
+ # 0.5, 2.57, Point3(1.5, 1.5, 1.5)) #DELETE LATER
+
+ extraArgsForShowProp = [pencil, suit.getRightHand()] # Begin extraArgs for __showProp function
+ extraArgsForShowProp.extend(pencilPosPoints) # Add in pos points for extraArgs
+
+ pencilPropTrack = Sequence(
+ Wait(0.5), # Wait the duration of appearDelay
+ Func(__showProp, *extraArgsForShowProp), # Use showProp
+ LerpScaleInterval(pencil, 0.5, Point3(1.5, 1.5, 1.5), startScale=Point3(0.01)),
+ Wait(2), # Wait while suit animation continues #2.57
+ #Checkmark Intervals
+ Func(battle.movie.needRestoreRenderProp,
+ checkmark),
+ Func(checkmark.reparentTo, render), # Give to render
+ Func(checkmark.setScale, 1.6), # Scale the checkmark
+ Func(checkmark.setPosHpr, pencil, 0, 0, 0, 0, 0, 0),
+ Func(checkmark.setP, 0),
+ Func(checkmark.setR, 0),
+ )
+
+ # Adding the intervals to throw the checkmark at the toon
+ pencilPropTrack.append(getPropThrowTrack(attack, checkmark, [__toonFacePoint(toon)],
+ [missPoint]))
+ pencilPropTrack.append(Func(MovieUtil.removeProp, checkmark))
+ pencilPropTrack.append(Func(battle.movie.clearRenderProp,
+ checkmark))
+ pencilPropTrack.append(Wait(0.3)) # Wait before scaling down the pencil
+ pencilPropTrack.append(LerpScaleInterval(pencil, 0.5, MovieUtil.PNT3_NEARZERO)) # Scale down the pencil
+ pencilPropTrack.append(Func(MovieUtil.removeProp, pencil))
+ # All done, now make the PropTrack
+
+ toonTrack = getToonTrack(attack, 3.4, ['slip-forward'], 2.4, ['sidestep'])
+ soundTrack = Sequence(
+ Wait(2.3),
+ SoundInterval(globalBattleSoundCache.getSound('SA_writeoff_pen_only.mp3'), duration=0.9, node=suit),
+ SoundInterval(globalBattleSoundCache.getSound('SA_writeoff_ding_only.mp3'), node=suit),
+ )
+
+ return Parallel(suitTrack, toonTrack, padPropTrack, pencilPropTrack, soundTrack)
+
+
+"""
+def doDitto(attack): # top ym: special, simultaneously copy attack; depends; depends
+ pass #later
+"""
+
+def doRubberStamp(attack): # top ym: fixed
+ """ This function returns Tracks portraying the RubberStamp attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+
+ suitTrack = getSuitTrack(attack)
+
+ stamp = globalPropPool.getProp("rubber-stamp")
+ pad = globalPropPool.getProp("pad")
+ cancelled = __makeCancelledNodePath()
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): #YesMan
+ padPosPoints = [Point3(-0.65, 0.83, -0.04), VBase3(5.625, 4.456, -165.125)]
+ stampPosPoints = [Point3(-0.64, -0.17, -0.03), MovieUtil.PNT3_ZERO]
+ elif (suitType == 'c'):#Bottom Feeder
+ padPosPoints = [Point3(0.19, -0.55, -0.21), VBase3(-166.760, -4.001, -1.658)]
+ stampPosPoints = [Point3(-0.64, -0.08, 0.11), MovieUtil.PNT3_ZERO]
+ else:
+ padPosPoints = [Point3(-0.65, 0.83, -0.04), VBase3(5.625, 4.456, -165.125)]
+ stampPosPoints = [Point3(-0.64, -0.17, -0.03), MovieUtil.PNT3_ZERO]
+
+ padPropTrack = getPropTrack(pad, suit.getLeftHand(), padPosPoints, 0.000001, 3.2)
+
+ # Calculate missPoint of the cancelled stamp with lambda function
+ missPoint = lambda cancelled=cancelled, toon=toon: __toonMissPoint(cancelled, toon)
+
+ propTrack = Sequence(# Build intervals for the stamp and cancelled props as one track
+ Func(__showProp, stamp, suit.getRightHand(),
+ stampPosPoints[0], stampPosPoints[1]),
+ LerpScaleInterval(stamp, 0.5, MovieUtil.PNT3_ONE), # Scale up the stamp
+ Wait(2.6), # Wait while the suit animation continues
+ Func(battle.movie.needRestoreRenderProp, cancelled),
+ Func(cancelled.reparentTo, render), # Give to render
+ Func(cancelled.setScale, 0.6), # Scale the cancelled
+ Func(cancelled.setPosHpr, stamp, 0.81, -1.11, -0.16,
+ 0, 0, 90),
+ Func(cancelled.setP, 0),
+ Func(cancelled.setR, 0),
+ )
+ # Now add in the intervals to throw the cancelled stamp at the toon
+ propTrack.append(getPropThrowTrack(attack, cancelled, [__toonFacePoint(toon)],
+ [missPoint]))
+ propTrack.append(Func(MovieUtil.removeProp, cancelled))
+ propTrack.append(Func(battle.movie.clearRenderProp,
+ cancelled))
+ propTrack.append(Wait(0.3)) # Wait before scaling down the stamp
+ propTrack.append(LerpScaleInterval(stamp, 0.5, MovieUtil.PNT3_NEARZERO)) # Scale down the stamp
+ propTrack.append(Func(MovieUtil.removeProp, stamp))
+
+ toonTrack = getToonTrack(attack, 3.4, ['conked'], 1.9, ['sidestep'])
+ soundTrack = getSoundTrack('SA_rubber_stamp.mp3', delay=1.3, duration=1.1, node=suit)
+
+ return Parallel(suitTrack, toonTrack, propTrack, padPropTrack, soundTrack)
+
+
+def doRazzleDazzle(attack): # top ym:fixed
+ """This function returns Tracks portraying the RazzleDazzle attack"""
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ hitSuit = (dmg > 0)
+ sign = globalPropPool.getProp('smile')
+
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect('Smile')
+
+ #Suit Track
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+
+ #Combined Prop/Particle Track
+ """Note: Tracks combined because part effect parented to prop"""
+ signPosPoints = [Point3(0.00, -0.42, -0.04), VBase3(105.715, 73.977, 65.932)]
+
+ if hitSuit:
+ hitPoint = lambda toon=toon: __toonFacePoint(toon)
+ else:
+ hitPoint = lambda particleEffect=particleEffect, toon=toon, suit=suit: \
+ __toonMissPoint(particleEffect, toon, parent=suit.getRightHand())
+
+ signPropTrack = Sequence(
+ Wait(0.5), # Wait the duration of appearDelay
+ Func(__showProp, sign, suit.getRightHand(),
+ signPosPoints[0], signPosPoints[1]),
+ LerpScaleInterval(sign, 0.5, Point3(1.39, 1.39, 1.39)),
+ Wait(0.5),
+ Func(battle.movie.needRestoreParticleEffect,
+ particleEffect),
+ Func(particleEffect.start, sign),
+ Func(particleEffect.wrtReparentTo, render),
+ LerpPosInterval(particleEffect, 2.0, pos=hitPoint),
+ Func(particleEffect.cleanup),
+ Func(battle.movie.clearRestoreParticleEffect,
+ particleEffect)
+ )
+
+ """This track necessary to sync smile sign animation with start of particle effect"""
+ signPropAnimTrack = ActorInterval(sign, 'smile', duration=4, startTime=0)
+
+ toonTrack = getToonTrack(attack, 2.6, ['cringe'], 1.9, ['sidestep'])
+ soundTrack = getSoundTrack('SA_razzle_dazzle.mp3', delay=1.6, node=suit)
+
+ return Sequence(
+ Parallel(suitTrack, signPropTrack, signPropAnimTrack, toonTrack, soundTrack),
+ Func(MovieUtil.removeProp, sign),
+ )
+
+
+def doSynergy(attack): # top ym: fixed
+ """ This function returns Tracks portraying the Synergy attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ targets = attack['target']
+ damageDelay = 1.7
+ hitAtleastOneToon = 0
+ for t in targets:
+ if (t['hp'] > 0):
+ hitAtleastOneToon = 1
+
+ particleEffect = BattleParticles.createParticleEffect('Synergy')
+ waterfallEffect = BattleParticles.createParticleEffect(file='synergyWaterfall')
+
+ suitTrack = getSuitAnimTrack(attack) # Get suit animation track
+ partTrack = getPartTrack(particleEffect, 1.0, 1.9, [particleEffect, suit, 0])
+ waterfallTrack = getPartTrack(waterfallEffect, 0.8, 1.9,
+ [waterfallEffect, suit, 0])
+
+ damageAnims = [['slip-forward']] # Standard slip-forward damage animation
+ dodgeAnims = [] # Dodge will be slowed down
+ dodgeAnims.append(['jump', 0.01, 0, 0.6]) # Begin to jump
+ # Now get animation interval with time inserted to slow down the jump at its peak
+ dodgeAnims.extend(getSplicedLerpAnims('jump', 0.31, 1.3, startTime=0.6))
+ dodgeAnims.append(['jump', 0, 0.91]) # Complete the jump
+ toonTracks = getToonTracks(
+ attack, damageDelay=damageDelay,
+ damageAnimNames=['slip-forward'], dodgeDelay=0.91,
+ splicedDodgeAnims=dodgeAnims, showMissedExtraTime=1.0)
+
+ synergySoundTrack = Sequence(Wait(0.9),
+ SoundInterval(globalBattleSoundCache.getSound('SA_synergy.mp3'), node=suit))
+
+ if (hitAtleastOneToon > 0):
+ fallingSoundTrack = Sequence(Wait(damageDelay+0.5),
+ SoundInterval(globalBattleSoundCache.getSound('Toon_bodyfall_synergy.mp3'), node=suit))
+ return Parallel(suitTrack, partTrack, waterfallTrack,
+ synergySoundTrack, fallingSoundTrack, toonTracks)
+ else:
+ return Parallel(suitTrack, partTrack, waterfallTrack,
+ synergySoundTrack, toonTracks)
+
+
+def doTeeOff(attack): # top ym: fixed
+ """ This function returns Tracks portraying the TeeOff attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ club = globalPropPool.getProp('golf-club')
+ ball = globalPropPool.getProp('golf-ball')
+
+ suitTrack = getSuitTrack(attack)
+ clubPosPoints = [MovieUtil.PNT3_ZERO, VBase3(63.097, 43.988, -18.435)]
+ clubPropTrack = getPropTrack(club, suit.getLeftHand(), clubPosPoints, 0.5, 5.2,
+ Point3(1.1, 1.1, 1.1))
+
+ # The ball is positioned differently for each suit
+ suitName = attack['suitName']
+ if (suitName == 'ym'):
+ ballPosPoints = [Point3(2.1, 0, 0.1)]
+ elif (suitName == 'tbc'):
+ ballPosPoints = [Point3(4.1, 0, 0.1)]
+ elif (suitName == 'm'):
+ ballPosPoints = [Point3(3.2, 0, 0.1)]
+ elif (suitName == 'rb'):
+ ballPosPoints = [Point3(4.2, 0, 0.1)]
+ else:
+ ballPosPoints = [Point3(2.1, 0, 0.1)]
+
+ ballPropTrack = Sequence(
+ getPropAppearTrack(ball, suit, ballPosPoints, 1.7, Point3(1.5, 1.5, 1.5)),
+ # Now leave the ball in place, not on club while it swings
+ Func(battle.movie.needRestoreRenderProp, ball),
+ Func(ball.wrtReparentTo, render),
+ Wait(2.15), # Wait for club to swing
+ )
+
+ # Calculate missPoint with lambda function
+ missPoint = lambda ball=ball, toon=toon: __toonMissPoint(ball, toon)
+ ballPropTrack.append(getPropThrowTrack(attack, ball, [__toonFacePoint(toon)],
+ [missPoint]))
+ ballPropTrack.append(Func(battle.movie.clearRenderProp, ball))
+
+ dodgeDelay = suitTrack.getDuration()-4.35
+ toonTrack = getToonTrack(attack, suitTrack.getDuration()-2.25, ['conked'],
+ dodgeDelay, ['duck'], showMissedExtraTime=1.7)
+ soundTrack = getSoundTrack('SA_tee_off.mp3', delay=4.1, node=suit)
+
+ return Parallel(suitTrack, toonTrack, clubPropTrack, ballPropTrack, soundTrack)
+
+
+def doBrainStorm(attack): # top mm(c), m/s(b): fixed
+ """ This function returns Tracks portraying the BrainStorm attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ BattleParticles.loadParticles()
+ snowEffect = BattleParticles.createParticleEffect('BrainStorm')
+ snowEffect2 = BattleParticles.createParticleEffect('BrainStorm')
+ snowEffect3 = BattleParticles.createParticleEffect('BrainStorm')
+ effectColor = Vec4(0.65, 0.79, 0.93, 0.85)
+ BattleParticles.setEffectTexture(snowEffect, 'brainstorm-box', color=effectColor)
+ BattleParticles.setEffectTexture(snowEffect2, 'brainstorm-env', color=effectColor)
+ BattleParticles.setEffectTexture(snowEffect3, 'brainstorm-track', color=effectColor)
+ # cloud = MovieUtil.copyProp(toon.cloudActors[0])
+ cloud = globalPropPool.getProp('stormcloud')
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): # not used with type a
+ partDelay = 1.2
+ damageDelay = 4.5
+ dodgeDelay = 3.3
+ elif (suitType == 'b'):
+ partDelay = 1.2
+ damageDelay = 4.5
+ dodgeDelay = 3.3
+ elif (suitType == 'c'):
+ partDelay = 1.2
+ damageDelay = 4.5
+ dodgeDelay = 3.3
+
+ suitTrack = getSuitTrack(attack, delay=0.9)
+ initialCloudHeight = suit.height + 3 #Initial height of cloud set to 3 meters above suit
+ cloudPosPoints = [Point3(0, 3, initialCloudHeight), VBase3(180, 0, 0)]
+ # Make the storm cloud appear over and slightly in front of the suit
+ cloudPropTrack = Sequence()
+ cloudPropTrack.append(Func(cloud.pose, 'stormcloud', 0))
+ cloudPropTrack.append(getPropAppearTrack(cloud, suit, cloudPosPoints, 0.000001,
+ Point3(3, 3, 3), scaleUpTime=0.7))
+ cloudPropTrack.append(Func(battle.movie.needRestoreRenderProp,
+ cloud))
+ cloudPropTrack.append(Func(cloud.wrtReparentTo, render))
+ # Now calculate the targetPoint for the cloud to move to (over the target toon)
+ targetPoint = __toonFacePoint(toon)
+ targetPoint.setZ(targetPoint[2]+ 3)# Set end height of cloud to 3 meters above toon head
+ # Push the cloud over to the target point (whether it hits or misses)
+ cloudPropTrack.append(Wait(1.1)) # Wait to be pushed by suit
+ cloudPropTrack.append(LerpPosInterval(cloud, 1, pos=targetPoint))
+ # Must include particle track within cloud intervals to be safe...if the cloud is on
+ # a separate track and get removed before the effect is parented, it will crash
+ cloudPropTrack.append(Wait(partDelay)) # Wait before snow falls from cloud
+ cloudPropTrack.append(Parallel(
+ ParticleInterval(snowEffect, cloud, worldRelative=0,
+ duration=2.2, cleanup = True),
+ Sequence(Wait(0.5),
+ ParticleInterval(snowEffect2, cloud,
+ worldRelative=0, duration=1.7, cleanup = True)),
+ Sequence(Wait(1.0),
+ ParticleInterval(snowEffect3, cloud,
+ worldRelative=0, duration=1.2, cleanup = True)),
+ Sequence(ActorInterval(cloud, 'stormcloud', startTime=3,
+ duration=0.5),
+ ActorInterval(cloud, 'stormcloud', startTime=2.5,
+ duration=0.5),
+ ActorInterval(cloud, 'stormcloud', startTime=1,
+ duration=1.5)),
+ ))
+
+ cloudPropTrack.append(Wait(0.4)) # Wait a moment before cloud goes away
+ cloudPropTrack.append(LerpScaleInterval(cloud, 0.5,
+ MovieUtil.PNT3_NEARZERO))
+ # The particle effect has already been cleaned up, it's safe to remove the cloud
+ cloudPropTrack.append(Func(MovieUtil.removeProp, cloud))
+ cloudPropTrack.append(Func(battle.movie.clearRenderProp, cloud))
+
+ damageAnims = [['cringe', 0.01, 0.4, 0.8], ['duck', 0.000001, 1.6]]
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'],
+ showMissedExtraTime=1.1)
+ soundTrack = getSoundTrack('SA_brainstorm.mp3', delay=2.6, node=suit)
+
+ return Parallel(suitTrack, toonTrack, cloudPropTrack, soundTrack)
+
+
+def doBuzzWord(attack): # top mm(c), dt(a): fixed
+ """ This function returns Tracks portraying the BuzzWord attack """
+ suit = attack['suit']
+ target = attack['target']
+ toon = target['toon']
+ battle = attack['battle']
+ BattleParticles.loadParticles()
+ particleEffects = []
+ texturesList = ['buzzwords-crash', 'buzzwords-inc', 'buzzwords-main',
+ 'buzzwords-over', 'buzzwords-syn']
+ for i in range(0, 5):
+ effect = BattleParticles.createParticleEffect('BuzzWord')
+ if (random.random() > 0.5): # 50% chance the words will be yellow or black
+ BattleParticles.setEffectTexture(effect, texturesList[i],
+ color=Vec4(1, 0.94, 0.02, 1))
+ else:
+ BattleParticles.setEffectTexture(effect, texturesList[i],
+ color=Vec4(0, 0, 0, 1))
+ particleEffects.append(effect)
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): # dt
+ partDelay = 4.0
+ partDuration = 2.2
+ damageDelay = 4.5
+ dodgeDelay = 3.8
+ elif (suitType == 'b'): # not used with type B
+ partDelay = 1.3
+ partDuration = 2
+ damageDelay = 2.5
+ dodgeDelay = 1.8
+ elif (suitType == 'c'): # mm
+ partDelay = 4.0
+ partDuration = 2.2
+ damageDelay = 4.5
+ dodgeDelay = 3.8
+
+ # Some suits need to move the position and orientation of the particle effects
+ suitName = suit.getStyleName()
+ if (suitName == 'm'):
+ for effect in particleEffects:
+ effect.setPos(0, 2.8, suit.getHeight()-2.5)
+ effect.setHpr(0, -20, 0)
+ elif (suitName == 'mm'):
+ for effect in particleEffects:
+ effect.setPos(0, 2.1, suit.getHeight()-0.8)
+
+ suitTrack = getSuitTrack(attack)
+ particleTracks = []
+ for effect in particleEffects:
+ particleTracks.append(getPartTrack(effect, partDelay, partDuration,
+ [effect, suit, 0]))
+
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, damageAnimNames=['cringe'],
+ splicedDodgeAnims=[['duck', dodgeDelay, 1.4]],
+ showMissedExtraTime=dodgeDelay+0.5)
+ soundTrack = getSoundTrack('SA_buzz_word.mp3', delay=3.9, node=suit)
+
+ return Parallel(suitTrack, toonTrack, soundTrack, *particleTracks)
+
+
+def doDemotion(attack): # top mm: fixed
+ """ This function returns Tracks portraying the Demotion attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ BattleParticles.loadParticles()
+ sprayEffect = BattleParticles.createParticleEffect('DemotionSpray')
+ freezeEffect = BattleParticles.createParticleEffect('DemotionFreeze')
+ unFreezeEffect = BattleParticles.createParticleEffect(file='demotionUnFreeze')
+ BattleParticles.setEffectTexture(sprayEffect, 'snow-particle')
+ BattleParticles.setEffectTexture(freezeEffect, 'snow-particle')
+ BattleParticles.setEffectTexture(unFreezeEffect, 'snow-particle')
+
+ # Now set the freezeEffect and unFreezeEffect height to the head of the toon
+ facePoint = __toonFacePoint(toon)
+ freezeEffect.setPos(0, 0, facePoint.getZ())
+ unFreezeEffect.setPos(0, 0, facePoint.getZ())
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ partTrack = getPartTrack(sprayEffect, 0.7, 1.1, [sprayEffect, suit, 0])
+ partTrack2 = getPartTrack(freezeEffect, 1.4, 2.9, [freezeEffect, toon, 0])
+ partTrack3 = getPartTrack(unFreezeEffect, 6.65, 0.5, [unFreezeEffect, toon, 0])
+
+ dodgeAnims = [['duck', 0.000001, 0.8]] # Standard duck animation
+ damageAnims = [] # Damage animation will gradually slow down and freeze
+ damageAnims.append(['cringe', 0.01, 0, 0.5]) # Start to cringe
+ # Now gradually continue to cringe but become slower until freezing completely
+ damageAnims.extend(getSplicedLerpAnims('cringe', 0.4, 0.5, startTime=0.5))
+ damageAnims.extend(getSplicedLerpAnims('cringe', 0.3, 0.5, startTime=0.9))
+ damageAnims.extend(getSplicedLerpAnims('cringe', 0.3, 0.6, startTime=1.2))
+ # Finish the cringe animation after holding frozen for a moment
+ damageAnims.append(['cringe', 2.6, 1.5])
+ toonTrack = getToonTrack(attack, damageDelay=1.0, splicedDamageAnims=damageAnims,
+ splicedDodgeAnims=dodgeAnims, showMissedExtraTime=1.6,
+ showDamageExtraTime=1.3)
+ soundTrack = getSoundTrack('SA_demotion.mp3', delay=1.2, node=suit)
+
+ if (dmg > 0): # If toon takes damage then use the freezing particle effects
+ return Parallel(suitTrack, toonTrack, soundTrack,
+ partTrack, partTrack2, partTrack3)
+ else: # If toon doesn't take damage, then don't include the freeze particle effect
+ return Parallel(suitTrack, toonTrack, soundTrack, partTrack)
+
+
+def doCanned(attack): # ds(b), cr(c) throw/particles?; struggle, slip-backward; sidestep
+ """ This function returns Tracks portraying the Canned attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ dmg = target['hp']
+ toon = target['toon']
+ hips = toon.getHipsParts()
+ propDelay = 0.8
+
+ # Timing slightly different for suits of different types
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'c'): # cr
+ suitDelay = 1.13
+ dodgeDelay = 3.1
+ else:
+ suitDelay = 1.83
+ dodgeDelay = 3.6
+ throwDuration = 1.5
+ can = globalPropPool.getProp('can') # Get prop from pool
+
+ # The can will have an initial scale, but it grows larger after thrown
+ # The can's scale is affected by the target toon
+ scale = 26
+ torso = toon.style.torso
+ torso = torso[0] # just want to examine the first letter of the torso
+ if (torso == 's'):
+ scaleUpPoint = Point3(scale*2.63, scale*2.63, scale*1.9975)
+ elif (torso == 'm'):
+ scaleUpPoint = Point3(scale*2.63, scale*2.63, scale*1.7975)
+ elif (torso == 'l'):
+ scaleUpPoint = Point3(scale*2.63, scale*2.63, scale*2.31)
+ canHpr = VBase3(-173.47, -0.42, 162.09)
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(-0.14, 0.15, 0.08), VBase3(-10.584, 11.945, -161.684)]
+ throwTrack = Sequence(
+ getPropAppearTrack(can, suit.getRightHand(), posPoints, propDelay,
+ Point3(6, 6, 6), scaleUpTime=0.5)
+ )
+ propDelay = propDelay + 0.5 # Add in the time to scale the prop
+ throwTrack.append(Wait(suitDelay)) # Wait while suit animates
+ hitPoint = toon.getPos(battle)
+ hitPoint.setX(hitPoint.getX() + 1.1)
+ hitPoint.setY(hitPoint.getY() - 0.5)
+ hitPoint.setZ(hitPoint.getZ() + toon.height + 1.1)
+ throwTrack.append(Func(battle.movie.needRestoreRenderProp, can))
+ throwTrack.append(getThrowTrack(can, hitPoint, duration=throwDuration, parent=battle))
+
+ # At this point, the can either cans the toon or bounces because the toon dodged
+ if (dmg > 0): # The can cans the toon
+ # Need a second can for the next lod of the toon
+ can2 = MovieUtil.copyProp(can)
+ hips1 = hips.getPath(2)
+ hips2 = hips.getPath(1)
+ can2Point = Point3(hitPoint.getX(), hitPoint.getY()+6.4, hitPoint.getZ())
+ can2.setPos(can2Point)
+ can2.setScale(scaleUpPoint)
+ can2.setHpr(canHpr)
+ throwTrack.append(Func(battle.movie.needRestoreHips))
+ throwTrack.append(Func(can.wrtReparentTo, hips1))
+ throwTrack.append(Func(can2.reparentTo, hips2))
+ throwTrack.append(Wait(2.4))
+ throwTrack.append(Func(MovieUtil.removeProp, can2))
+ throwTrack.append(Func(battle.movie.clearRestoreHips))
+
+ scaleTrack = Sequence(
+ Wait(propDelay + suitDelay),
+ LerpScaleInterval(can, throwDuration, scaleUpPoint),
+ )
+ hprTrack = Sequence(
+ Wait(propDelay + suitDelay),
+ LerpHprInterval(can, throwDuration, canHpr),
+ )
+ soundTrack = Sequence(
+ Wait(2.6),
+ SoundInterval(globalBattleSoundCache.getSound('SA_canned_tossup_only.mp3'), node=suit),
+ SoundInterval(globalBattleSoundCache.getSound('SA_canned_impact_only.mp3'), node=suit),
+ )
+ else:
+ land = toon.getPos(battle)
+ land.setZ(land.getZ() + 0.7)
+ bouncePoint1 = Point3(land.getX(), land.getY()-1.5, land.getZ()+2.5)
+ bouncePoint2 = Point3(land.getX(), land.getY()-2.1, land.getZ()-0.2)
+ bouncePoint3 = Point3(land.getX(), land.getY()-3.1, land.getZ()+1.5)
+ bouncePoint4 = Point3(land.getX(), land.getY()-4.1, land.getZ()+0.3)
+ throwTrack.append(LerpPosInterval(can, 0.4, land))
+ throwTrack.append(LerpPosInterval(can, 0.4, bouncePoint1))
+ throwTrack.append(LerpPosInterval(can, 0.3, bouncePoint2))
+ throwTrack.append(LerpPosInterval(can, 0.3, bouncePoint3))
+ throwTrack.append(LerpPosInterval(can, 0.3, bouncePoint4))
+ throwTrack.append(Wait(1.1))
+ throwTrack.append(LerpScaleInterval(can, 0.3, MovieUtil.PNT3_NEARZERO))
+
+ scaleTrack = Sequence(
+ Wait(propDelay + suitDelay),
+ LerpScaleInterval(can, throwDuration, Point3(11, 11, 11)),
+ )
+ hprTrack = Sequence(
+ Wait(propDelay + suitDelay),
+ LerpHprInterval(can, throwDuration, canHpr),
+ Wait(0.4),
+ LerpHprInterval(can, 0.4, Point3(83.27, 19.52, -177.92)),
+ LerpHprInterval(can, 0.3, Point3(95.24, -72.09, 88.65)),
+ LerpHprInterval(can, 0.2, Point3(-96.34, -2.63, 179.89)),
+ )
+ soundTrack = getSoundTrack('SA_canned_tossup_only.mp3', delay=2.6, node=suit)
+
+ canTrack = Sequence(
+ Parallel(throwTrack, scaleTrack, hprTrack),
+ Func(MovieUtil.removeProp, can),
+ Func(battle.movie.clearRenderProp, can),
+ )
+
+ damageAnims = [['struggle', propDelay+suitDelay+throwDuration, 0.01, 0.7],
+ ['slip-backward', 0.01, 0.45],]
+ toonTrack = getToonTrack(attack, splicedDamageAnims = damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'],
+ showDamageExtraTime=propDelay+suitDelay+2.4)
+
+ return Parallel(suitTrack, toonTrack, canTrack, soundTrack)
+
+
+def doDownsize(attack): # ds(b) special, toon shrinks; cringe; jump
+ """ This function returns Tracks portraying the Downsize attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ damageDelay = 2.3
+
+ sprayEffect = BattleParticles.createParticleEffect(file='downsizeSpray')
+ cloudEffect = BattleParticles.createParticleEffect(file='downsizeCloud')
+ toonPos = toon.getPos(toon)
+ cloudPos = Point3(toonPos.getX(), toonPos.getY(), toonPos.getZ()+toon.getHeight()*0.55)
+ cloudEffect.setPos(cloudPos)
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ sprayTrack = getPartTrack(sprayEffect, 1.0, 1.28, [sprayEffect, suit, 0])
+ cloudTrack = getPartTrack(cloudEffect, 2.1, 1.9, [cloudEffect, toon, 0])
+
+ # Need a track to shrink the toon down
+ if (dmg > 0): # Only shrinks if takes damage
+ initialScale = toon.getScale()
+ downScale = Vec3(0.4, 0.4, 0.4)
+ shrinkTrack = Sequence(
+ Wait(damageDelay+0.5),
+ Func(battle.movie.needRestoreToonScale),
+ LerpScaleInterval(toon, 1.0, downScale*1.1),
+ LerpScaleInterval(toon, 0.1, downScale*0.9),
+ LerpScaleInterval(toon, 0.1, downScale*1.05),
+ LerpScaleInterval(toon, 0.1, downScale*0.95),
+ LerpScaleInterval(toon, 0.1, downScale),
+ Wait(2.1),
+ LerpScaleInterval(toon, 0.5, initialScale*1.5),
+ LerpScaleInterval(toon, 0.15, initialScale*0.5),
+ LerpScaleInterval(toon, 0.15, initialScale*1.2),
+ LerpScaleInterval(toon, 0.15, initialScale*0.8),
+ LerpScaleInterval(toon, 0.15, initialScale),
+ Func(battle.movie.clearRestoreToonScale),
+ )
+
+ damageAnims = []
+ damageAnims.append(['juggle', 0.01, 0.87, 0.5])
+ damageAnims.append(['lose', 0.01, 2.17, 0.93])
+ damageAnims.append(['lose', 0.01, 3.10, -0.93])
+ damageAnims.append(['struggle', 0.01, 0.8, 1.8])
+ damageAnims.append(['sidestep-right', 0.01, 2.97, 1.49])
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=0.6, dodgeAnimNames=['sidestep'])
+
+ if (dmg > 0): # Then include the shrinking cloud and shrinkTrack
+ return Parallel(suitTrack, sprayTrack, cloudTrack, shrinkTrack, toonTrack)
+ else:
+ return Parallel(suitTrack, sprayTrack, toonTrack)
+
+
+def doPinkSlip(attack): # ds(b) throw; slip-forward; sidestep
+ """ This function returns Tracks portraying the Pinkslip attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ paper = globalPropPool.getProp('pink-slip') # Get prop from pool
+ throwDelay = 3.03
+ throwDuration = 0.5
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(0.07, -0.06, -0.18), VBase3(-172.075, -26.715, -89.131)]
+ paperAppearTrack = Sequence(
+ getPropAppearTrack(paper, suit.getRightHand(), posPoints, 0.8,
+ Point3(8, 8, 8), scaleUpTime=0.5)
+ )
+ paperAppearTrack.append(Wait(1.73)) # Wait while suit animates
+ hitPoint = __toonGroundPoint(attack, toon, 0.2, parent=battle)
+ paperAppearTrack.append(Func(battle.movie.needRestoreRenderProp,
+ paper))
+ paperAppearTrack.append(Func(paper.wrtReparentTo, battle))
+ paperAppearTrack.append(LerpPosInterval(paper, throwDuration, hitPoint))
+
+ if (dmg > 0):
+ paperPause = 0.01
+ slidePoint = Point3(hitPoint.getX(), hitPoint.getY()-5, hitPoint.getZ()+4)
+ landPoint = Point3(hitPoint.getX(), hitPoint.getY()-5, hitPoint.getZ())
+ paperAppearTrack.append(Wait(paperPause))
+ paperAppearTrack.append(LerpPosInterval(paper, 0.2, slidePoint))
+ paperAppearTrack.append(LerpPosInterval(paper, 1.1, landPoint))
+
+ paperSpinTrack = Sequence(
+ Wait(throwDelay),
+ LerpHprInterval(paper, throwDuration, VBase3(300, 0, 0)),
+ Wait(paperPause),
+ LerpHprInterval(paper, 1.3, VBase3(-200, 100, 100)),
+ )
+ else:
+ slidePoint = Point3(hitPoint.getX(), hitPoint.getY()-5, hitPoint.getZ())
+ paperAppearTrack.append(LerpPosInterval(paper, 0.5, slidePoint))
+
+ paperSpinTrack = Sequence(
+ Wait(throwDelay),
+ LerpHprInterval(paper, throwDuration, VBase3(300, 0, 0)),
+ LerpHprInterval(paper, 0.5, VBase3(10, 0, 0)),
+ )
+
+ propTrack = Sequence()
+ propTrack.append(Parallel(paperAppearTrack, paperSpinTrack))
+ propTrack.append(LerpScaleInterval(paper, 0.4, MovieUtil.PNT3_NEARZERO))
+ propTrack.append(Func(MovieUtil.removeProp, paper))
+ propTrack.append(Func(battle.movie.clearRenderProp, paper))
+
+ damageAnims = [['jump', 0.01, 0.3, 0.7], ['slip-forward', 0.01]]
+ toonTrack = getToonTrack(attack, damageDelay=2.81, splicedDamageAnims=damageAnims,
+ dodgeDelay=2.8, dodgeAnimNames=['jump'],
+ showDamageExtraTime=0.9)
+ soundTrack = getSoundTrack('SA_pink_slip.mp3', delay=2.9, duration=1.1, node=suit)
+
+ return Parallel(suitTrack, toonTrack, propTrack, soundTrack)
+
+
+def doReOrg(attack): # special, reassign toon parts; cringe, jump; jump
+ """ This function returns Tracks portraying the ReOrg attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ damageDelay = 1.7
+ attackDelay = 1.7
+ sprayEffect = BattleParticles.createParticleEffect(file='reorgSpray')
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ partTrack = getPartTrack(sprayEffect, 1.0, 1.9, [sprayEffect, suit, 0])
+
+ if (dmg > 0): # Then manipulate the head and chest
+ # The head track pops off the head, plays with it a bit, puts it back on upside down,
+ # fixes it, rotates it backwards, and then restores it
+ headParts = toon.getHeadParts()
+ print '***********headParts pos=', headParts[0].getPos()
+ print '***********headParts hpr=', headParts[0].getHpr()
+ headTracks = Parallel()
+ for partNum in range(0, headParts.getNumPaths()):
+ part = headParts.getPath(partNum)
+ x = part.getX()
+ y = part.getY()
+ z = part.getZ()
+ h = part.getH()
+ p = part.getP()
+ r = part.getR()
+ headTracks.append(Sequence(
+ Wait(attackDelay),
+ LerpPosInterval(part, 0.1, Point3(x-0.2, y, z-0.03)), # prepare to pop off head
+ LerpPosInterval(part, 0.1, Point3(x+0.4, y, z-0.03)), # prepare to pop off head
+ LerpPosInterval(part, 0.1, Point3(x-0.4, y, z-0.03)), # prepare to pop off head
+ LerpPosInterval(part, 0.1, Point3(x+0.4, y, z-0.03)), # prepare to pop off head
+ LerpPosInterval(part, 0.1, Point3(x-0.2, y, z-0.04)), # prepare to pop off head
+ LerpPosInterval(part, 0.25, Point3(x, y, z+2.2)), # pop off head
+ LerpHprInterval(part, 0.4, VBase3(360, 0, 180)), # spin and flip it
+ LerpPosInterval(part, 0.3, Point3(x, y, z+3.1)), # raise it a bit
+ LerpPosInterval(part, 0.15, Point3(x, y, z+0.3)), # put it back on
+ Wait(0.15),
+ LerpHprInterval(part, 0.6, VBase3(-745, 0, 180),
+ startHpr=VBase3(0, 0, 180)), # spin
+ LerpHprInterval(part, 0.8, VBase3(25, 0, 180),
+ startHpr=VBase3(0, 0, 180)), # back
+ LerpPosInterval(part, 0.15, Point3(x, y, z+1)), # pull it up again
+ LerpHprInterval(part, 0.3, VBase3(h, p, r)), # flip it back over
+ Wait(0.2),
+ LerpPosInterval(part, 0.1, Point3(x, y, z)), # put it back down finally
+ Wait(0.9),
+ ))
+
+ # The chest tracks involvement movement for the arms, sleeves, and hands,
+ # each of which involve three LOD's. The getChestTrack function returns the
+ # interval for movement involving the chest performed upon the body part parameter.
+ def getChestTrack(part, attackDelay=attackDelay):
+ origScale = part.getScale()
+ return Sequence(
+ Wait(attackDelay),
+ LerpHprInterval(part, 1.1, VBase3(180, 0, 0)),
+ Wait(1.1),
+ LerpHprInterval(part, 1.1, part.getHpr()),
+ )
+
+ chestTracks = Parallel()
+ arms = toon.findAllMatches('**/arms')
+ sleeves = toon.findAllMatches('**/sleeves')
+ hands = toon.findAllMatches('**/hands')
+ print '*************arms hpr=', arms[0].getHpr()
+
+ for partNum in range(0, arms.getNumPaths()):
+ chestTracks.append(getChestTrack(arms.getPath(partNum)))
+ chestTracks.append(getChestTrack(sleeves.getPath(partNum)))
+ chestTracks.append(getChestTrack(hands.getPath(partNum)))
+
+ damageAnims = [['neutral', 0.01, 0.01, 0.5],
+ ['juggle', 0.01, 0.01, 1.48],
+ ['think', 0.01, 2.28]]
+ dodgeAnims = [] # Dodge will be slowed down
+ dodgeAnims.append(['think', 0.01, 0, 0.6]) # Begin to jump
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=0.01, dodgeAnimNames=['duck'],
+ showDamageExtraTime=2.1, showMissedExtraTime=2.0)
+
+ if (dmg > 0): # Use the head and chest tracks
+ return Parallel(suitTrack, partTrack, toonTrack, headTracks, chestTracks)
+ else:
+ return Parallel(suitTrack, partTrack, toonTrack)
+
+def doSacked(attack): # ds(b) throw; struggle, jump; sidestep
+ """ This function returns Tracks portraying the Sacked attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ dmg = target['hp']
+ toon = target['toon']
+ hips = toon.getHipsParts()
+ propDelay = 0.85
+ suitDelay = 1.93
+ throwDuration = 0.9
+
+ sack = globalPropPool.getProp('sandbag')
+
+ initialScale = Point3(0.65, 1.47, 1.28)
+ scaleUpPoint = Point3(1.05, 1.67, 0.98) * 4.1
+ sackHpr = VBase3(-154.33, -6.33, 163.80)
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(0.51, -2.03, -0.73), VBase3(90.000, -24.980, 77.730)]
+ sackAppearTrack = Sequence(
+ getPropAppearTrack(sack, suit.getRightHand(), posPoints, propDelay,
+ initialScale, scaleUpTime=0.2)
+ )
+ propDelay = propDelay + 0.2 # Add in the time to scale the prop
+ sackAppearTrack.append(Wait(suitDelay)) # Wait while suit animates
+ hitPoint = toon.getPos(battle)
+ if (dmg > 0): # Different points to throw at for hit or miss
+ hitPoint.setX(hitPoint.getX() + 2.1)
+ hitPoint.setY(hitPoint.getY() + 0.9)
+ hitPoint.setZ(hitPoint.getZ() + toon.height + 1.2)
+ else:
+ hitPoint.setZ(hitPoint.getZ() - 0.2)
+ sackAppearTrack.append(Func(battle.movie.needRestoreRenderProp, sack))
+ sackAppearTrack.append(getThrowTrack(sack, hitPoint, duration=throwDuration, parent=battle))
+
+ # At this point, the sack either sack the toon or bounces because the toon dodged
+ if (dmg > 0): # The sack sacks the toon
+ # Need a second sack for the next lod of the toon
+ sack2 = MovieUtil.copyProp(sack)
+ hips1 = hips.getPath(2)
+ hips2 = hips.getPath(1)
+ sack2.hide()
+ sack2.reparentTo(battle)
+ sack2.setPos(Point3(hitPoint.getX(), hitPoint.getY(), hitPoint.getZ()))
+ sack2.setScale(scaleUpPoint)
+ sack2.setHpr(sackHpr)
+ sackAppearTrack.append(Func(battle.movie.needRestoreHips))
+ sackAppearTrack.append(Func(sack.wrtReparentTo, hips1))
+ sackAppearTrack.append(Func(sack2.show))
+ sackAppearTrack.append(Func(sack2.wrtReparentTo, hips2))
+ sackAppearTrack.append(Wait(2.4))
+ sackAppearTrack.append(Func(MovieUtil.removeProp, sack2))
+ sackAppearTrack.append(Func(battle.movie.clearRestoreHips))
+
+ scaleTrack = Sequence(
+ Wait(propDelay + suitDelay),
+ LerpScaleInterval(sack, throwDuration, scaleUpPoint),
+ Wait(1.8),
+ LerpScaleInterval(sack, 0.3, MovieUtil.PNT3_NEARZERO),
+ )
+ hprTrack = Sequence(
+ Wait(propDelay + suitDelay),
+ LerpHprInterval(sack, throwDuration, sackHpr),
+ )
+ sackTrack = Sequence(
+ Parallel(sackAppearTrack, scaleTrack, hprTrack),
+ Func(MovieUtil.removeProp, sack),
+ Func(battle.movie.clearRenderProp, sack),
+ )
+ else:
+ sackAppearTrack.append(Wait(1.1))
+ sackAppearTrack.append(LerpScaleInterval(sack, 0.3, MovieUtil.PNT3_NEARZERO))
+ sackTrack = Sequence(
+ sackAppearTrack,
+ Func(MovieUtil.removeProp, sack),
+ Func(battle.movie.clearRenderProp, sack),
+ )
+
+ damageAnims = [['struggle', 0.01, 0.01, 0.7],
+ ['slip-backward', 0.01, 0.45],]
+ toonTrack = getToonTrack(attack, damageDelay=propDelay+suitDelay+throwDuration,
+ splicedDamageAnims=damageAnims, dodgeDelay=3.0,
+ dodgeAnimNames=['sidestep'], showDamageExtraTime=1.8,
+ showMissedExtraTime=0.8)
+
+ return Parallel(suitTrack, toonTrack, sackTrack)
+
+
+def doGlowerPower(attack): # tw: fixed
+ """ This function returns Tracks portraying the GlowerPower attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ leftKnives = []
+ rightKnives = []
+ for i in range(0, 3):
+ leftKnives.append(globalPropPool.getProp('dagger'))
+ rightKnives.append(globalPropPool.getProp('dagger'))
+
+ suitTrack = getSuitTrack(attack)
+ # Different suits have different places for their faces when they glower
+ suitName = suit.getStyleName()
+ if (suitName == 'hh'):
+ leftPosPoints = [Point3(0.3, 4.3, 5.3), MovieUtil.PNT3_ZERO]
+ rightPosPoints = [Point3(-0.3, 4.3, 5.3), MovieUtil.PNT3_ZERO]
+ elif (suitName == 'tbc'):
+ leftPosPoints = [Point3(0.6, 4.5, 6), MovieUtil.PNT3_ZERO]
+ rightPosPoints = [Point3(-0.6, 4.5, 6), MovieUtil.PNT3_ZERO]
+ else:
+ leftPosPoints = [Point3(0.4, 3.8, 3.7), MovieUtil.PNT3_ZERO]
+ rightPosPoints = [Point3(-0.4, 3.8, 3.7), MovieUtil.PNT3_ZERO]
+ leftKnifeTracks = Parallel() # Build a track for the left knives being thrown
+ rightKnifeTracks = Parallel() # Build a track for the right knives being thrown
+
+ for i in range(0, 3):
+ knifeDelay = 0.11
+ leftTrack = Sequence() # Start new interval for next left knife
+ leftTrack.append(Wait(1.1)) # All knives wait atleast this much
+ leftTrack.append(Wait(i*knifeDelay)) # Put delay between each knife throw
+ # Now make the knife scale up quickly
+ leftTrack.append(getPropAppearTrack(leftKnives[i], suit, leftPosPoints,
+ 0.000001, Point3(.4, .4, .4), scaleUpTime=0.1))
+ # Throw the knife toward the toon's face or the miss point
+ leftTrack.append(getPropThrowTrack(attack, leftKnives[i],
+ hitPointNames=['face'], missPointNames=['miss'],
+ hitDuration=0.3, missDuration=0.3))
+ leftKnifeTracks.append(leftTrack)
+
+ rightTrack = Sequence() # Start new interval for next right knife
+ rightTrack.append(Wait(1.1)) # All knives wait atleast this much
+ rightTrack.append(Wait(i*knifeDelay)) # Put delay between each knife throw
+ # Now make the knife scale up quickly
+ rightTrack.append(getPropAppearTrack(rightKnives[i], suit, rightPosPoints,
+ 0.000001, Point3(.4, .4, .4), scaleUpTime=0.1))
+ # Throw the knife toward the toon's face or the miss point
+ rightTrack.append(getPropThrowTrack(attack, rightKnives[i],
+ hitPointNames=['face'], missPointNames=['miss'],
+ hitDuration=0.3, missDuration=0.3))
+ rightKnifeTracks.append(rightTrack)
+
+ damageAnims = [['slip-backward', 0.01, 0.35]]
+ toonTrack = getToonTrack(attack, damageDelay=1.6, splicedDamageAnims=damageAnims,
+ dodgeDelay=0.7, dodgeAnimNames=['sidestep'])
+ soundTrack = getSoundTrack('SA_glower_power.mp3', delay=1.1, node=suit)
+
+ return Parallel(suitTrack, toonTrack, soundTrack, leftKnifeTracks, rightKnifeTracks)
+
+
+def doHalfWindsor(attack): # hh(a) throw; conked; sidestep
+ """ This function returns Tracks portraying the Half Windsor attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ tie = globalPropPool.getProp('half-windsor') # Get tie from pool
+ throwDelay = 2.17
+ damageDelay = 3.4
+ dodgeDelay = 2.4
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(0.02, 0.88, 0.48), VBase3(99, -3, -108.2)]
+ tiePropTrack = getPropAppearTrack(tie, suit.getRightHand(), posPoints, 0.5,
+ Point3(7, 7, 7), scaleUpTime=0.5)
+ tiePropTrack.append(Wait(throwDelay)) # Wait while suit animates
+ missPoint = __toonMissBehindPoint(toon, parent=battle)
+ missPoint.setX(missPoint.getX() - 1.1)
+ missPoint.setZ(missPoint.getZ() + 4)
+ hitPoint = __toonFacePoint(toon, parent=battle)
+ hitPoint.setX(hitPoint.getX() - 1.1)
+ hitPoint.setY(hitPoint.getY() - 0.7)
+ hitPoint.setZ(hitPoint.getZ() + 0.9)
+ tiePropTrack.append(getPropThrowTrack(attack, tie, [hitPoint], [missPoint],
+ hitDuration=0.4, missDuration=0.8, missScaleDown=0.3, parent=battle))
+
+ damageAnims = [['conked', 0.01, 0.01, 0.4], ['cringe', 0.01, 0.7]]
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'])
+
+ return Parallel(suitTrack, toonTrack, tiePropTrack)
+
+
+def doHeadShrink(attack): # hh(a) special, shrink head; cringe; jump
+ """ This function returns Tracks portraying the Guilt Trip attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ damageDelay = 2.1
+ dodgeDelay = 1.4
+
+ shrinkSpray = BattleParticles.createParticleEffect(file='headShrinkSpray')
+ shrinkCloud = BattleParticles.createParticleEffect(file='headShrinkCloud')
+ shrinkDrop = BattleParticles.createParticleEffect(file='headShrinkDrop')
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ sprayTrack = getPartTrack(shrinkSpray, 0.3, 1.4, [shrinkSpray, suit, 0])
+
+ shrinkCloud.reparentTo(battle)
+ adjust = 0.4 # We push the cloud back slightly to be over the head during the animation
+ x = toon.getX(battle)
+ y = toon.getY(battle) - adjust
+ z = 8
+ shrinkCloud.setPos(Point3(x, y, z))
+ shrinkDrop.setPos(Point3(0, 0-adjust, 7.5))
+ off = 0.7
+ cloudPoints = [Point3(x+off, y, z), Point3(x+off/2, y+off/2, z), Point3(x, y+off, z),
+ Point3(x-off/2, y+off/2, z), Point3(x-off, y, z), Point3(x-off/2, y-off/2, z),
+ Point3(x, y-off, z), Point3(x+off/2, y-off/2, z), Point3(x+off, y, z), Point3(x, y, z)]
+
+ # The cloud will "circle" around the toon a bit as it sprinkles down shrinking dust
+ circleTrack = Sequence()
+ for point in cloudPoints:
+ circleTrack.append(LerpPosInterval(shrinkCloud, 0.14, point, other=battle))
+
+ cloudTrack = Sequence()
+ cloudTrack.append(Wait(1.42))
+ cloudTrack.append(Func(battle.movie.needRestoreParticleEffect,
+ shrinkCloud))
+ cloudTrack.append(Func(shrinkCloud.start, battle))
+ cloudTrack.append(circleTrack)
+ cloudTrack.append(circleTrack)
+ cloudTrack.append(LerpFunctionInterval(shrinkCloud.setAlphaScale, fromData=1,
+ toData=0, duration=0.7))
+ cloudTrack.append(Func(shrinkCloud.cleanup))
+ cloudTrack.append(Func(battle.movie.clearRestoreParticleEffect,
+ shrinkCloud))
+
+ shrinkDelay = 0.8
+ shrinkDuration = 1.1
+ shrinkTrack = Sequence()
+ # Now create intervals to shrink the toons head
+ if (dmg > 0):
+ headParts = toon.getHeadParts()
+ initialScale = headParts.getPath(0).getScale()[0]
+ shrinkTrack.append(Wait(damageDelay+shrinkDelay))
+
+ def scaleHeadParallel(scale, duration, headParts=headParts):
+ headTracks = Parallel()
+ for partNum in range(0, headParts.getNumPaths()):
+ nextPart = headParts.getPath(partNum)
+ headTracks.append(LerpScaleInterval(nextPart, duration,
+ Point3(scale, scale, scale)))
+ return headTracks
+
+ shrinkTrack.append(Func(battle.movie.needRestoreHeadScale))
+ shrinkTrack.append(scaleHeadParallel(0.6, shrinkDuration))
+ shrinkTrack.append(Wait(1.6))
+ shrinkTrack.append(scaleHeadParallel(initialScale*3.2, 0.4))
+ shrinkTrack.append(scaleHeadParallel(initialScale*0.7, 0.4))
+ shrinkTrack.append(scaleHeadParallel(initialScale*2.5, 0.3))
+ shrinkTrack.append(scaleHeadParallel(initialScale*0.8, 0.3))
+ shrinkTrack.append(scaleHeadParallel(initialScale*1.9, 0.2))
+ shrinkTrack.append(scaleHeadParallel(initialScale*0.85, 0.2))
+ shrinkTrack.append(scaleHeadParallel(initialScale*1.7, 0.15))
+ shrinkTrack.append(scaleHeadParallel(initialScale*0.9, 0.15))
+ shrinkTrack.append(scaleHeadParallel(initialScale*1.3, 0.1))
+ shrinkTrack.append(scaleHeadParallel(initialScale, 0.1))
+ shrinkTrack.append(Func(battle.movie.clearRestoreHeadScale))
+ shrinkTrack.append(Wait(0.7))
+
+ dropTrack = getPartTrack(shrinkDrop, 1.5, 2.5, [shrinkDrop, toon, 0])
+ damageAnims = []
+ damageAnims.append(['cringe', 0.01, 0.65, 0.2])
+ damageAnims.extend(getSplicedLerpAnims('cringe', 0.64, 1.0, startTime=0.85))
+ damageAnims.append(['cringe', 0.4, 1.49])
+ damageAnims.append(['conked', 0.01, 3.6, -1.6])
+ damageAnims.append(['conked', 0.01, 3.1, 0.4])
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'])
+
+ if (dmg > 0): # then use the shrinkTrack
+ shrinkSound = globalBattleSoundCache.getSound('SA_head_shrink_only.mp3')
+ growSound = globalBattleSoundCache.getSound('SA_head_grow_back_only.mp3')
+ soundTrack = Sequence(
+ Wait(2.1),
+ SoundInterval(shrinkSound, duration=2.1, node=suit),
+ Wait(1.6),
+ SoundInterval(growSound, node=suit),
+ )
+
+ return Parallel(suitTrack, sprayTrack, cloudTrack, dropTrack,
+ toonTrack, shrinkTrack, soundTrack)
+ else:
+ return Parallel(suitTrack, sprayTrack, cloudTrack,
+ dropTrack, toonTrack)
+
+
+def doRolodex(attack): # top ac/tm(b), nd(a): fixed
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ rollodex = globalPropPool.getProp('rollodex')
+
+# particleEffect = BattleParticles.createParticleEffect(file='rollodexVortex')
+ particleEffect2 = BattleParticles.createParticleEffect(file='rollodexWaterfall')
+ particleEffect3 = BattleParticles.createParticleEffect(file='rollodexStream')
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'):
+ propPosPoints = [Point3(-0.51, -0.03, -0.10), VBase3(89.673, 2.166, 177.786)]
+ propScale = Point3(1.20, 1.20, 1.20)
+ partDelay = 2.6
+ part2Delay = 2.8
+ part3Delay = 3.2
+ partDuration = 1.6
+ part2Duration = 1.9
+ part3Duration = 1
+ damageDelay = 3.8
+ dodgeDelay = 2.5
+ elif (suitType == 'b'):
+ propPosPoints = [Point3(0.12, 0.24, 0.01), VBase3(99.032, 5.973, -179.839)]
+ propScale = Point3(0.91, 0.91, 0.91)
+ partDelay = 2.9
+ part2Delay = 3.1
+ part3Delay = 3.5
+ partDuration = 1.6
+ part2Duration = 1.9
+ part3Duration = 1
+ damageDelay = 4
+ dodgeDelay = 2.5
+ elif (suitType == 'c'): # not used with type C
+ propPosPoints = [Point3(-0.51, -0.03, -0.10), VBase3(89.673, 2.166, 177.786)]
+ propScale = Point3(1.20, 1.20, 1.20)
+ partDelay = 2.3
+ part2Delay = 2.8
+ part3Delay = 3.2
+ partDuration = 1.9
+ part2Duration = 1.9
+ part3Duration = 1
+ damageDelay = 3.5
+ dodgeDelay = 2.5
+
+ hitPoint = lambda toon=toon: __toonFacePoint(toon)
+
+ #Particle Effect: Vortex
+ # Building w/out getPartTrack because of Lerp to move votex effect
+# partTrack = Sequence(
+# (partDelay, Func(battle.movie.needRestoreParticleEffect,
+# particleEffect)),
+# Func(particleEffect.start, suit),
+# Func(particleEffect.wrtReparentTo, render),
+# LerpPosInterval(particleEffect, partDuration, pos=hitPoint),
+# Func(particleEffect.cleanup),
+# Func(battle.movie.clearRestoreParticleEffect,
+# particleEffect),
+# )
+
+ #Particle Effect 2: Waterfall
+ partTrack2 = getPartTrack(particleEffect2, part2Delay, part2Duration,
+ [particleEffect2, suit, 0])
+
+ #Particle Effect 3: Stream
+ partTrack3 = getPartTrack(particleEffect3, part3Delay, part3Duration,
+ [particleEffect3, suit, 0])
+
+ suitTrack = getSuitTrack(attack)
+ propTrack = getPropTrack(rollodex, suit.getLeftHand(), propPosPoints, 0.000001,
+ 4.7, scaleUpPoint=propScale,
+ anim=0, propName='rollodex', animDuration=0,
+ animStartTime=0)
+
+ toonTrack = getToonTrack(attack, damageDelay, ['conked'], dodgeDelay, ['sidestep'])
+ soundTrack = getSoundTrack('SA_rolodex.mp3', delay=2.8, node=suit)
+
+ return Parallel(suitTrack, toonTrack, propTrack, soundTrack, partTrack2, partTrack3)
+
+def doEvilEye(attack): # cr(c) throw; cringe, slip-backward; duck
+ """ This function returns Tracks portraying the Evil Eye attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ eye = globalPropPool.getProp('evil-eye')
+ damageDelay = 2.44
+ dodgeDelay = 1.64
+
+ suitName = suit.getStyleName()
+ if (suitName == 'cr'):
+ posPoints = [Point3(-0.46, 4.85, 5.28), VBase3(-155.000, -20.000, 0.000)]
+ elif (suitName == 'tf'):
+ posPoints = [Point3(-0.4, 3.65, 5.01), VBase3(-155.000, -20.000, 0.000)]
+ elif (suitName == 'le'):
+ posPoints = [Point3(-0.64, 4.45, 5.91), VBase3(-155.000, -20.000, 0.000)]
+ else:
+ posPoints = [Point3(-0.4, 3.65, 5.01), VBase3(-155.000, -20.000, 0.000)]
+
+ appearDelay = 0.8
+ suitHoldStart = 1.06
+ suitHoldStop = 1.69
+ suitHoldDuration = suitHoldStop - suitHoldStart
+ eyeHoldDuration = 1.1
+ moveDuration = 1.1
+
+ suitSplicedAnims = []
+ suitSplicedAnims.append(['glower', 0.01, 0.01, suitHoldStart])
+ suitSplicedAnims.extend(getSplicedLerpAnims('glower', suitHoldDuration, 1.1,
+ startTime=suitHoldStart))
+ suitSplicedAnims.append(['glower', 0.01, suitHoldStop])
+ suitTrack = getSuitTrack(attack, splicedAnims=suitSplicedAnims)
+
+ eyeAppearTrack = Sequence(
+ Wait(suitHoldStart),
+ Func(__showProp, eye, suit, posPoints[0], posPoints[1]),
+ LerpScaleInterval(eye, suitHoldDuration, Point3(11, 11, 11)),
+ Wait(eyeHoldDuration*.3),
+ LerpHprInterval(eye, 0.02, Point3(205, 40, 0)),
+ Wait(eyeHoldDuration*.7),
+ Func(battle.movie.needRestoreRenderProp, eye),
+ Func(eye.wrtReparentTo, battle),
+ )
+
+ toonFace = __toonFacePoint(toon, parent=battle)
+ if (dmg > 0):
+ lerpInterval = LerpPosInterval(eye, moveDuration, toonFace)
+ else:
+ lerpInterval = LerpPosInterval(eye, moveDuration, Point3(toonFace.getX(),
+ toonFace.getY()-5, toonFace.getZ()-2))
+
+ eyeMoveTrack = lerpInterval
+ eyeRollTrack = LerpHprInterval(eye, moveDuration, Point3(0, 0, -180))
+
+ eyePropTrack = Sequence(
+ eyeAppearTrack,
+ Parallel(eyeMoveTrack, eyeRollTrack),
+ Func(battle.movie.clearRenderProp, eye),
+ Func(MovieUtil.removeProp, eye),
+ )
+
+ damageAnims = [['duck', 0.01, 0.01, 1.4], ['cringe', 0.01, 0.3]]
+ toonTrack = getToonTrack(attack, splicedDamageAnims=damageAnims, damageDelay=damageDelay,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['duck'],
+ showDamageExtraTime=1.7, showMissedExtraTime=1.7)
+ soundTrack = getSoundTrack('SA_evil_eye.mp3', delay=1.3, node=suit)
+
+ return Parallel(suitTrack, toonTrack, eyePropTrack, soundTrack)
+
+
+def doPlayHardball(attack): # ls(b) throw; slip-backward; sidestep
+ """ This function returns Tracks portraying the Play Hardball attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ ball = globalPropPool.getProp('baseball') # Get prop from pool
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'):
+ suitDelay = 1.09
+ damageDelay = 2.76
+ dodgeDelay = 1.86
+ elif (suitType == 'b'): # ls
+ suitDelay = 1.79
+ damageDelay = 3.46
+ dodgeDelay = 2.56
+ elif (suitType == 'c'): # cr
+ suitDelay = 1.09
+ damageDelay = 2.76
+ dodgeDelay = 1.86
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ ballPosPoints = [Point3(0.04, 0.03, -0.31), VBase3(-1.152, 86.581, -76.784)]
+ propTrack = Sequence(
+ getPropAppearTrack(ball, suit.getRightHand(), ballPosPoints,
+ 0.8, Point3(5, 5, 5), scaleUpTime=0.5))
+ propTrack.append(Wait(suitDelay)) # Wait while suit animates
+ propTrack.append(Func(battle.movie.needRestoreRenderProp,
+ ball))
+ propTrack.append(Func(ball.wrtReparentTo, battle))
+
+ toonPos = toon.getPos(battle)
+ x = toonPos.getX()
+ y = toonPos.getY()
+ z = toonPos.getZ()
+ z = z + 0.2 # Should be slightly higher off the ground
+
+ if (dmg > 0): # If hit toons, bounce the ball back toward the suit
+ propTrack.append(LerpPosInterval(ball, 0.5, __toonFacePoint(toon, parent=battle)))
+ propTrack.append(LerpPosInterval(ball, 0.5, Point3(x, y+3, z)))
+ propTrack.append(LerpPosInterval(ball, 0.4, Point3(x, y+5, z+2)))
+ propTrack.append(LerpPosInterval(ball, 0.3, Point3(x, y+6, z)))
+ propTrack.append(LerpPosInterval(ball, 0.1, Point3(x, y+7, z+1)))
+ propTrack.append(LerpPosInterval(ball, 0.1, Point3(x, y+8, z)))
+ propTrack.append(LerpPosInterval(ball, 0.1, Point3(x, y+8.5, z+0.6)))
+ propTrack.append(LerpPosInterval(ball, 0.1, Point3(x, y+9, z+0.2)))
+ propTrack.append(Wait(0.4))
+ soundTrack = getSoundTrack('SA_hardball_impact_only.mp3', delay=2.8, node=suit)
+ else: # If misses, keep bouncing beyond the toon
+ propTrack.append(LerpPosInterval(ball, 0.5, Point3(x, y+2, z)))
+ propTrack.append(LerpPosInterval(ball, 0.4, Point3(x, y-1, z+2)))
+ propTrack.append(LerpPosInterval(ball, 0.3, Point3(x, y-3, z)))
+ propTrack.append(LerpPosInterval(ball, 0.1, Point3(x, y-4, z+1)))
+ propTrack.append(LerpPosInterval(ball, 0.1, Point3(x, y-5, z)))
+ propTrack.append(LerpPosInterval(ball, 0.1, Point3(x, y-5.5, z+0.6)))
+ propTrack.append(LerpPosInterval(ball, 0.1, Point3(x, y-6, z+0.2)))
+ propTrack.append(Wait(0.4))
+ soundTrack = getSoundTrack('SA_hardball.mp3', delay=3.1, node=suit)
+ propTrack.append(LerpScaleInterval(ball, 0.3, MovieUtil.PNT3_NEARZERO))
+ propTrack.append(Func(MovieUtil.removeProp, ball))
+ propTrack.append(Func(battle.movie.clearRenderProp,
+ ball))
+
+ damageAnims = [['conked', damageDelay, 0.01, 0.5], ['slip-backward', 0.01, 0.7]]
+ toonTrack = getToonTrack(attack, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'],
+ showDamageExtraTime=3.9)
+
+ return Parallel(suitTrack, toonTrack, propTrack, soundTrack)
+
+
+def doPowerTie(attack): # cr(c) throw; conked; sidestep
+ """ This function returns Tracks portraying the Power Tie attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ tie = globalPropPool.getProp('power-tie') # Get tie from pool
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): # type A not yet used
+ throwDelay = 2.17
+ damageDelay = 3.3
+ dodgeDelay = 3.1
+ elif (suitType == 'b'):
+ throwDelay = 2.17
+ damageDelay = 3.3
+ dodgeDelay = 3.1
+ elif (suitType == 'c'):
+ throwDelay = 1.45
+ damageDelay = 2.61
+ dodgeDelay = 2.34
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(1.16, 0.24, 0.63), VBase3(171.561, 1.745, -163.443)]
+ # First make the tie appear
+ tiePropTrack = Sequence(
+ getPropAppearTrack(tie, suit.getRightHand(), posPoints, 0.5,
+ Point3(3.5, 3.5, 3.5), scaleUpTime=0.5))
+ tiePropTrack.append(Wait(throwDelay)) # Wait while suit animates
+ tiePropTrack.append(Func(tie.setBillboardPointEye))
+ tiePropTrack.append(getPropThrowTrack(attack, tie, [__toonFacePoint(toon)],
+ [__toonGroundPoint(attack, toon, 0.1)],
+ hitDuration=0.4, missDuration=0.8))
+
+ toonTrack = getToonTrack(attack, damageDelay, ['conked'], dodgeDelay, ['sidestep'])
+
+ throwSound = getSoundTrack('SA_powertie_throw.mp3', delay=2.3, node=suit)
+ if (dmg > 0): # If take damage include sound effect
+ hitSound = getSoundTrack('SA_powertie_impact.mp3', delay=2.9, node=suit)
+ return Parallel(suitTrack, toonTrack, tiePropTrack, throwSound, hitSound)
+ else:
+ return Parallel(suitTrack, toonTrack, tiePropTrack, throwSound)
+
+
+"""
+def doCigarSmoke(attack): # project w/ 1 prop; cringe; sidestep
+ pass #later
+
+def doFloodTheMarket(attack): # project w/ 1 prop; slip-backward, jump; sidestep
+ pass #later
+
+def doSongAndDance(attack): # group2 particle w/ 0 props; bounce, slip-backward; sidestep
+ pass #later
+"""
+
+def doDoubleTalk(attack): # top cc(c), tm(b), dt(a) : fixed
+ """ This function returns Tracks portraying the DoubleTalk attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect('DoubleTalkLeft')
+ particleEffect2 = BattleParticles.createParticleEffect('DoubleTalkRight')
+ BattleParticles.setEffectTexture(particleEffect, 'doubletalk-double',
+ color=Vec4(0, 1.0, 0.0, 1))
+ BattleParticles.setEffectTexture(particleEffect2, 'doubletalk-good',
+ color=Vec4(0, 1.0, 0.0, 1))
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): # dt
+ partDelay = 3.3 # was 1.3 w/ finger-wag
+ damageDelay = 3.5 # was 1.5 w/ finger-wag
+ dodgeDelay = 3.3 # was 1.3 w/ finger-wag
+ elif (suitType == 'b'): # tm
+ partDelay = 3.3 # was 1.3 w/ finger-wag
+ damageDelay = 3.5 # was 1.5 w/ finger-wag
+ dodgeDelay = 3.3 # was 1.3 w/ finger-wag
+ elif (suitType == 'c'): # cc
+ partDelay = 3.3 # was 1.3 w/ finger-wag
+ damageDelay = 3.5 # was 1.5 w/ finger-wag
+ dodgeDelay = 3.3 # was 1.3 w/ finger-wag
+
+ suitTrack = getSuitTrack(attack)
+ partTrack = getPartTrack(particleEffect, partDelay, 1.8, [particleEffect, suit, 0])
+ partTrack2 = getPartTrack(particleEffect2, partDelay, 1.8, [particleEffect2, suit, 0])
+ damageAnims = [['duck', 0.01, 0.4, 1.05], ['cringe', 0.000001, 0.8]]
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, splicedDodgeAnims=[['duck', 0.01, 1.4]],
+ showMissedExtraTime=0.9, showDamageExtraTime=0.8)
+ soundTrack = getSoundTrack('SA_filibuster.mp3', delay=2.5, node=suit)
+
+ return Parallel(suitTrack, toonTrack, partTrack, partTrack2, soundTrack)
+
+
+def doFreezeAssets(attack): # top cc(c), pp(a): fixed
+ """ This function returns Tracks portraying the FreezeAssets attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+
+ BattleParticles.loadParticles()
+ snowEffect = BattleParticles.createParticleEffect('FreezeAssets')
+ BattleParticles.setEffectTexture(snowEffect, 'snow-particle')
+ # cloud = MovieUtil.copyProp(toon.cloudActors[0])
+ cloud = globalPropPool.getProp('stormcloud')
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'):
+ partDelay = 0.2
+ damageDelay = 3.5
+ dodgeDelay = 2.3
+ elif (suitType == 'b'): # not used with type B
+ partDelay = 0.2
+ damageDelay = 3.5
+ dodgeDelay = 2.3
+ elif (suitType == 'c'):
+ partDelay = 0.2
+ damageDelay = 3.5
+ dodgeDelay = 2.3
+
+ suitTrack = getSuitTrack(attack, delay=0.9)
+ initialCloudHeight = suit.height + 3 #Initial cloud height set to 3 meters above suit
+ cloudPosPoints = [Point3(0, 3, initialCloudHeight), MovieUtil.PNT3_ZERO]
+ # Make the storm cloud appear over and slightly in front of the suit
+ cloudPropTrack = Sequence()
+ cloudPropTrack.append(Func(cloud.pose, 'stormcloud', 0))
+ cloudPropTrack.append(getPropAppearTrack(cloud, suit, cloudPosPoints, 0.000001,
+ Point3(3, 3, 3), scaleUpTime=0.7))
+ cloudPropTrack.append(Func(battle.movie.needRestoreRenderProp, cloud))
+ cloudPropTrack.append(Func(cloud.wrtReparentTo, render))
+ # Now calculate the targetPoint for the cloud to move to (right over the target toon)
+ targetPoint = __toonFacePoint(toon)
+ targetPoint.setZ(targetPoint[2] + 3)
+ # Push the cloud over to the target point (whether it hits or misses)
+ cloudPropTrack.append(Wait(1.1)) # Wait to be pushed by suit
+ cloudPropTrack.append(LerpPosInterval(cloud, 1, pos=targetPoint))
+ # Must include particle track within cloud intervals to be safe...if the cloud is on
+ # a separate track and get removed before the effect is parented, it will crash
+ cloudPropTrack.append(Wait(partDelay)) # Wait before snow falls from cloud
+ cloudPropTrack.append(ParticleInterval(snowEffect, cloud, worldRelative=0,
+ duration=2.1, cleanup = True))
+ cloudPropTrack.append(Wait(0.4)) # Wait a moment before cloud goes away
+ cloudPropTrack.append(LerpScaleInterval(cloud, 0.5,
+ MovieUtil.PNT3_NEARZERO))
+ # The particle effect has already been cleaned up, it's safe to remove the cloud
+ cloudPropTrack.append(Func(MovieUtil.removeProp, cloud))
+ cloudPropTrack.append(Func(battle.movie.clearRenderProp, cloud))
+
+ damageAnims = [['cringe', 0.01, 0.4, 0.8], ['duck', 0.01, 1.6]]
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'],
+ showMissedExtraTime=1.2)
+
+ return Parallel(suitTrack, toonTrack, cloudPropTrack)
+
+
+def doHotAir(attack): # top cc(c): fixed
+ """ This function returns Tracks portraying the HotAir attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ BattleParticles.loadParticles()
+ # Create spray of flames
+ sprayEffect = BattleParticles.createParticleEffect('HotAir')
+ # That spray starts a burning flame under the toon
+ baseFlameEffect = BattleParticles.createParticleEffect(file='firedBaseFlame')
+ # Add additional lense dense flames on top of the base flame
+ flameEffect = BattleParticles.createParticleEffect('FiredFlame')
+ # Include the flecks off the flame
+ flecksEffect = BattleParticles.createParticleEffect('SpriteFiredFlecks')
+ BattleParticles.setEffectTexture(sprayEffect, 'fire')
+ BattleParticles.setEffectTexture(baseFlameEffect, 'fire')
+ BattleParticles.setEffectTexture(flameEffect, 'fire')
+ # Using a texture to make the flame flecks larger than points
+ BattleParticles.setEffectTexture(flecksEffect, 'roll-o-dex',
+ color=Vec4(0.95, 0.95, 0.0, 1))
+
+ sprayDelay = 1.3 # was 1.1 w/ finger-wag
+ flameDelay = 3.2 # was 3.0 w/ finger-wag
+ flameDuration = 2.6 # was 1.8 w/ finger-wag
+ flecksDelay = flameDelay + 0.8
+ flecksDuration = flameDuration - 0.8 # was 1.0 w/ finger-wag
+ damageDelay = 3.6 # was 2.6
+ dodgeDelay = 2.0 # was 1.7
+
+ suitTrack = getSuitTrack(attack)
+ sprayTrack = getPartTrack(sprayEffect, sprayDelay, 2.3, [sprayEffect, suit, 0])
+ baseFlameTrack = getPartTrack(baseFlameEffect, flameDelay, flameDuration,
+ [baseFlameEffect, toon, 0])
+ flameTrack = getPartTrack(flameEffect, flameDelay, flameDuration, [flameEffect, toon, 0])
+ flecksTrack = getPartTrack(flecksEffect, flecksDelay, flecksDuration,
+ [flecksEffect, toon, 0])
+
+ def changeColor(parts): # Function to change each lod part in list parts
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ track.append(Func(nextPart.setColorScale, Vec4(0, 0, 0, 1)))
+ return track
+
+ def resetColor(parts): # Reset the color of each lod part
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ track.append(Func(nextPart.clearColorScale))
+ return track
+
+ if (dmg > 0):
+ # Now create a track to change the toon's color if hit (burned), but we must
+ # extract the individual parts and change their color since some particle effects
+ # are parented to the toon and would be changed as well
+ headParts = toon.getHeadParts()
+ torsoParts = toon.getTorsoParts()
+ legsParts = toon.getLegsParts()
+
+ colorTrack = Sequence()
+ colorTrack.append(Wait(4.0)) # Wait before changing the color
+ colorTrack.append(Func(battle.movie.needRestoreColor))
+ colorTrack.append(changeColor(headParts))
+ colorTrack.append(changeColor(torsoParts))
+ colorTrack.append(changeColor(legsParts))
+ colorTrack.append(Wait(3.5)) # Wait while color changed
+ colorTrack.append(resetColor(headParts))
+ colorTrack.append(resetColor(torsoParts))
+ colorTrack.append(resetColor(legsParts))
+ colorTrack.append(Func(battle.movie.clearRestoreColor))
+
+ damageAnims = []
+ damageAnims.append(['cringe', 0.01, 0.7, 0.62])
+ damageAnims.append(['slip-forward', 0.01, 0.4, 1.2])
+ damageAnims.append(['slip-forward', 0.01, 1.0])
+ # Damage animation is toon cringing as gets hit then, then falls to the floor
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'])
+ soundTrack = getSoundTrack('SA_hot_air.mp3', delay=1.6, node=suit)
+
+ if (dmg > 0): # If toon takes damage, use the flames
+ return Parallel(suitTrack, toonTrack, sprayTrack, soundTrack,
+ baseFlameTrack, flameTrack, flecksTrack, colorTrack)
+ else: # Else just spray the fire but don't turn into a flame
+ return Parallel(suitTrack, toonTrack, sprayTrack, soundTrack)
+
+
+def doPickPocket(attack): # top tm: fixed
+ """ This function returns Tracks portraying the Pickpocket attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ dmg = target['hp']
+ bill = globalPropPool.getProp('1dollar') # Get the dollar bill from prop pool
+
+ suitTrack = getSuitTrack(attack)
+ billPosPoints = [Point3(-0.01, 0.45, -0.25), VBase3(136.424, -46.434, -129.712)]
+ billPropTrack = getPropTrack(bill, suit.getRightHand(), billPosPoints, 0.6, 0.55,
+ scaleUpPoint=Point3(1.41, 1.41, 1.41))
+ toonTrack = getToonTrack(attack, 0.6, ['cringe'], 0.01, ['sidestep'])
+
+
+ multiTrackList = Parallel(suitTrack, toonTrack)
+ if (dmg > 0): # if toon takes damage (bill was stolen)
+ soundTrack = getSoundTrack('SA_pick_pocket.mp3', delay=0.2, node=suit)
+ multiTrackList.append(billPropTrack) # show the bill in the suit's hand
+ multiTrackList.append(soundTrack) # add the sound of bill wagging
+ return multiTrackList
+
+
+def doFilibuster(attack): # top gh(c): fixed
+ """ This function returns Tracks portraying the Filibuster attack """
+ suit = attack['suit']
+ target = attack['target']
+ dmg = target['hp']
+ battle = attack['battle']
+ BattleParticles.loadParticles()
+ sprayEffect = BattleParticles.createParticleEffect(file='filibusterSpray')
+ sprayEffect2 = BattleParticles.createParticleEffect(file='filibusterSpray')
+ sprayEffect3 = BattleParticles.createParticleEffect(file='filibusterSpray')
+ sprayEffect4 = BattleParticles.createParticleEffect(file='filibusterSpray')
+ color = Vec4(0.4, 0, 0, 1)
+ BattleParticles.setEffectTexture(sprayEffect, 'filibuster-cut', color=color)
+ BattleParticles.setEffectTexture(sprayEffect2, 'filibuster-fiscal', color=color)
+ BattleParticles.setEffectTexture(sprayEffect3, 'filibuster-impeach', color=color)
+ BattleParticles.setEffectTexture(sprayEffect4, 'filibuster-inc', color=color)
+
+ partDelay = 1.3
+ partDuration = 1.15
+ damageDelay = 2.45
+ dodgeDelay = 1.70
+
+ suitTrack = getSuitTrack(attack)
+ sprayTrack = getPartTrack(sprayEffect, partDelay, partDuration,
+ [sprayEffect, suit, 0])
+ sprayTrack2 = getPartTrack(sprayEffect2, partDelay+0.8, partDuration,
+ [sprayEffect2, suit, 0])
+ sprayTrack3 = getPartTrack(sprayEffect3, partDelay+1.6, partDuration,
+ [sprayEffect3, suit, 0])
+ sprayTrack4 = getPartTrack(sprayEffect4, partDelay+2.4, partDuration,
+ [sprayEffect4, suit, 0])
+ damageAnims = []
+ # Take damage for each particle attack hit (4 times)
+ for i in range(0, 4):
+ damageAnims.append(['cringe', 0.00001, 0.3, 0.8])
+ # Damage animation is toon cringing as gets hit then, then falls to the floor
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'])
+ soundTrack = getSoundTrack('SA_filibuster.mp3', delay=1.1, node=suit)
+
+ if (dmg > 0): # Use all particle effect words
+ return Parallel(suitTrack, toonTrack, soundTrack,
+ sprayTrack, sprayTrack2, sprayTrack3, sprayTrack4)
+ else: # If they miss, cut off the last word
+ return Parallel(suitTrack, toonTrack, soundTrack,
+ sprayTrack, sprayTrack2, sprayTrack3)
+
+
+def doSchmooze(attack): # top gh(c), mingler(a): fixed
+ """ This function returns Tracks portraying the Schmooze attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ BattleParticles.loadParticles()
+ upperEffects = [] # Upper wave of schmooze particles
+ lowerEffects = [] # Lower wave of schmooze particles
+ textureNames = ['schmooze-genius', 'schmooze-instant',
+ 'schmooze-master', 'schmooze-viz']
+ for i in range(0, 4):
+ upperEffect = BattleParticles.createParticleEffect(file='schmoozeUpperSpray')
+ lowerEffect = BattleParticles.createParticleEffect(file='schmoozeLowerSpray')
+ BattleParticles.setEffectTexture(upperEffect, textureNames[i],
+ color=Vec4(0, 0, 1, 1))
+ BattleParticles.setEffectTexture(lowerEffect, textureNames[i],
+ color=Vec4(0, 0, 1, 1))
+ upperEffects.append(upperEffect)
+ lowerEffects.append(lowerEffect)
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): # mingler not active yet
+ partDelay = 1.3
+ damageDelay = 1.8
+ dodgeDelay = 1.1
+ elif (suitType == 'b'): # not used with type B
+ partDelay = 1.3
+ damageDelay = 2.5
+ dodgeDelay = 1.8
+ elif (suitType == 'c'): # gh
+ partDelay = 1.3 # was 1.3 w/ finger-wag
+ damageDelay = partDelay + 1.4 # was partDelay+1.4 w/ finger-wag
+ dodgeDelay = 0.9 # was 0.9 w/ finger-wag
+
+ suitTrack = getSuitTrack(attack)
+ upperPartTracks = Parallel()
+ lowerPartTracks = Parallel()
+ for i in range(0, 4): # Add each particle track for all four phrases
+ upperPartTracks.append(getPartTrack(upperEffects[i], partDelay + (i*0.65),
+ 0.8, [upperEffects[i], suit, 0]))
+ # Extra delay to add the lower particle wave to blend the two together in one wave
+ lowerPartTracks.append(getPartTrack(lowerEffects[i], partDelay + (i*0.65) + 0.7,
+ 1.0, [lowerEffects[i], suit, 0]))
+
+ damageAnims = []
+ for i in range(0, 3): # Take damage three times
+ damageAnims.append(['conked', 0.01, 0.3, 0.71])
+ damageAnims.append(['conked', 0.01, 0.3]) # Take damage fourth time and finish
+
+ dodgeAnims = []
+ dodgeAnims.append(['duck', 0.01, 0.2, 2.7]) # Duck for first time
+ dodgeAnims.append(['duck', 0.01, 1.22, 1.28]) # Duck again
+ dodgeAnims.append(['duck', 0.01, 3.16]) # Finish off the duck animation (stand up)
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, splicedDodgeAnims=dodgeAnims,
+ showMissedExtraTime=1.9, showDamageExtraTime=1.1)
+# soundTrack = getSoundTrack('SA_schmooze.mp3', delay=2.1, node=suit))
+
+ return Parallel(suitTrack, toonTrack, upperPartTracks, lowerPartTracks)
+
+
+def doQuake(attack): # group2 ms(b) special, camera shake; shake; sidestep
+ """ This function returns Tracks portraying the Quake attack """
+ suit = attack['suit']
+ suitTrack = getSuitAnimTrack(attack)
+
+ damageAnims = [['slip-forward'], ['slip-forward', 0.01]]
+ dodgeAnims = [['jump'], ['jump', 0.01], ['jump', 0.01]]
+ toonTracks = getToonTracks(
+ attack, damageDelay=1.8, splicedDamageAnims=damageAnims,
+ dodgeDelay=1.1, splicedDodgeAnims=dodgeAnims,
+ showMissedExtraTime=2.8, showDamageExtraTime=1.1)
+
+ return Parallel(suitTrack, toonTracks)
+
+
+def doShake(attack): # top ac: improve
+ # falls to soon, double suit anim speed on lose (miss)
+ """ This function returns Tracks portraying the Shake attack """
+ suit = attack['suit']
+ suitTrack = getSuitAnimTrack(attack)
+
+ damageAnims = [['slip-forward'], ['slip-forward', 0.01]]
+ dodgeAnims = [['jump'], ['jump', 0.01]]
+ toonTracks = getToonTracks(
+ attack, damageDelay=1.1, splicedDamageAnims=damageAnims,
+ dodgeDelay=0.7, splicedDodgeAnims=dodgeAnims,
+ showMissedExtraTime=2.8, showDamageExtraTime=1.1)
+
+ return Parallel(suitTrack, toonTracks)
+
+
+def doTremor(attack): # group2 special, camera shake; shake; sidestep
+ """ This function returns Tracks portraying the Tremor attack """
+ suit = attack['suit']
+ suitTrack = getSuitAnimTrack(attack)
+
+ damageAnims = [['slip-forward'], ['slip-forward', 0.01]]
+ dodgeAnims = [['jump'], ['jump', 0.01]]
+ toonTracks = getToonTracks(
+ attack, damageDelay=1.1, splicedDamageAnims=damageAnims,
+ dodgeDelay=0.7, splicedDodgeAnims=dodgeAnims,
+ showMissedExtraTime=2.8, showDamageExtraTime=1.1)
+ soundTrack = getSoundTrack('SA_tremor.mp3', delay=0.9, node=suit)
+
+ return Parallel(suitTrack, soundTrack, toonTracks)
+
+
+def doHangUp(attack): # top ac: fixed
+ """ This function returns Tracks portraying the HangUp attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ phone = globalPropPool.getProp('phone')
+ receiver = globalPropPool.getProp('receiver')
+
+ suitTrack = getSuitTrack(attack)
+
+ # Some suits need different positioning and timing for the phone
+ suitName = suit.getStyleName()
+ if (suitName == 'tf'):
+ phonePosPoints = [Point3(-0.23, 0.01, -0.26), VBase3(5.939, 2.763, -177.591)]
+ receiverPosPoints = [Point3(-0.13, -0.07, -0.06), VBase3(-1.854, 2.434, -177.579)]
+ receiverAdjustScale = Point3(0.8, 0.8, 0.8)
+ pickupDelay = 0.44
+ dialDuration = 3.07
+ finalPhoneDelay = 0.01
+ scaleUpPoint = Point3(0.75, 0.75, 0.75)
+ else:
+ phonePosPoints = [Point3(0.23, 0.17, -0.11), VBase3(5.939, 2.763, -177.591)]
+ receiverPosPoints = [Point3(0.23, 0.17, -0.11), VBase3(5.939, 2.763, -177.591)]
+ receiverAdjustScale = MovieUtil.PNT3_ONE
+ pickupDelay = 0.74
+ dialDuration = 3.07
+ finalPhoneDelay = 0.69
+ scaleUpPoint = MovieUtil.PNT3_ONE
+
+ propTrack = Sequence(# Single propTrack combines both phone and receiver
+ Wait(0.3), # Wait to show the phone and receiver
+ Func(__showProp, phone, suit.getLeftHand(),
+ phonePosPoints[0], phonePosPoints[1]), # Show phone
+ Func(__showProp, receiver, suit.getLeftHand(),
+ receiverPosPoints[0], receiverPosPoints[1]), # Show receiver
+ LerpScaleInterval(phone, 0.5, scaleUpPoint, MovieUtil.PNT3_NEARZERO), # Scale up phone
+ Wait(pickupDelay), # Wait until suit picks up phone
+ # Now reparent receiver to suit's right hand (he picks it up)
+ Func(receiver.wrtReparentTo, suit.getRightHand()),
+ LerpScaleInterval(receiver, 0.01, receiverAdjustScale),
+ # Jam receiver into position in case reparenting is early or late.
+ # Note: These coordinates are specific for type B suits
+ LerpPosHprInterval(receiver, 0.0001, Point3(-0.53, 0.21, -0.54),
+ VBase3(-99.49, -35.27, 1.84)),
+ Wait(dialDuration), # Wait while suit uses the phone
+ # Now put the receiver back down on the phone
+ Func(receiver.wrtReparentTo, phone),
+ Wait(finalPhoneDelay), # Leave the phone around a bit longer
+ LerpScaleInterval(phone, 0.5, MovieUtil.PNT3_NEARZERO), # Scale down phone & child receiver
+ # Now destroy the receiver and then the phone
+ Func(MovieUtil.removeProps, [receiver, phone]),
+ )
+
+ toonTrack = getToonTrack(attack, 5.5, ['slip-backward'], 4.7, ['jump'])
+ soundTrack = getSoundTrack('SA_hangup.mp3', delay=1.3, node=suit)
+
+ return Parallel(suitTrack, toonTrack, propTrack, soundTrack)
+
+
+def doRedTape(attack): # top ac: fixed
+ """ This function returns Tracks portraying the RedTape attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ tape = globalPropPool.getProp('redtape') # Get prop from pool
+ tubes = [] # Need a separate tube for each lod of the toon
+ for i in range(0, 3):
+ tubes.append(globalPropPool.getProp('redtape-tube')) # Get prop from pool
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ # Tape's positioning depends a bit on who is holding it
+ suitName = suit.getStyleName()
+ if (suitName == 'tf' or suitName == 'nc'):
+ tapePosPoints = [Point3(-0.24, 0.09, -0.38), VBase3(-1.152, 86.581, -76.784)]
+ else:
+ tapePosPoints = [Point3(0.24, 0.09, -0.38), VBase3(-1.152, 86.581, -76.784)]
+ tapeScaleUpPoint = Point3(0.9, 0.9, 0.24)
+ propTrack = Sequence(
+ getPropAppearTrack(tape, suit.getRightHand(), tapePosPoints,
+ 0.8, tapeScaleUpPoint, scaleUpTime=0.5))
+ propTrack.append(Wait(1.73)) # Wait while suit animates
+ hitPoint = lambda toon=toon: __toonTorsoPoint(toon)
+ propTrack.append(getPropThrowTrack(attack, tape, [hitPoint],
+ [__toonGroundPoint(attack, toon, 0.7)])) # Throw paper
+
+ hips = toon.getHipsParts()
+ animal = toon.style.getAnimal()
+ scale = ToontownGlobals.toonBodyScales[animal]
+ legs = toon.style.legs
+ torso = toon.style.torso
+ torso = torso[0] # just want to examine the first letter of the torso
+ animal = animal[0] # just want to examine the first letter of the animal
+ tubeHeight = -0.8
+
+ # Adjust the scale of the tube depending on the torso size of the toon
+ if (torso == 's'):
+ scaleUpPoint = Point3(scale*2.03, scale*2.03, scale*.7975)
+ elif (torso == 'm'):
+ scaleUpPoint = Point3(scale*2.03, scale*2.03, scale*.7975)
+ elif (torso == 'l'):
+ scaleUpPoint = Point3(scale*2.03, scale*2.03, scale*1.11)
+
+ # The horse and the dog reduce scales a bit and lowers the tube some
+ if ((animal=='h') or (animal=='d')):
+ tubeHeight = -0.87
+ scaleUpPoint = Point3(scale*1.69, scale*1.69, scale*.67)
+
+ tubePosPoints = [Point3(0, 0, tubeHeight), MovieUtil.PNT3_ZERO]
+ tubeTracks = Parallel()
+ tubeTracks.append(Func(battle.movie.needRestoreHips))
+ for partNum in range(0, hips.getNumPaths()):
+ nextPart = hips.getPath(partNum)
+ tubeTracks.append(getPropTrack(tubes[partNum], nextPart, tubePosPoints, 3.25,
+ 3.17, scaleUpPoint=scaleUpPoint))
+ tubeTracks.append(Func(battle.movie.clearRestoreHips))
+
+ toonTrack = getToonTrack(attack, 3.4, ['struggle'], 2.8, ['jump'])
+ soundTrack = getSoundTrack('SA_red_tape.mp3', delay=2.9, node=suit)
+
+ if (dmg > 0): # If toon takes damage, show the red tape tube
+ return Parallel(suitTrack, toonTrack, propTrack, soundTrack, tubeTracks)
+ else: # Otherwise, do not show the tube
+ return Parallel(suitTrack, toonTrack, propTrack, soundTrack)
+
+
+def doParadigmShift(attack): # m(a) group2 particle w/ 0 props; shift; sidestep
+ """ This function returns Tracks portraying the Paradigm Shift attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ targets = attack['target']
+ hitAtleastOneToon = 0
+ for t in targets:
+ if (t['hp'] > 0):
+ hitAtleastOneToon = 1
+
+ damageDelay = 1.95
+ dodgeDelay = 0.95
+ sprayEffect = BattleParticles.createParticleEffect('ShiftSpray')
+ suitName = suit.getStyleName()
+ if (suitName == 'm'):
+ sprayEffect.setPos(Point3(-5.2, 4.6, 2.7))
+ elif (suitName == 'sd'):
+ sprayEffect.setPos(Point3(-5.2, 4.6, 2.7))
+ else:
+ sprayEffect.setPos(Point3(0.1, 4.6, 2.7))
+
+ suitTrack = getSuitAnimTrack(attack) # Get suit animation track
+ sprayTrack = getPartTrack(sprayEffect, 1.0, 1.9, [sprayEffect, suit, 0])
+
+ liftTracks = Parallel()
+ toonRiseTracks = Parallel()
+
+ for t in targets:
+ toon = t['toon']
+ dmg = t['hp']
+
+ if (dmg > 0):
+ liftEffect = BattleParticles.createParticleEffect('ShiftLift')
+ liftEffect.setPos(toon.getPos(battle))
+ liftEffect.setZ(liftEffect.getZ()-1.3)
+ liftTracks.append(getPartTrack(liftEffect, 1.1, 4.1, [liftEffect, battle, 0]))
+
+ shadow = toon.dropShadow
+ # We'll manipulate a fake shadow and hide the normal one so they don't conflict
+ fakeShadow = MovieUtil.copyProp(shadow)
+
+ x = toon.getX()
+ y = toon.getY()
+ z = toon.getZ()
+ height = 3
+ groundPoint = Point3(x, y, z)
+ risePoint = Point3(x, y, z+height)
+ shakeRight = Point3(x, y+0.7, z+height)
+ shakeLeft = Point3(x, y-0.7, z+height)
+
+ shakeTrack = Sequence()
+ shakeTrack.append(Wait(damageDelay+0.25))
+ shakeTrack.append(Func(shadow.hide))
+ shakeTrack.append(LerpPosInterval(toon, 1.1, risePoint))
+ for i in range(0, 17):
+ shakeTrack.append(LerpPosInterval(toon, 0.03, shakeLeft))
+ shakeTrack.append(LerpPosInterval(toon, 0.03, shakeRight))
+ shakeTrack.append(LerpPosInterval(toon, 0.1, risePoint))
+ shakeTrack.append(LerpPosInterval(toon, 0.1, groundPoint))
+ shakeTrack.append(Func(shadow.show))
+
+ shadowTrack = Sequence()
+ shadowTrack.append(Func(battle.movie.needRestoreRenderProp,
+ fakeShadow))
+ shadowTrack.append(Wait(damageDelay+0.25))
+ shadowTrack.append(Func(fakeShadow.hide))
+ shadowTrack.append(Func(fakeShadow.setScale, 0.27))
+ shadowTrack.append(Func(fakeShadow.reparentTo, toon))
+ shadowTrack.append(Func(fakeShadow.setPos, MovieUtil.PNT3_ZERO))
+ shadowTrack.append(Func(fakeShadow.wrtReparentTo, battle))
+ shadowTrack.append(Func(fakeShadow.show))
+ shadowTrack.append(LerpScaleInterval(fakeShadow, 0.4, Point3(0.17, 0.17, 0.17)))
+ shadowTrack.append(Wait(1.81))
+ shadowTrack.append(LerpScaleInterval(fakeShadow, 0.1, Point3(0.27, 0.27, 0.27)))
+ shadowTrack.append(Func(MovieUtil.removeProp,
+ fakeShadow))
+ shadowTrack.append(Func(battle.movie.clearRenderProp,
+ fakeShadow))
+
+ toonRiseTracks.append(Parallel(shakeTrack, shadowTrack))
+
+ damageAnims = []
+ damageAnims.extend(getSplicedLerpAnims('think', 0.66, 1.9, startTime=2.06))
+ damageAnims.append(['slip-backward', 0.01, 0.5])
+ dodgeAnims = [] # Dodge will be slowed down
+ dodgeAnims.append(['jump', 0.01, 0, 0.6]) # Begin to jump
+ # Now get animation interval with time inserted to slow down the jump at its peak
+ dodgeAnims.extend(getSplicedLerpAnims('jump', 0.31, 1.0, startTime=0.6))
+ dodgeAnims.append(['jump', 0, 0.91]) # Complete the jump
+ toonTracks = getToonTracks(
+ attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, splicedDodgeAnims=dodgeAnims,
+ showDamageExtraTime=2.7)
+
+ if (hitAtleastOneToon == 1):
+ soundTrack = getSoundTrack('SA_paradigm_shift.mp3', delay=2.1, node=suit)
+ return Parallel(suitTrack, sprayTrack, soundTrack,
+ liftTracks, toonTracks, toonRiseTracks)
+ else:
+ return Parallel(suitTrack, sprayTrack,
+ liftTracks, toonTracks, toonRiseTracks)
+
+
+def doPowerTrip(attack): # group2 particle w/ 0 props; slip-forward; jump
+ """ This function returns Tracks portraying the Power Trip attack """
+ suit = attack['suit']
+ battle = attack['battle']
+
+ # Set up all the particle effects, colors, and positions
+ centerColor = Vec4(0.1, 0.1, 0.1, 0.4)
+ edgeColor = Vec4(0.4, 0.1, 0.9, 0.7)
+ powerBar1 = BattleParticles.createParticleEffect(file='powertrip')
+ powerBar2 = BattleParticles.createParticleEffect(file='powertrip2')
+ powerBar1.setPos(0, 6.1, 0.4)
+ powerBar1.setHpr(-60, 0, 0)
+ powerBar2.setPos(0, 6.1, 0.4)
+ powerBar2.setHpr(60, 0, 0)
+ powerBar1Particles = powerBar1.getParticlesNamed('particles-1')
+ powerBar2Particles = powerBar2.getParticlesNamed('particles-1')
+ powerBar1Particles.renderer.setCenterColor(centerColor)
+ powerBar1Particles.renderer.setEdgeColor(edgeColor)
+ powerBar2Particles.renderer.setCenterColor(centerColor)
+ powerBar2Particles.renderer.setEdgeColor(edgeColor)
+ waterfallEffect = BattleParticles.createParticleEffect('Waterfall')
+ waterfallEffect.setScale(11)
+ waterfallParticles = waterfallEffect.getParticlesNamed('particles-1')
+ waterfallParticles.renderer.setCenterColor(centerColor)
+ waterfallParticles.renderer.setEdgeColor(edgeColor)
+
+ # Different suits position the waterfall effect differently
+ suitName = suit.getStyleName()
+ if (suitName == 'mh'):
+ waterfallEffect.setPos(0, 4, 3.6)
+
+ suitTrack = getSuitAnimTrack(attack) # Get suit animation track
+
+ def getPowerTrack(effect, suit=suit, battle=battle):
+ partTrack = Sequence(
+ Wait(1.0),
+ Func(battle.movie.needRestoreParticleEffect, effect),
+ Func(effect.start, suit),
+ Wait(0.4),
+ LerpPosInterval(effect, 1.0, Point3(0, 15, 0.4)),
+ LerpFunctionInterval(effect.setAlphaScale, fromData=1,
+ toData=0, duration=0.4),
+ Func(effect.cleanup),
+ Func(battle.movie.clearRestoreParticleEffect,
+ effect),
+ )
+ return partTrack
+ partTrack1 = getPowerTrack(powerBar1)
+ partTrack2 = getPowerTrack(powerBar2)
+ waterfallTrack = getPartTrack(waterfallEffect, 0.6, 1.3,
+ [waterfallEffect, suit, 0])
+
+ toonTracks = getToonTracks(attack, 1.8, ['slip-forward'], 1.29, ['jump'])
+
+
+ return Parallel(suitTrack, partTrack1, partTrack2, waterfallTrack, toonTracks)
+
+
+"""
+def doSandtrap(attack): # particle w/ 1 prop; slip-forward; duck
+ pass #later
+"""
+
+#Note: throwEndPoint set with function at runtime
+# parent= battle because render is variable. Using suit as parent creates
+# a better bounce path, but check jumps when suit moves.
+def getThrowEndPoint(suit, toon, battle, whichBounce):
+ pnt = toon.getPos(toon)
+
+ if whichBounce == 'one':
+ pnt.setY(pnt[1] + 8) # adjust distance in front of toon
+ elif whichBounce == 'two':
+ pnt.setY(pnt[1] + 5) # adjust distance in front of toon
+ elif whichBounce == 'threeHit':
+ pnt.setZ(pnt[2] + toon.shoulderHeight + 0.3)#set height to the toon's face
+ elif whichBounce == 'threeMiss':
+ pass # do nothing, it's the toon's position
+ elif whichBounce == 'four':
+ pnt.setY(pnt[1] - 5) # adjust distance from toon
+
+ return Point3(pnt)
+
+def doBounceCheck(attack): # top pp: fixed
+ """ This function returns Tracks portraying the BounceCheck attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ battle = attack['battle']
+ toon = target['toon']
+ dmg = target['hp']
+ hitSuit = (dmg > 0)
+
+ check = globalPropPool.getProp('bounced-check')
+ checkPosPoints = [MovieUtil.PNT3_ZERO, VBase3(95.247, 79.025, 88.849)]
+
+ bounce1Point = lambda suit=suit, toon=toon, battle=battle: \
+ getThrowEndPoint(suit, toon, battle, 'one')
+ bounce2Point = lambda suit=suit, toon=toon, battle=battle: \
+ getThrowEndPoint(suit, toon, battle, 'two')
+ hit3Point = lambda suit=suit, toon=toon, battle=battle: \
+ getThrowEndPoint(suit, toon, battle, 'threeHit')
+ miss3Point = lambda suit=suit, toon=toon, battle=battle: \
+ getThrowEndPoint(suit, toon, battle, 'threeMiss')
+ bounce4Point = lambda suit=suit, toon=toon, battle=battle: \
+ getThrowEndPoint(suit, toon, battle, 'four')
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): # PennyPincher(pp)
+ throwDelay = 2.5
+ dodgeDelay = 4.3
+ damageDelay = 5.1
+ elif (suitType == 'b'): # type b not yet used
+ throwDelay = 1.8
+ dodgeDelay = 3.6
+ damageDelay = 4.4
+ elif (suitType == 'c'): # ShortChange(sc), TightWad(tw)
+ throwDelay = 1.8
+ dodgeDelay = 3.6
+ damageDelay = 4.4
+
+ suitTrack = getSuitTrack(attack)
+
+ checkPropTrack = Sequence(
+ getPropAppearTrack(check, suit.getRightHand(), checkPosPoints, 0.00001,
+ Point3(8.5, 8.5, 8.5), startScale=MovieUtil.PNT3_ONE))
+ checkPropTrack.append(Wait(throwDelay)) #Wait amount of time before release
+ checkPropTrack.append(Func(check.wrtReparentTo, toon))
+ checkPropTrack.append(Func(check.setHpr, Point3(0, -90, 0)))
+
+ # Bounce the check twice towards the toon
+ checkPropTrack.append(getThrowTrack(check, bounce1Point, duration=0.5, parent=toon))
+ checkPropTrack.append(getThrowTrack(check, bounce2Point, duration=0.9, parent=toon))
+
+ if hitSuit: # hit the toon in the face with the check
+ checkPropTrack.append(getThrowTrack(check, hit3Point, duration=0.7, parent=toon))
+ else: # the toon dodged, bounce right on by
+ checkPropTrack.append(getThrowTrack(check, miss3Point, duration=0.7, parent=toon))
+ checkPropTrack.append(getThrowTrack(check, bounce4Point, duration=0.7, parent=toon))
+ checkPropTrack.append(LerpScaleInterval(check, 0.3, MovieUtil.PNT3_NEARZERO))
+
+ checkPropTrack.append(Func(MovieUtil.removeProp, check))
+
+ toonTrack = getToonTrack(attack, damageDelay, ['conked'], dodgeDelay, ['sidestep'])
+
+ soundTracks = Sequence(
+ getSoundTrack('SA_pink_slip.mp3', delay=throwDelay+0.5, duration=0.6, node=suit),
+ getSoundTrack('SA_pink_slip.mp3', delay=0.4, duration=0.6, node=suit),
+ )
+
+ return Parallel(suitTrack, checkPropTrack, toonTrack, soundTracks)
+
+
+def doWatercooler(attack): # top sc: fixed
+ """ This function returns Tracks portraying the Watercooler attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ watercooler = globalPropPool.getProp('watercooler')
+
+ def getCoolerSpout(watercooler=watercooler):
+ spout = watercooler.find("**/joint_toSpray")
+ return spout.getPos(render)
+
+ hitPoint = lambda toon=toon: __toonFacePoint(toon)
+ missPoint = lambda prop=watercooler, toon=toon: \
+ __toonMissPoint(prop, toon, 0, parent=render)
+ hitSprayTrack = MovieUtil.getSprayTrack(battle,
+ Point4(0.75, 0.75, 1.0, 0.8),
+ getCoolerSpout, hitPoint, 0.2, 0.2, 0.2, horizScale=0.3,
+ vertScale=0.3)
+ missSprayTrack = MovieUtil.getSprayTrack(battle,
+ Point4(0.75, 0.75, 1.0, 0.8),
+ getCoolerSpout, missPoint, 0.2, 0.2, 0.2, horizScale=0.3,
+ vertScale=0.3)
+
+ suitTrack = getSuitTrack(attack)
+ posPoints = [Point3(0.48, 0.11, -0.92), VBase3(20.403, 33.158, 69.511)]
+ propTrack = Sequence(# Build intervals for watercooler, including its spray
+ Wait(1.01), # Wait the duration of appearDelay
+ Func(__showProp, watercooler, suit.getLeftHand(),
+ posPoints[0], posPoints[1]),
+ LerpScaleInterval(watercooler, 0.5, Point3(1.15, 1.15, 1.15)),
+ Wait(1.6), # Wait until time to spray
+ )
+
+ if (dmg > 0): # If toon takes damage use the hit spray
+ propTrack.append(hitSprayTrack) # Add in the spray intervals
+ else: # Otherwise use the miss spray
+ propTrack.append(missSprayTrack) # Add in the spray intervals
+ propTrack += [# Add the rest of the prop intervals
+ Wait(0.01), # Wait while suit animation continues
+ LerpScaleInterval(watercooler, 0.5, MovieUtil.PNT3_NEARZERO), # Scale down the prop
+ Func(MovieUtil.removeProp, watercooler),
+ ]
+
+ splashTrack = Sequence()
+ if (dmg > 0): # If toon is hit
+ def prepSplash(splash, targetPoint):
+ splash.reparentTo(render)
+ splash.setPos(targetPoint)
+ scale = splash.getScale()
+ splash.setBillboardPointWorld()
+ splash.setScale(scale)
+ splash = globalPropPool.getProp('splash-from-splat')
+ splash.setColor(0.75, 0.75, 1, 0.8)
+ splash.setScale(0.3)
+ splashTrack = Sequence(
+ Func(battle.movie.needRestoreRenderProp,
+ splash),
+ Wait(3.2),
+ Func(prepSplash, splash, __toonFacePoint(toon)),
+ ActorInterval(splash, 'splash-from-splat'),
+ Func(MovieUtil.removeProp, splash),
+ Func(battle.movie.clearRenderProp, splash),
+ )
+
+ toonTrack = getToonTrack(attack, suitTrack.getDuration()-1.5, ['cringe'],
+ 2.4, ['sidestep']) # Get toon track
+ soundTrack = Sequence(
+ Wait(1.1),
+ SoundInterval(globalBattleSoundCache.getSound('SA_watercooler_appear_only.mp3'),
+ node=suit, duration=1.4722),
+ Wait(0.4),
+ SoundInterval(globalBattleSoundCache.getSound('SA_watercooler_spray_only.mp3'),
+ node=suit, duration=2.313),
+ )
+
+ return Parallel(suitTrack, toonTrack, propTrack, soundTrack, splashTrack)
+
+
+"""
+def doPennyPinch(attack): # top sc: needs: model(pincers, penny) project w/ 1 prop; conked; sidestep
+ pass #later
+"""
+
+def doFired(attack): # top tw: fixed
+ """ This function returns Tracks portraying the Fired attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ BattleParticles.loadParticles()
+ baseFlameEffect = BattleParticles.createParticleEffect(file='firedBaseFlame')
+ flameEffect = BattleParticles.createParticleEffect('FiredFlame')
+ flecksEffect = BattleParticles.createParticleEffect('SpriteFiredFlecks')
+ BattleParticles.setEffectTexture(baseFlameEffect, 'fire')
+ BattleParticles.setEffectTexture(flameEffect, 'fire')
+ # Using textures instead of points to make the flame flecks larger
+ # Reusing plain roll-o-dex texture and making it yellow
+ BattleParticles.setEffectTexture(flecksEffect, 'roll-o-dex',
+ color=Vec4(0.8, 0.8, 0.8, 1))
+
+ # Now we create smaller flame effects for when the suit misses the toon
+ baseFlameSmall = BattleParticles.createParticleEffect(file='firedBaseFlame')
+ flameSmall = BattleParticles.createParticleEffect('FiredFlame')
+ flecksSmall = BattleParticles.createParticleEffect('SpriteFiredFlecks')
+ BattleParticles.setEffectTexture(baseFlameSmall, 'fire')
+ BattleParticles.setEffectTexture(flameSmall, 'fire')
+ # Using textures instead of points to make the flame flecks larger
+ # Reusing plain roll-o-dex texture and making it yellow
+ BattleParticles.setEffectTexture(flecksSmall, 'roll-o-dex',
+ color=Vec4(0.8, 0.8, 0.8, 1))
+ baseFlameSmall.setScale(0.7)
+ flameSmall.setScale(0.7)
+ flecksSmall.setScale(0.7)
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ baseFlameTrack = getPartTrack(baseFlameEffect, 1.0, 1.9,
+ [baseFlameEffect, toon, 0])
+ flameTrack = getPartTrack(flameEffect, 1.0, 1.9, [flameEffect, toon, 0])
+ flecksTrack = getPartTrack(flecksEffect, 1.8, 1.1, [flecksEffect, toon, 0])
+ baseFlameSmallTrack = getPartTrack(baseFlameSmall, 1.0, 1.9,
+ [baseFlameSmall, toon, 0])
+ flameSmallTrack = getPartTrack(flameSmall, 1.0, 1.9, [flameSmall, toon, 0])
+ flecksSmallTrack = getPartTrack(flecksSmall, 1.8, 1.1, [flecksSmall, toon, 0])
+
+ # Now create a track to change the toon's color if hit (burned), but we must
+ # extract the individual parts and change their color since some particle effects
+ # are parented to the toon and would be changed as well
+ def changeColor(parts): # Function to change each lod part in list parts
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ track.append(Func(nextPart.setColorScale, Vec4(0, 0, 0, 1)))
+ return track
+
+ def resetColor(parts): # Reset the color of each lod part
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ track.append(Func(nextPart.clearColorScale))
+ return track
+
+ if (dmg > 0):
+ headParts = toon.getHeadParts()
+ torsoParts = toon.getTorsoParts()
+ legsParts = toon.getLegsParts()
+
+ colorTrack = Sequence()
+ colorTrack.append(Wait(2.0)) # Wait before changing the color
+ colorTrack.append(Func(battle.movie.needRestoreColor))
+ colorTrack.append(changeColor(headParts))
+ colorTrack.append(changeColor(torsoParts))
+ colorTrack.append(changeColor(legsParts))
+ colorTrack.append(Wait(3.5)) # Wait while color changed
+ colorTrack.append(resetColor(headParts))
+ colorTrack.append(resetColor(torsoParts))
+ colorTrack.append(resetColor(legsParts))
+ colorTrack.append(Func(battle.movie.clearRestoreColor))
+
+ damageAnims = []
+ damageAnims.append(['cringe', 0.01, 0.7, 0.62])
+ damageAnims.append(['slip-forward', 0.00001, 0.4, 1.2])
+ damageAnims.extend(getSplicedLerpAnims('slip-forward', 0.31, 0.8, startTime=1.2))
+ # Damage animation is toon cringing as gets hit then, then falls to the floor
+ toonTrack = getToonTrack(attack, damageDelay=1.5, splicedDamageAnims=damageAnims,
+ dodgeDelay=0.3, dodgeAnimNames=['sidestep'])
+
+ soundTrack = getSoundTrack('SA_hot_air.mp3', delay=1.0, node=suit)
+
+ if (dmg > 0): # If toon takes damage, use the flames
+ return Parallel(suitTrack, baseFlameTrack, flameTrack, flecksTrack,
+ toonTrack, colorTrack, soundTrack)
+ else: # Otherwise don't use the flames
+ return Parallel(suitTrack, baseFlameSmallTrack, flameSmallTrack,
+ flecksSmallTrack, toonTrack, soundTrack)
+
+
+def doAudit(attack): # top bc: fixed
+ """ This function returns Tracks portraying the Audit attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ calculator = globalPropPool.getProp('calculator')
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect, 'audit-one',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect2 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect2, 'audit-two',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect3 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect3, 'audit-three',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect4 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect4, 'audit-four',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect5 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect5, 'audit-mult',
+ color=Vec4(0, 0, 0, 1))
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ partTrack = getPartTrack(particleEffect, 2.1, 1.9, [particleEffect, suit, 0])
+ partTrack2 = getPartTrack(particleEffect2, 2.2, 2.0, [particleEffect2, suit, 0])
+ partTrack3 = getPartTrack(particleEffect3, 2.3, 2.1, [particleEffect3, suit, 0])
+ partTrack4 = getPartTrack(particleEffect4, 2.4, 2.2, [particleEffect4, suit, 0])
+ partTrack5 = getPartTrack(particleEffect5, 2.5, 2.3, [particleEffect5, suit, 0])
+
+ # Some suits need different calculator pos points and timing
+ suitName = attack['suitName']
+ if (suitName == 'nc'):
+ calcPosPoints = [Point3(-0.15, 0.37, 0.03), VBase3(1.352, -6.518, -6.045)]
+ calcDuration = 0.76
+ scaleUpPoint = Point3(1.1, 1.85, 1.81)
+ else:
+ calcPosPoints = [Point3(0.35, 0.52, 0.03), VBase3(1.352, -6.518, -6.045)]
+ calcDuration = 1.87
+ scaleUpPoint = Point3(1.0, 1.37, 1.31)
+
+ calcPropTrack = getPropTrack(calculator, suit.getLeftHand(), calcPosPoints, 0.000001,
+ calcDuration, scaleUpPoint=scaleUpPoint,
+ anim=1, propName='calculator',
+ animStartTime=0.5, animDuration=3.4)
+ toonTrack = getToonTrack(attack, 3.2, ['conked'], 0.9, ['duck'], showMissedExtraTime=2.2)
+ soundTrack = getSoundTrack('SA_audit.mp3', delay=1.9, node=suit)
+
+ return Parallel(suitTrack, toonTrack, calcPropTrack, soundTrack,
+ partTrack, partTrack2, partTrack3, partTrack4, partTrack5)
+
+
+def doCalculate(attack): # top bc: fixed
+ """ This function returns Tracks portraying the Calculate attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ calculator = globalPropPool.getProp('calculator')
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect, 'audit-one',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect2 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect2, 'audit-plus',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect3 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect3, 'audit-mult',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect4 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect4, 'audit-three',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect5 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect5, 'audit-div',
+ color=Vec4(0, 0, 0, 1))
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ partTrack = getPartTrack(particleEffect, 2.1, 1.9, [particleEffect, suit, 0])
+ partTrack2 = getPartTrack(particleEffect2, 2.2, 2.0, [particleEffect2, suit, 0])
+ partTrack3 = getPartTrack(particleEffect3, 2.3, 2.1, [particleEffect3, suit, 0])
+ partTrack4 = getPartTrack(particleEffect4, 2.4, 2.2, [particleEffect4, suit, 0])
+ partTrack5 = getPartTrack(particleEffect5, 2.5, 2.3, [particleEffect5, suit, 0])
+
+ # Some suits need different calculator pos points and timing
+ suitName = attack['suitName']
+ if (suitName == 'nc'):
+ calcPosPoints = [Point3(-0.15, 0.37, 0.03), VBase3(1.352, -6.518, -6.045)]
+ calcDuration = 0.76
+ scaleUpPoint = Point3(1.1, 1.85, 1.81)
+ else:
+ calcPosPoints = [Point3(0.35, 0.52, 0.03), VBase3(1.352, -6.518, -6.045)]
+ calcDuration = 1.87
+ scaleUpPoint = Point3(1.0, 1.37, 1.31)
+
+ calcPropTrack = getPropTrack(calculator, suit.getLeftHand(), calcPosPoints, 0.000001,
+ calcDuration, scaleUpPoint=scaleUpPoint,
+ anim=1, propName='calculator',
+ animStartTime=0.5, animDuration=3.4)
+ toonTrack = getToonTrack(attack, 3.2, ['conked'], 1.8, ['sidestep'])
+
+ return Parallel(suitTrack, toonTrack, calcPropTrack, partTrack, partTrack2,
+ partTrack3, partTrack4, partTrack5)
+
+
+def doTabulate(attack): # top bc: fixed
+ """ This function returns Tracks portraying the Tabulate attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ calculator = globalPropPool.getProp('calculator')
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect, 'audit-plus',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect2 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect2, 'audit-minus',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect3 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect3, 'audit-mult',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect4 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect4, 'audit-div',
+ color=Vec4(0, 0, 0, 1))
+ particleEffect5 = BattleParticles.createParticleEffect('Calculate')
+ BattleParticles.setEffectTexture(particleEffect5, 'audit-one',
+ color=Vec4(0, 0, 0, 1))
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ partTrack = getPartTrack(particleEffect, 2.1, 1.9, [particleEffect, suit, 0])
+ partTrack2 = getPartTrack(particleEffect2, 2.2, 2.0, [particleEffect2, suit, 0])
+ partTrack3 = getPartTrack(particleEffect3, 2.3, 2.1, [particleEffect3, suit, 0])
+ partTrack4 = getPartTrack(particleEffect4, 2.4, 2.2, [particleEffect4, suit, 0])
+ partTrack5 = getPartTrack(particleEffect5, 2.5, 2.3, [particleEffect5, suit, 0])
+
+ # Some suits need different calculator pos points and timing
+ suitName = attack['suitName']
+ if (suitName == 'nc'):
+ calcPosPoints = [Point3(-0.15, 0.37, 0.03), VBase3(1.352, -6.518, -6.045)]
+ calcDuration = 0.76
+ scaleUpPoint = Point3(1.1, 1.85, 1.81)
+ else:
+ calcPosPoints = [Point3(0.35, 0.52, 0.03), VBase3(1.352, -6.518, -6.045)]
+ calcDuration = 1.87
+ scaleUpPoint = Point3(1.0, 1.37, 1.31)
+
+ calcPropTrack = getPropTrack(calculator, suit.getLeftHand(), calcPosPoints, 0.000001,
+ calcDuration, scaleUpPoint=scaleUpPoint,
+ anim=1, propName='calculator',
+ animStartTime=0.5, animDuration=3.4)
+ toonTrack = getToonTrack(attack, 3.2, ['conked'], 1.8, ['sidestep'])
+
+ return Parallel(suitTrack, toonTrack, calcPropTrack, partTrack, partTrack2,
+ partTrack3, partTrack4, partTrack5)
+
+
+def doCrunch(attack): # nc(a) throw, multiple props; slip-forward, flatten, slip...; sidestep
+ """ This function returns Tracks portraying the Crunch attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ throwDuration = 3.03
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ numberNames = ['one', 'two', 'three', 'four', 'five', 'six']
+ BattleParticles.loadParticles()
+ numberSpill1 = BattleParticles.createParticleEffect(file='numberSpill')
+ numberSpill2 = BattleParticles.createParticleEffect(file='numberSpill')
+ spillTexture1 = random.choice(numberNames)
+ spillTexture2 = random.choice(numberNames)
+ BattleParticles.setEffectTexture(numberSpill1, 'audit-'+spillTexture1)
+ BattleParticles.setEffectTexture(numberSpill2, 'audit-'+spillTexture2)
+ numberSpillTrack1 = getPartTrack(numberSpill1, 1.1, 2.2,
+ [numberSpill1, suit, 0])
+ numberSpillTrack2 = getPartTrack(numberSpill2, 1.5, 1.0,
+ [numberSpill2, suit, 0])
+
+ numberSprayTracks = Parallel()
+ numOfNumbers = random.randint(5, 9)
+ for i in range(0, numOfNumbers-1):
+ nextSpray = BattleParticles.createParticleEffect(file='numberSpray')
+ nextTexture = random.choice(numberNames)
+ BattleParticles.setEffectTexture(nextSpray, 'audit-'+nextTexture)
+ nextStartTime = random.random()*0.6 + throwDuration
+ nextDuration = random.random()*0.4 + 1.4
+ nextSprayTrack = getPartTrack(nextSpray, nextStartTime, nextDuration,
+ [nextSpray, suit, 0])
+ numberSprayTracks.append(nextSprayTrack)
+
+ numberTracks = Parallel()
+ # First create the handful of numbers in the suit's hand
+ for i in range(0, numOfNumbers):
+ texture = random.choice(numberNames)
+ next = MovieUtil.copyProp(BattleParticles.getParticle('audit-'+texture))
+ next.reparentTo(suit.getRightHand())
+ next.setScale(0.01, 0.01, 0.01)
+ next.setColor(Vec4(0.0, 0.0, 0.0, 1.0))
+ next.setPos(random.random()*0.6-0.3,
+ random.random()*0.6-0.3,
+ random.random()*0.6-0.3)
+ next.setHpr(VBase3(-1.15, 86.58, -76.78))
+ numberTrack = Sequence(
+ Wait(0.9),
+ LerpScaleInterval(next, 0.6, MovieUtil.PNT3_ONE),
+ Wait(1.7),
+ Func(MovieUtil.removeProp, next),
+ )
+ numberTracks.append(numberTrack)
+
+ damageAnims = []
+ damageAnims.append(['cringe', 0.01, 0.14, 0.28])
+ damageAnims.append(['cringe', 0.01, 0.16, 0.3])
+ damageAnims.append(['cringe', 0.01, 0.13, 0.22])
+ damageAnims.append(['slip-forward', 0.01, 0.6]) #, 0.9])
+ toonTrack = getToonTrack(attack, damageDelay=4.7, splicedDamageAnims=damageAnims,
+ dodgeDelay=3.6, dodgeAnimNames=['sidestep'])
+
+ return Parallel(suitTrack, toonTrack, numberSpillTrack1,
+ numberSpillTrack2, numberTracks, numberSprayTracks)
+
+
+def doLiquidate(attack): # top b(b), moneybags(c) fixed
+ """ This function returns Tracks portraying the Liquidate attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ dmg = target['hp']
+ toon = target['toon']
+ BattleParticles.loadParticles()
+ rainEffect = BattleParticles.createParticleEffect(file='liquidate')
+ rainEffect2 = BattleParticles.createParticleEffect(file='liquidate')
+ rainEffect3 = BattleParticles.createParticleEffect(file='liquidate')
+
+ # cloud = MovieUtil.copyProp(toon.cloudActors[0])
+ cloud = globalPropPool.getProp('stormcloud')
+
+ suitType = getSuitBodyType(attack['suitName'])
+ if (suitType == 'a'): # not used with type a
+ partDelay = 0.2
+ damageDelay = 3.5
+ dodgeDelay = 2.45
+ elif (suitType == 'b'):
+ partDelay = 0.2
+ damageDelay = 3.5
+ dodgeDelay = 2.45
+ elif (suitType == 'c'):
+ partDelay = 0.2
+ damageDelay = 3.5
+ dodgeDelay = 2.45
+
+ suitTrack = getSuitTrack(attack, delay=0.9)
+ initialCloudHeight = suit.height + 3 # Initial cloud height set to 3 meters above suit
+ cloudPosPoints = [Point3(0, 3, initialCloudHeight), VBase3(180, 0, 0)]
+ # Make the storm cloud appear over and slightly in front of the suit
+ cloudPropTrack = Sequence()
+ cloudPropTrack.append(Func(cloud.pose, 'stormcloud', 0))
+ cloudPropTrack.append(getPropAppearTrack(cloud, suit, cloudPosPoints, 0.000001,
+ Point3(3, 3, 3), scaleUpTime=0.7))
+ cloudPropTrack.append(Func(battle.movie.needRestoreRenderProp,
+ cloud))
+ cloudPropTrack.append(Func(cloud.wrtReparentTo, render))
+ # Now calculate the targetPoint for the cloud to move to (right over the target toon)
+ targetPoint = __toonFacePoint(toon)
+ targetPoint.setZ(targetPoint[2]+3)
+ # Push the cloud over to the target point (whether it hits or misses)
+ cloudPropTrack.append(Wait(1.1)) # Wait to be pushed by suit
+ cloudPropTrack.append(LerpPosInterval(cloud, 1, pos=targetPoint))
+ # Must include particle track within cloud intervals to be safe...if the cloud is on
+ # a separate track and get removed before the effect is parented, it will crash
+ cloudPropTrack.append(Wait(partDelay)) # Wait before rain falls from cloud
+
+ cloudPropTrack.append(Parallel(
+ Sequence(ParticleInterval(rainEffect, cloud, worldRelative=0,
+ duration=2.1, cleanup = True)),
+ Sequence(Wait(0.1),
+ ParticleInterval(rainEffect2, cloud,
+ worldRelative=0, duration=2.0, cleanup = True)),
+ Sequence(Wait(0.1),
+ ParticleInterval(rainEffect3, cloud,
+ worldRelative=0, duration=2.0, cleanup = True)),
+ Sequence(ActorInterval(cloud, 'stormcloud', startTime=3,
+ duration=0.1),
+ ActorInterval(cloud, 'stormcloud', startTime=1,
+ duration=2.3)),
+ ))
+
+ cloudPropTrack.append(Wait(0.4)) # Wait a moment before cloud goes away
+ cloudPropTrack.append(LerpScaleInterval(cloud, 0.5,
+ MovieUtil.PNT3_NEARZERO))
+ # The particle effect has already been cleaned up, it's safe to remove the cloud
+ cloudPropTrack.append(Func(MovieUtil.removeProp, cloud))
+ cloudPropTrack.append(Func(battle.movie.clearRenderProp, cloud))
+
+ damageAnims = [['melt'], ['jump', 1.5, 0.4]]
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'])
+ soundTrack = getSoundTrack('SA_liquidate.mp3', delay=2.0, node=suit)
+
+ # If toon is hit, need puddle for it to melt in
+ if (dmg > 0):
+ puddle = globalPropPool.getProp('quicksand')
+ puddle.setColor(Vec4(0.0, 0.0, 1.0, 1))
+ puddle.setHpr(Point3(120, 0, 0))
+ puddle.setScale(0.01)
+ puddleTrack = Sequence(
+ Func(battle.movie.needRestoreRenderProp, puddle),
+ Wait(damageDelay-0.7),
+ Func(puddle.reparentTo, battle),
+ Func(puddle.setPos, toon.getPos(battle)),
+ LerpScaleInterval(puddle, 1.7, Point3(1.7, 1.7, 1.7),
+ startScale=MovieUtil.PNT3_NEARZERO),
+ Wait(3.2),
+ LerpFunctionInterval(puddle.setAlphaScale, fromData=1, toData=0, duration=0.8),
+ Func(MovieUtil.removeProp, puddle),
+ Func(battle.movie.clearRenderProp, puddle),
+ )
+ return Parallel(suitTrack, toonTrack, cloudPropTrack, soundTrack,
+ puddleTrack)
+ else:
+ return Parallel(suitTrack, toonTrack, cloudPropTrack, soundTrack)
+
+
+def doMarketCrash(attack): # mb(c) fixed
+ """ This function returns Tracks portraying the Market Crash attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ suitDelay = 1.32
+ propDelay = 0.6
+ throwDuration = 1.5
+ paper = globalPropPool.getProp('newspaper')
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(-0.07, 0.17, -0.13), VBase3(161.867, -33.149, -48.086)]
+ paperTrack = Sequence(
+ getPropAppearTrack(paper, suit.getRightHand(), posPoints, propDelay,
+ Point3(3, 3, 3), scaleUpTime=0.5))
+ paperTrack.append(Wait(suitDelay)) # Wait while suit animates
+
+ hitPoint = toon.getPos(battle)
+ hitPoint.setX(hitPoint.getX() + 1.2)
+ hitPoint.setY(hitPoint.getY() + 1.5)
+ # If paper hits toon, should sit on top, not pass through
+ if (dmg > 0):
+ hitPoint.setZ(hitPoint.getZ() + 1.1)
+ # We push the paper back as it scale down
+ movePoint = Point3(hitPoint.getX(), hitPoint.getY()-1.8, hitPoint.getZ()+0.2)
+
+ paperTrack.append(Func(battle.movie.needRestoreRenderProp, paper))
+ paperTrack.append(Func(paper.wrtReparentTo, battle))
+ paperTrack.append(getThrowTrack(paper, hitPoint, duration=throwDuration, parent=battle))
+ paperTrack.append(Wait(0.6))
+ paperTrack.append(LerpPosInterval(paper, 0.4, movePoint))
+
+ # Add in a track to spin the paper as it is tossed, and make it get larger
+ spinTrack = Sequence(
+ Wait(propDelay+suitDelay+0.2),
+ LerpHprInterval(paper, throwDuration, Point3(-360, 0, 0)),
+ )
+ sizeTrack = Sequence(
+ Wait(propDelay+suitDelay+0.2),
+ LerpScaleInterval(paper, throwDuration, Point3(6, 6, 6)),
+ Wait(0.95),
+ LerpScaleInterval(paper, 0.4, MovieUtil.PNT3_NEARZERO),
+ )
+ propTrack = Sequence(
+ Parallel(paperTrack, spinTrack, sizeTrack),
+ Func(MovieUtil.removeProp, paper),
+ Func(battle.movie.clearRenderProp, paper),
+ )
+
+ damageAnims = []
+ damageAnims.append(['cringe', 0.01, 0.21, 0.08])
+ damageAnims.append(['slip-forward', 0.01, 0.6, 0.85])
+ damageAnims.extend(getSplicedLerpAnims('slip-forward', 0.31, 0.95, startTime=1.2))
+ damageAnims.append(['slip-forward', 0.01, 1.51])
+ toonTrack = getToonTrack(attack, damageDelay=3.8, splicedDamageAnims=damageAnims,
+ dodgeDelay=2.4, dodgeAnimNames=['sidestep'],
+ showDamageExtraTime=0.4, showMissedExtraTime=1.3)
+
+ return Parallel(suitTrack, toonTrack, propTrack)
+
+
+def doBite(attack): # ls(b) throw; conked; duck
+ """ This function returns Tracks portraying the Bite attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ teeth = globalPropPool.getProp('teeth')
+ propDelay = 0.8
+ propScaleUpTime = 0.5
+ suitDelay = 1.73
+ throwDelay = propDelay + propScaleUpTime + suitDelay
+ throwDuration = 0.4
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(-0.05, 0.41, -0.54), VBase3(4.465, -3.563, 51.479)]
+
+ teethAppearTrack = Sequence(
+ getPropAppearTrack(teeth, suit.getRightHand(), posPoints, propDelay,
+ Point3(3, 3, 3), scaleUpTime=propScaleUpTime))
+ teethAppearTrack.append(Wait(suitDelay)) # Wait while suit animates
+ teethAppearTrack.append(Func(battle.movie.needRestoreRenderProp, teeth))
+ teethAppearTrack.append(Func(teeth.wrtReparentTo, battle))
+
+ if (dmg > 0): # If strikes toon, fly to its face
+ x = toon.getX(battle)
+ y = toon.getY(battle)
+ z = toon.getZ(battle)
+ toonHeight = z + toon.getHeight()
+ flyPoint = Point3(x, y + 2.7, toonHeight*0.8)
+
+ teethAppearTrack.append(LerpPosInterval(teeth, throwDuration, pos=flyPoint))
+ # Chatter the teeth, then open them wide, pull them back as they rev up, then
+ # chomp the toon's head
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.4, pos=Point3(x, y+3.2, toonHeight*0.7)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.3, pos=Point3(x, y+4.7, toonHeight*0.5)))
+ teethAppearTrack.append(Wait(0.2))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.1, pos=Point3(x, y-0.2, toonHeight*0.9)))
+ teethAppearTrack.append(Wait(0.4))
+
+ scaleTrack = Sequence(
+ Wait(throwDelay),
+ LerpScaleInterval(teeth, throwDuration, Point3(8, 8, 8)),
+ Wait(0.9),
+ LerpScaleInterval(teeth, 0.2, Point3(14, 14, 14)),
+ Wait(1.2),
+ LerpScaleInterval(teeth, 0.3, MovieUtil.PNT3_NEARZERO),
+ )
+ hprTrack = Sequence(
+ Wait(throwDelay),
+ LerpHprInterval(teeth, 0.3, Point3(180, 0, 0)),
+ Wait(0.2),
+ LerpHprInterval(teeth, 0.4, Point3(180, -35, 0), startHpr=Point3(180, 0, 0)),
+ Wait(0.6),
+ LerpHprInterval(teeth, 0.1, Point3(180, -75, 0), startHpr=Point3(180, -35, 0)),
+ )
+ animTrack = Sequence(
+ Wait(throwDelay),
+ ActorInterval(teeth, 'teeth', duration=throwDuration),
+ ActorInterval(teeth, 'teeth', duration=0.3),
+ Func(teeth.pose, 'teeth', 1),
+ Wait(0.7),
+ ActorInterval(teeth, 'teeth', duration=0.9),
+ )
+
+ propTrack = Sequence(
+ Parallel(teethAppearTrack, scaleTrack, hprTrack, animTrack),
+ Func(MovieUtil.removeProp, teeth),
+ Func(battle.movie.clearRenderProp, teeth),
+ )
+ else: # Else fly past the toon
+ flyPoint = __toonFacePoint(toon, parent=battle)
+ flyPoint.setY(flyPoint.getY() - 7.1)
+ teethAppearTrack.append(LerpPosInterval(teeth, throwDuration, pos=flyPoint))
+ teethAppearTrack.append(Func(MovieUtil.removeProp, teeth))
+ teethAppearTrack.append(Func(battle.movie.clearRenderProp, teeth))
+ propTrack = teethAppearTrack
+
+ damageAnims = [['cringe', 0.01, 0.7, 1.2],
+ ['conked', 0.01, 0.2, 2.1],
+ ['conked', 0.01, 3.2],]
+ dodgeAnims = [['cringe', 0.01, 0.7, 0.2],
+ ['duck', 0.01, 1.6],]
+ toonTrack = getToonTrack(attack, damageDelay=3.2, splicedDamageAnims=damageAnims,
+ dodgeDelay=2.9, splicedDodgeAnims=dodgeAnims,
+ showDamageExtraTime=2.4)
+
+ return Parallel(suitTrack, toonTrack, propTrack)
+
+
+def doChomp(attack): # ls(b) throw; slip-backward; sidestep
+ """ This function returns Tracks portraying the Chomp attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ teeth = globalPropPool.getProp('teeth')
+ propDelay = 0.8
+ propScaleUpTime = 0.5
+ suitDelay = 1.73
+ throwDelay = propDelay + propScaleUpTime + suitDelay
+ throwDuration = 0.4
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(-0.05, 0.41, -0.54), VBase3(4.465, -3.563, 51.479)]
+
+ teethAppearTrack = Sequence(
+ getPropAppearTrack(teeth, suit.getRightHand(), posPoints, propDelay,
+ Point3(3, 3, 3), scaleUpTime=propScaleUpTime))
+ teethAppearTrack.append(Wait(suitDelay)) # Wait while suit animates
+ teethAppearTrack.append(Func(battle.movie.needRestoreRenderProp, teeth))
+ teethAppearTrack.append(Func(teeth.wrtReparentTo, battle))
+
+ if (dmg > 0): # If strikes toon, fly to its face
+ x = toon.getX(battle)
+ y = toon.getY(battle)
+ z = toon.getZ(battle)
+ toonHeight = z + toon.getHeight()
+ flyPoint = Point3(x, y + 2.7, toonHeight*0.7)
+
+ teethAppearTrack.append(LerpPosInterval(teeth, throwDuration, pos=flyPoint))
+ # Chatter the teeth, then open them wide, pull them back as they rev up, then
+ # chomp the toon's head
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.4, pos=Point3(x, y+3.2, toonHeight*0.7)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.3, pos=Point3(x, y+4.7, toonHeight*0.5)))
+ teethAppearTrack.append(Wait(0.2))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.1, pos=Point3(x, y, toonHeight+3)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.1, pos=Point3(x, y-1.2, toonHeight*0.7)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.1, pos=Point3(x, y-0.7, toonHeight*0.4)))
+ teethAppearTrack.append(Wait(0.4))
+
+ scaleTrack = Sequence(
+ Wait(throwDelay),
+ LerpScaleInterval(teeth, throwDuration, Point3(6, 6, 6)),
+ Wait(0.9),
+ LerpScaleInterval(teeth, 0.2, Point3(10, 10, 10)),
+ Wait(1.2),
+ LerpScaleInterval(teeth, 0.3, MovieUtil.PNT3_NEARZERO),
+ )
+ hprTrack = Sequence(
+ Wait(throwDelay),
+ LerpHprInterval(teeth, 0.3, Point3(180, 0, 0)),
+ Wait(0.2),
+ LerpHprInterval(teeth, 0.4, Point3(180, -35, 0), startHpr=Point3(180, 0, 0)),
+ Wait(0.6),
+ LerpHprInterval(teeth, 0.1, Point3(0, -35, 0), startHpr=Point3(180, -35, 0)),
+ )
+ animTrack = Sequence(
+ Wait(throwDelay),
+ ActorInterval(teeth, 'teeth', duration=throwDuration),
+ ActorInterval(teeth, 'teeth', duration=0.3),
+ Func(teeth.pose, 'teeth', 1),
+ Wait(0.7),
+ ActorInterval(teeth, 'teeth', duration=0.9),
+ )
+
+ propTrack = Sequence(
+ Parallel(teethAppearTrack, scaleTrack, hprTrack, animTrack),
+ Func(MovieUtil.removeProp, teeth),
+ Func(battle.movie.clearRenderProp, teeth),
+ )
+ else: # Else fly past the toon
+ x = toon.getX(battle)
+ y = toon.getY(battle)
+ z = toon.getZ(battle)
+ z = z + 0.2
+ flyPoint = Point3(x, y - 2.1, z)
+ teethAppearTrack.append(LerpPosInterval(teeth, throwDuration, pos=flyPoint))
+ teethAppearTrack.append(Wait(0.2))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x+0.5, y-2.5, z)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x+1.0, y-3.0, z+0.4)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x+1.3, y-3.6, z)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x+0.9, y-3.1, z+0.4)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x+0.3, y-2.6, z)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x-0.1, y-2.2, z+0.4)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x-0.4, y-1.9, z)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x-0.7, y-2.1, z+0.4)))
+ teethAppearTrack.append(LerpPosInterval(teeth, 0.2, pos=Point3(x-0.8, y-2.3, z)))
+ teethAppearTrack.append(LerpScaleInterval(teeth, 0.6, MovieUtil.PNT3_NEARZERO))
+
+ hprTrack = Sequence(
+ Wait(throwDelay),
+ LerpHprInterval(teeth, 0.3, Point3(180, 0, 0)),
+ Wait(0.5),
+ LerpHprInterval(teeth, 0.4, Point3(80, 0, 0), startHpr=Point3(180, 0, 0)),
+ LerpHprInterval(teeth, 0.8, Point3(-10, 0, 0), startHpr=Point3(80, 0, 0)),
+ )
+ animTrack = Sequence(Wait(throwDelay),
+ ActorInterval(teeth, 'teeth', duration=3.6))
+
+ propTrack = Sequence(
+ Parallel(teethAppearTrack, hprTrack, animTrack),
+ Func(MovieUtil.removeProp, teeth),
+ Func(battle.movie.clearRenderProp, teeth),
+ )
+
+ damageAnims = [['cringe', 0.01, 0.7, 1.2],
+ ['spit', 0.01, 2.95, 1.47],
+ ['spit', 0.01, 4.42, 0.07],
+ ['spit', 0.08, 4.49, -0.07],
+ ['spit', 0.08, 4.42, 0.07],
+ ['spit', 0.08, 4.49, -0.07],
+ ['spit', 0.08, 4.42, 0.07],
+ ['spit', 0.08, 4.49, -0.07],
+ ['spit', 0.01, 4.42],]
+ dodgeAnims = [['jump', 0.01, 0.01],]
+ toonTrack = getToonTrack(attack, damageDelay=3.2, splicedDamageAnims=damageAnims,
+ dodgeDelay=2.75, splicedDodgeAnims=dodgeAnims,
+ showDamageExtraTime=1.4)
+
+ return Parallel(suitTrack, toonTrack, propTrack)
+
+
+"""
+def doFiveOClockShadow(attack): # throw; slip-forward; jump
+ pass #later
+"""
+
+def doEvictionNotice(attack): # top b: fixed
+ """ This function returns Tracks portraying the Eviction-Notice attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ paper = globalPropPool.getProp('shredder-paper') # Get prop from pool
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(-0.04, 0.15, -1.38), VBase3(10.584, -11.945, 18.316)]
+ # Now make paper appear
+ propTrack = Sequence(
+ getPropAppearTrack(paper, suit.getRightHand(), posPoints, 0.8,
+ MovieUtil.PNT3_ONE, scaleUpTime=0.5))
+ propTrack.append(Wait(1.73)) # Wait while suit animates
+ hitPoint = __toonFacePoint(toon, parent=battle)
+ hitPoint.setX(hitPoint.getX() - 1.4)
+ missPoint = __toonGroundPoint(attack, toon, 0.7, parent=battle)
+ missPoint.setX(missPoint.getX() - 1.1)
+ propTrack.append(getPropThrowTrack(attack, paper, [hitPoint],
+ [missPoint], parent=battle))
+
+ # Toon jumps to dodge so don't need friend toon dodge tracks
+ toonTrack = getToonTrack(attack, 3.4, ['conked'], 2.8, ['jump'])
+
+ return Parallel(suitTrack, toonTrack, propTrack)
+
+
+def doWithdrawal(attack): # top b: improve: turn white
+ """ This function returns Tracks portraying the Withdrawal attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect('Withdrawal')
+ BattleParticles.setEffectTexture(particleEffect, 'snow-particle')
+
+ suitTrack = getSuitAnimTrack(attack) # Get suit animation track
+ partTrack = getPartTrack(particleEffect, 0.00001, suitTrack.getDuration()+1.2,
+ [particleEffect, suit, 0])
+ # Toon jumps to dodge so don't need friend toon dodge tracks
+
+ toonTrack = getToonTrack(attack, 1.2, ['cringe'], 0.2,
+ splicedDodgeAnims=[['duck', 0.00001, 0.8]],
+ showMissedExtraTime=0.8)
+
+ # Now create a track to change the toon's color if hit (burned), but we must
+ # extract the individual parts and change their color since some particle effects
+ # are parented to the toon and would be changed as well
+ headParts = toon.getHeadParts()
+ torsoParts = toon.getTorsoParts()
+ legsParts = toon.getLegsParts()
+
+ def changeColor(parts): # Function to change each lod part in list parts
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ track.append(Func(nextPart.setColorScale,
+ Vec4(0, 0, 0, 1)))
+# track.append(LerpFunctionInterval(nextPart.setAllColorScale,
+# fromData = 1, toData = 0, duration=0.7))
+ return track
+
+ def resetColor(parts): # Reset the color of each lod part
+ track = Parallel()
+ for partNum in range(0, parts.getNumPaths()):
+ nextPart = parts.getPath(partNum)
+ track.append(Func(nextPart.clearColorScale))
+ return track
+
+ soundTrack = getSoundTrack('SA_withdrawl.mp3', delay=1.4, node=suit)
+
+ if (dmg > 0): # If takes damage, change color
+ colorTrack = Sequence()
+ colorTrack.append(Wait(1.6)) # Wait before changing the color
+ colorTrack.append(Func(battle.movie.needRestoreColor))
+ colorTrack.append(Parallel(changeColor(headParts),
+ changeColor(torsoParts),
+ changeColor(legsParts)))
+
+ colorTrack.append(Wait(2.9)) # Wait while color changed
+ colorTrack.append(resetColor(headParts))
+ colorTrack.append(resetColor(torsoParts))
+ colorTrack.append(resetColor(legsParts))
+ colorTrack.append(Func(battle.movie.clearRestoreColor))
+ return Parallel(suitTrack, partTrack, toonTrack, soundTrack, colorTrack)
+ else:
+ return Parallel(suitTrack, partTrack, toonTrack, soundTrack)
+
+
+def doJargon(attack): # top dt(a): fixed
+ """ This function returns Tracks portraying the Jargon attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect(file='jargonSpray')
+ particleEffect2 = BattleParticles.createParticleEffect(file='jargonSpray')
+ particleEffect3 = BattleParticles.createParticleEffect(file='jargonSpray')
+ particleEffect4 = BattleParticles.createParticleEffect(file='jargonSpray')
+ BattleParticles.setEffectTexture(particleEffect, 'jargon-brow',
+ color=Vec4(1, 0, 0, 1))
+ BattleParticles.setEffectTexture(particleEffect2, 'jargon-deep',
+ color=Vec4(0, 0, 0, 1))
+ BattleParticles.setEffectTexture(particleEffect3, 'jargon-hoop',
+ color=Vec4(1, 0, 0, 1))
+ BattleParticles.setEffectTexture(particleEffect4, 'jargon-ipo',
+ color=Vec4(0, 0, 0, 1))
+
+ damageDelay = 2.2 # was 2.4 w/ finger-wag
+ dodgeDelay = 1.5 # was 1.9 w/ finger-wag
+ partDelay = 1.1 # was 1.5 w/ finger-wag
+ partInterval = 1.2 # was 0.2 w/ finger-wag
+
+ suitTrack = getSuitTrack(attack)
+ partTrack = getPartTrack(particleEffect, partDelay+(partInterval*0), 2,
+ [particleEffect, suit, 0])
+ partTrack2 = getPartTrack(particleEffect2, partDelay+(partInterval*1), 2,
+ [particleEffect2, suit, 0])
+ partTrack3 = getPartTrack(particleEffect3, partDelay+(partInterval*2), 2,
+ [particleEffect3, suit, 0])
+ partTrack4 = getPartTrack(particleEffect4, partDelay+(partInterval*3), 1.0,
+ [particleEffect4, suit, 0])
+
+ damageAnims = []
+ damageAnims.append(['conked', 0.0001, 0, 0.4])
+ damageAnims.append(['conked', 0.0001, 2.7, 0.85])
+ damageAnims.append(['conked', 0.0001, 0.4, 0.09])
+ damageAnims.append(['conked', 0.0001, 0.4, 0.09])
+ damageAnims.append(['conked', 0.0001, 0.4, 0.66])
+ damageAnims.append(['conked', 0.0001, 0.4, 0.09])
+ damageAnims.append(['conked', 0.0001, 0.4, 0.09])
+ damageAnims.append(['conked', 0.0001, 0.4, 0.86])
+ damageAnims.append(['conked', 0.0001, 0.4, 0.14])
+ damageAnims.append(['conked', 0.0001, 0.4, 0.14])
+ damageAnims.append(['conked', 0.0001, 0.4])
+ dodgeAnims = [['duck', 0.0001, 1.2], ['duck', 0.0001, 1.3]]
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, splicedDodgeAnims=dodgeAnims,
+ showMissedExtraTime=1.6, showDamageExtraTime=0.7)
+ soundTrack = getSoundTrack('SA_jargon.mp3', delay=2.1, node=suit)
+
+ return Parallel(suitTrack, toonTrack, soundTrack,
+ partTrack, partTrack2, partTrack3, partTrack4)
+
+
+def doMumboJumbo(attack): # top dt: fixed
+ """ This function returns Tracks portraying the MumboJumbo attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ BattleParticles.loadParticles()
+ particleEffect = BattleParticles.createParticleEffect(file='mumboJumboSpray')
+ particleEffect2 = BattleParticles.createParticleEffect(file='mumboJumboSpray')
+ particleEffect3 = BattleParticles.createParticleEffect(file='mumboJumboSmother')
+ particleEffect4 = BattleParticles.createParticleEffect(file='mumboJumboSmother')
+ particleEffect5 = BattleParticles.createParticleEffect(file='mumboJumboSmother')
+ BattleParticles.setEffectTexture(particleEffect, 'mumbojumbo-boiler',
+ color=Vec4(1, 0, 0, 1))
+ BattleParticles.setEffectTexture(particleEffect2, 'mumbojumbo-creative',
+ color=Vec4(1, 0, 0, 1))
+ BattleParticles.setEffectTexture(particleEffect3, 'mumbojumbo-deben',
+ color=Vec4(1, 0, 0, 1))
+ BattleParticles.setEffectTexture(particleEffect4, 'mumbojumbo-high',
+ color=Vec4(1, 0, 0, 1))
+ BattleParticles.setEffectTexture(particleEffect5, 'mumbojumbo-iron',
+ color=Vec4(1, 0, 0, 1))
+
+ suitTrack = getSuitTrack(attack)
+ partTrack = getPartTrack(particleEffect, 2.5, 2, [particleEffect, suit, 0])
+ partTrack2 = getPartTrack(particleEffect2, 2.5, 2, [particleEffect2, suit, 0])
+ partTrack3 = getPartTrack(particleEffect3, 3.3, 1.7, [particleEffect3, toon, 0])
+ partTrack4 = getPartTrack(particleEffect4, 3.3, 1.7, [particleEffect4, toon, 0])
+ partTrack5 = getPartTrack(particleEffect5, 3.3, 1.7, [particleEffect5, toon, 0])
+
+ toonTrack = getToonTrack(attack, 3.2, ['cringe'], 2.2, ['sidestep'])
+ soundTrack = getSoundTrack('SA_mumbo_jumbo.mp3', delay=2.5, node=suit)
+
+ if (dmg > 0):
+ return Parallel(suitTrack, toonTrack, soundTrack, partTrack,
+ partTrack2, partTrack3, partTrack4, partTrack5)
+ else:
+ return Parallel(suitTrack, toonTrack, soundTrack, partTrack, partTrack2)
+
+
+def doGuiltTrip(attack): # bs(a) fixed
+ """ This function returns Tracks portraying the Guilt Trip attack """
+ suit = attack['suit']
+ battle = attack['battle']
+
+ # Set up all the particle effects, colors, and positions
+ centerColor = Vec4(1.0, 0.2, 0.2, 0.9)
+ edgeColor = Vec4(0.9, 0.9, 0.9, 0.4)
+ powerBar1 = BattleParticles.createParticleEffect(file='guiltTrip')
+ powerBar2 = BattleParticles.createParticleEffect(file='guiltTrip')
+ powerBar1.setPos(0, 6.1, 0.4)
+ powerBar1.setHpr(-90, 0, 0)
+ powerBar2.setPos(0, 6.1, 0.4)
+ powerBar2.setHpr(90, 0, 0)
+ powerBar1.setScale(5)
+ powerBar2.setScale(5)
+ powerBar1Particles = powerBar1.getParticlesNamed('particles-1')
+ powerBar2Particles = powerBar2.getParticlesNamed('particles-1')
+ powerBar1Particles.renderer.setCenterColor(centerColor)
+ powerBar1Particles.renderer.setEdgeColor(edgeColor)
+ powerBar2Particles.renderer.setCenterColor(centerColor)
+ powerBar2Particles.renderer.setEdgeColor(edgeColor)
+ waterfallEffect = BattleParticles.createParticleEffect('Waterfall')
+ waterfallEffect.setScale(11)
+ waterfallParticles = waterfallEffect.getParticlesNamed('particles-1')
+ waterfallParticles.renderer.setCenterColor(centerColor)
+ waterfallParticles.renderer.setEdgeColor(edgeColor)
+
+ suitTrack = getSuitAnimTrack(attack) # Get suit animation track
+
+ def getPowerTrack(effect, suit=suit, battle=battle):
+ partTrack = Sequence(
+ Wait(0.7),
+ Func(battle.movie.needRestoreParticleEffect, effect),
+ Func(effect.start, suit),
+ Wait(0.4),
+ LerpPosInterval(effect, 1.0, Point3(0, 15, 0.4)),
+ LerpFunctionInterval(effect.setAlphaScale, fromData=1,
+ toData=0, duration=0.4),
+ Func(effect.cleanup),
+ Func(battle.movie.clearRestoreParticleEffect, effect),
+ )
+ return partTrack
+ partTrack1 = getPowerTrack(powerBar1)
+ partTrack2 = getPowerTrack(powerBar2)
+ waterfallTrack = getPartTrack(waterfallEffect, 0.6, 0.6,
+ [waterfallEffect, suit, 0])
+
+ toonTracks = getToonTracks(attack, 1.5, ['slip-forward'], 0.86, ['jump'])
+ soundTrack = getSoundTrack('SA_guilt_trip.mp3', delay=1.1, node=suit)
+
+ return Parallel(suitTrack, partTrack1, partTrack2, soundTrack,
+ waterfallTrack, toonTracks)
+
+
+def doRestrainingOrder(attack): # bs(a) throw; conked, bound; sidestep
+ """ This function returns Tracks portraying the Restraining-Order attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ paper = globalPropPool.getProp('shredder-paper')
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+ posPoints = [Point3(-0.04, 0.15, -1.38), VBase3(10.584, -11.945, 18.316)]
+ # Now make paper appear
+ propTrack = Sequence(
+ getPropAppearTrack(paper, suit.getRightHand(), posPoints, 0.8,
+ MovieUtil.PNT3_ONE, scaleUpTime=0.5))
+ propTrack.append(Wait(1.73)) # Wait while suit animates
+ hitPoint = __toonFacePoint(toon, parent=battle)
+ hitPoint.setX(hitPoint.getX() - 1.4)
+ missPoint = __toonGroundPoint(attack, toon, 0.7, parent=battle)
+ missPoint.setX(missPoint.getX() - 1.1)
+ propTrack.append(getPropThrowTrack(attack, paper, [hitPoint],
+ [missPoint], parent=battle))
+
+ damageAnims = [['conked', 0.01, 0.3, 0.2], ['struggle', 0.01, 0.2]]
+ toonTrack = getToonTrack(attack, damageDelay=3.4, splicedDamageAnims=damageAnims,
+ dodgeDelay=2.8, dodgeAnimNames=['sidestep'])
+
+ if (dmg > 0): # If takes damage, paper sprays over the toon
+ restraintCloud = BattleParticles.createParticleEffect(file='restrainingOrderCloud')
+ restraintCloud.setPos(hitPoint.getX(), hitPoint.getY()+0.5, hitPoint.getZ())
+ cloudTrack = getPartTrack(restraintCloud, 3.5, 0.2, [restraintCloud, battle, 0])
+ return Parallel(suitTrack, cloudTrack, toonTrack, propTrack)
+ else:
+ return Parallel(suitTrack, toonTrack, propTrack)
+
+
+def doSpin(attack): # sd(b) fixed
+ """ This function returns Tracks portraying the Spin attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ damageDelay = 1.7
+
+ sprayEffect = BattleParticles.createParticleEffect(file='spinSpray')
+ spinEffect1 = BattleParticles.createParticleEffect(file='spinEffect')
+ spinEffect2 = BattleParticles.createParticleEffect(file='spinEffect')
+ spinEffect3 = BattleParticles.createParticleEffect(file='spinEffect')
+ spinEffect1.reparentTo(toon)
+ spinEffect2.reparentTo(toon)
+ spinEffect3.reparentTo(toon)
+
+ # To add to the randomness of the spinning effects, tinker with the numbers
+ height1 = toon.getHeight() * (random.random()*0.2 + 0.7) # from 0.7-0.9
+ height2 = toon.getHeight() * (random.random()*0.2 + 0.4) # from 0.4-0.6
+ height3 = toon.getHeight() * (random.random()*0.2 + 0.1) # from 0.1-0.3
+ spinEffect1.setPos(0.8, -0.7, height1)
+ spinEffect1.setHpr(0, 0, -random.random()*10-85)
+ spinEffect1.setHpr(spinEffect1, 0, 50, 0)
+ spinEffect2.setPos(0.8, -0.7, height2)
+ spinEffect2.setHpr(0, 0, -random.random()*10-85)
+ spinEffect2.setHpr(spinEffect2, 0, 50, 0)
+ spinEffect3.setPos(0.8, -0.7, height3)
+ spinEffect3.setHpr(0, 0, -random.random()*10-85)
+ spinEffect3.setHpr(spinEffect3, 0, 50, 0)
+ spinEffect1.wrtReparentTo(battle)
+ spinEffect2.wrtReparentTo(battle)
+ spinEffect3.wrtReparentTo(battle)
+
+
+ suitTrack = getSuitTrack(attack) # Get suit animation track
+ sprayTrack = getPartTrack(sprayEffect, 1.0, 1.9, [sprayEffect, suit, 0])
+ spinTrack1 = getPartTrack(spinEffect1, 2.1, 3.9, [spinEffect1, battle, 0])
+ spinTrack2 = getPartTrack(spinEffect2, 2.1, 3.9, [spinEffect2, battle, 0])
+ spinTrack3 = getPartTrack(spinEffect3, 2.1, 3.9, [spinEffect3, battle, 0])
+
+ damageAnims = []
+ damageAnims.append(['duck', 0.01, 0.01, 1.1])
+ damageAnims.extend(getSplicedLerpAnims('think', 0.66, 1.1, startTime=2.26))
+ damageAnims.extend(getSplicedLerpAnims('think', 0.66, 1.1, startTime=2.26))
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=0.91, dodgeAnimNames=['sidestep'],
+ showDamageExtraTime=2.1, showMissedExtraTime=1.0)
+
+ if (dmg > 0): # use the spinning particle effects
+ # Create the track to spin the toon around
+ toonSpinTrack = Sequence(
+ Wait(damageDelay+0.9),
+ LerpHprInterval(toon, 0.7, Point3(-10, 0, 0)),
+ LerpHprInterval(toon, 0.5, Point3(-30, 0, 0)),
+ LerpHprInterval(toon, 0.2, Point3(-60, 0, 0)),
+ LerpHprInterval(toon, 0.7, Point3(-700, 0, 0)),
+ LerpHprInterval(toon, 1.0, Point3(-1310, 0, 0)),
+ LerpHprInterval(toon, 0.4, toon.getHpr()),
+ Wait(0.5),
+ )
+ return Parallel(suitTrack, sprayTrack, toonTrack, toonSpinTrack,
+ spinTrack1, spinTrack2, spinTrack3)
+ else:
+ return Parallel(suitTrack, sprayTrack, toonTrack)
+
+
+def doLegalese(attack): # le(a) fixed
+ """ This function returns Tracks portraying the Legalese attack """
+ suit = attack['suit']
+
+ BattleParticles.loadParticles()
+ sprayEffect1 = BattleParticles.createParticleEffect(file='legaleseSpray')
+ sprayEffect2 = BattleParticles.createParticleEffect(file='legaleseSpray')
+ sprayEffect3 = BattleParticles.createParticleEffect(file='legaleseSpray')
+ color = Vec4(0.4, 0, 0, 1)
+ BattleParticles.setEffectTexture(sprayEffect1, 'legalese-hc', color=color)
+ BattleParticles.setEffectTexture(sprayEffect2, 'legalese-qpq', color=color)
+ BattleParticles.setEffectTexture(sprayEffect3, 'legalese-vd', color=color)
+
+ partDelay = 1.3
+ partDuration = 1.15
+ damageDelay = 1.9
+ dodgeDelay = 1.1
+
+ suitTrack = getSuitTrack(attack)
+ sprayTrack1 = getPartTrack(sprayEffect1, partDelay, partDuration,
+ [sprayEffect1, suit, 0])
+ sprayTrack2 = getPartTrack(sprayEffect2, partDelay+0.8, partDuration,
+ [sprayEffect2, suit, 0])
+ sprayTrack3 = getPartTrack(sprayEffect3, partDelay+1.6, partDuration,
+ [sprayEffect3, suit, 0])
+
+ damageAnims = []
+ damageAnims.append(['cringe', 0.00001, 0.3, 0.8])
+ damageAnims.append(['cringe', 0.00001, 0.3, 0.8])
+ damageAnims.append(['cringe', 0.00001, 0.3])
+ # Damage animation is toon cringing as gets hit then, then falls to the floor
+ toonTrack = getToonTrack(attack, damageDelay=damageDelay, splicedDamageAnims=damageAnims,
+ dodgeDelay=dodgeDelay, dodgeAnimNames=['sidestep'],
+ showMissedExtraTime=0.8)
+
+ return Parallel(suitTrack, toonTrack, sprayTrack1, sprayTrack2, sprayTrack3)
+
+
+def doPeckingOrder(attack): # throw, multiple props; duck; sidestep
+ """ This function returns Tracks portraying the Pecking Order attack """
+ suit = attack['suit']
+ battle = attack['battle']
+ target = attack['target']
+ toon = target['toon']
+ dmg = target['hp']
+ throwDuration = 3.03
+ throwDelay = 3.2
+
+ suitTrack = getSuitTrack(attack) # Get standard suit track
+
+ numBirds = random.randint(4, 7)
+ birdTracks = Parallel()
+ propDelay = 1.5
+ for i in range(0, numBirds):
+ next = globalPropPool.getProp('bird')
+ next.setScale(0.01)
+ next.reparentTo(suit.getRightHand())
+ next.setPos(random.random()*0.6-0.3,
+ random.random()*0.6-0.3,
+ random.random()*0.6-0.3)
+
+ if (dmg > 0): # If toon takes damage, hit face, otherwise go farther
+ hitPoint = Point3(random.random()*5-2.5,
+ random.random()*2-1 - 6,
+ random.random()*3-1.5 + toon.getHeight() - 0.9,)
+ else:
+ hitPoint = Point3(random.random()*2-1,
+ random.random()*4-2 - 15,
+ random.random()*4-2 + 2.2,)
+
+ birdTrack = Sequence(
+ Wait(throwDelay),
+ Func(battle.movie.needRestoreRenderProp, next),
+ Func(next.wrtReparentTo, battle),
+ Func(next.setHpr, Point3(90, 20, 0)),
+ LerpPosInterval(next, 1.1, hitPoint),
+ )
+
+ scaleTrack = Sequence(
+ Wait(throwDelay),
+ LerpScaleInterval(next, 0.15, Point3(9, 9, 9)),
+ )
+
+ birdTracks.append(Sequence(
+ Parallel(birdTrack, scaleTrack),
+ Func(MovieUtil.removeProp, next),
+ ))
+
+
+ damageAnims = []
+ damageAnims.append(['cringe', 0.01, 0.14, 0.21])
+ damageAnims.append(['cringe', 0.01, 0.14, 0.13])
+ damageAnims.append(['cringe', 0.01, 0.43])
+ toonTrack = getToonTrack(attack, damageDelay=4.2, splicedDamageAnims=damageAnims,
+ dodgeDelay=2.8, dodgeAnimNames=['sidestep'],
+ showMissedExtraTime=1.1)
+
+ return Parallel(suitTrack, toonTrack, birdTracks)
+
+
+"""
+def doGavel(attack): # project; slip-backward; sidestep
+ pass #later
+
+def doThrowBook(attack): # throw; conked; sidestep
+ pass #later
+"""
+
+############
+# End Revamped attacks using sub functions defined above.
+############
+
diff --git a/toontown/src/battle/MovieThrow.py b/toontown/src/battle/MovieThrow.py
new file mode 100644
index 0000000..d24e5ad
--- /dev/null
+++ b/toontown/src/battle/MovieThrow.py
@@ -0,0 +1,829 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from BattleSounds import *
+from toontown.toon.ToonDNA import *
+from toontown.suit.SuitDNA import *
+
+from direct.directnotify import DirectNotifyGlobal
+import random
+import MovieCamera
+import MovieUtil
+from MovieUtil import calcAvgSuitPos
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieThrow')
+
+# still need miss 'hitting' sounds to be separated from the 'throw' sounds
+# for creampie and fruitpie, wholecreampie and whole fruitpie and bdaycake
+# so the sound and animation can be synced at the end of variable-length throw
+# until we get these sounds, we just use these 'only' sounds as placeholders
+hitSoundFiles = ('AA_tart_only.mp3',
+ 'AA_slice_only.mp3',
+ 'AA_slice_only.mp3',
+ 'AA_slice_only.mp3',
+ 'AA_slice_only.mp3',
+ 'AA_wholepie_only.mp3',
+ 'AA_wholepie_only.mp3',) #UBER
+
+# need miss 'hitting' sounds to be separated from the 'throw' sounds
+# so the sound and animation can be synced at the end of variable-length throw
+# until we get these sounds, we just use 'AA_pie_throw_only.mp3' for all misses
+#missSoundFiles = ('AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',
+# 'AA_pie_throw_only.mp3',)
+
+#tPieLeavesHand = 2.755
+tPieLeavesHand = 2.7
+tPieHitsSuit = 3.0
+tSuitDodges = 2.45
+
+# length of pie flight path when you miss relative to length
+# of pie flight when you hit the suit
+ratioMissToHit = 1.5
+# this is based on the [0..1] time range of the pie flight when it misses
+tPieShrink = 0.7
+
+pieFlyTaskName = "MovieThrow-pieFly"
+
+
+def addHit (dict, suitId, hitCount):
+ if (dict.has_key(suitId)):
+ dict[suitId] += hitCount
+ else:
+ dict[suitId] = hitCount
+
+def doThrows(throws):
+
+ """ Throws occur in the following order:
+ a) by suit, in order of increasing number of throws per suit
+ 1) level 1 throws, right to left, (TOON_THROW_DELAY later)
+ 2) level 2 throws, right to left, (TOON_THROW_DELAY later)
+ 3) level 3 throws, right to left, (TOON_THROW_DELAY later)
+ etc.
+ b) next suit, (TOON_THROW_SUIT_DELAY later)
+ """
+
+ #import pdb; pdb.set_trace()
+ if (len(throws) == 0):
+ return (None, None)
+
+ # Group the throws by targeted suit
+ suitThrowsDict = {}
+ # throw['toon'] is the thrower.
+ for throw in throws:
+ # TODO: Count suits, and if there is only one, save that variable.
+ if attackAffectsGroup( throw['track'], throw['level']):
+ #hmmm lets try throwing at all of them
+ #for target in throw['target']:
+ # suitId = targett['suit'].doId
+ # if (suitThrowsDict.has_key(suitId)):
+ # suitThrowsDict[suitId].append(throw)
+ # else:
+ # suitThrowsDict[suitId] = [throw]
+ pass
+ else:
+ suitId = throw['target']['suit'].doId
+ if (suitThrowsDict.has_key(suitId)):
+ suitThrowsDict[suitId].append(throw)
+ else:
+ suitThrowsDict[suitId] = [throw]
+ # A list of lists of throws grouped by suit
+ suitThrows = suitThrowsDict.values()
+ # Sort the suits based on the number of throws per suit
+ def compFunc(a, b):
+ if (len(a) > len(b)):
+ return 1
+ elif (len(a) < len(b)):
+ return -1
+ return 0
+ suitThrows.sort(compFunc)
+
+ #since we have group throws now, we calculate how
+ #many times each suit gets hit over here
+ totalHitDict = {}
+ singleHitDict = {}
+ groupHitDict = {}
+
+ for throw in throws:
+ if attackAffectsGroup( throw['track'], throw['level']):
+ for i in range(len (throw['target'])):
+ target= throw['target'][i]
+ suitId = target['suit'].doId
+ if target['hp'] > 0:
+ addHit(groupHitDict,suitId,1)
+ addHit(totalHitDict,suitId,1)
+ else:
+ addHit(groupHitDict,suitId,0)
+ addHit(totalHitDict,suitId,0)
+
+ else:
+ suitId = throw['target']['suit'].doId
+ if throw['target']['hp'] > 0:
+ addHit(singleHitDict,suitId,1)
+ addHit(totalHitDict,suitId,1)
+ else:
+ addHit(singleHitDict,suitId,0)
+ addHit(totalHitDict,suitId,0)
+
+ notify.debug('singleHitDict = %s' % singleHitDict)
+ notify.debug('groupHitDict = %s' % groupHitDict)
+ notify.debug('totalHitDict = %s' % totalHitDict)
+
+
+ # Apply attacks in order
+ delay = 0.0
+ mtrack = Parallel()
+ for st in suitThrows:
+ if (len(st) > 0):
+ ival = __doSuitThrows(st)
+ if (ival):
+ mtrack.append(Sequence(Wait(delay), ival))
+ delay = delay + TOON_THROW_SUIT_DELAY
+
+ retTrack = Sequence()
+ retTrack.append(mtrack)
+
+
+ #we've done the single target throws, handle the group throws
+ groupThrowIvals = Parallel()
+ groupThrows = []
+ for throw in throws:
+ # TODO: Count suits, and if there is only one, save that variable.
+ if attackAffectsGroup( throw['track'], throw['level']):
+ groupThrows.append(throw)
+
+ for throw in groupThrows:
+ tracks = None
+ tracks = __throwGroupPie(throw, 0, groupHitDict)
+
+ if (tracks ):
+ #groupThrowIvals.append(Sequence(Wait(delay), ival))
+ for track in tracks:
+ groupThrowIvals.append(track)
+ #delay = delay + TOON_THROW_SUIT_DELAY
+
+ retTrack.append(groupThrowIvals)
+
+
+ camDuration = retTrack.getDuration()
+ camTrack = MovieCamera.chooseThrowShot(throws, suitThrowsDict,
+ camDuration)
+ return (retTrack, camTrack)
+
+def __doSuitThrows(throws):
+ """ __doSuitThrows(throws)
+ Create the intervals for the attacks on a particular suit.
+ 1 or more toons can throw at the same target suit
+ Note: attacks are sorted by increasing level (as are toons)
+ Returns a multitrack with toon throws in the following order:
+ 1) level 1 throws, right to left, (TOON_THROW_DELAY later)
+ 2) level 2 throws, right to left, (TOON_THROW_DELAY later)
+ etc.
+ The intervals actually overlap in the case of multiple hits,
+ prematurely cutting off the end of the react animation.
+ """
+ toonTracks = Parallel()
+ delay = 0.0
+ # See if suit is hit multiple times, if it is, don't show stun animation
+ hitCount = 0
+ for throw in throws:
+ if throw['target']['hp'] > 0:
+ # Hit, continue counting
+ hitCount += 1
+ else:
+ # Miss, no need to think about stun effect
+ break
+ for throw in throws:
+ tracks = __throwPie(throw, delay, hitCount)
+ if (tracks):
+ for track in tracks:
+ toonTracks.append(track)
+ delay = delay + TOON_THROW_DELAY
+ return toonTracks
+
+def __showProp(prop, parent, pos):
+ prop.reparentTo(parent)
+ prop.setPos(pos)
+
+def __animProp(props, propName, propType):
+ if 'actor' == propType:
+ for prop in props:
+ prop.play(propName)
+ elif 'model' == propType:
+ pass
+ else:
+ notify.error("No such propType as: %s" % propType)
+
+def __billboardProp(prop):
+ scale = prop.getScale()
+ #prop.lookAt(camera)
+ prop.setBillboardPointWorld()
+ prop.setScale(scale)
+
+def __suitMissPoint(suit, other=render):
+ # this is a point just above the suit's head
+ pnt = suit.getPos(other)
+ pnt.setZ(pnt[2] + (suit.getHeight() * 1.3))
+ return pnt
+
+def __propPreflight(props, suit, toon, battle):
+ assert(len(props) >= 2)
+ prop = props[0]
+ # make sure the 0 lod toon is in the right pose of the animation
+ toon.update(0)
+ # take the prop from the toon
+ prop.wrtReparentTo(battle)
+ props[1].reparentTo(hidden)
+
+ # make the top of the pie point along +Y
+ for ci in range(prop.getNumChildren()):
+ prop.getChild(ci).setHpr(0, -90, 0)
+
+ # figure out where it's going to end up
+ targetPnt = MovieUtil.avatarFacePoint(suit, other=battle)
+
+ # make the pie point towards the suit's face
+ prop.lookAt(targetPnt)
+
+def __propPreflightGroup(props, suits, toon, battle):
+ """
+ same as __propPreflight, but face the avg suit pt
+ """
+ assert(len(props) >= 2)
+ prop = props[0]
+ # make sure the 0 lod toon is in the right pose of the animation
+ toon.update(0)
+ # take the prop from the toon
+ prop.wrtReparentTo(battle)
+ props[1].reparentTo(hidden)
+
+ # make the top of the pie point along +Y
+ for ci in range(prop.getNumChildren()):
+ prop.getChild(ci).setHpr(0, -90, 0)
+
+ # figure out where it's going to end up
+ avgTargetPt = Point3(0,0,0)
+ for suit in suits:
+ avgTargetPt += MovieUtil.avatarFacePoint(suit, other=battle)
+ avgTargetPt /= len(suits)
+
+ # make the pie point towards the suit's face
+ prop.lookAt(avgTargetPt)
+
+
+def __piePreMiss(missDict, pie, suitPoint, other=render):
+ # called after propPreFlight
+ missDict['pie'] = pie
+ missDict['startScale'] = pie.getScale()
+ missDict['startPos'] = pie.getPos(other)
+ v = Vec3(suitPoint - missDict['startPos'])
+ endPos = missDict['startPos'] + (v * ratioMissToHit)
+ missDict['endPos'] = endPos
+
+def __pieMissLerpCallback(t, missDict):
+ pie = missDict['pie']
+ newPos = (missDict['startPos'] * (1.0 - t)) + (missDict['endPos'] * t)
+ if t < tPieShrink:
+ tScale = 0.0001
+ else:
+ tScale = (t - tPieShrink) / (1.0 - tPieShrink)
+ newScale = (missDict['startScale'] * max((1.0 - tScale), 0.01))
+ pie.setPos(newPos)
+ pie.setScale(newScale)
+
+
+def __piePreMissGroup(missDict, pies, suitPoint, other=render):
+ """
+ Same as __piePreMiss, but handles multiple pie parts
+ # called after propPreFlight
+ """
+
+ missDict['pies'] = pies
+ missDict['startScale'] = pies[0].getScale()
+ missDict['startPos'] = pies[0].getPos(other)
+ v = Vec3(suitPoint - missDict['startPos'])
+ endPos = missDict['startPos'] + (v * ratioMissToHit)
+ missDict['endPos'] = endPos
+
+ notify.debug('startPos=%s' % missDict['startPos'])
+ notify.debug('v=%s' % v)
+ notify.debug('endPos=%s' % missDict['endPos'])
+
+def __pieMissGroupLerpCallback(t, missDict):
+ """
+ Same as __pieMissLerpCallback, but handles multiple pie parts
+ """
+
+ pies = missDict['pies']
+ newPos = (missDict['startPos'] * (1.0 - t)) + (missDict['endPos'] * t)
+ if t < tPieShrink:
+ tScale = 0.0001
+ else:
+ tScale = (t - tPieShrink) / (1.0 - tPieShrink)
+ newScale = (missDict['startScale'] * max((1.0 - tScale), 0.01))
+
+ for pie in pies:
+ pie.setPos(newPos)
+ pie.setScale(newScale)
+
+
+def __getWeddingCakeSoundTrack(level, hitSuit, node=None):
+ throwTrack = Sequence()
+ if hitSuit:
+ throwSound = globalBattleSoundCache.getSound('AA_throw_wedding_cake.mp3')
+ songTrack = Sequence()
+ songTrack.append(Wait(1.0))
+ songTrack.append ( SoundInterval(throwSound, node=node))
+
+ splatSound = globalBattleSoundCache.getSound('AA_throw_wedding_cake_cog.mp3')
+ splatTrack = Sequence()
+ splatTrack.append(Wait(tPieHitsSuit))
+ splatTrack.append ( SoundInterval(splatSound, node=node))
+
+ throwTrack.append(Parallel(
+ songTrack,
+ splatTrack,
+ ))
+
+ else:
+ throwSound = globalBattleSoundCache.getSound('AA_throw_wedding_cake_miss.mp3')
+ throwTrack.append(Wait(tSuitDodges))
+ throwTrack.append ( SoundInterval(throwSound, node=node))
+
+
+ return throwTrack
+
+
+
+def __getSoundTrack(level, hitSuit, node=None):
+ #level: the level of attack, int 0-5
+ #hitSuit: does the attack hit toon, bool
+ if level == UBER_GAG_LEVEL_INDEX:
+ return __getWeddingCakeSoundTrack(level, hitSuit, node)
+
+ throwSound = globalBattleSoundCache.getSound('AA_pie_throw_only.mp3')
+
+ throwTrack = Sequence(Wait(2.6), SoundInterval(throwSound, node=node))
+
+ if hitSuit: # Add in impact sound if throw hits
+ hitSound = globalBattleSoundCache.getSound(hitSoundFiles[level])
+ hitTrack = Sequence(Wait(tPieLeavesHand),
+ SoundInterval(hitSound, node=node))
+ return Parallel(throwTrack, hitTrack)
+ else: # Just returns the sound of the throw if you miss
+ return throwTrack
+
+
+def __throwPie(throw, delay, hitCount):
+ toon = throw['toon']
+ hpbonus = throw['hpbonus']
+ target = throw['target']
+ suit = target['suit']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ sidestep = throw['sidestep']
+ died = target['died']
+ revived = target['revived']
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ level = throw['level']
+ battle = throw['battle']
+ suitPos = suit.getPos(battle)
+ origHpr = toon.getHpr(battle)
+ notify.debug('toon: %s throws tart at suit: %d for hp: %d died: %d' % \
+ (toon.getName(), suit.doId, hp, died))
+
+ pieName = pieNames[level]
+
+ hitSuit = (hp > 0)
+
+ pie = globalPropPool.getProp(pieName)
+ pieType = globalPropPool.getPropType(pieName)
+ pie2 = MovieUtil.copyProp(pie)
+ pies = [pie, pie2]
+ hands = toon.getRightHands()
+
+ splatName = 'splat-' + pieName
+ if pieName == 'wedding-cake':
+ splatName = 'splat-birthday-cake'
+ splat = globalPropPool.getProp(splatName)
+ splatType = globalPropPool.getPropType(splatName)
+
+ # make the toon throw the pie
+ toonTrack = Sequence()
+ toonFace = Func(toon.headsUp, battle, suitPos)
+ toonTrack.append(Wait(delay))
+ toonTrack.append(toonFace)
+ toonTrack.append(ActorInterval(toon, 'throw'))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.setHpr, battle, origHpr))
+
+ # take the pie from the toon and make it fly
+ pieShow = Func(MovieUtil.showProps, pies, hands)
+ pieAnim = Func(__animProp, pies, pieName, pieType)
+ pieScale1 = LerpScaleInterval(pie, 1.0, pie.getScale(),
+ startScale=MovieUtil.PNT3_NEARZERO)
+ pieScale2 = LerpScaleInterval(pie2, 1.0, pie2.getScale(),
+ startScale=MovieUtil.PNT3_NEARZERO)
+ pieScale = Parallel(pieScale1, pieScale2)
+ piePreflight = Func(__propPreflight, pies, suit, toon, battle)
+
+ pieTrack = Sequence(
+ Wait(delay),
+ pieShow,
+ pieAnim,
+ pieScale,
+ Func(battle.movie.needRestoreRenderProp, pies[0]),
+ Wait(tPieLeavesHand - 1.0),
+ piePreflight,
+ )
+
+ soundTrack = __getSoundTrack(level, hitSuit, toon)
+
+ if hitSuit:
+ # make the pie fly up to the suit's face and disappear
+ pieFly = LerpPosInterval(pie, tPieHitsSuit - tPieLeavesHand,
+ pos=MovieUtil.avatarFacePoint(suit, other=battle),
+ name=pieFlyTaskName, other=battle)
+ pieHide = Func(MovieUtil.removeProps, pies)
+ # play the splat animation
+ splatShow = Func(__showProp, splat, suit, Point3(0, 0, suit.getHeight()))
+ splatBillboard = Func(__billboardProp, splat)
+ splatAnim = ActorInterval(splat, splatName)
+ splatHide = Func(MovieUtil.removeProp, splat)
+ pieTrack.append(pieFly)
+ pieTrack.append(pieHide)
+ pieTrack.append(Func(battle.movie.clearRenderProp, pies[0]))
+ pieTrack.append(splatShow)
+ pieTrack.append(splatBillboard)
+ pieTrack.append(splatAnim)
+ pieTrack.append(splatHide)
+ else:
+ # the suit is going to dodge, or we missed
+ # make the pie fly past the suit's face, and shrink to nothing
+ missDict = {}
+ if sidestep:
+ suitPoint = MovieUtil.avatarFacePoint(suit, other=battle)
+ else:
+ suitPoint = __suitMissPoint(suit, other=battle)
+ piePreMiss = Func(__piePreMiss, missDict, pie, suitPoint, battle)
+ pieMiss = LerpFunctionInterval(
+ __pieMissLerpCallback,
+ extraArgs=[missDict],
+ duration = ((tPieHitsSuit - tPieLeavesHand)*ratioMissToHit))
+ pieHide = Func(MovieUtil.removeProps, pies)
+ pieTrack.append(piePreMiss)
+ pieTrack.append(pieMiss)
+ pieTrack.append(pieHide)
+ pieTrack.append(Func(battle.movie.clearRenderProp, pies[0]))
+
+ if hitSuit:
+ suitResponseTrack = Sequence()
+ showDamage = Func(suit.showHpText, -hp, openEnded=0, attackTrack = THROW_TRACK)
+ updateHealthBar = Func(suit.updateHealthBar, hp)
+ # If the suit gets knocked back, animate it
+ # No stun animation shown here
+ sival = [] # Suit interval of its animation
+ if (kbbonus > 0):
+ suitPos, suitHpr = battle.getActorPosHpr(suit)
+ suitType = getSuitBodyType(suit.getStyleName())
+ animTrack = Sequence()
+ animTrack.append(ActorInterval(suit, 'pie-small-react', duration=0.2))
+ if (suitType == 'a'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=2.43))
+ elif (suitType == 'b'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=1.94))
+ elif (suitType == 'c'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=2.58))
+ # Be sure to unlure the suit so it doesn't walk back (already knocked back)
+ animTrack.append(Func(battle.unlureSuit, suit))
+
+ moveTrack = Sequence(
+ Wait(0.2),
+ LerpPosInterval(suit, 0.6, pos=suitPos, other=battle),
+ )
+ sival = Parallel(animTrack, moveTrack)
+ else:
+ if (hitCount == 1):
+ sival = Parallel(
+ ActorInterval(suit, 'pie-small-react'),
+ MovieUtil.createSuitStunInterval(suit, 0.3, 1.3),
+ )
+ else:
+ sival = ActorInterval(suit, 'pie-small-react')
+ #sival = ActorInterval(suit, 'pie-small-react')
+ suitResponseTrack.append(Wait(delay + tPieHitsSuit))
+ suitResponseTrack.append(showDamage)
+ suitResponseTrack.append(updateHealthBar)
+ suitResponseTrack.append(sival)
+ # Make a bonus track for any hp bonus
+ bonusTrack = Sequence(Wait(delay + tPieHitsSuit))
+ if (kbbonus > 0):
+ bonusTrack.append(Wait(0.75))
+ bonusTrack.append(Func(suit.showHpText, -kbbonus, 2, openEnded=0, attackTrack = THROW_TRACK))
+ if (hpbonus > 0):
+ bonusTrack.append(Wait(0.75))
+ bonusTrack.append(Func(suit.showHpText, -hpbonus, 1, openEnded=0, attackTrack = THROW_TRACK))
+
+ if (revived != 0):
+ suitResponseTrack.append(MovieUtil.createSuitReviveTrack(suit, toon, battle))
+ elif (died != 0):
+ suitResponseTrack.append(MovieUtil.createSuitDeathTrack(suit, toon, battle))
+ else:
+ suitResponseTrack.append(Func(suit.loop, 'neutral'))
+
+ suitResponseTrack = Parallel(suitResponseTrack, bonusTrack)
+
+ else:
+ # suit dodges
+ # other suits may need to dodge as well
+ suitResponseTrack = MovieUtil.createSuitDodgeMultitrack(delay + tSuitDodges,
+ suit, leftSuits, rightSuits)
+
+ # Since it's possible for there to be simultaneous throws, we only
+ # want the suit to dodge one time at most. Thus if the suit is
+ # not hit (dodges) and that dodge is not from the first dodge
+ # (which has delay=0) then we don't add another suit reaction.
+ # Otherwise, we add the suit track as normal
+
+ if (not hitSuit and delay > 0):
+ return [toonTrack, soundTrack, pieTrack]
+ else:
+ return [toonTrack, soundTrack, pieTrack, suitResponseTrack]
+
+
+
+
+
+
+def __createWeddingCakeFlight(throw, groupHitDict, pie, pies):
+ toon = throw['toon']
+ battle = throw['battle']
+ level = throw['level']
+ sidestep = throw['sidestep']
+ hpbonus = throw['hpbonus']
+ numTargets = len(throw['target'])
+ pieName = pieNames[level]
+
+ #so loop through our targets, and throw 1 layer of the wedding cake at each one
+ splatName = 'splat-' + pieName
+ if pieName == 'wedding-cake':
+ splatName = 'splat-birthday-cake'
+ splat = globalPropPool.getProp(splatName)
+ splats = [splat]
+ for i in range (numTargets-1):
+ splats.append( MovieUtil.copyProp(splat) )
+
+ splatType = globalPropPool.getPropType(splatName)
+
+ cakePartStrs = ['cake1','cake2','cake3','caketop']
+ cakeParts = []
+ for part in cakePartStrs:
+ cakeParts.append(pie.find('**/%s' % part))
+
+ #figure out which cake part goes to which suit
+ cakePartDivisions = {}
+ cakePartDivisions[1] = [[cakeParts[0], cakeParts[1], cakeParts[2], cakeParts[3] ],]
+ cakePartDivisions[2] = [ [cakeParts[0], cakeParts[1]], [cakeParts[2], cakeParts[3]] ]
+ cakePartDivisions[3] = [ [cakeParts[0], cakeParts[1]], [cakeParts[2],], [cakeParts[3],] ]
+ cakePartDivisions[4] = [ [cakeParts[0],], [cakeParts[1],], [cakeParts[2],], [cakeParts[3],] ]
+
+ cakePartDivToUse = cakePartDivisions[len(throw['target'])]
+ groupPieTracks = Parallel()
+ for i in range(numTargets):
+ target = throw['target'][i]
+ suit = target['suit']
+ hitSuit = (target['hp'] > 0)
+
+ singlePieTrack = Sequence()
+ if hitSuit:
+ # make the pie fly up to the suit's face and disappear
+ piePartReparent = Func( reparentCakePart, pie, cakePartDivToUse[i])
+ singlePieTrack.append(piePartReparent)
+
+ cakePartTrack = Parallel()
+ for cakePart in cakePartDivToUse[i]:
+ pieFly = LerpPosInterval(cakePart, tPieHitsSuit - tPieLeavesHand,
+ pos=MovieUtil.avatarFacePoint(suit, other=battle),
+ name=pieFlyTaskName, other=battle)
+ cakePartTrack.append(pieFly)
+ singlePieTrack.append(cakePartTrack)
+
+ pieRemoveCakeParts = Func(MovieUtil.removeProps, cakePartDivToUse[i])
+
+ pieHide = Func(MovieUtil.removeProps, pies)
+
+ # play the splat animation
+ splatShow = Func(__showProp, splats[i], suit, Point3(0, 0, suit.getHeight()))
+ splatBillboard = Func(__billboardProp, splats[i])
+ splatAnim = ActorInterval(splats[i], splatName)
+ splatHide = Func(MovieUtil.removeProp, splats[i])
+
+
+ singlePieTrack.append(pieRemoveCakeParts)
+ singlePieTrack.append(pieHide)
+ singlePieTrack.append(Func(battle.movie.clearRenderProp, pies[0]))
+ singlePieTrack.append(splatShow)
+ singlePieTrack.append(splatBillboard)
+ singlePieTrack.append(splatAnim)
+ singlePieTrack.append(splatHide)
+ else:
+ # the suit is going to dodge, or we missed
+ # make the pie fly past the suit's face, and shrink to nothing
+ #untested for now
+
+ missDict = {}
+ if sidestep:
+ suitPoint = MovieUtil.avatarFacePoint(suit, other=battle)
+ else:
+ suitPoint = __suitMissPoint(suit, other=battle)
+ piePartReparent = Func( reparentCakePart, pie, cakePartDivToUse[i])
+ piePreMiss = Func(__piePreMissGroup, missDict, cakePartDivToUse[i], suitPoint, battle)
+ pieMiss = LerpFunctionInterval(
+ __pieMissGroupLerpCallback,
+ extraArgs=[missDict],
+ duration = ((tPieHitsSuit - tPieLeavesHand)*ratioMissToHit) )
+ pieHide = Func(MovieUtil.removeProps, pies)
+ pieRemoveCakeParts = Func(MovieUtil.removeProps, cakePartDivToUse[i])
+
+ singlePieTrack.append(piePartReparent)
+ singlePieTrack.append(piePreMiss)
+ singlePieTrack.append(pieMiss)
+ singlePieTrack.append(pieRemoveCakeParts)
+ singlePieTrack.append(pieHide)
+ singlePieTrack.append(Func(battle.movie.clearRenderProp, pies[0]))
+
+ groupPieTracks.append(singlePieTrack)
+
+ return groupPieTracks
+
+def __throwGroupPie(throw, delay, groupHitDict):
+ """
+ TODO this can be made to __throwGroupPie and wedding cake stuff is called in a diff function
+ """
+ toon = throw['toon']
+ battle = throw['battle']
+ level = throw['level']
+ sidestep = throw['sidestep']
+ hpbonus = throw['hpbonus']
+ numTargets = len(throw['target'])
+
+ avgSuitPos = calcAvgSuitPos(throw)
+
+ # make the toon throw the wedding cake
+ origHpr = toon.getHpr(battle)
+
+ toonTrack = Sequence()
+ toonFace = Func(toon.headsUp, battle, avgSuitPos)
+ toonTrack.append(Wait(delay))
+ toonTrack.append(toonFace)
+ toonTrack.append(ActorInterval(toon, 'throw'))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.setHpr, battle, origHpr))
+
+
+ # take the pie from the toon and make it fly
+ suits = []
+ for i in range(numTargets):
+ suits.append ( throw['target'][i]['suit'])
+ pieName = pieNames[level]
+ pie = globalPropPool.getProp(pieName)
+ pieType = globalPropPool.getPropType(pieName)
+ pie2 = MovieUtil.copyProp(pie)
+ pies = [pie, pie2]
+ hands = toon.getRightHands()
+ pieShow = Func(MovieUtil.showProps, pies, hands)
+ pieAnim = Func(__animProp, pies, pieName, pieType)
+ pieScale1 = LerpScaleInterval(pie, 1.0, pie.getScale() * 1.5,
+ startScale=MovieUtil.PNT3_NEARZERO)
+ pieScale2 = LerpScaleInterval(pie2, 1.0, pie2.getScale() *1.5,
+ startScale=MovieUtil.PNT3_NEARZERO)
+ pieScale = Parallel(pieScale1, pieScale2)
+ piePreflight = Func(__propPreflightGroup, pies, suits, toon, battle)
+
+ pieTrack = Sequence(
+ Wait(delay),
+ pieShow,
+ pieAnim,
+ pieScale,
+ Func(battle.movie.needRestoreRenderProp, pies[0]),
+ Wait(tPieLeavesHand - 1.0),
+ piePreflight,
+ )
+
+ #create the pie flight interval
+ if level == UBER_GAG_LEVEL_INDEX:
+ groupPieTracks = __createWeddingCakeFlight(throw, groupHitDict, pie, pies)
+ else:
+ notify.error('unhandled throw level %d' % level)
+
+ pieTrack.append(groupPieTracks)
+
+
+ didThrowHitAnyone = False
+ for i in range(numTargets):
+ target = throw['target'][i]
+ hitSuit = (target['hp'] > 0)
+ if hitSuit:
+ didThrowHitAnyone = True
+ soundTrack = __getSoundTrack(level, didThrowHitAnyone, toon)
+
+ groupSuitResponseTrack = Parallel()
+ #handle the suit response
+ for i in range(numTargets):
+ target = throw['target'][i]
+ suit = target['suit']
+ hitSuit = (target['hp'] > 0)
+ leftSuits = target['leftSuits']
+ rightSuits = target['rightSuits']
+ hp = target['hp']
+ kbbonus = target['kbbonus']
+ died = target['died']
+ revived = target['revived']
+ if hitSuit:
+ singleSuitResponseTrack = Sequence()
+ showDamage = Func(suit.showHpText, -hp, openEnded=0, attackTrack=THROW_TRACK)
+ updateHealthBar = Func(suit.updateHealthBar, hp)
+ # If the suit gets knocked back, animate it
+ # No stun animation shown here
+ sival = [] # Suit interval of its animation
+ if (kbbonus > 0):
+ suitPos, suitHpr = battle.getActorPosHpr(suit)
+ suitType = getSuitBodyType(suit.getStyleName())
+ animTrack = Sequence()
+ animTrack.append(ActorInterval(suit, 'pie-small-react', duration=0.2))
+ if (suitType == 'a'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=2.43))
+ elif (suitType == 'b'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=1.94))
+ elif (suitType == 'c'):
+ animTrack.append(ActorInterval(suit, 'slip-forward', startTime=2.58))
+ # Be sure to unlure the suit so it doesn't walk back (already knocked back)
+ animTrack.append(Func(battle.unlureSuit, suit))
+
+ moveTrack = Sequence(
+ Wait(0.2),
+ LerpPosInterval(suit, 0.6, pos=suitPos, other=battle),
+ )
+ sival = Parallel(animTrack, moveTrack)
+ else:
+ if (groupHitDict[suit.doId] == 1):
+ sival = Parallel(
+ ActorInterval(suit, 'pie-small-react'),
+ MovieUtil.createSuitStunInterval(suit, 0.3, 1.3),
+ )
+ else:
+ sival = ActorInterval(suit, 'pie-small-react')
+ #sival = ActorInterval(suit, 'pie-small-react')
+ singleSuitResponseTrack.append(Wait(delay + tPieHitsSuit))
+ singleSuitResponseTrack.append(showDamage)
+ singleSuitResponseTrack.append(updateHealthBar)
+ singleSuitResponseTrack.append(sival)
+
+ # Make a bonus track for any hp bonus
+ bonusTrack = Sequence(Wait(delay + tPieHitsSuit))
+ if (kbbonus > 0):
+ bonusTrack.append(Wait(0.75))
+ bonusTrack.append(Func(suit.showHpText, -kbbonus, 2, openEnded=0, attackTrack = THROW_TRACK))
+ if (hpbonus > 0):
+ bonusTrack.append(Wait(0.75))
+ bonusTrack.append(Func(suit.showHpText, -hpbonus, 1, openEnded=0, attackTrack = THROW_TRACK))
+
+ if (revived != 0):
+ singleSuitResponseTrack.append(MovieUtil.createSuitReviveTrack(suit, toon, battle))
+
+ elif (died != 0):
+ singleSuitResponseTrack.append(MovieUtil.createSuitDeathTrack(suit, toon, battle))
+ else:
+ singleSuitResponseTrack.append(Func(suit.loop, 'neutral'))
+
+ singleSuitResponseTrack = Parallel(singleSuitResponseTrack, bonusTrack)
+
+ else:
+ #if none of the group throws hit at all, we can do a dodge
+ groupHitValues = groupHitDict.values()
+ if groupHitValues.count(0) == len(groupHitValues):
+ singleSuitResponseTrack = MovieUtil.createSuitDodgeMultitrack(delay + tSuitDodges,
+ suit, leftSuits, rightSuits)
+ else:
+ #because all the group pies fire at the same time, do not dodge at all
+ #in case one toon hits and another toon misses
+ singleSuitResponseTrack = Sequence(Wait(tPieHitsSuit - 0.1),
+ Func(MovieUtil.indicateMissed,
+ suit, 1.0))
+
+
+ groupSuitResponseTrack.append(singleSuitResponseTrack)
+
+ return [toonTrack,pieTrack, soundTrack, groupSuitResponseTrack]
+
+
+def reparentCakePart(pie, cakeParts):
+ pieParent = pie.getParent()
+ notify.debug('pieParent = %s' % pieParent)
+ for cakePart in cakeParts:
+ cakePart.wrtReparentTo(pieParent)
diff --git a/toontown/src/battle/MovieToonVictory.py b/toontown/src/battle/MovieToonVictory.py
new file mode 100644
index 0000000..8147392
--- /dev/null
+++ b/toontown/src/battle/MovieToonVictory.py
@@ -0,0 +1,80 @@
+from direct.interval.IntervalGlobal import *
+from RewardPanel import *
+from BattleSounds import *
+
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+import types
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieToonVictory')
+
+def __findToonReward(rewards, toon):
+ for r in rewards:
+ if (r['toon'] == toon):
+ return r
+ return None
+
+def doToonVictory(localToonActive, toons, rewardToonIds, rewardDicts,
+ deathList, rpanel, allowGroupShot = 1, uberList = [], helpfulToonsList= []):
+ track = Sequence()
+ if (localToonActive == 1):
+ track.append(Func(rpanel.show))
+ track.append(Func(NametagGlobals.setOnscreenChatForced, 1))
+
+ camTrack = Sequence()
+ endTrack = Sequence()
+ danceSound = globalBattleSoundCache.getSound('ENC_Win.mp3')
+
+ # The toons list might be a list of toons, or it might be a list
+ # of toonId's. In either case, build a list of toons out of it.
+ toonList = []
+ countToons = 0
+ uberListNew = []
+ for t in toons:
+ if isinstance(t, types.IntType):
+ t = base.cr.doId2do.get(t)
+ if t:
+ toonList.append(t)
+ uberListNew.append(uberList[countToons])
+ countToons += 1
+
+
+ # make a list of toons/None from the rewardToonIds list. This list
+ # corresponds with the bitmasks embedded in the deathList. We need this
+ # in case a toon leaves in between the creation of the bitmasks and
+ # the client-side interpretation of them. (the bitmasks tell us who
+ # of the remaining toons was around when each cog was defeated)
+ toonId2toon = {}
+ for toon in toonList:
+ toonId2toon[toon.doId] = toon
+ rewardToonList = []
+ for id in rewardToonIds:
+ rewardToonList.append(toonId2toon.get(id))
+
+ for tIndex in range(len(toonList)):
+ t = toonList[tIndex]
+ rdict = __findToonReward(rewardDicts, t)
+ # To prevent client from crashing in this case.
+ if rdict != None:
+ expTrack = rpanel.getExpTrack(t, rdict['origExp'], rdict['earnedExp'],
+ deathList, rdict['origQuests'], rdict['items'], rdict['missedItems'],
+ rdict['origMerits'], rdict['merits'],
+ rdict['parts'], rewardToonList, uberListNew[tIndex],
+ helpfulToonsList)
+ if expTrack:
+ track.append(expTrack)
+ camDuration = expTrack.getDuration()
+ camExpTrack = MovieCamera.chooseRewardShot(t, camDuration)
+ assert camDuration == camExpTrack.getDuration()
+ camTrack.append(MovieCamera.chooseRewardShot(
+ t, camDuration, allowGroupShot = allowGroupShot))
+ if (localToonActive == 1):
+ track.append(Func(rpanel.hide))
+ track.append(Func(NametagGlobals.setOnscreenChatForced, 0))
+ track.append(endTrack)
+ trackdur = track.getDuration()
+ soundTrack = SoundInterval(danceSound, duration=trackdur, loop=1)
+ mtrack = Parallel(track, soundTrack)
+
+ return (mtrack, camTrack)
+
diff --git a/toontown/src/battle/MovieTrap.py b/toontown/src/battle/MovieTrap.py
new file mode 100644
index 0000000..fa9029f
--- /dev/null
+++ b/toontown/src/battle/MovieTrap.py
@@ -0,0 +1,763 @@
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+from BattleSounds import *
+
+import MovieUtil
+import MovieCamera
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownBattleGlobals
+from direct.actor import Actor
+from direct.particles import ParticleEffect
+import BattleParticles
+import BattleProps
+import MovieNPCSOS
+from MovieSound import createSuitResetPosTrack
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieTrap')
+
+def doTraps(traps):
+ """ Traps occur simultaneously - if more than one trap is
+ thrown at the same suit, they explode
+ """
+ if (len(traps) == 0):
+ return (None, None)
+
+ npcArrivals, npcDepartures, npcs = MovieNPCSOS.doNPCTeleports(traps)
+
+ hasUberTrapConflict = False
+
+ # Group the traps by targeted suit
+ suitTrapsDict = {}
+ for trap in traps:
+ targets = trap['target']
+ if (len(targets) == 1):
+ suitId = targets[0]['suit'].doId
+ if (suitTrapsDict.has_key(suitId)):
+ suitTrapsDict[suitId].append(trap)
+ else:
+ suitTrapsDict[suitId] = [trap]
+ else:
+ # We're dealing with an NPC trap, which can have multiple
+ # targets - we only want to use the first valid target,
+ # however, to avoid tossing too many traps
+ for target in targets:
+ suitId = target['suit'].doId
+ if (not suitTrapsDict.has_key(suitId)):
+ suitTrapsDict[suitId] = [trap]
+ break
+ if trap['level'] == UBER_GAG_LEVEL_INDEX:
+ if len(traps) > 1:
+ hasUberTrapConflict = True
+ for oneTarget in trap['target']:
+ suit = oneTarget['suit']
+ if suit.battleTrap != NO_TRAP:
+ #we can get here in a 3 suit battle, by placing quicksand then doing train
+ hasUberTrapConflict = True
+
+
+ suitTrapLists = suitTrapsDict.values()
+
+ mtrack = Parallel()
+ for trapList in suitTrapLists:
+ # Set the appropriate trapProps for use with this trap
+ trapPropList = []
+ for i in range(len(trapList)):
+ trap = trapList[i]
+ level = trap['level']
+ if (level == 0): # banana
+ banana = globalPropPool.getProp('banana')
+ banana2 = MovieUtil.copyProp(banana)
+ trapPropList.append([banana, banana2])
+ elif (level == 1): # rake
+ rake = globalPropPool.getProp('rake')
+ rake2 = MovieUtil.copyProp(rake)
+ rake.pose('rake', 0)
+ rake2.pose('rake', 0)
+ trapPropList.append([rake, rake2])
+ elif (level == 2): # marbles
+ marbles = globalPropPool.getProp('marbles')
+ marbles2 = MovieUtil.copyProp(marbles)
+ trapPropList.append([marbles, marbles2])
+ elif (level == 3): # quicksand
+ trapPropList.append([globalPropPool.getProp('quicksand')])
+ elif (level == 4): # trapdoor
+ trapPropList.append([globalPropPool.getProp('trapdoor')])
+ elif (level == 5): # tnt
+ tnt = globalPropPool.getProp('tnt')
+ tnt2 = MovieUtil.copyProp(tnt)
+ trapPropList.append([tnt, tnt2])
+ elif (level == 6): # tnt #UBER
+ tnt = globalPropPool.getProp('traintrack') #('trapdoor')
+ tnt2 = MovieUtil.copyProp(tnt)
+ trapPropList.append([tnt, tnt2])
+ else:
+ notify.warning('__doTraps() - Incorrect trap level: \
+ %d' % level)
+
+ if (len(trapList) == 1) and not hasUberTrapConflict:
+ # Trap is successfully placed
+ ival = __doTrapLevel(trapList[0], trapPropList[0])
+ if (ival):
+ mtrack.append(ival)
+ else:
+ assert(len(trapList) > 1 or hasUberTrapConflict)
+ subMtrack = Parallel()
+ for i in range(len(trapList)):
+ trap = trapList[i]
+ trapProps = trapPropList[i]
+ ival = __doTrapLevel(trap, trapProps, explode=1)
+ if (ival):
+ subMtrack.append(ival)
+ mtrack.append(subMtrack)
+
+ trapTrack = Sequence(npcArrivals, mtrack, npcDepartures)
+
+ camDuration = mtrack.getDuration()
+ enterDuration = npcArrivals.getDuration()
+ exitDuration = npcDepartures.getDuration()
+ camTrack = MovieCamera.chooseTrapShot(traps, camDuration, enterDuration,
+ exitDuration)
+ return (trapTrack, camTrack)
+
+def __doTrapLevel(trap, trapProps, explode=0):
+ """ __doTrapLevel(trap)
+ """
+ level = trap['level']
+ if (level == 0):
+ return __trapBanana(trap, trapProps, explode)
+ elif (level == 1):
+ return __trapRake(trap, trapProps, explode)
+ elif (level == 2):
+ return __trapMarbles(trap, trapProps, explode)
+ elif (level == 3):
+ return __trapQuicksand(trap, trapProps, explode)
+ elif (level == 4):
+ return __trapTrapdoor(trap, trapProps, explode)
+ elif (level == 5):
+ return __trapTNT(trap, trapProps, explode)
+ elif (level == 6):
+ return __trapTrain(trap, trapProps, explode)
+ return None
+
+def getSoundTrack(fileName, delay=0.01, duration=None, node=None):
+ """ This functions returns the standard sound track, which involves one sound
+ effect with a possible delay beforehand and an optional duration specification.
+ """
+
+ soundEffect = globalBattleSoundCache.getSound(fileName)
+
+ if duration:
+ return Sequence(Wait(delay),
+ SoundInterval(soundEffect, duration=duration, node=node))
+ else:
+ return Sequence(Wait(delay),
+ SoundInterval(soundEffect, node=node))
+
+
+def __createThrownTrapMultiTrack(trap, propList, propName, propPos=None,
+ propHpr=None, anim=0, explode=0):
+ toon = trap['toon']
+ level = trap['level']
+ battle = trap['battle']
+ target = trap['target']
+ suit = target[0]['suit']
+ targetPos = suit.getPos(battle)
+ thrownProp = propList[0]
+ unthrownProp = propList[1]
+
+ # Different toon body types throw at different times
+ torso = toon.style.torso
+ torso = torso[0] # only want the first letter (s, m, l)
+ if (torso == 'l'):
+ throwDelay = 2.3
+ elif (torso == 'm'):
+ throwDelay = 2.3
+ else:
+ throwDelay = 1.9
+
+ throwDuration = 0.9
+ animBreakPoint = throwDelay + throwDuration
+ animDelay = 3.1
+ # All the traps use the default trap distance, except the rake (gets stepped into)
+ trapTrack = ToontownBattleGlobals.TRAP_TRACK
+ trapTrackNames = ToontownBattleGlobals.AvProps[trapTrack]
+ trapName = trapTrackNames[level]
+
+ hands = toon.getRightHands()
+ propTrack = Sequence()
+ if propPos and propHpr:
+ propTrack.append(Func(MovieUtil.showProps, propList,
+ hands, propPos, propHpr))
+ else:
+ propTrack.append(Func(MovieUtil.showProps, propList, hands))
+
+ if (anim == 1):
+ pTracks = Parallel()
+ for prop in propList:
+ pTracks.append(ActorInterval(prop, propName,
+ duration=animBreakPoint))
+ propTrack.append(pTracks)
+
+ throwTrack = Sequence()
+ throwTrack.append(Wait(throwDelay))
+ # Must hide the unthrown prop so there doesn't appear to be two, since the
+ # thrown prop will be parented to the battle and therefore visible for
+ # every level of detail
+ throwTrack.append(Func(unthrownProp.reparentTo, hidden))
+ # Update on the toon to be sure that all LOD's see the same thrown trap right
+ throwTrack.append(Func(toon.update))
+
+ # Set the trap prop as the suit's trap prop
+ if (suit.battleTrap != NO_TRAP):
+ notify.debug('trapSuit() - trap: %d destroyed existing trap: %d' % \
+ (level, suit.battleTrap))
+ battle.removeTrap(suit)
+ if (trapName == 'rake'):
+ trapProp = globalPropPool.getProp('rake-react')
+ else:
+ trapProp = MovieUtil.copyProp(thrownProp)
+ suit.battleTrapProp = trapProp
+ suit.battleTrap = level
+ suit.battleTrapIsFresh = 1
+
+ # Each prop varies slightly for the throw
+ if (trapName == 'banana'):
+ trapPoint, trapHpr = battle.getActorPosHpr(suit)
+ trapPoint.setY(MovieUtil.SUIT_TRAP_DISTANCE)
+ slidePoint = Vec3(trapPoint.getX(), trapPoint.getY()-2, trapPoint.getZ())
+ throwingTrack = createThrowingTrack(
+ thrownProp, slidePoint, duration=0.9, parent=battle)
+ moveTrack = LerpPosInterval(thrownProp, 0.8, pos=trapPoint,
+ other=battle)
+ animTrack = ActorInterval(thrownProp, propName,
+ startTime=animBreakPoint)
+ slideTrack = Parallel(moveTrack, animTrack)
+ motionTrack = Sequence(throwingTrack, slideTrack)
+ hprTrack = LerpHprInterval(thrownProp, 1.7, hpr=Point3(0, 0, 0))
+ soundTrack = getSoundTrack('TL_banana.mp3', node=toon)
+ scaleTrack = LerpScaleInterval(thrownProp, 1.7, scale=MovieUtil.PNT3_ONE)
+ throwTrack.append(Wait(0.25)) # Wait a bit more before throwing
+ throwTrack.append(Func(thrownProp.wrtReparentTo, suit))
+ throwTrack.append(Parallel(motionTrack, hprTrack, scaleTrack, soundTrack))
+ elif (trapName == 'tnt'): # Tnt must start its fuse (particle effect)
+ trapPoint, trapHpr = battle.getActorPosHpr(suit)
+ trapPoint.setY(MovieUtil.SUIT_TRAP_TNT_DISTANCE - 3.9)
+ trapPoint.setZ(trapPoint.getZ() + 0.4)
+ throwingTrack = createThrowingTrack(
+ thrownProp, trapPoint, duration=throwDuration, parent=battle)
+ hprTrack = LerpHprInterval(thrownProp, 0.9, hpr=Point3(0, 90, 0))
+ scaleTrack = LerpScaleInterval(thrownProp, 0.9, scale=MovieUtil.PNT3_ONE)
+ soundTrack = getSoundTrack('TL_dynamite.mp3', delay=0.8, duration=0.7, node=suit)
+ throwTrack.append(Wait(0.2)) # Wait a bit more before throwing
+ throwTrack.append(Parallel(throwingTrack, hprTrack, scaleTrack, soundTrack))
+ elif (trapName == 'marbles'): # Marbles aren't tossed but lerped
+ trapPoint, trapHpr = battle.getActorPosHpr(suit)
+ trapPoint.setY(MovieUtil.SUIT_TRAP_MARBLES_DISTANCE)
+ flingDuration = 0.2
+ rollDuration = 1.0
+ throwDuration = flingDuration + rollDuration
+ landPoint = Point3(0, trapPoint.getY()+2, trapPoint.getZ())
+ throwPoint = Point3(0, trapPoint.getY(), trapPoint.getZ())
+ moveTrack = Sequence(
+ Func(thrownProp.wrtReparentTo, suit),
+ Func(thrownProp.setHpr, Point3(94, 0, 0)),
+ LerpPosInterval(thrownProp, flingDuration, pos=landPoint, other=suit),
+ LerpPosInterval(thrownProp, rollDuration, pos=throwPoint, other=suit),
+ )
+ animTrack = ActorInterval(thrownProp, propName,
+ startTime=throwDelay+0.9)
+ scaleTrack = LerpScaleInterval(thrownProp, throwDuration,
+ scale=MovieUtil.PNT3_ONE)
+ soundTrack = getSoundTrack('TL_marbles.mp3', delay=0.1, node=toon)
+ throwTrack.append(Wait(0.2)) # Need to wait a bit more before throwing
+ throwTrack.append(Parallel(moveTrack, animTrack, scaleTrack, soundTrack))
+ elif (trapName == 'rake'):
+ trapPoint, trapHpr = battle.getActorPosHpr(suit)
+ trapPoint.setY(MovieUtil.SUIT_TRAP_RAKE_DISTANCE)
+ throwDuration = 1.1
+ throwingTrack = createThrowingTrack(
+ thrownProp, trapPoint, duration=throwDuration, parent=suit)
+ hprTrack = LerpHprInterval(thrownProp, throwDuration,
+ hpr=VBase3(63.43, -90.00, 63.43))
+ scaleTrack = LerpScaleInterval(thrownProp, 0.9,
+ scale=Point3(0.7, 0.7, 0.7))
+ soundTrack = SoundInterval(globalBattleSoundCache.getSound('TL_rake_throw_only.mp3'), duration=1.1, node=suit)
+
+ throwTrack.append(Wait(0.2))
+ throwTrack.append(Parallel(throwingTrack, hprTrack, scaleTrack, soundTrack))
+ else:
+ notify.warning('__createThrownTrapMultiTrack() - Incorrect trap: \
+ %s thrown from toon' % trapName)
+
+ def placeTrap(trapProp, suit, battle=battle, trapName=trapName):
+ # In case multiple traps are thrown to the same spot, we must return (doing
+ # nothing) if the trapProp we have has actually been removed and replaced
+ # by another trap (it won't exist anymore)
+ if not trapProp or trapProp.isEmpty():
+ return
+ trapProp.wrtReparentTo(suit)
+ trapProp.show()
+ # Set the new trap prop
+ # All the traps use the default trap distance, except the rake
+ if (trapName == 'rake'):
+ # The rake will actually be completed replaced with a different rake model
+ # which has a different animation on it for when the rake is stepped on
+ trapProp.setPos(0, MovieUtil.SUIT_TRAP_RAKE_DISTANCE, 0)
+ # reorient the rake correctly to be stepped on
+ trapProp.setHpr(Point3(0, 270, 0))
+ trapProp.setScale(Point3(0.7, 0.7, 0.7))
+ # The rake must be a specific distance from each suit for that
+ # suit to be able to walk into the rake
+ rakeOffset = MovieUtil.getSuitRakeOffset(suit)
+ trapProp.setY(trapProp.getY() + rakeOffset)
+ elif (trapName == 'banana'):
+ trapProp.setHpr(0, 0, 0)
+ trapProp.setPos(0, MovieUtil.SUIT_TRAP_DISTANCE, -0.35)
+ trapProp.pose(trapName, trapProp.getNumFrames(trapName) - 1)
+ elif (trapName == 'marbles'):
+ trapProp.setHpr(Point3(94, 0, 0))
+ trapProp.setPos(0, MovieUtil.SUIT_TRAP_MARBLES_DISTANCE, 0)
+ trapProp.pose(trapName, trapProp.getNumFrames(trapName) - 1)
+ elif (trapName == 'tnt'):
+ trapProp.setHpr(0, 90, 0)
+ trapProp.setPos(0, MovieUtil.SUIT_TRAP_TNT_DISTANCE, 0.4)
+ else:
+ notify.warning('placeTrap() - Incorrect trap: %s placed on a suit' \
+ % trapName)
+
+
+ # Need hidden node to take the place of the exploding trap prop, we'll parent
+ # the explosion itself to this hidden node
+ dustNode = hidden.attachNewNode("DustNode")
+ def placeDustExplosion(dustNode=dustNode, thrownProp=thrownProp, battle=battle):
+ dustNode.reparentTo(battle)
+ dustNode.setPos(thrownProp.getPos(battle))
+
+ if (explode == 1): # If the trap prop collides with other traps, destroy it
+ throwTrack.append(Func(thrownProp.wrtReparentTo, hidden))
+ throwTrack.append(Func(placeDustExplosion))
+ throwTrack.append(createCartoonExplosionTrack(
+ dustNode, 'dust', explosionPoint=Point3(0, 0, 0)))
+ throwTrack.append(Func(battle.removeTrap, suit))
+ else:
+ throwTrack.append(Func(placeTrap, trapProp, suit))
+ # If the trap is the tnt, start the fuse on the trapProp we just placed
+ if (trapName == 'tnt'):
+ tip = trapProp.find("**/joint_attachEmitter")
+ sparks = BattleParticles.createParticleEffect(file='tnt')
+ trapProp.sparksEffect = sparks
+ throwTrack.append(Func(sparks.start, tip))
+ throwTrack.append(Func(MovieUtil.removeProps, propList))
+
+ # If using the tnt, must add in a track for the sparks particle effect
+# if (trapName == 'tnt'):
+# tip = thrownProp.find("**/joint_attachEmitter")
+# sparks = BattleParticles.createParticleEffect(file='tnt')
+# sparksTrack = Sequence(
+# Func(sparks.start, tip),
+# Wait(throwDelay+throwDuration),
+# Func(sparks.cleanup),
+# )
+# throwTrack = Parallel(throwTrack, sparksTrack)
+
+ toonTrack = Sequence(
+ Func(toon.headsUp, battle, targetPos),
+ ActorInterval(toon, 'toss'),
+ Func(toon.loop, 'neutral'),
+ )
+ return Parallel(propTrack, throwTrack, toonTrack)
+
+def __createPlacedTrapMultiTrack(trap, prop, propName, propPos=None,
+ propHpr=None, explode=0, visibleOnlyForThisSuitId = None):
+ toon = trap['toon']
+ if (trap.has_key('npc')):
+ toon = trap['npc']
+ level = trap['level']
+ battle = trap['battle']
+ origHpr = toon.getHpr(battle)
+ trapPoint = Point3(0, MovieUtil.SUIT_TRAP_DISTANCE, 0.025)
+ trapDelay = 2.5
+ hands = toon.getLeftHands()
+
+ def placeDustExplosion(dustNode, trapProp, battle):
+ dustNode.reparentTo(battle)
+ dustNode.setPos(trapProp.getPos(battle))
+
+ trapTracks = Parallel()
+
+ firstTime = 1
+ targets = trap['target']
+ for target in targets:
+ suit = target['suit']
+ suitPos = suit.getPos(battle)
+ targetPos = suitPos
+ trapProp = MovieUtil.copyProp(prop)
+
+ showThisTrap = True
+ if visibleOnlyForThisSuitId and visibleOnlyForThisSuitId != suit.doId :
+ showThisTrap = False
+
+ trapTrack = Sequence()
+ trapTrack.append(Wait(trapDelay))
+ if showThisTrap:
+ notify.debug('showing trap %s for %d' % (trapProp.getName(), suit.doId))
+ trapTrack.append(Func(trapProp.show))
+ else:
+ notify.debug('hiding trap %s for %d' % (trapProp.getName(), suit.doId))
+ trapTrack.append(Func(trapProp.hide))
+ trapTrack.append(Func(trapProp.setScale, Point3(0.1, 0.1, 0.1)))
+ trapTrack.append(Func(trapProp.reparentTo, suit))
+ trapTrack.append(Func(trapProp.setPos, trapPoint))
+ trapTrack.append(LerpScaleInterval(trapProp, 1.2, Point3(1.7, 1.7, 1.7)))
+
+ if (explode == 1): # If the trap prop collides with another trap, destroy it
+ # Need hidden node to take the place of the exploding trap prop,
+ # we'll parent the explosion itself to this hidden node
+ dustNode = hidden.attachNewNode("DustNode")
+
+ trapTrack.append(Func(trapProp.wrtReparentTo, hidden))
+ trapTrack.append(Func(placeDustExplosion, dustNode, trapProp,
+ battle))
+ trapTrack.append(createCartoonExplosionTrack(
+ dustNode, 'dust', explosionPoint=Point3(0, 0, 0)))
+ trapTrack.append(Func(MovieUtil.removeProp, trapProp))
+ trapTrack.append(Func(battle.removeTrap, suit))
+
+ else:
+
+ # Set the trap prop as the suit's trap prop
+ if (suit.battleTrap != NO_TRAP):
+ notify.debug('trapSuit() - trap: %d destroyed existing trap: %d' % (level, suit.battleTrap))
+ battle.removeTrap(suit)
+ suit.battleTrapProp = trapProp
+ suit.battleTrap = level
+ suit.battleTrapIsFresh = 1
+
+ trapTracks.append(trapTrack)
+
+ button = globalPropPool.getProp('button')
+ button2 = MovieUtil.copyProp(button)
+ buttons = [button, button2]
+
+ toonTrack = Sequence()
+ toonTrack.append(Func(MovieUtil.showProps, buttons, hands))
+ toonTrack.append(Func(toon.headsUp, battle, suitPos))
+ toonTrack.append(ActorInterval(toon, 'pushbutton'))
+ toonTrack.append(Func(MovieUtil.removeProps, buttons))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.setHpr, battle, origHpr))
+
+ if (propName == 'quicksand'):
+ propSound = globalBattleSoundCache.getSound('TL_quicksand.mp3')
+ else:
+ propSound = globalBattleSoundCache.getSound('TL_trap_door.mp3')
+
+ buttonSound = globalBattleSoundCache.getSound('AA_drop_trigger_box.mp3')
+ soundTrack = Sequence(
+ Wait(2.3),
+ SoundInterval(buttonSound, duration = 0.67, node=toon),
+ Wait(0.3),
+ SoundInterval(propSound, duration=0.5, node=toon),
+ )
+
+ return Parallel(trapTracks, toonTrack, soundTrack)
+
+def __trapBanana(trap, trapProps, explode):
+ """ __trapBanana(trap)
+ """
+ toon = trap['toon']
+ suit = trap['target'][0]['suit']
+ notify.debug('toon: %s lays banana peel in front of suit: %d' % \
+ (toon.getName(), suit.doId))
+ bananas = trapProps
+ return __createThrownTrapMultiTrack(trap, bananas, 'banana', anim=1, explode=explode)
+
+def __trapRake(trap, trapProps, explode):
+ """ __trapRake(trap)
+ """
+ toon = trap['toon']
+ suit = trap['target'][0]['suit']
+ notify.debug('toon: %s lays rake in front of suit: %d' % \
+ (toon.getName(), suit.doId))
+ rakes = trapProps
+ return __createThrownTrapMultiTrack(trap, rakes, 'rake', anim=1, explode=explode)
+
+def __trapMarbles(trap, trapProps, explode):
+ """ __trapMarbles(trap)
+ """
+ toon = trap['toon']
+ suit = trap['target'][0]['suit']
+ notify.debug('toon: %s lays marbles in front of suit: %d' % \
+ (toon.getName(), suit.doId))
+ bothMarbles = trapProps
+ pos = Point3(0, 0, 0)
+ hpr = Point3(0, 0, -30)
+ return __createThrownTrapMultiTrack(trap, bothMarbles, 'marbles',
+ pos, hpr, anim=1, explode=explode)
+
+def __trapQuicksand(trap, trapProps, explode):
+ """ __trapQuicksand(trap)
+ """
+ toon = trap['toon']
+ suit = trap['target'][0]['suit']
+ notify.debug('toon: %s lays quicksand in front of suit: %d' % \
+ (toon.getName(), suit.doId))
+ quicksand = trapProps[0]
+ return __createPlacedTrapMultiTrack(trap, quicksand, 'quicksand', explode=explode)
+
+def __trapTrapdoor(trap, trapProps, explode):
+ """ __trapTrapdoor(trap)
+ """
+ toon = trap['toon']
+ if (trap.has_key('npc')):
+ toon = trap['npc']
+ targets = trap['target']
+ for target in targets:
+ suit = target['suit']
+ notify.debug('toon: %s lays trapdoor in front of suit: %d' % \
+ (toon.getName(), suit.doId))
+ trapdoor = trapProps[0]
+ return __createPlacedTrapMultiTrack(trap, trapdoor, 'trapdoor', explode=explode)
+
+def __trapTNT(trap, trapProps, explode):
+ """ __trapTNT(trap)
+ """
+ toon = trap['toon']
+ suit = trap['target'][0]['suit']
+ notify.debug('toon: %s lays TNT in front of suit: %d' % \
+ (toon.getName(), suit.doId))
+ tnts = trapProps
+ return __createThrownTrapMultiTrack(trap, tnts, 'tnt', anim=0, explode=explode)
+
+def __trapTrain(trap, trapProps, explode):
+ """ __trapTraun(trap, trapProps, explode)
+ Do some funky stuff since we want to place only 1 train track, not 4
+ """
+ toon = trap['toon']
+ if (trap.has_key('npc')):
+ toon = trap['npc']
+ targets = trap['target']
+ battle = trap['battle']
+
+ visibleOnlyForThisSuitId = 0
+ centerSuit = None
+ closestXDistance = 10000
+ for target in targets:
+ suit = target['suit']
+ suitPoint, suitHpr = battle.getActorPosHpr(suit)
+ xDistance = abs(suitPoint.getX())
+ if xDistance < closestXDistance:
+ visibleOnlyForThisSuitId = suit.doId
+ closestXDistance = xDistance
+ centerSuit = suit
+ notify.debug('toon: %s doing traintrack in front of suit: %d' % \
+ (toon.getName(), suit.doId))
+
+ traintrack = trapProps[0]
+
+ #return __createPlacedTrapMultiTrack(trap, trapdoor, 'trapdoor', explode=explode,
+ # visibleOnlyForThisSuitId = visibleOnlyForThisSuitId )
+ return __createPlacedGroupTrapTrack (trap, traintrack, 'traintrack', centerSuit,
+ explode=explode )
+
+
+
+def createThrowingTrack(object, target, duration=1.0, parent=render, gravity=-32.144):
+
+ values = {}
+ values['origin'] = None
+ values['velocity'] = None
+ def calcOriginAndVelocity(object=object, target=target, values=values,
+ duration=duration, parent=parent, gravity=gravity):
+ object.wrtReparentTo(parent)
+ values['origin'] = object.getPos(parent)
+ origin = object.getPos(parent)
+ # Calculate the initial velocity required for the object to land at the target
+ values['velocity'] = ((target[2]-origin[2]) - (0.5*gravity*duration*duration)) \
+ / duration
+
+ def throwPos(t, object, duration, target, values=values, gravity=-32.144):
+ if (values['origin'] != None):
+ origin = values['origin']
+ else:
+ origin = object.getPos()
+
+ if (values['velocity'] != None):
+ velocity = values['velocity']
+ else:
+ velocity = 16
+
+ x = origin[0]*(1-t) + target[0]*t
+ y = origin[1]*(1-t) + target[1]*t
+ time = t*duration
+ z = origin[2] + velocity*time + (0.5*gravity*time*time)
+ object.setPos(x, y, z)
+
+ return Sequence(
+ Func(calcOriginAndVelocity),
+ LerpFunctionInterval(throwPos, fromData=0., toData=1., duration=duration,
+ extraArgs=[object, duration, target])
+ )
+
+def createCartoonExplosionTrack(parent, animName, explosionPoint=None):
+ explosionTrack = Sequence()
+ explosion = BattleProps.globalPropPool.getProp(animName)
+ explosion.setBillboardPointEye()
+ if not explosionPoint:
+ explosionPoint = Point3(0, 3.6, 2.1)
+ if (animName == 'dust'):
+ scale = Point3(0.1, 0.9, 1)
+ explosionTrack.append(Func(explosion.reparentTo, parent))
+ explosionTrack.append(Func(explosion.setPos, explosionPoint))
+ explosionTrack.append(Func(explosion.setScale, scale))
+ explosionTrack.append(ActorInterval(explosion, animName))
+ explosionTrack.append(Func(MovieUtil.removeProp, explosion))
+ return explosionTrack
+
+
+def __createPlacedGroupTrapTrack(trap, prop, propName, centerSuit,propPos=None,
+ propHpr=None, explode=0, ):
+ """
+ mainly to make the train track appear. We only want one, and parented to the battle
+ """
+ toon = trap['toon']
+ if (trap.has_key('npc')):
+ toon = trap['npc']
+ level = trap['level']
+ battle = trap['battle']
+ origHpr = toon.getHpr(battle)
+ #5 was determined empirically
+ trapPoint = Point3(0, 5 - MovieUtil.SUIT_TRAP_DISTANCE, 0.025)
+ trapDelay = 2.5
+ hands = toon.getLeftHands()
+
+ def placeDustExplosion(dustNode, trapProp, battle):
+ dustNode.reparentTo(battle)
+ dustNode.setPos(trapProp.getPos(battle))
+
+ trapTracks = Parallel()
+
+ firstTime = 1
+ targets = trap['target']
+ #for target in targets:
+ if True:
+ suit = centerSuit # target['suit']
+ suitPos = suit.getPos(battle)
+ targetPos = suitPos
+ trapProp = MovieUtil.copyProp(prop)
+
+ showThisTrap = True
+ #if visibleOnlyForThisSuitId and visibleOnlyForThisSuitId != suit.doId :
+ # showThisTrap = False
+
+ trapTrack = Sequence()
+ trapTrack.append(Wait(trapDelay))
+ if showThisTrap:
+ notify.debug('showing trap %s for %d' % (trapProp.getName(), suit.doId))
+ trapTrack.append(Func(trapProp.show))
+ else:
+ notify.debug('hiding trap %s for %d' % (trapProp.getName(), suit.doId))
+ trapTrack.append(Func(trapProp.hide))
+ trapTrack.append(Func(trapProp.setScale, Point3(0.1, 0.1, 0.1)))
+ trapTrack.append(Func(trapProp.reparentTo, battle))
+ trapTrack.append(Func(trapProp.setPos, trapPoint))
+ trapTrack.append(Func(trapProp.setH, 0))
+ trapTrack.append(LerpScaleInterval(trapProp, 1.2, Point3(1.0, 1.0, 1.0)))
+
+ if (explode == 1): # If the trap prop collides with another trap, destroy it
+ # Need hidden node to take the place of the exploding trap prop,
+ # we'll parent the explosion itself to this hidden node
+ dustNode = hidden.attachNewNode("DustNode")
+
+ removeTrapsParallel = Parallel()
+
+ oneTrapTrack = Sequence()
+ oneTrapTrack.append(Func(trapProp.wrtReparentTo, hidden))
+ oneTrapTrack.append(Func(placeDustExplosion, dustNode, trapProp,
+ battle))
+ oneTrapTrack.append(createCartoonExplosionTrack(
+ dustNode, 'dust', explosionPoint=Point3(0, 0, 0)))
+ oneTrapTrack.append(Func(MovieUtil.removeProp, trapProp))
+ #oneTrapTrack.append(Func(battle.removeTrap, suit))
+
+ removeTrapsParallel.append(oneTrapTrack)
+
+ #we need to remove the battle traps of the other suits too
+ for target in trap['target']:
+ otherSuit = target['suit']
+ #if otherSuit != suit and otherSuit.battleTrapProp:
+ if otherSuit.battleTrapProp:
+ otherDustNode = hidden.attachNewNode("DustNodeOtherSuit")
+ otherTrapTrack = Sequence()
+ otherTrapTrack.append(Func(otherSuit.battleTrapProp.wrtReparentTo, hidden))
+ otherTrapTrack.append(Func(placeDustExplosion, dustNode, otherSuit.battleTrapProp,
+ battle))
+ otherTrapTrack.append(createCartoonExplosionTrack(
+ otherDustNode, 'dust', explosionPoint=Point3(0, 0, 0)))
+ otherTrapTrack.append(Func(battle.removeTrap, otherSuit))
+ removeTrapsParallel.append(otherTrapTrack)
+
+ trapTrack.append(removeTrapsParallel)
+ else:
+ # Set the trap prop as the suit's trap prop
+ if (suit.battleTrap != NO_TRAP):
+ notify.debug('trapSuit() - trap: %d destroyed existing trap: %d' % (level, suit.battleTrap))
+ battle.removeTrap(suit)
+ suit.battleTrapProp = trapProp
+ suit.battleTrap = level
+ suit.battleTrapIsFresh = 1
+
+ #check if we have a case of a train trap appearing under a lured suit
+ unlureSuits = Parallel()
+ for target in targets:
+ kbbonus = target['kbbonus']
+ if (kbbonus == 0):
+ unluredSuit = target['suit']
+ suitTrack = Sequence()
+ suitTrack.append(createSuitResetPosTrack(unluredSuit, battle))
+ suitTrack.append(Func(battle.unlureSuit, unluredSuit))
+ unlureSuits.append(suitTrack)
+ trapTrack.append(unlureSuits)
+
+ #also set up the other suits
+ for otherSuit in battle.suits:
+ if not otherSuit == suit:
+ if (otherSuit.battleTrap != NO_TRAP):
+ notify.debug('trapSuit() - trap: %d destroyed existing trap: %d' % (level, suit.battleTrap))
+ battle.removeTrap(otherSuit)
+ otherSuit.battleTrapProp = trapProp
+ otherSuit.battleTrap = level
+ otherSuit.battleTrapIsFresh = 1
+ trapTracks.append(trapTrack)
+
+ button = globalPropPool.getProp('button')
+ button2 = MovieUtil.copyProp(button)
+ buttons = [button, button2]
+
+ toonTrack = Sequence()
+ toonTrack.append(Func(MovieUtil.showProps, buttons, hands))
+ toonTrack.append(Func(toon.headsUp, battle, suitPos))
+ toonTrack.append(ActorInterval(toon, 'pushbutton'))
+ toonTrack.append(Func(MovieUtil.removeProps, buttons))
+ toonTrack.append(Func(toon.loop, 'neutral'))
+ toonTrack.append(Func(toon.setHpr, battle, origHpr))
+
+ if (propName == 'quicksand'):
+ propSound = globalBattleSoundCache.getSound('TL_quicksand.mp3')
+ elif (propName == 'traintrack'):
+ propSound = globalBattleSoundCache.getSound('TL_train_track_appear.mp3')
+ else:
+ propSound = globalBattleSoundCache.getSound('TL_trap_door.mp3')
+
+ buttonSound = globalBattleSoundCache.getSound('AA_drop_trigger_box.mp3')
+ #timing of prop sound is from the start of the button press
+ soundTrack = Sequence(
+ Wait(2.3),
+ Parallel(
+ SoundInterval(buttonSound, duration = 0.67, node=toon),
+ SoundInterval(propSound, node=toon)),
+ )
+
+
+ return Parallel(trapTracks, toonTrack, soundTrack)
diff --git a/toontown/src/battle/MovieUtil.py b/toontown/src/battle/MovieUtil.py
new file mode 100644
index 0000000..c6bbe11
--- /dev/null
+++ b/toontown/src/battle/MovieUtil.py
@@ -0,0 +1,793 @@
+from direct.interval.IntervalGlobal import *
+from BattleBase import *
+from BattleProps import *
+
+from direct.directnotify import DirectNotifyGlobal
+import random
+from direct.particles import ParticleEffect
+import BattleParticles
+import BattleProps
+from toontown.toonbase import TTLocalizer
+
+notify = DirectNotifyGlobal.directNotify.newCategory('MovieUtil')
+
+SUIT_LOSE_DURATION = 6.0
+SUIT_LURE_DISTANCE = 2.6
+SUIT_LURE_DOLLAR_DISTANCE = 5.1
+SUIT_EXTRA_REACH_DISTANCE = 0.9
+SUIT_EXTRA_RAKE_DISTANCE = 1.1
+SUIT_TRAP_DISTANCE = 2.6
+SUIT_TRAP_RAKE_DISTANCE = 4.5 # Rake is farther from suit for it to walk into the rake
+SUIT_TRAP_MARBLES_DISTANCE = 3.7 # Marbles are out farther
+SUIT_TRAP_TNT_DISTANCE = 5.1
+
+PNT3_NEARZERO = Point3(0.01, 0.01, 0.01)
+PNT3_ZERO = Point3(0.0, 0.0, 0.0)
+PNT3_ONE = Point3(1.0, 1.0, 1.0)
+
+# Certain suits are so large that their movement needs curtail on the reach animation
+largeSuits = ['f', 'cc', 'gh', 'tw', 'bf', 'sc', \
+ 'ds', 'hh', 'cr', 'tbc', 'bs', 'sd', 'le', 'bw', \
+ 'nc', 'mb', 'ls', 'rb', 'ms', 'tf', 'm', 'mh']
+
+# The shot direction refers to which side of the battle the movie camera will
+# remain within during one series of toon and suit attacks. This value is randomly set
+# to left or right (50% chance) in Movie.play right before the action is played.
+shotDirection = 'left'
+
+def avatarDodge(leftAvatars, rightAvatars, leftData, rightData):
+ # when an avatar dodges, other avatars may need to dodge as well
+ if len(leftAvatars) > len(rightAvatars):
+ # Path of Least/Most Resistance
+ PoLR = rightAvatars
+ PoMR = leftAvatars
+ else:
+ PoLR = leftAvatars
+ PoMR = rightAvatars
+ # most of the time, choose the side with the least avatars
+ # base the random choice on the difference between the
+ # number of avatars on the left versus the right
+ upper = 1 + (4 * abs(len(leftAvatars) - len(rightAvatars)))
+ if (random.randint(0, upper) > 0):
+ avDodgeList = PoLR
+ else:
+ avDodgeList = PoMR
+ # select the correct data
+ if avDodgeList is leftAvatars:
+ data = leftData
+ else:
+ data = rightData
+
+ return avDodgeList, data
+
+def avatarHide(avatar):
+ notify.debug('avatarHide(%d)' % avatar.doId)
+ #import pdb; pdb.set_trace()
+ if hasattr(avatar,'battleTrapProp'):
+ notify.debug('avatar.battleTrapProp = %s' % avatar.battleTrapProp)
+ avatar.detachNode()
+
+def copyProp(prop):
+ from direct.actor import Actor
+ if (isinstance(prop, Actor.Actor)):
+ return Actor.Actor(other=prop)
+ else:
+ return prop.copyTo(hidden)
+
+def showProp(prop, hand, pos=None, hpr=None, scale=None):
+ prop.reparentTo(hand)
+ if pos:
+ if callable(pos):
+ pos = pos()
+ prop.setPos(pos)
+ if hpr:
+ if callable(hpr):
+ hpr = hpr()
+ prop.setHpr(hpr)
+ if scale:
+ if callable(scale):
+ scale = scale()
+ prop.setScale(scale)
+
+def showProps(props, hands, pos=None, hpr=None, scale=None):
+ assert(len(props) <= len(hands))
+ index = 0
+ for prop in props:
+ prop.reparentTo(hands[index])
+ if pos:
+ prop.setPos(pos)
+ if hpr:
+ prop.setHpr(hpr)
+ if scale:
+ prop.setScale(scale)
+ index += 1
+
+def hideProps(props):
+ for prop in props:
+ prop.detachNode()
+
+def removeProp(prop):
+ from direct.actor import Actor
+ if ((prop.isEmpty() == 1) or (prop == None)):
+ return
+ prop.detachNode()
+ if (isinstance(prop, Actor.Actor)):
+ prop.cleanup()
+ else:
+ prop.removeNode()
+
+def removeProps(props):
+ for prop in props:
+ removeProp(prop)
+
+def getActorIntervals(props, anim):
+ tracks = Parallel()
+ for prop in props:
+ tracks.append(ActorInterval(prop, anim))
+ return tracks
+
+def getScaleIntervals(props, duration, startScale, endScale):
+ tracks = Parallel()
+ for prop in props:
+ tracks.append(LerpScaleInterval(prop, duration, endScale,
+ startScale=startScale))
+ return tracks
+
+def avatarFacePoint(av, other=render):
+ pnt = av.getPos(other)
+ pnt.setZ(pnt[2] + av.getHeight())
+ return pnt
+
+def insertDeathSuit(suit, deathSuit, battle=None, pos=None, hpr=None):
+ holdParent = suit.getParent()
+ if suit.getVirtual():
+ virtualize(deathSuit)
+ avatarHide(suit)
+ if (deathSuit != None and not deathSuit.isEmpty()):
+ if holdParent and 0:# seems like a good idea if everything wasn't hosed JML
+ #import pdb; pdb.set_trace()
+ deathSuit.reparentTo(holdParent)
+ else:
+ deathSuit.reparentTo(render)
+ if (battle != None and pos != None):
+ deathSuit.setPos(battle, pos)
+ if (battle != None and hpr != None):
+ deathSuit.setHpr(battle, hpr)
+
+def removeDeathSuit(suit, deathSuit):
+ notify.debug('removeDeathSuit()')
+ if (not deathSuit.isEmpty()):
+ deathSuit.detachNode()
+ suit.cleanupLoseActor()
+
+
+def insertReviveSuit(suit, deathSuit, battle=None, pos=None, hpr=None):
+ holdParent = suit.getParent()
+ if suit.getVirtual():
+ virtualize(deathSuit)
+ #avatarHide(suit)
+ suit.hide()
+ if (deathSuit != None and not deathSuit.isEmpty()):
+ if holdParent and 0:# seems like a good idea if everything wasn't hosed JML
+ #import pdb; pdb.set_trace()
+ deathSuit.reparentTo(holdParent)
+ else:
+ deathSuit.reparentTo(render)
+ if (battle != None and pos != None):
+ deathSuit.setPos(battle, pos)
+ if (battle != None and hpr != None):
+ deathSuit.setHpr(battle, hpr)
+
+def removeReviveSuit(suit, deathSuit):
+ notify.debug('removeDeathSuit()')
+ suit.setSkelecog(1)
+ #suit.makeSkeleton()
+ suit.show()
+ if (not deathSuit.isEmpty()):
+ deathSuit.detachNode()
+ suit.cleanupLoseActor()
+ #suit.removeHealthBar()
+ suit.healthBar.show()
+ suit.reseatHealthBarForSkele()
+
+def virtualize(deathsuit):
+ actorNode = deathsuit.find("**/__Actor_modelRoot")
+ actorCollection = actorNode.findAllMatches("*")
+ parts = ()
+ for thingIndex in range(0,actorCollection.getNumPaths()):
+ thing = actorCollection[thingIndex]
+ if thing.getName() not in ('joint_attachMeter', 'joint_nameTag'):
+ thing.setColorScale(1.0,0.0,0.0,1.0)
+ thing.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd))
+ thing.setDepthWrite(False)
+ thing.setBin('fixed', 1)
+
+
+
+
+def createTrainTrackAppearTrack( dyingSuit, toon, battle, npcs):
+ """
+ so if the suit which has the visible train track dies
+ we need to make it visible for the other suits which have survived, if any
+ """
+
+ retval = Sequence()
+ return retval
+ possibleSuits = []
+ #we assume that if a suit attacked, it's still alive
+ #darn we need to consider lured suits... well maybe not,
+ #can it have a train trap and be lured at the same time?
+ for suitAttack in battle.movie.suitAttackDicts:
+ suit = suitAttack['suit']
+ if not suit == dyingSuit:
+ if hasattr(suit,'battleTrapProp') and suit.battleTrapProp and \
+ suit.battleTrapProp.getName() == 'traintrack':
+ possibleSuits.append(suitAttack['suit'])
+
+ #so we have the possible suits, see which one is closest to the center
+ closestXDistance = 10000
+ closestSuit = None
+ for suit in possibleSuits:
+ suitPoint, suitHpr = battle.getActorPosHpr(suit)
+ xDistance = abs(suitPoint.getX())
+ if xDistance < closestXDistance:
+ closestSuit = suit
+ closestXDistance = xDistance
+
+ if closestSuit and closestSuit.battleTrapProp.isHidden():
+ #immediately set the alpha to zero, and show the the train track
+ #this will prevent this sequence happening twice when 2 cogs die
+ #import pdb; pdb.set_trace()
+
+ closestSuit.battleTrapProp.setColorScale(1,1,1,0)
+ closestSuit.battleTrapProp.show()
+ newRelativePos = dyingSuit.battleTrapProp.getPos(closestSuit)
+ newHpr = dyingSuit.battleTrapProp.getHpr(closestSuit)
+ closestSuit.battleTrapProp.setPos(newRelativePos)
+ closestSuit.battleTrapProp.setHpr(newHpr)
+
+
+ retval.append(LerpColorScaleInterval(closestSuit.battleTrapProp, 3.0, Vec4(1,1,1,1)))
+ else:
+ notify.debug('could not find closest suit, returning empty sequence')
+
+ return retval
+
+
+def createSuitReviveTrack(suit, toon, battle, npcs = []):
+ suitTrack = Sequence()
+
+ suitPos, suitHpr = battle.getActorPosHpr(suit)
+
+ #import pdb; pdb.set_trace()
+
+ if hasattr(suit,'battleTrapProp') and suit.battleTrapProp and \
+ suit.battleTrapProp.getName() == 'traintrack' and \
+ not suit.battleTrapProp.isHidden():
+ suitTrack.append( createTrainTrackAppearTrack( suit, toon, battle, npcs))
+
+ deathSuit = suit.getLoseActor()
+ assert(deathSuit != None)
+ #suitTrack.append(Wait(10))
+ suitTrack.append(Func(notify.debug, 'before insertDeathSuit'))
+ suitTrack.append(Func(insertReviveSuit, suit, deathSuit, battle, suitPos, suitHpr))
+ #suitTrack.append(Wait(10))
+ suitTrack.append(Func(notify.debug, 'before actorInterval lose'))
+ suitTrack.append(ActorInterval(deathSuit, 'lose', duration=SUIT_LOSE_DURATION))
+ #suitTrack.append(Wait(10))
+ suitTrack.append(Func(notify.debug, 'before removeDeathSuit'))
+ suitTrack.append(Func(removeReviveSuit, suit, deathSuit, name='remove-death-suit'))
+ #suitTrack.append(Wait(10))
+ suitTrack.append(Func(notify.debug, 'after removeDeathSuit'))
+ suitTrack.append(Func(suit.loop, 'neutral'))
+
+
+
+
+ spinningSound = base.loadSfx("phase_3.5/audio/sfx/Cog_Death.mp3")
+ deathSound = base.loadSfx("phase_3.5/audio/sfx/ENC_cogfall_apart.mp3")
+ deathSoundTrack = Sequence(
+ Wait(0.8),
+ SoundInterval(spinningSound, duration=1.2, startTime = 1.5, volume=0.2, node=suit),
+ SoundInterval(spinningSound, duration=3.0, startTime = 0.6, volume=0.8, node=suit),
+ SoundInterval(deathSound, volume = 0.32, node=suit),
+ )
+
+ BattleParticles.loadParticles()
+ smallGears = BattleParticles.createParticleEffect(file='gearExplosionSmall')
+ singleGear = BattleParticles.createParticleEffect('GearExplosion',
+ numParticles=1)
+ smallGearExplosion = BattleParticles.createParticleEffect('GearExplosion',
+ numParticles=10)
+ bigGearExplosion = BattleParticles.createParticleEffect('BigGearExplosion',
+ numParticles=30)
+
+ gearPoint = Point3(suitPos.getX(), suitPos.getY(), suitPos.getZ()+suit.height-0.2)
+ smallGears.setPos(gearPoint)
+ singleGear.setPos(gearPoint)
+ smallGears.setDepthWrite(False)
+ singleGear.setDepthWrite(False)
+ smallGearExplosion.setPos(gearPoint)
+ bigGearExplosion.setPos(gearPoint)
+ smallGearExplosion.setDepthWrite(False)
+ bigGearExplosion.setDepthWrite(False)
+
+ explosionTrack = Sequence()
+ explosionTrack.append(Wait(5.4))
+ explosionTrack.append(createKapowExplosionTrack(battle, explosionPoint=gearPoint))
+
+ gears1Track = Sequence(
+ Wait(2.1),
+ ParticleInterval(smallGears, battle, worldRelative=0, duration=4.3, cleanup = True),
+ name='gears1Track')
+ gears2MTrack = Track(
+ (0.0, explosionTrack),
+ (0.7, ParticleInterval(singleGear, battle, worldRelative=0,
+ duration=5.7, cleanup = True)),
+ (5.2, ParticleInterval(smallGearExplosion, battle,
+ worldRelative=0, duration=1.2, cleanup = True)),
+ (5.4, ParticleInterval(bigGearExplosion, battle,
+ worldRelative=0, duration=1.0, cleanup = True)),
+ name='gears2MTrack')
+ toonMTrack = Parallel(name='toonMTrack')
+ for mtoon in battle.toons:
+ toonMTrack.append(Sequence(
+ Wait(1.0),
+ ActorInterval(mtoon, 'duck'),
+ ActorInterval(mtoon, 'duck', startTime=1.8),
+ Func(mtoon.loop, 'neutral'),
+ ))
+ for mtoon in npcs:
+ toonMTrack.append(Sequence(
+ Wait(1.0),
+ ActorInterval(mtoon, 'duck'),
+ ActorInterval(mtoon, 'duck', startTime=1.8),
+ Func(mtoon.loop, 'neutral'),
+ ))
+
+ return Parallel(suitTrack, deathSoundTrack, gears1Track, gears2MTrack,
+ toonMTrack)
+
+def createSuitDeathTrack(suit, toon, battle, npcs = []):
+ suitTrack = Sequence()
+
+ suitPos, suitHpr = battle.getActorPosHpr(suit)
+
+ #import pdb; pdb.set_trace()
+
+ if hasattr(suit,'battleTrapProp') and suit.battleTrapProp and \
+ suit.battleTrapProp.getName() == 'traintrack' and \
+ not suit.battleTrapProp.isHidden():
+ suitTrack.append( createTrainTrackAppearTrack( suit, toon, battle, npcs))
+
+ deathSuit = suit.getLoseActor()
+ assert(deathSuit != None)
+ #suitTrack.append(Wait(10))
+ suitTrack.append(Func(notify.debug, 'before insertDeathSuit'))
+ suitTrack.append(Func(insertDeathSuit, suit, deathSuit, battle, suitPos, suitHpr))
+ #suitTrack.append(Wait(10))
+ suitTrack.append(Func(notify.debug, 'before actorInterval lose'))
+ suitTrack.append(ActorInterval(deathSuit, 'lose', duration=SUIT_LOSE_DURATION))
+ #suitTrack.append(Wait(10))
+ suitTrack.append(Func(notify.debug, 'before removeDeathSuit'))
+ suitTrack.append(Func(removeDeathSuit, suit, deathSuit, name='remove-death-suit'))
+ #suitTrack.append(Wait(10))
+ suitTrack.append(Func(notify.debug, 'after removeDeathSuit'))
+
+
+
+
+ spinningSound = base.loadSfx("phase_3.5/audio/sfx/Cog_Death.mp3")
+ deathSound = base.loadSfx("phase_3.5/audio/sfx/ENC_cogfall_apart.mp3")
+ deathSoundTrack = Sequence(
+ Wait(0.8),
+ SoundInterval(spinningSound, duration=1.2, startTime = 1.5, volume=0.2, node=deathSuit),
+ SoundInterval(spinningSound, duration=3.0, startTime = 0.6, volume=0.8, node=deathSuit),
+ SoundInterval(deathSound, volume = 0.32, node=deathSuit),
+ )
+
+ BattleParticles.loadParticles()
+ smallGears = BattleParticles.createParticleEffect(file='gearExplosionSmall')
+ singleGear = BattleParticles.createParticleEffect('GearExplosion',
+ numParticles=1)
+ smallGearExplosion = BattleParticles.createParticleEffect('GearExplosion',
+ numParticles=10)
+ bigGearExplosion = BattleParticles.createParticleEffect('BigGearExplosion',
+ numParticles=30)
+
+ gearPoint = Point3(suitPos.getX(), suitPos.getY(), suitPos.getZ()+suit.height-0.2)
+ smallGears.setPos(gearPoint)
+ singleGear.setPos(gearPoint)
+ smallGears.setDepthWrite(False)
+ singleGear.setDepthWrite(False)
+ smallGearExplosion.setPos(gearPoint)
+ bigGearExplosion.setPos(gearPoint)
+ smallGearExplosion.setDepthWrite(False)
+ bigGearExplosion.setDepthWrite(False)
+
+ explosionTrack = Sequence()
+ explosionTrack.append(Wait(5.4))
+ explosionTrack.append(createKapowExplosionTrack(battle, explosionPoint=gearPoint))
+
+ gears1Track = Sequence(
+ Wait(2.1),
+ ParticleInterval(smallGears, battle, worldRelative=0, duration=4.3, cleanup = True),
+ name='gears1Track')
+ gears2MTrack = Track(
+ (0.0, explosionTrack),
+ (0.7, ParticleInterval(singleGear, battle, worldRelative=0,
+ duration=5.7, cleanup = True)),
+ (5.2, ParticleInterval(smallGearExplosion, battle,
+ worldRelative=0, duration=1.2, cleanup = True)),
+ (5.4, ParticleInterval(bigGearExplosion, battle,
+ worldRelative=0, duration=1.0, cleanup = True)),
+ name='gears2MTrack')
+ toonMTrack = Parallel(name='toonMTrack')
+ for mtoon in battle.toons:
+ toonMTrack.append(Sequence(
+ Wait(1.0),
+ ActorInterval(mtoon, 'duck'),
+ ActorInterval(mtoon, 'duck', startTime=1.8),
+ Func(mtoon.loop, 'neutral'),
+ ))
+ for mtoon in npcs:
+ toonMTrack.append(Sequence(
+ Wait(1.0),
+ ActorInterval(mtoon, 'duck'),
+ ActorInterval(mtoon, 'duck', startTime=1.8),
+ Func(mtoon.loop, 'neutral'),
+ ))
+
+ return Parallel(suitTrack, deathSoundTrack, gears1Track, gears2MTrack,
+ toonMTrack)
+
+def createSuitDodgeMultitrack(tDodge, suit, leftSuits, rightSuits):
+ suitTracks = Parallel()
+ suitDodgeList, sidestepAnim = avatarDodge(leftSuits, rightSuits,
+ 'sidestep-left', 'sidestep-right')
+ # make the other suits dodge
+ for s in suitDodgeList:
+ suitTracks.append(Sequence(ActorInterval(s, sidestepAnim),
+ Func(s.loop, 'neutral')))
+ # finally, make the target suit dodge
+ suitTracks.append(Sequence(ActorInterval(suit, sidestepAnim),
+ Func(suit.loop, 'neutral')))
+ # indicate that the suit was missed
+ suitTracks.append(Func(indicateMissed, suit))
+
+ ## play the dodge sounds
+ #jumpSound = base.loadSfx("phase_5/audio/sfx/ENC_cogjump_to_side.mp3")
+ #stepSound = base.loadSfx("phase_5/audio/sfx/ENC_cogside_step.mp3")
+ #if jumpSound:
+ # suitTracks.append(SoundInterval(jumpSound))
+ #if stepSound:
+ # suitTracks.append(Sequence(Wait(1.5), SoundInterval(stepSound)))
+
+ return Sequence(Wait(tDodge), suitTracks)
+
+def createToonDodgeMultitrack(tDodge, toon, leftToons, rightToons):
+ # Unlike suits, the toon side step right animation occurs behind
+ # the adjacent toon thus we only need to move other toons with the
+ # sidestep left dodge. But even with this difference, we still
+ # use the same probability for which direction to dodge.
+ toonTracks = Parallel()
+
+ # when an avatar dodges, other avatars may need to dodge as well
+ if len(leftToons) > len(rightToons):
+ # Path of Least/Most Resistance
+ PoLR = rightToons
+ PoMR = leftToons
+ else:
+ PoLR = leftToons
+ PoMR = rightToons
+ # most of the time, choose the side with the least avatars
+ # base the random choice on the difference between the
+ # number of avatars on the left versus the right
+ upper = 1 + (4 * abs(len(leftToons) - len(rightToons)))
+ if (random.randint(0, upper) > 0):
+ toonDodgeList = PoLR
+ else:
+ toonDodgeList = PoMR
+
+ # select the correct data
+ if toonDodgeList is leftToons:
+ sidestepAnim = 'sidestep-left'
+ # Make the other toons dodge
+ for t in toonDodgeList:
+ toonTracks.append(Sequence(ActorInterval(t, sidestepAnim),
+ Func(t.loop, 'neutral')))
+ else:
+ sidestepAnim = 'sidestep-right'
+
+ # finally, make the target toon dodge
+ toonTracks.append(Sequence(ActorInterval(toon, sidestepAnim),
+ Func(toon.loop, 'neutral')))
+ # indicate that the toon was missed
+ toonTracks.append(Func(indicateMissed, toon))
+
+ return Sequence(Wait(tDodge), toonTracks)
+
+def createSuitTeaseMultiTrack(suit, delay=0.01):
+ # Used if the suit teases a toon for missing an attack, (large drops)
+ suitTrack = Sequence(
+ Wait(delay),
+ ActorInterval(suit, 'victory', startTime=0.5, endTime=1.9),
+ Func(suit.loop, 'neutral'),
+ )
+ missedTrack = Sequence(Wait(delay+0.2),
+ Func(indicateMissed, suit, 0.9))
+ return Parallel(suitTrack, missedTrack)
+
+
+# spray intervals
+
+SPRAY_LEN = 1.5
+
+# spray head extends from origin to target, holds,
+# then spray tail extends from origin to target
+def getSprayTrack(battle, color, origin, target, dScaleUp, dHold,
+ dScaleDown, horizScale = 1.0, vertScale = 1.0, parent = render):
+ track = Sequence()
+
+ # sprayRot
+ # |__ sprayScale
+ # |__ sprayProp
+
+ sprayProp = globalPropPool.getProp('spray')
+ # make a parent node for the spray that will hold the scale
+ sprayScale = hidden.attachNewNode('spray-parent')
+ # the rotation must be on a separate node so that the
+ # lerpScale doesn't muck with the rotation
+ sprayRot = hidden.attachNewNode('spray-rotate')
+
+ spray = sprayRot
+ spray.setColor(color)
+ if (color[3] < 1.0):
+ spray.setTransparency(1)
+
+ # show the spray
+ def showSpray(sprayScale, sprayRot, sprayProp, origin, target, parent):
+ if callable(origin):
+ origin = origin()
+ if callable(target):
+ target = target()
+ sprayRot.reparentTo(parent)
+ sprayRot.clearMat()
+ sprayScale.reparentTo(sprayRot)
+ sprayScale.clearMat()
+ sprayProp.reparentTo(sprayScale)
+ sprayProp.clearMat()
+ sprayRot.setPos(origin)
+ sprayRot.lookAt(Point3(target))
+ track.append(Func(battle.movie.needRestoreRenderProp, sprayProp))
+ track.append(Func(showSpray, sprayScale, sprayRot, sprayProp,
+ origin, target, parent))
+
+ # scale the spray up
+ def calcTargetScale(target = target, origin = origin, horizScale = horizScale, vertScale = vertScale):
+ if callable(target):
+ target = target()
+ if callable(origin):
+ origin = origin()
+ distance = Vec3(target - origin).length()
+ yScale = distance / SPRAY_LEN
+ #targetScale = Point3(yScale, yScale*horizScale, yScale*vertScale)
+ targetScale = Point3(yScale*horizScale, yScale, yScale*vertScale)
+ return targetScale
+ track.append(LerpScaleInterval(sprayScale, dScaleUp, calcTargetScale, startScale=PNT3_NEARZERO))
+
+ # hold the spray
+ track.append(Wait(dHold))
+
+ # bring the back of the spray up to the front, using a scale
+
+ # first we need to adjust the spray's parent node so that it
+ # is positioned at the end of the spray
+ def prepareToShrinkSpray(spray, sprayProp, origin, target):
+ if callable(target):
+ target = target()
+ if callable(origin):
+ origin = origin()
+ #localSprayHeadPos = target - origin
+ sprayProp.setPos(Point3(0., -SPRAY_LEN, 0.))
+ spray.setPos(target)
+ track.append(Func(prepareToShrinkSpray, spray, sprayProp,
+ origin, target))
+
+ # shrink the spray down
+ track.append(LerpScaleInterval(sprayScale, dScaleDown, PNT3_NEARZERO))
+
+ # hide the spray
+ def hideSpray(spray, sprayScale, sprayRot, sprayProp, propPool):
+ sprayProp.detachNode()
+ removeProp(sprayProp)
+ sprayRot.removeNode()
+ sprayScale.removeNode()
+
+ track.append(Func(hideSpray, spray, sprayScale, sprayRot,
+ sprayProp, globalPropPool))
+ track.append(Func(battle.movie.clearRenderProp, sprayProp))
+
+ return track
+
+T_HOLE_LEAVES_HAND = 1.708
+T_TELEPORT_ANIM = 3.3
+T_HOLE_CLOSES = 0.3
+
+def getToonTeleportOutInterval(toon):
+ """ getToonTeleportOutInterval(toon)
+ """
+ holeActors = toon.getHoleActors()
+ holes = [holeActors[0], holeActors[1]]
+ hole = holes[0]
+ hole2 = holes[1]
+ hands = toon.getRightHands()
+ delay = T_HOLE_LEAVES_HAND
+ dur = T_TELEPORT_ANIM
+ holeTrack = Sequence()
+ holeTrack.append(Func(showProps, holes, hands))
+ holeTrack.append(Wait(0.5)),
+ holeTrack.append(Func(base.playSfx, toon.getSoundTeleport()))
+ holeTrack.append(Wait(delay - 0.5))
+ holeTrack.append(Func(hole.reparentTo, toon))
+ holeTrack.append(Func(hole2.reparentTo, hidden))
+ holeAnimTrack = Sequence()
+ holeAnimTrack.append(ActorInterval(hole, 'hole', duration=dur))
+ holeAnimTrack.append(Func(hideProps, holes))
+
+ runTrack = Sequence(ActorInterval(toon, 'teleport', duration=dur),
+ Wait(T_HOLE_CLOSES),
+ Func(toon.detachNode))
+ return Parallel(runTrack, holeAnimTrack, holeTrack)
+
+def getToonTeleportInInterval(toon):
+ """ getToonTeleportInInterval(toon)
+ """
+ hole = toon.getHoleActors()[0]
+ holeAnimTrack = Sequence()
+ holeAnimTrack.append(Func(toon.detachNode))
+ holeAnimTrack.append(Func(hole.reparentTo, toon))
+ pos = Point3(0, -2.4, 0)
+ holeAnimTrack.append(Func(hole.setPos, toon, pos))
+ holeAnimTrack.append(ActorInterval(hole, 'hole', startTime=T_TELEPORT_ANIM,
+ endTime=T_HOLE_LEAVES_HAND))
+ holeAnimTrack.append(ActorInterval(hole, 'hole',
+ startTime=T_HOLE_LEAVES_HAND,
+ endTime=T_TELEPORT_ANIM))
+ holeAnimTrack.append(Func(hole.reparentTo, hidden))
+
+ delay = T_TELEPORT_ANIM - T_HOLE_LEAVES_HAND
+ jumpTrack = Sequence(Wait(delay),
+ Func(toon.reparentTo, render),
+ ActorInterval(toon, 'jump'))
+ return Parallel(holeAnimTrack, jumpTrack)
+
+def getSuitRakeOffset(suit):
+ """ getSuitRakeOffset(suit)
+ """
+ suitName = suit.getStyleName()
+ if (suitName == 'gh'):
+ return 1.4
+ elif (suitName == 'f'):
+ return 1.0
+ elif (suitName == 'cc'):
+ return 0.7
+ elif (suitName == 'tw'):
+ return 1.3
+ elif (suitName == 'bf'):
+ return 1.0
+ elif (suitName == 'sc'):
+ return 0.8
+ elif (suitName == 'ym'):
+ return 0.1
+ elif (suitName == 'mm'):
+ return 0.05
+ elif (suitName == 'tm'):
+ return 0.07
+ elif (suitName == 'nd'):
+ return 0.07
+ elif (suitName == 'pp'):
+ return 0.04
+ elif (suitName == 'bc'):
+ return 0.36
+ elif (suitName == 'b'):
+ return 0.41
+ elif (suitName == 'dt'):
+ return 0.31
+ elif (suitName == 'ac'):
+ return 0.39
+ elif (suitName == 'ds'):
+ return 0.41
+ elif (suitName == 'hh'):
+ return 0.8
+ elif (suitName == 'cr'):
+ return 2.1
+ elif (suitName == 'tbc'):
+ return 1.4
+ elif (suitName == 'bs'):
+ return 0.4
+ elif (suitName == 'sd'):
+ return 1.02
+ elif (suitName == 'le'):
+ return 1.3
+ elif (suitName == 'bw'):
+ return 1.4
+ elif (suitName == 'nc'):
+ return 0.6
+ elif (suitName == 'mb'):
+ return 1.85
+ elif (suitName == 'ls'):
+ return 1.4
+ elif (suitName == 'rb'):
+ return 1.6
+ elif (suitName == 'ms'):
+ return 0.7
+ elif (suitName == 'tf'):
+ return 0.75
+ elif (suitName == 'm'):
+ return 0.9
+ elif (suitName == 'mh'):
+ return 1.3
+ else:
+ notify.warning('getSuitRakeOffset(suit) - Unknown suit name: %s' % suitName)
+ return 0
+
+def startSparksIval(tntProp):
+ tip = tntProp.find("**/joint_attachEmitter")
+ sparks = BattleParticles.createParticleEffect(file='tnt')
+ return Func(sparks.start, tip)
+
+def indicateMissed(actor, duration=1.1, scale=0.7):
+ """ indicateMissed() shows the text missed above an actor """
+ actor.showHpString(TTLocalizer.AttackMissed, duration=duration, scale=scale)
+
+def createKapowExplosionTrack(parent, explosionPoint=None, scale = 1.0):
+ explosionTrack = Sequence()
+ explosion = loader.loadModel("phase_3.5/models/props/explosion.bam")
+ explosion.setBillboardPointEye()
+ explosion.setDepthWrite(False)
+ if not explosionPoint:
+ explosionPoint = Point3(0, 3.6, 2.1)
+ explosionTrack.append(Func(explosion.reparentTo, parent))
+ explosionTrack.append(Func(explosion.setPos, explosionPoint))
+ explosionTrack.append(Func(explosion.setScale, 0.4 * scale))
+ explosionTrack.append(Wait(0.6))
+ explosionTrack.append(Func(removeProp, explosion))
+ return explosionTrack
+
+
+def createSuitStunInterval(suit, before, after):
+ # Some temp point vectors
+ p1 = Point3(0)
+ p2 = Point3(0)
+ # Show reaction and stun prop
+ stars = globalPropPool.getProp('stun')
+ # Give it a color so we can override any head colors
+ stars.setColor(1, 1, 1, 1)
+ # Override head colors and textures
+ stars.adjustAllPriorities(100)
+ # How high is head? Assume head is part 0 (this is good for suits)
+ head = suit.getHeadParts()[0]
+ head.calcTightBounds(p1, p2)
+ # Show stun prop at proper height with specified delay before and after
+ return Sequence(Wait(before),
+ Func(stars.reparentTo, head),
+ Func(stars.setZ, max(0.0, p2[2] - 1.0)),
+ Func(stars.loop, 'stun'),
+ Wait(after),
+ Func(stars.removeNode))
+
+
+
+def calcAvgSuitPos(throw):
+ """
+ Calculate the average suit positions for the all the targets in this throw
+ """
+ battle = throw['battle']
+ avgSuitPos = Point3(0,0,0)
+ numTargets = len(throw['target'])
+ for i in range(numTargets):
+ suit = throw['target'][i]['suit']
+ avgSuitPos += suit.getPos(battle)
+ avgSuitPos /= numTargets
+ return avgSuitPos
diff --git a/toontown/src/battle/PlayByPlayPanel.py b/toontown/src/battle/PlayByPlayPanel.py
new file mode 100644
index 0000000..e666994
--- /dev/null
+++ b/toontown/src/battle/PlayByPlayPanel.py
@@ -0,0 +1,25 @@
+
+from pandac.PandaModules import *
+from toontown.toonbase.ToontownBattleGlobals import *
+from toontown.toonbase.ToontownGlobals import *
+from SuitBattleGlobals import *
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.gui import OnscreenText
+
+class PlayByPlayText(OnscreenText.OnscreenText):
+ """
+ This shows info about names of attacks as they happen, etc.
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory('PlayByPlayText')
+
+ def __init__(self):
+ OnscreenText.OnscreenText.__init__(
+ self,
+ mayChange = 1,
+ pos = (0.0, 0.75),
+ scale = 0.1,
+ font = getSignFont(),
+ )
+
+
diff --git a/toontown/src/battle/PlayByPlayText.py b/toontown/src/battle/PlayByPlayText.py
new file mode 100644
index 0000000..3be1f8a
--- /dev/null
+++ b/toontown/src/battle/PlayByPlayText.py
@@ -0,0 +1,61 @@
+
+from pandac.PandaModules import *
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase.ToontownBattleGlobals import *
+from toontown.toonbase.ToontownGlobals import *
+from SuitBattleGlobals import *
+from direct.interval.IntervalGlobal import *
+
+from direct.directnotify import DirectNotifyGlobal
+import string
+from direct.gui import OnscreenText
+import BattleBase
+
+class PlayByPlayText(OnscreenText.OnscreenText):
+ """
+ This shows info about names of attacks as they happen, etc.
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory('PlayByPlayText')
+
+ def __init__(self):
+ OnscreenText.OnscreenText.__init__(
+ self,
+ mayChange = 1,
+ pos = (0.0, 0.75),
+ scale = TTLocalizer.PBPTonscreenText,
+ fg = (1, 0, 0, 1),
+ font = getSignFont(),
+ wordwrap = 13
+ )
+
+ def getShowInterval(self, text, duration):
+ return Sequence(
+ Func(self.hide),
+ Wait(duration * 0.3),
+ Func(self.setText, text),
+ Func(self.show),
+ Wait(duration * 0.7),
+ Func(self.hide),
+ )
+
+ def getToonsDiedInterval(self, textList, duration):
+ assert len(textList) >= 0 and len(textList) <= 4
+ track = Sequence(
+ Func(self.hide),
+ Wait(duration * 0.3)
+ )
+ waitGap = (0.6 / len(textList)) * duration
+ for text in textList:
+ newList = [
+ Func(self.setText, text),
+ Func(self.show),
+ Wait(waitGap),
+ Func(self.hide),
+ ]
+ track += newList
+ track.append(
+ Wait(duration * 0.1)
+ )
+
+ return track
+
diff --git a/toontown/src/battle/RewardPanel.py b/toontown/src/battle/RewardPanel.py
new file mode 100644
index 0000000..5486aac
--- /dev/null
+++ b/toontown/src/battle/RewardPanel.py
@@ -0,0 +1,1235 @@
+from pandac.PandaModules import *
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from toontown.toonbase import ToontownBattleGlobals
+import BattleBase
+from direct.directnotify import DirectNotifyGlobal
+import random
+import string
+from toontown.quest import Quests
+import copy
+from toontown.suit import SuitDNA
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from toontown.toon import NPCToons
+import math
+from toontown.coghq import CogDisguiseGlobals
+from toontown.shtiker import DisguisePage
+import Fanfare
+from otp.otpbase import OTPGlobals
+
+class RewardPanel(DirectFrame):
+ """
+ This panel shows experience gained during a battle
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory('RewardPanel')
+
+ def __init__(self, name):
+ DirectFrame.__init__(self,
+ relief=None,
+ geom = DGG.getDefaultDialogGeom(),
+ geom_color = ToontownGlobals.GlobalDialogColor,
+ geom_scale = TTLocalizer.RPdirectFrame,
+ pos = (0, 0, 0.587),
+ )
+ self.initialiseoptions(RewardPanel)
+
+ self.avNameLabel = DirectLabel(
+ parent = self,
+ relief = None,
+ pos = (0, 0, 0.3),
+ text = name,
+ text_scale = 0.08,
+ )
+
+ self.gagExpFrame = DirectFrame(
+ parent = self,
+ relief = None,
+ pos = (-0.32, 0, 0.24),
+ )
+
+ self.itemFrame = DirectFrame(
+ parent = self,
+ relief = None,
+ text = TTLocalizer.RewardPanelItems,
+ text_pos = (0, 0.2),
+ text_scale = 0.08,
+ )
+
+ self.cogPartFrame = DirectFrame(
+ parent = self,
+ relief = None,
+ text = TTLocalizer.RewardPanelCogPart,
+ text_pos = (0, 0.2),
+ text_scale = 0.08,
+ )
+
+ self.missedItemFrame = DirectFrame(
+ parent = self,
+ relief = None,
+ text = TTLocalizer.RewardPanelMissedItems,
+ text_pos = (0, 0.2),
+ text_scale = 0.08,
+ )
+
+ self.itemLabel = DirectLabel(
+ parent = self.itemFrame,
+ text = "",
+ text_scale = 0.06,
+ )
+
+ self.cogPartLabel = DirectLabel(
+ parent = self.cogPartFrame,
+ text = "",
+ text_scale = 0.06,
+ )
+
+ self.missedItemLabel = DirectLabel(
+ parent = self.missedItemFrame,
+ text = "",
+ text_scale = 0.06,
+ )
+
+ self.questFrame = DirectFrame(
+ parent = self,
+ relief = None,
+ text = TTLocalizer.RewardPanelToonTasks,
+ text_pos = (0, 0.2),
+ text_scale = 0.06,
+ )
+
+
+
+ self.questLabelList = []
+ for i in range(ToontownGlobals.MaxQuestCarryLimit):
+ label = DirectLabel(
+ parent = self.questFrame,
+ relief = None,
+ pos = (-0.85, 0, (-0.1*i)),
+ text = (TTLocalizer.RewardPanelQuestLabel % (i)),
+ text_scale = 0.05,
+ text_align = TextNode.ALeft,
+ )
+ label.hide()
+ self.questLabelList.append(label)
+
+ self.newGagFrame = DirectFrame(
+ parent = self,
+ relief = None,
+ pos = (0, 0, 0.24),
+ text = "",
+ text_wordwrap = 14.4,
+ text_pos = (0, -0.46),
+ text_scale = 0.06,
+ )
+
+ self.endTrackFrame = DirectFrame(
+ parent = self,
+ relief = None,
+ pos = (0,0,0.24),
+ text = "",
+ text_wordwrap = 14.4,
+ text_pos = (0,-0.46),
+ text_scale = 0.06,
+ )
+
+ self.congratsLeft = DirectLabel(
+ parent = self.newGagFrame,
+ pos = (-0.2, 0, -0.1),
+ text = "",
+ text_pos = (0, 0),
+ text_scale = 0.06,
+ )
+ self.congratsLeft.setHpr(0, 0, -30)
+ self.congratsRight = DirectLabel(
+ parent = self.newGagFrame,
+ pos = (0.2, 0, -0.1),
+ text = "",
+ text_pos = (0, 0),
+ text_scale = 0.06,
+ )
+ self.congratsRight.setHpr(0, 0, 30)
+
+ self.promotionFrame = DirectFrame(
+ parent = self,
+ relief = None,
+ pos = (0, 0, 0.24),
+ text = "",
+ text_wordwrap = 14.4,
+ text_pos = (0, -0.46),
+ text_scale = 0.06,
+ )
+
+ self.trackLabels = []
+ self.trackIncLabels = []
+ self.trackBars = []
+ self.trackBarsOffset = 0
+
+ self.meritLabels = []
+ self.meritIncLabels = []
+ self.meritBars = []
+
+ # loop and make all the cog merit bars
+ for i in range(len(SuitDNA.suitDepts)):
+ deptName = TextEncoder.upper(SuitDNA.suitDeptFullnames[SuitDNA.suitDepts[i]])
+
+ # dept label
+ self.meritLabels.append(DirectLabel(
+ parent = self.gagExpFrame,
+ relief = None,
+ text = deptName,
+ text_scale = 0.05,
+ text_align = TextNode.ARight,
+ pos = (TTLocalizer.RPmeritLabelXPosition, 0, (-0.09*i) - 0.125),
+ text_pos = (0, -0.02),
+ ))
+
+ # increment indicator labels
+ self.meritIncLabels.append(DirectLabel(
+ parent = self.gagExpFrame,
+ relief = None,
+ text = "",
+ text_scale = 0.05,
+ text_align = TextNode.ALeft,
+ pos = (0.7, 0, (-0.09*i) - 0.125),
+ text_pos = (0, -0.02),
+ ))
+
+ # merit progress bar
+ self.meritBars.append(DirectWaitBar(
+ parent = self.gagExpFrame,
+ relief = DGG.SUNKEN,
+ frameSize = (-1, 1, -0.15, 0.15),
+ borderWidth = (0.02, 0.02),
+ scale = 0.25,
+ frameColor = (DisguisePage.DeptColors[i][0]*0.7,
+ DisguisePage.DeptColors[i][1]*0.7,
+ DisguisePage.DeptColors[i][2]*0.7,
+ 1),
+ barColor = (DisguisePage.DeptColors[i][0],
+ DisguisePage.DeptColors[i][1],
+ DisguisePage.DeptColors[i][2],
+ 1),
+ text = "0/0 " + TTLocalizer.RewardPanelMeritBarLabels[i],
+ text_scale = TTLocalizer.RPmeritBarLabels,
+ text_fg = (0, 0, 0, 1),
+ text_align = TextNode.ALeft,
+ text_pos = (-0.96, -0.05),
+ pos = (TTLocalizer.RPmeritBarsXPosition, 0, (-0.09*i) - 0.125),
+ ))
+
+ # loop and make all the track exp bars
+ for i in range(len(ToontownBattleGlobals.Tracks)):
+ trackName = TextEncoder.upper(ToontownBattleGlobals.Tracks[i])
+
+ # Name of track label
+ self.trackLabels.append(DirectLabel(
+ parent = self.gagExpFrame,
+ relief = None,
+ text = trackName,
+ text_scale = TTLocalizer.RPtrackLabels,
+ text_align = TextNode.ARight,
+ pos = (0.13, 0, -0.09*i),
+ text_pos = (0, -0.02),
+ ))
+
+ # increment indicator labels
+ self.trackIncLabels.append(DirectLabel(
+ parent = self.gagExpFrame,
+ relief = None,
+ text = "",
+ text_scale = 0.05,
+ text_align = TextNode.ALeft,
+ pos = (0.65, 0, -0.09*i),
+ text_pos = (0, -0.02),
+ ))
+
+ # track exp bar
+ self.trackBars.append(DirectWaitBar(
+ parent = self.gagExpFrame,
+ relief = DGG.SUNKEN,
+ frameSize = (-1, 1, -0.15, 0.15),
+ borderWidth = (0.02, 0.02),
+ scale = 0.25,
+ frameColor = (ToontownBattleGlobals.TrackColors[i][0]*0.7,
+ ToontownBattleGlobals.TrackColors[i][1]*0.7,
+ ToontownBattleGlobals.TrackColors[i][2]*0.7,
+ 1),
+ barColor = (ToontownBattleGlobals.TrackColors[i][0],
+ ToontownBattleGlobals.TrackColors[i][1],
+ ToontownBattleGlobals.TrackColors[i][2],
+ 1),
+ text = "0/0",
+ text_scale = 0.18,
+ text_fg = (0, 0, 0, 1),
+ text_align = TextNode.ACenter,
+ text_pos = (0, -0.05),
+ pos = (0.40, 0, -0.09*i),
+ ))
+
+ return
+
+ # Elemental operations:
+ # Set track(title, curSkill, next):
+ # - Set track title
+ # - clear gags
+ # - set skill
+ # - set next
+ # - set background color
+
+ # Add gag(gagType, i, n)
+ # - Position gag at i/n
+ # - Increment skill
+ # - play sound fx
+
+ # Play track sequence(track, curSkill, next, gagTypeList)
+ # - Set track
+ # - for i = 1 to n, add gag (with delays between)
+
+ # Play reward sequence(tracks, skills, nexts, gagTypeLists)
+ # - for i = 1 to n, play track sequences (with delays between)
+
+ # Play reward sequences(sequences)
+ # - for i = 1 to n, play reward sequences
+
+ #def clearGags(self):
+ # for gag in self.gagList:
+ # gag.removeNode()
+ # self.gagList = []
+ # return None
+
+ def getNextExpValue(self, curSkill, trackIndex):
+ """
+ Return the number of total experience to get to the next
+ track. If the current experience equals or exceeds the highest
+ next value, the highest next value is returned.
+ """
+ # The last value is the default
+ retVal = ToontownBattleGlobals.UberSkill
+ for amount in ToontownBattleGlobals.Levels[trackIndex]:
+ if curSkill < amount:
+ retVal = amount
+ return retVal
+ return retVal
+
+ def getNextExpValueUber(self, curSkill, trackIndex):
+ """
+ Return the number of total experience to get to the next
+ track. If the current experience equals or exceeds the highest
+ next value, the highest next value is returned.
+ """
+ # The last value is the default
+ return ToontownBattleGlobals.UberSkill
+
+ def getNextMeritValue(self, curMerits, toon, dept):
+ """
+ Return the number of total merits to get to the next
+ cog level. If the current merits equals or exceeds the highest
+ next value, the highest next value is returned.
+ """
+ totalMerits = CogDisguiseGlobals.getTotalMerits(toon, dept)
+ # The last value is the default
+ retVal = totalMerits
+ if curMerits > totalMerits:
+ retVal = amount
+ return retVal
+
+ def initItemFrame(self, toon):
+ self.endTrackFrame.hide()
+ self.gagExpFrame.hide()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.show()
+ self.cogPartFrame.hide()
+ self.missedItemFrame.hide()
+
+ def initMissedItemFrame(self, toon):
+ self.endTrackFrame.hide()
+ self.gagExpFrame.hide()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.cogPartFrame.hide()
+ self.missedItemFrame.show()
+
+ def initCogPartFrame(self, toon):
+ self.endTrackFrame.hide()
+ self.gagExpFrame.hide()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.cogPartFrame.show()
+ self.cogPartLabel['text'] = ''
+ self.missedItemFrame.hide()
+
+ def initQuestFrame(self, toon, avQuests):
+ self.endTrackFrame.hide()
+ self.gagExpFrame.hide()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.show()
+ self.itemFrame.hide()
+ self.cogPartFrame.hide()
+ self.missedItemFrame.hide()
+
+ # First hide all quest labels in case we do not fill
+ # them in with our own quests (and reset the color to black)
+ for i in range(ToontownGlobals.MaxQuestCarryLimit):
+ questLabel = self.questLabelList[i]
+ questLabel['text_fg'] = (0, 0, 0, 1)
+ questLabel.hide()
+
+ for i in range(len(avQuests)):
+ questDesc = avQuests[i]
+ questId, npcId, toNpcId, rewardId, toonProgress = questDesc
+ quest = Quests.getQuest(questId)
+ if quest:
+ questString = quest.getString()
+ progressString = quest.getProgressString(toon, questDesc)
+ rewardString = quest.getRewardString(progressString)
+ rewardString = Quests.fillInQuestNames(rewardString, toNpcId = toNpcId)
+ completed = (quest.getCompletionStatus(toon, questDesc) == Quests.COMPLETE)
+ questLabel = self.questLabelList[i]
+ questLabel.show()
+
+ # the reward movie looks wonky in the tutorial...
+ if base.localAvatar.tutorialAck:
+ questLabel['text'] = rewardString
+ if completed:
+ questLabel['text_fg'] = (0, 0.3, 0, 1)
+ else:
+ questLabel['text'] = questString + " :"
+
+ def initGagFrame(self, toon, expList, meritList):
+ self.avNameLabel['text'] = toon.getName()
+ self.endTrackFrame.hide()
+ self.gagExpFrame.show()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.cogPartFrame.hide()
+ self.missedItemFrame.hide()
+
+ trackBarOffset = 0
+
+ # Initialize the cog merit bars if enabled
+ for i in range(len(SuitDNA.suitDepts)):
+ meritBar = self.meritBars[i]
+ meritLabel = self.meritLabels[i]
+ totalMerits = CogDisguiseGlobals.getTotalMerits(toon, i)
+ merits = meritList[i]
+ self.meritIncLabels[i].hide()
+ # if we are have a full suit then we are working on promotions
+ if CogDisguiseGlobals.isSuitComplete(toon.cogParts, i):
+ # if we don't show the merit bar, we must shift the skill bars left
+ if not self.trackBarsOffset:
+ trackBarOffset = 0.47
+ # only do this once! :)
+ self.trackBarsOffset = 1
+ meritBar.show()
+ meritLabel.show()
+ meritLabel.show()
+ if totalMerits:
+ meritBar["range"] = totalMerits
+ meritBar["value"] = merits
+ if merits == totalMerits:
+ meritBar["text"] = TTLocalizer.RewardPanelMeritAlert
+ else:
+ meritBar["text"] = ("%s/%s %s" % (merits,
+ totalMerits,
+ TTLocalizer.RewardPanelMeritBarLabels[i],))
+ else:
+ # if total merits = None, this dept is maxed out
+ meritBar["range"] = 1
+ meritBar["value"] = 1
+ meritBar["text"] = TTLocalizer.RewardPanelMeritsMaxed
+ self.resetMeritBarColor(i)
+ else:
+ meritBar.hide()
+ meritLabel.hide()
+
+ # Initialize all the bars with the current and next exp
+ for i in range(len(expList)):
+ curExp = expList[i]
+ trackBar = self.trackBars[i]
+ trackLabel = self.trackLabels[i]
+ trackIncLabel = self.trackIncLabels[i]
+ trackBar.setX(trackBar.getX() - trackBarOffset)
+ trackLabel.setX(trackLabel.getX() - trackBarOffset)
+ trackIncLabel.setX(trackIncLabel.getX() - trackBarOffset)
+ trackIncLabel.hide()
+ if toon.hasTrackAccess(i):
+ trackBar.show()
+
+ if curExp >= ToontownBattleGlobals.UnpaidMaxSkill and toon.getGameAccess() != OTPGlobals.AccessFull:
+ nextExp = self.getNextExpValue(curExp, i)
+ trackBar["range"] = nextExp
+ trackBar["value"] = ToontownBattleGlobals.UnpaidMaxSkill
+ trackBar["text"] = (TTLocalizer.InventoryGuestExp)
+
+ elif curExp >= ToontownBattleGlobals.regMaxSkill:
+ nextExp = self.getNextExpValueUber(curExp, i)
+ trackBar["range"] = nextExp
+ uberCurrExp = curExp - ToontownBattleGlobals.regMaxSkill
+ trackBar["value"] = uberCurrExp
+ trackBar["text"] = (TTLocalizer.InventoryUberTrackExp %
+ {"nextExp": ToontownBattleGlobals.MaxSkill - curExp,})
+ else:
+ nextExp = self.getNextExpValue(curExp, i)
+ trackBar["range"] = nextExp
+ trackBar["value"] = curExp
+ trackBar["text"] = ("%s/%s" % (curExp, nextExp))
+ self.resetBarColor(i)
+ else:
+ trackBar.hide()
+
+
+ return
+
+ def incrementExp(self, track, newValue, toon):
+ trackBar = self.trackBars[track]
+ oldValue = trackBar["value"]
+ newValue = min(ToontownBattleGlobals.MaxSkill, newValue)
+ nextExp = self.getNextExpValue(newValue, track)
+
+ if newValue >= ToontownBattleGlobals.UnpaidMaxSkill and toon.getGameAccess() != OTPGlobals.AccessFull:
+ newValue = oldValue
+ trackBar["text"] = (TTLocalizer.InventoryGuestExp)
+
+ elif newValue >= ToontownBattleGlobals.regMaxSkill:
+ newValue = newValue - ToontownBattleGlobals.regMaxSkill
+ nextExp = self.getNextExpValueUber(newValue, track)
+ trackBar["text"] = (TTLocalizer.InventoryUberTrackExp %
+ {"nextExp": ToontownBattleGlobals.UberSkill - newValue,})
+ else:
+ trackBar["text"] = ("%s/%s" % (newValue, nextExp))
+ trackBar["range"] = nextExp
+ trackBar["value"] = newValue
+ trackBar["barColor"] = (ToontownBattleGlobals.TrackColors[track][0],
+ ToontownBattleGlobals.TrackColors[track][1],
+ ToontownBattleGlobals.TrackColors[track][2],
+ 1)
+ return
+
+ def resetBarColor(self, track):
+ self.trackBars[track]["barColor"] = (ToontownBattleGlobals.TrackColors[track][0]*0.8,
+ ToontownBattleGlobals.TrackColors[track][1]*0.8,
+ ToontownBattleGlobals.TrackColors[track][2]*0.8,
+ 1)
+
+
+ def incrementMerits(self, toon, dept, newValue, totalMerits):
+ meritBar = self.meritBars[dept]
+ oldValue = meritBar["value"]
+ # don't bother to inc if toon is maxed already
+ if totalMerits:
+ newValue = min(totalMerits, newValue)
+ meritBar["range"] = totalMerits
+ meritBar["value"] = newValue
+ if newValue == totalMerits:
+ meritBar["text"] = TTLocalizer.RewardPanelMeritAlert
+ meritBar["barColor"] = (DisguisePage.DeptColors[dept][0],
+ DisguisePage.DeptColors[dept][1],
+ DisguisePage.DeptColors[dept][2],
+ 1)
+ else:
+ meritBar["text"] = ("%s/%s %s" % (newValue,
+ totalMerits,
+ TTLocalizer.RewardPanelMeritBarLabels[dept],))
+ return
+
+
+ def resetMeritBarColor(self, dept):
+ self.meritBars[dept]["barColor"] = (DisguisePage.DeptColors[dept][0]*0.8,
+ DisguisePage.DeptColors[dept][1]*0.8,
+ DisguisePage.DeptColors[dept][2]*0.8,
+ 1)
+
+
+ def getRandomCongratsPair(self, toon):
+ congratsStrings = TTLocalizer.RewardPanelCongratsStrings
+
+ numStrings = len(congratsStrings)
+ assert(numStrings >= 2)
+
+ indexList = range(numStrings)
+
+ index1 = random.choice(indexList)
+ indexList.remove(index1)
+ index2 = random.choice(indexList)
+
+ string1 = congratsStrings[index1]
+ string2 = congratsStrings[index2]
+
+ return(string1, string2)
+
+ def uberGagInterval(self, toon, track, level):
+ #import pdb; pdb.set_trace()
+ self.endTrackFrame.hide()
+ self.gagExpFrame.hide()
+ self.newGagFrame.show()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.missedItemFrame.hide()
+
+ self.newGagFrame['text'] = (TTLocalizer.RewardPanelUberGag %
+ {"gagName": ToontownBattleGlobals.Tracks[track].capitalize(),
+ "exp": str(ToontownBattleGlobals.UberSkill),
+ "avName": toon.getName(),})
+ self.congratsLeft['text'] = ("")
+ self.congratsRight['text'] = ("")
+
+ #self.gagText.setText(AvPropStrings[track][level])
+
+ # copy a gag
+ gagOriginal = base.localAvatar.inventory.buttonLookup(track, level)
+ self.newGagIcon = gagOriginal.copyTo(self.newGagFrame)
+ # Set position... x is x, z is y
+ self.newGagIcon.setPos(0, 0, -0.25)
+ # Set scale (big)
+ self.newGagIcon.setScale(1.5)
+
+ return
+
+ def newGag(self, toon, track, level):
+ #import pdb; pdb.set_trace()
+ self.endTrackFrame.hide()
+ self.gagExpFrame.hide()
+ self.newGagFrame.show()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.missedItemFrame.hide()
+
+ self.newGagFrame['text'] = (TTLocalizer.RewardPanelNewGag %
+ {"gagName": ToontownBattleGlobals.Tracks[track].capitalize(),
+ "avName": toon.getName(),})
+ self.congratsLeft['text'] = ("")
+ self.congratsRight['text'] = ("")
+
+ #self.gagText.setText(AvPropStrings[track][level])
+
+ # copy a gag
+ gagOriginal = base.localAvatar.inventory.buttonLookup(track, level)
+ self.newGagIcon = gagOriginal.copyTo(self.newGagFrame)
+ # Set position... x is x, z is y
+ self.newGagIcon.setPos(0, 0, -0.25)
+ # Set scale (big)
+ self.newGagIcon.setScale(1.5)
+
+ return
+
+ def cleanupNewGag(self):
+ self.endTrackFrame.hide()
+ if self.newGagIcon:
+ self.newGagIcon.removeNode()
+ self.newGagIcon = None
+ self.gagExpFrame.show()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.missedItemFrame.hide()
+
+ def getNewGagIntervalList(self, toon, track, level):
+ leftCongratsAnticipate = 1.0
+ rightCongratsAnticipate = 1.0
+ finalDelay = 1.5
+ (leftString, rightString) = self.getRandomCongratsPair(toon)
+ intervalList = [Func(self.newGag, toon, track, level),
+ Wait(leftCongratsAnticipate),
+ Func(self.congratsLeft.setProp, 'text', leftString),
+ Wait(rightCongratsAnticipate),
+ Func(self.congratsRight.setProp, 'text', rightString),
+ Wait(finalDelay),
+ Func(self.cleanupNewGag),
+ ]
+ return intervalList
+
+ def getUberGagIntervalList(self, toon, track, level):
+ leftCongratsAnticipate = 1.0
+ rightCongratsAnticipate = 1.0
+ finalDelay = 1.5
+ (leftString, rightString) = self.getRandomCongratsPair(toon)
+ intervalList = [Func(self.uberGagInterval, toon, track, level),
+ Wait(leftCongratsAnticipate),
+ Func(self.congratsLeft.setProp, 'text', leftString),
+ Wait(rightCongratsAnticipate),
+ Func(self.congratsRight.setProp, 'text', rightString),
+ Wait(finalDelay),
+ Func(self.cleanupNewGag),
+ ]
+ return intervalList
+
+ # hides everything, used for the endtrack to make sure everything vanishes
+ # so we can see the fanfare
+ def vanishFrames(self):
+ self.hide()
+ self.endTrackFrame.hide()
+ self.gagExpFrame.hide()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.missedItemFrame.hide()
+ self.cogPartFrame.hide()
+ self.missedItemFrame.hide()
+
+ def endTrack(self, toon, toonList, track):
+ #import pdb; pdb.set_trace()1
+ # we make the RewardPanel show up again for all toons in combat
+ for t in toonList:
+ if t == base.localAvatar:
+ self.show()
+ self.endTrackFrame.show()
+
+ self.endTrackFrame['text'] = (TTLocalizer.RewardPanelEndTrack %
+ {"gagName": ToontownBattleGlobals.Tracks[track].capitalize(),
+ "avName": toon.getName(),})
+ gagLast = base.localAvatar.inventory.buttonLookup(track, ToontownBattleGlobals.UBER_GAG_LEVEL_INDEX)
+ self.gagIcon = gagLast.copyTo(self.endTrackFrame)
+ self.gagIcon.setPos(0,0,-0.25)
+ self.gagIcon.setScale(1.5)
+
+ return
+
+ # clears the icon on the endTrackFrame, just in case they happen
+ # to get to the end of two tracks in one battle
+ def cleanIcon(self):
+ self.gagIcon.removeNode()
+ self.gagIcon = None
+
+ # causes all other parts to show up again
+ def cleanupEndTrack(self):
+ self.endTrackFrame.hide()
+ self.gagExpFrame.show()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.missedItemFrame.hide()
+
+ # shows a message frame telling the toon that they've reached the end
+ # of one of the gag tracks. Shows up after the fanfare
+ def getEndTrackIntervalList(self, toon, toonList, track):
+ intervalList = [Func(self.endTrack, toon, toonList, track),
+ Wait(2.0),
+ Func(self.cleanIcon),
+ ]
+ return intervalList
+
+ def showTrackIncLabel(self, track, earnedSkill, guestWaste = 0):
+ if guestWaste:
+ self.trackIncLabels[track]["text"] = " " + str(earnedSkill) + TTLocalizer.GuestLostExp
+ elif earnedSkill > 0:
+ self.trackIncLabels[track]["text"] = "+ " + str(earnedSkill)
+ elif earnedSkill < 0:
+ self.trackIncLabels[track]["text"] = " " + str(earnedSkill)
+ self.trackIncLabels[track].show()
+
+
+ def showMeritIncLabel(self, dept, earnedMerits):
+ self.meritIncLabels[dept]["text"] = "+ " + str(earnedMerits)
+ self.meritIncLabels[dept].show()
+
+
+ def getTrackIntervalList(self, toon, track, origSkill, earnedSkill, hasUber, guestWaste = 0):
+ """
+ returns a list of intervals that, if played, will show experience
+ gained for the given track.
+ """
+
+ #check for corruptUberList
+ #import pdb; pdb.set_trace()
+ if hasUber < 0:
+ print(toon.doId, 'Reward Panel received an invalid hasUber from an uberList')
+
+ tickDelay = 0.16
+ intervalList = []
+ if (origSkill + earnedSkill) >= ToontownBattleGlobals.UnpaidMaxSkill and toon.getGameAccess() != OTPGlobals.AccessFull:
+ lostExp = (origSkill + earnedSkill) - ToontownBattleGlobals.UnpaidMaxSkill
+ intervalList.append(Func(self.showTrackIncLabel, track, lostExp, 1))
+ else:
+ intervalList.append(Func(self.showTrackIncLabel, track, earnedSkill))
+
+ # How much time should it take to increment the bar? It
+ # should take more time for a larger boost, but not linearly
+ # more--the larger the boost, the faster the bar moves to get
+ # there. The total time will be logarithmic with the number
+ # of points earned. It should be small (actually, tickDelay)
+ # for a one-point boost, and maybe four or five seconds for
+ # 100 points. Conveniently, this is the ratio that natural
+ # log gives us.
+ barTime = math.log(earnedSkill + 1)
+ numTicks = int(math.ceil(barTime / tickDelay))
+
+ for i in range(numTicks):
+ t = (i + 1) / float(numTicks)
+ newValue = int(origSkill + t * earnedSkill + 0.5)
+ intervalList.append(Func(self.incrementExp, track, newValue, toon))
+ intervalList.append(Wait(tickDelay))
+
+ intervalList.append(Func(self.resetBarColor, track))
+ intervalList.append(Wait(0.4))
+
+ # Insert "new gag" panel here, if needed.
+ nextExpValue = self.getNextExpValue(origSkill, track)
+ finalGagFlag = 0
+ while ((origSkill + earnedSkill >= nextExpValue) and
+ (origSkill < nextExpValue) and
+ (not finalGagFlag)):
+ # Add the new gag interval... Look up the new gag level based on
+ # the nextExpValue
+ if newValue >= ToontownBattleGlobals.UnpaidMaxSkill and toon.getGameAccess() != OTPGlobals.AccessFull:
+ pass
+ elif nextExpValue != ToontownBattleGlobals.MaxSkill:
+ intervalList += self.getNewGagIntervalList(
+ toon, track, ToontownBattleGlobals.Levels[track].index(nextExpValue))
+ # Get the next value
+ newNextExpValue = self.getNextExpValue(nextExpValue, track)
+ # If the next value is the old one, we hit the top, and
+ # have nothing more to add. Otherwise, set the nextExpValue,
+ # and loop around again to see if we have another new gag
+ # to show...
+ if newNextExpValue == nextExpValue:
+ finalGagFlag = 1
+ else:
+ nextExpValue = newNextExpValue
+
+ #test for Uber gag
+ uberIndex = ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1
+ #hasUber = toon.inventory.numItem(track, uberIndex)
+
+ currentSkill = origSkill + earnedSkill
+ uberSkill = ToontownBattleGlobals.UberSkill + ToontownBattleGlobals.Levels[track][ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1]
+ #print("Track %s hasUber %s current %s req %s" % (track, hasUber, currentSkill, uberSkill))
+ if (currentSkill >= uberSkill) and not (hasUber > 0):
+ #print("adding Uber track")
+ intervalList += self.getUberGagIntervalList(
+ toon, track, (ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1))
+ intervalList.append(Wait(0.4))
+ skillDiff = currentSkill - ToontownBattleGlobals.Levels[track][ToontownBattleGlobals.LAST_REGULAR_GAG_LEVEL + 1]
+
+ barTime = math.log(skillDiff + 1)
+ numTicks = int(math.ceil(barTime / tickDelay))
+
+ displayedSkillDiff = skillDiff
+ if displayedSkillDiff > ToontownBattleGlobals.UberSkill:
+ displayedSkillDiff = ToontownBattleGlobals.UberSkill
+
+ intervalList.append(Func(self.showTrackIncLabel, track, -displayedSkillDiff))
+
+ for i in range(numTicks):
+ t = (i + 1) / float(numTicks)
+ newValue = int(currentSkill - t * skillDiff + 0.5)
+ intervalList.append(Func(self.incrementExp, track, newValue, toon))
+ intervalList.append(Wait(tickDelay * 0.5))
+ intervalList.append(Wait(0.4))
+
+ return intervalList
+
+ def getMeritIntervalList(self, toon, dept, origMerits, earnedMerits):
+ """
+ returns a list of intervals that, if played, will show merits
+ gained for the given dept.
+ """
+ tickDelay = 0.08
+ intervalList = []
+ totalMerits = CogDisguiseGlobals.getTotalMerits(toon, dept)
+ neededMerits = 0
+
+ # Only show the inc value if we are not maxed
+ if totalMerits and (origMerits != totalMerits):
+ neededMerits = totalMerits - origMerits
+ intervalList.append(Func(self.showMeritIncLabel, dept, min(neededMerits, earnedMerits)))
+
+ # How much time should it take to increment the bar?
+ barTime = math.log(earnedMerits + 1)
+ numTicks = int(math.ceil(barTime / tickDelay))
+
+ for i in range(numTicks):
+ t = (i + 1) / float(numTicks)
+ newValue = int(origMerits + t * earnedMerits + 0.5)
+ intervalList.append(Func(self.incrementMerits, toon, dept, newValue, totalMerits))
+ intervalList.append(Wait(tickDelay))
+
+ intervalList.append(Func(self.resetMeritBarColor, dept))
+ intervalList.append(Wait(0.4))
+
+ # we don't need a promotion if we've reach level 50
+ if (toon.cogLevels[dept] < ToontownGlobals.MaxCogSuitLevel):
+ if neededMerits and toon.readyForPromotion(dept):
+ intervalList.append(Wait(0.4))
+ intervalList += self.getPromotionIntervalList(toon, dept)
+
+ return intervalList
+
+ def promotion(self, toon, dept):
+ self.endTrackFrame.hide()
+ self.gagExpFrame.hide()
+ self.newGagFrame.hide()
+ self.promotionFrame.show()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.missedItemFrame.hide()
+ name = SuitDNA.suitDepts[dept]
+ self.promotionFrame['text'] = (TTLocalizer.RewardPanelPromotion % (SuitDNA.suitDeptFullnames[name]))
+
+ # copy a gag
+ icons = loader.loadModel('phase_3/models/gui/cog_icons')
+ if dept == 0:
+ self.deptIcon = icons.find('**/CorpIcon').copyTo(self.promotionFrame)
+ elif dept == 1:
+ self.deptIcon = icons.find('**/LegalIcon').copyTo(self.promotionFrame)
+ elif dept == 2:
+ self.deptIcon = icons.find('**/MoneyIcon').copyTo(self.promotionFrame)
+ elif dept == 3:
+ self.deptIcon = icons.find('**/SalesIcon').copyTo(self.promotionFrame)
+ icons.removeNode()
+ # Set position... x is x, z is y
+ self.deptIcon.setPos(0, 0, -0.225)
+ # Set scale (big)
+ self.deptIcon.setScale(0.33)
+ return
+
+ def cleanupPromotion(self):
+ # protect against multiple cleanups
+ if not hasattr(self, 'deptIcon'):
+ return
+ self.deptIcon.removeNode()
+ self.deptIcon = None
+ self.endTrackFrame.hide()
+ self.gagExpFrame.show()
+ self.newGagFrame.hide()
+ self.promotionFrame.hide()
+ self.questFrame.hide()
+ self.itemFrame.hide()
+ self.missedItemFrame.hide()
+
+ def getPromotionIntervalList(self, toon, dept):
+ finalDelay = 2.0
+ intervalList = [Func(self.promotion, toon, dept),
+ Wait(finalDelay),
+ Func(self.cleanupPromotion),
+ ]
+ return intervalList
+
+ def getQuestIntervalList(self, toon, deathList, toonList, origQuestsList, itemList, helpfulToonsList = []):
+ # This is the battle notifying us that a toon killed some cogs
+ # See if this toon has a quest on these cogs. If so, update the
+ # progress.
+ # Note that toonList corresponds order-wise to the bitmasks in the
+ # deathList, and might have 'None' entries for toons that are no
+ # longer present.
+
+ avId = toon.getDoId()
+ tickDelay = 0.2
+ intervalList = []
+
+ # create a non-ordered list of the toons (without the None entries)
+ toonShortList = []
+ for t in toonList:
+ if t is not None:
+ toonShortList.append(t)
+
+ cogList = []
+ for i in range(0, len(deathList), 4):
+ cogIndex = deathList[i]
+ cogLevel = deathList[i+1]
+ activeToonBits = deathList[i+2]
+ flags = deathList[i+3]
+ activeToonIds = []
+ for j in range(8):
+ if activeToonBits & (1 << j):
+ if toonList[j] is not None:
+ activeToonIds.append(toonList[j].getDoId())
+ isSkelecog = flags & ToontownBattleGlobals.DLF_SKELECOG
+ isForeman = flags & ToontownBattleGlobals.DLF_FOREMAN
+ isVP = flags & ToontownBattleGlobals.DLF_VP
+ isCFO = flags & ToontownBattleGlobals.DLF_CFO
+ isSupervisor = flags & ToontownBattleGlobals.DLF_SUPERVISOR
+ isVirtual = flags & ToontownBattleGlobals.DLF_VIRTUAL
+ hasRevives = flags & ToontownBattleGlobals.DLF_REVIVES
+ if isVP or isCFO:
+ cogType = None
+ cogTrack = SuitDNA.suitDepts[cogIndex]
+ else:
+ cogType = SuitDNA.suitHeadTypes[cogIndex]
+ cogTrack = SuitDNA.getSuitDept(cogType)
+ cogList.append({'type': cogType,
+ 'level': cogLevel,
+ 'track': cogTrack,
+ 'isSkelecog': isSkelecog,
+ 'isForeman': isForeman,
+ 'isVP': isVP,
+ 'isCFO': isCFO,
+ 'isSupervisor': isSupervisor,
+ 'isVirtual': isVirtual,
+ 'hasRevives': hasRevives,
+ 'activeToons': activeToonIds,
+ })
+
+ # It would be nice if there were some more elegant way to get the zoneId.
+ try:
+ zoneId = base.cr.playGame.getPlace().getTaskZoneId()
+ except:
+ # Maybe the local toon is just teleporting in, and we
+ # haven't got a place yet. This is a band-aid; we should
+ # revisit this later.
+ zoneId = 0
+
+
+ # We could not trust the toons quests not to be updated by the AI, so now we pass in original
+ # quests, just like we do original experience
+ # unflatten orig quest list
+ avQuests = []
+ for i in range(0, len(origQuestsList), 5):
+ avQuests.append(origQuestsList[i:i+5])
+
+ # Now append intervals that will show our quests
+ for i in range(len(avQuests)):
+ questDesc = avQuests[i]
+ questId, npcId, toNpcId, rewardId, toonProgress = questDesc
+ quest = Quests.getQuest(questId)
+ if quest:
+ questString = quest.getString()
+ progressString = quest.getProgressString(toon, questDesc)
+ questLabel = self.questLabelList[i]
+ earned = 0
+ orig = questDesc[4] & (pow(2,16) - 1)
+ num = 0
+
+ # Did we recovered items?
+ if quest.getType() == Quests.RecoverItemQuest:
+ questItem = quest.getItem()
+ if questItem in itemList:
+ earned = itemList.count(questItem)
+
+ # Then we just defeated cogs
+ else:
+ for cogDict in cogList:
+ if cogDict['isVP']:
+ num = quest.doesVPCount(avId, cogDict,
+ zoneId, toonShortList)
+ elif cogDict['isCFO']:
+ num = quest.doesCFOCount(avId, cogDict,
+ zoneId, toonShortList)
+ else:
+ num = quest.doesCogCount(avId, cogDict,
+ zoneId, toonShortList)
+ if num:
+ if base.config.GetBool('battle-passing-no-credit', True):
+ if avId in helpfulToonsList:
+ earned += num
+ else:
+ self.notify.debug('avId=%d not getting %d kill cog quest credit' % (avId,num))
+ else:
+ earned += num
+
+ # import pdb; pdb.set_trace()
+
+ # if we are not in the tutorial
+ if base.localAvatar.tutorialAck:
+ # make sure we still need some items before playing movie
+ if earned > 0:
+ earned = min(earned, quest.getNumQuestItems() - questDesc[4])
+
+ if earned > 0 or (base.localAvatar.tutorialAck==0 and num==1):
+ # See getTrackIntervalList() for timing comments.
+ barTime = math.log(earned + 1)
+ numTicks = int(math.ceil(barTime / tickDelay))
+
+ for i in range(numTicks):
+ t = (i + 1) / float(numTicks)
+ newValue = int(orig + t * earned + 0.5)
+
+ # Add num to progress
+ questDesc[4] = newValue
+ progressString = quest.getProgressString(toon, questDesc)
+ str = ("%s : %s" % (questString, progressString))
+ if (quest.getCompletionStatus(toon, questDesc) == Quests.COMPLETE):
+ intervalList.append(Func(questLabel.setProp, 'text_fg', (0, 0.3, 0, 1)))
+ intervalList.append(Func(questLabel.setProp, 'text', str))
+ intervalList.append(Wait(tickDelay))
+
+ return intervalList
+
+
+ def getItemIntervalList(self, toon, itemList):
+ intervalList = []
+ for itemId in itemList:
+ itemName = Quests.getItemName(itemId)
+ intervalList.append(Func(self.itemLabel.setProp, 'text', itemName))
+ intervalList.append(Wait(1))
+ return intervalList
+
+ def getCogPartIntervalList(self, toon, cogPartList):
+ itemName = CogDisguiseGlobals.getPartName(cogPartList)
+ intervalList = []
+ intervalList.append(Func(self.cogPartLabel.setProp, 'text', itemName))
+ intervalList.append(Wait(1))
+ return intervalList
+
+ def getMissedItemIntervalList(self, toon, missedItemList):
+ intervalList = []
+ for itemId in missedItemList:
+ itemName = Quests.getItemName(itemId)
+ intervalList.append(Func(self.missedItemLabel.setProp, 'text', itemName))
+ intervalList.append(Wait(1))
+ return intervalList
+
+
+ def getExpTrack(self, toon, origExp, earnedExp, deathList, origQuestsList, itemList,
+ missedItemList, origMeritList, meritList, partList,
+ toonList, uberEntry, helpfulToonsList):
+ """
+ Assumes for input:
+ origExp: a list of 7 values, corresponding to current experience
+ levels in each track.
+ earnedExp: a list of 7 values, corresponding to amount of experience
+ earned in the battle for each track.
+ deathList: a list of the suits that were killed in battle the
+ list will be flattened triplets of (suitIndex, level, involvedToonBits)
+ suitIndex is the index of the suit in SuitDNA.suitHeadTypes.
+ For example, Flunky is suitIndex 0, Yes Man is suitIndex 2.
+
+ toonList is a list of toons that positionally corresponds to the
+ bitmasks embedded in the deathlist; toons that are no longer
+ present are represented as 'None'. Note that this might be
+ different from a battle's current activeToons list if any toons
+ have just recently dropped.
+ """
+
+ track = Sequence(Func(self.initGagFrame, toon, origExp, origMeritList),
+ Wait(1.0))
+
+ endTracks = [0,0,0,0,0,0,0]
+ trackEnded = 0
+
+ #uberField = ToontownBattleGlobals.decodeUber(uberEntry)
+
+ for trackIndex in range(len(earnedExp)):
+ # Create a track for each gag track in which we gained experience
+ if earnedExp[trackIndex] > 0 or origExp[trackIndex] >= ToontownBattleGlobals.MaxSkill:
+ track += self.getTrackIntervalList(toon, trackIndex,
+ origExp[trackIndex],
+ earnedExp[trackIndex],
+ ToontownBattleGlobals.getUberFlagSafe(uberEntry, trackIndex))
+ # if we weren't at the end of the track yet, but we will be there
+ # after experience is divvied up, make a note of it
+ maxExp = ToontownBattleGlobals.MaxSkill - ToontownBattleGlobals.UberSkill
+ if (origExp[trackIndex] < maxExp) and (earnedExp[trackIndex] + origExp[trackIndex] >= maxExp):
+ endTracks[trackIndex] = 1
+ trackEnded = 1
+
+ for dept in range(len(SuitDNA.suitDepts)):
+ # Create a track for each cog dept in which we gained merits
+ if meritList[dept]:
+ track += self.getMeritIntervalList(toon,
+ dept,
+ origMeritList[dept],
+ meritList[dept])
+
+ track.append(Wait(1.0))
+
+ itemInterval = self.getItemIntervalList(toon, itemList)
+ if itemInterval:
+ track.append(Func(self.initItemFrame, toon))
+ track.append(Wait(1.0))
+ track += itemInterval
+ track.append(Wait(1.0))
+
+ missedItemInterval = self.getMissedItemIntervalList(toon, missedItemList)
+ if missedItemInterval:
+ track.append(Func(self.initMissedItemFrame, toon))
+ track.append(Wait(1.0))
+ track += missedItemInterval
+ track.append(Wait(1.0))
+
+ # debug
+ self.notify.debug("partList = %s" % partList)
+
+ newPart = 0
+ for part in partList:
+ if part != 0:
+ newPart = 1
+ break
+ if newPart:
+ partList = self.getCogPartIntervalList(toon, partList)
+ if partList:
+ track.append(Func(self.initCogPartFrame, toon))
+ track.append(Wait(1.0))
+ track += partList
+ track.append(Wait(1.0))
+
+ # Add on any quest progress intervals
+ questList = self.getQuestIntervalList(toon, deathList, toonList, origQuestsList, itemList, helpfulToonsList)
+ if questList:
+ # We have to make a copy of the toon's quest data now,
+ # when we build up the tracks, rather than later, while
+ # we're playing them back. This is because the AI will
+ # send our quest progress update any moment.
+ track.append(Func(self.initQuestFrame, toon, copy.deepcopy(toon.quests)))
+ track.append(Wait(1.0))
+ track += questList
+ track.append(Wait(2.0))
+
+ # if the player has reached the end of a track, queue up the frames vanishing
+ # and a fanfare occurring
+ if trackEnded:
+ track.append(Func(self.vanishFrames))
+ track.append(Fanfare.makeFanfare(0,toon)[0])
+
+ # for each track we've reached the end up, create an end track interval
+ for i in range(len(endTracks)):
+ if endTracks[i] is 1:
+ track += self.getEndTrackIntervalList(toon,toonList,i)
+
+ # at the end, cleanup and cause all the endtrack frames to vanish
+ track.append(Func(self.cleanupEndTrack))
+
+ return track
+
+
+ def testMovie(self, otherToons=[]):
+ track = Sequence()
+ # Add a show at the beginning
+ track.append(Func(self.show))
+ expTrack = self.getExpTrack(
+ base.localAvatar, # toon
+ [1999, 0, 20, 30, 10, 0, 60], # origExp
+ [2, 0, 2, 6, 1, 0, 8], # earnedExp
+ [3, 1, 3, 0, 2, 2, 1, 1, 30, 2, 1, 0], # deathList
+ [], # origQuestsList
+ [], # itemList
+ [], # missedItemList
+ [0, 0, 0, 0], # origMeritList
+ [0, 0, 0, 0], # meritList
+ [], # cogPartList
+ [base.localAvatar] + otherToons, # toonList
+ )
+ track.append(expTrack)
+ if len(track) > 0:
+ # Put a hide at the end
+ track.append(Func(self.hide))
+ # Put a neutral cycle at the end
+ track.append(Func(base.localAvatar.loop, "neutral"))
+ # Put the camera back at the end
+ track.append(Func(base.localAvatar.startUpdateSmartCamera))
+ # Play the track
+ track.start()
+ # Make localToon dance
+ base.localAvatar.loop('victory')
+ #base.localAvatar.avCam.stopUpdate()
+ base.localAvatar.stopUpdateSmartCamera()
+ camera.setPosHpr(0, 8, base.localAvatar.getHeight() * 0.66,
+ 179, 15, 0)
+ else:
+ self.notify.debug("no experience, no movie.")
+ return None
diff --git a/toontown/src/battle/Sources.pp b/toontown/src/battle/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/toontown/src/battle/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/toontown/src/battle/SuitBattleGlobals.py b/toontown/src/battle/SuitBattleGlobals.py
new file mode 100644
index 0000000..6883cc7
--- /dev/null
+++ b/toontown/src/battle/SuitBattleGlobals.py
@@ -0,0 +1,1253 @@
+from BattleBase import *
+
+import random
+from direct.directnotify import DirectNotifyGlobal
+from otp.otpbase import OTPLocalizer
+from toontown.toonbase import TTLocalizer
+
+notify = DirectNotifyGlobal.directNotify.newCategory('SuitBattleGlobals')
+
+"""
+CorporateTrack = ['Flunky',
+ 'PencilPusher',
+ 'YesMan',
+ 'MicroManager',
+ 'DownSizer',
+ 'HeadHunter',
+ 'CorporateRaider',
+ 'BigCheese']
+LegalTrack = ['BottomFeeder',
+ 'BloodSucker',
+ 'DoubleTalker',
+ 'AmbulanceChaser',
+ 'BackStabber',
+ 'SpinDoctor',
+ 'LegalEagle',
+ 'BigWig']
+MoneyTrack = ['ShortChange',
+ 'PennyPincher',
+ 'TightWad',
+ 'BeanCounter',
+ 'NumberCruncher',
+ 'MoneyBags',
+ 'LoanShark',
+ 'RobberBaron']
+SalesTrack = ['ColdCaller',
+ 'TeleMarketer',
+ 'NameDropper',
+ 'GladHander',
+ 'Mover&Shaker',
+ 'TwoFaced',
+ 'TheMingler',
+ 'MrHollywood']
+"""
+
+debugAttackSequence = {}
+
+def pickFromFreqList(freqList):
+ """
+ Function: randomly choose an entry from the specified list
+ of probability values which should all add up to
+ be 100
+ Parameters: list, list of probability values to choose from
+ Returns: index into 'list' for the element chosen
+ """
+ randNum = random.randint(0, 99)
+ count = 0
+ index = 0
+ level = None
+ for f in freqList:
+ count = count + f
+ if (randNum < count):
+ level = index
+ break
+ index = index + 1
+ #assert (level != None), "Element not found"
+ return level
+
+def getActualFromRelativeLevel(name, relLevel):
+ """
+ convert from a relative level (0-4) into the suit's
+ actual level (0-11), returns a 2 item list, the first
+ element being the actual level, the second being the
+ relative level given (adjusted if necessary)
+ """
+ data = SuitAttributes[name]
+ # Levels range 0-4, 1-5, 2-6, ..., 6-10, 11
+
+ # I don't know why there was code here to special-case the
+ # top-level guys to only appear to be level 12--but we don't want
+ # that behavior.
+ #if (name == 'tbc' or name == 'bw' or name == 'mh' or name == 'rb'):
+ # actualLevel = 11
+ #else:
+ # actualLevel = data['level'] + relLevel
+
+ actualLevel = data['level'] + relLevel
+ return actualLevel
+
+def getSuitVitals(name, level=-1):
+ data = SuitAttributes[name]
+ # If level isn't specified, choose one based on the frequency values
+ if (level == -1):
+ level = pickFromFreqList(data['freq'])
+ dict = {}
+ dict['level'] = getActualFromRelativeLevel(name, level)
+ if dict['level'] == 11:
+ level = 0
+ dict['hp'] = data['hp'][level]
+ dict['def'] = data['def'][level]
+ attacks = data['attacks']
+ alist = []
+ for a in attacks:
+ adict = {}
+ name = a[0]
+ adict['name'] = name
+ adict['animName'] = SuitAttacks[name][0]
+ adict['hp'] = a[1][level]
+ adict['acc'] = a[2][level]
+ adict['freq'] = a[3][level]
+ adict['group'] = SuitAttacks[name][1]
+ alist.append(adict)
+ dict['attacks'] = alist
+ return dict
+
+def pickSuitAttack(attacks, suitLevel):
+ """
+ Function: randomly choose an attack from a list of possible
+ ones based on each attack's frequency value
+ Parameters: attacks, list of attacks to choose from
+ suitLevel, the level of the suit we are picking the
+ attack for
+ Returns: index into the attacks list for the attack chosen
+ """
+ attackNum = None
+ randNum = random.randint(0, 99)
+ notify.debug('pickSuitAttack: rolled %d' % randNum)
+ count = 0
+ index = 0
+
+ total = 0
+ for c in attacks:
+ total = total + c[3][suitLevel]
+ assert (total == 100)
+
+ for c in attacks:
+ count = count + c[3][suitLevel]
+ if (randNum < count):
+ attackNum = index
+ notify.debug('picking attack %d' % attackNum)
+ break
+ index = index + 1
+ assert (attackNum != None), "No attack found"
+
+# configAttackNum = simbase.config.GetString('attack-type', 'random')
+# if configAttackNum == 'random':
+# return attackNum
+# else:
+# return configAttackNum
+
+ configAttackName = simbase.config.GetString('attack-type', 'random')
+ if configAttackName == 'random':
+ # Normally, attackNum is returned
+ return attackNum
+ elif configAttackName == 'sequence':
+ # Return each different attack only once globally, then random.
+ # A special debugging mode.
+ for i in range(len(attacks)):
+ if not debugAttackSequence.has_key(attacks[i]):
+ debugAttackSequence[attacks[i]] = 1
+ return i
+ return attackNum
+ else:
+ # However, if attack-type was specified in the configfile, look
+ # it up, and return the index if it is found.
+ for i in range(len(attacks)):
+ if attacks[i][0] == configAttackName:
+ return i
+
+ # If it is not found, this suit can't make that kind of
+ # attack, so choose a random attack.
+ return attackNum
+
+def getSuitAttack(suitName, suitLevel, attackNum=-1):
+ # If no attack is specified, choose one based on frequency values
+ attackChoices = SuitAttributes[suitName]['attacks']
+ if (attackNum == -1):
+ notify.debug('getSuitAttack: picking attacking for %s' % suitName)
+ attackNum = pickSuitAttack(attackChoices, suitLevel)
+
+ attack = attackChoices[attackNum]
+ adict = {}
+ adict['suitName'] = suitName
+ name = attack[0]
+ adict['name'] = name
+ adict['id'] = SuitAttacks.keys().index(name)
+ adict['animName'] = SuitAttacks[name][0]
+ adict['hp'] = attack[1][suitLevel]
+ adict['acc'] = attack[2][suitLevel]
+ adict['freq'] = attack[3][suitLevel]
+ adict['group'] = SuitAttacks[name][1]
+ return adict
+
+
+
+
+
+SuitAttributes = {
+ #### Corporate ####
+ # Flunky (C)
+ 'f': { 'name': TTLocalizer.SuitFlunky,
+ 'singularname': TTLocalizer.SuitFlunkyS,
+ 'pluralname': TTLocalizer.SuitFlunkyP,
+ 'level': 0,
+ 'hp': (6, 12, 20, 30, 42), # actual suit level * 4
+ 'def': (2, 5, 10, 12, 15),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('PoundKey', (2, 2, 3, 4, 6), # dmg
+ (75, 75, 80, 80, 90), # acc
+ (30, 35, 40, 45, 50)), # freq
+ # cringe & sidestep
+ ('Shred', (3, 4, 5, 6, 7),
+ (50, 55, 60, 65, 70),
+ (10, 15, 20, 25, 30)),
+ # conked & sidestep
+ ('ClipOnTie', (1, 1, 2, 2, 3),
+ (75, 80, 85, 90, 95),
+ (60, 50, 40, 30, 20)),
+ # conked & sidestep
+ # TODO: Carbon copy - not implemented
+ #('CarbonCopy', (0, 0, 0, 0, 0),
+ # (75, 75, 75, 75, 75),
+ # (0, 0, 0, 0, 0))
+ )
+ },
+
+ # Pencil Pusher (B)
+ 'p': { 'name': TTLocalizer.SuitPencilPusher,
+ 'singularname': TTLocalizer.SuitPencilPusherS,
+ 'pluralname': TTLocalizer.SuitPencilPusherP,
+ 'level': 1,
+ 'hp': (12, 20, 30, 42, 56), # actual suit level * 4
+ 'def': (5, 10, 15, 20, 25),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (45, 50, 55, 60, 65),
+ 'attacks': (('FountainPen', (2, 3, 4, 6, 9), # dmg
+ (75, 75, 75, 75, 75), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # flatten, sidestep-left/right
+ ('RubOut', (4, 5, 6, 8, 12),
+ (75, 75, 75, 75, 75),
+ (20, 20, 20, 20, 20)),
+ # conked&slip-backward, sidestep-l/r
+ ('FingerWag', (1, 2, 2, 3, 4),
+ (75, 75, 75, 75, 75),
+ (35, 30, 25, 20, 15)),
+ # conked&slip-backward, sidestep-l/r
+ ('WriteOff', (4, 6, 8, 10, 12),
+ (75, 75, 75, 75, 75),
+ (5, 10, 15, 20, 25)),
+ # conked&slip-backward, sidestep-l/r
+ ('FillWithLead', (3, 4, 5, 6, 7),
+ (75, 75, 75, 75, 75),
+ (20, 20, 20, 20, 20))) },
+
+ # Yes Man (A)
+ 'ym': { 'name': TTLocalizer.SuitYesman,
+ 'singularname': TTLocalizer.SuitYesmanS,
+ 'pluralname': TTLocalizer.SuitYesmanP,
+ 'level': 2,
+ 'hp': (20, 30, 42, 56, 72), # actual suit level * 4
+ 'def': (10, 15, 20, 25, 30),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (65, 70, 75, 80, 85),
+ 'attacks': (('RubberStamp', (2, 2, 3, 3, 4), # dmg
+ (75, 75, 75, 75, 75), # acc
+ (35, 35, 35, 35, 35)), # freq
+ # flatten, sidestep-left/right
+ ('RazzleDazzle', (1, 1, 1, 1, 1),
+ (50, 50, 50, 50, 50),
+ (25, 20, 15, 10, 5)),
+ # conked&slip-backward, sidestep-l/r
+ ('Synergy', (4, 5, 6, 7, 8),
+ (50, 60, 70, 80, 90),
+ (5, 10, 15, 20, 25)),
+ # slip-forward, jump
+ ('TeeOff', (3, 3, 4, 4, 5),
+ (50, 60, 70, 80, 90),
+ (35, 35, 35, 35, 35)),
+ # conked&slip-backward, duck
+ # Not implementing for now
+ #('Ditto', (0, 0, 0, 0, 0),
+ # (75, 75, 75, 75, 75),
+ # (0, 0, 0, 0, 0)),
+ ) },
+
+ # Micromanager (C)
+ 'mm': { 'name': TTLocalizer.SuitMicromanager,
+ 'singularname': TTLocalizer.SuitMicromanagerS,
+ 'pluralname': TTLocalizer.SuitMicromanagerP,
+ 'level': 3,
+ 'hp': (30, 42, 56, 72, 90), # actual suit level * 4
+ 'def': (15, 20, 25, 30, 35),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (70, 75, 80, 82, 85),
+ 'attacks': (('Demotion', (6, 8, 12, 15, 18), # dmg
+ (50, 60, 70, 80, 90), # acc
+ (30, 30, 30, 30, 30)), # freq
+ # flatten, sidestep-left/right
+ ('FingerWag', (4, 6, 9, 12, 15),
+ (50, 60, 70, 80, 90),
+ (10, 10, 10, 10, 10)),
+ # slip-forward, jump
+ ('FountainPen', (3, 4, 6, 8, 10),
+ (50, 60, 70, 80, 90),
+ (15, 15, 15, 15, 15)),
+ # conked&slip-backward, sidestep-l/r
+ ('BrainStorm', (4, 6, 9, 12, 15),
+ (5, 5, 5, 5, 5),
+ (25, 25, 25, 25, 25)),
+ # conked&slip-backward, duck
+ ('BuzzWord', (4, 6, 9, 12, 15),
+ (50, 60, 70, 80, 90),
+ (20, 20, 20, 20, 20))) },
+
+ # Downsizer (B)
+ 'ds': { 'name': TTLocalizer.SuitDownsizer,
+ 'singularname': TTLocalizer.SuitDownsizerS,
+ 'pluralname': TTLocalizer.SuitDownsizerP,
+ 'level': 4,
+ 'hp': (42, 56, 72, 90, 110), # actual suit level * 4
+ 'def': (20, 25, 30, 35, 40),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('Canned', (5, 6, 8, 10, 12), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (25, 25, 25, 25, 25)), # freq
+ # struggle&slip-backward, sidestep-left/right
+ ('Downsize', (8, 9, 11, 13, 15),
+ (50, 65, 70, 75, 80),
+ (35, 35, 35, 35, 35)),
+ # cringe, jump
+ ('PinkSlip', (4, 5, 6, 7, 8),
+ (60, 65, 75, 80, 85),
+ (25, 25, 25, 25, 25)),
+ # slip-forward, sidestep-l/r
+ #('ReOrg', (6, 8, 10, 12, 14),
+ # (70, 75, 80, 85, 90),
+ # (0, 0, 0, 0, 0)),
+ # not used until can safely return toon to normal
+ # if the movie gets cut off
+ # cringe&jump, jump
+ ('Sacked', (5, 6, 7, 8, 9),
+ (50, 50, 50, 50, 50),
+ (15, 15, 15, 15, 15))) },
+ # struggle&jump&neutral, sidestep-l/r
+
+ # Head Hunter (A)
+ 'hh': { 'name': TTLocalizer.SuitHeadHunter,
+ 'singularname': TTLocalizer.SuitHeadHunterS,
+ 'pluralname': TTLocalizer.SuitHeadHunterP,
+ 'level': 5,
+ 'hp': (56, 72, 90, 110, 132), # actual suit level * 4
+ 'def': (25, 30, 35, 40, 45),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('FountainPen', (5, 6, 8, 10, 12), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (15, 15, 15, 15, 15)), # freq
+ # conked&slip-backward, duck
+ ('GlowerPower', (7, 8, 10, 12, 13),
+ (50, 60, 70, 80, 90),
+ (20, 20, 20, 20, 20)),
+ # cringe&slip-backward, sidestep-l/r
+ ('HalfWindsor', (8, 10, 12, 14, 16),
+ (60, 65, 70, 75, 80),
+ (20, 20, 20, 20, 20)),
+ # conked&neutral, sidestep-l/r
+ ('HeadShrink', (10, 12, 15, 18, 21),
+ (65, 75, 80, 85, 95),
+ (35, 35, 35, 35, 35)),
+ # cringe&neutral, jump
+ ('Rolodex', (6, 7, 8, 9, 10),
+ (60, 65, 70, 75, 80),
+ (10, 10, 10, 10, 10))) },
+ # duck, sidestep-l/r
+
+ # Corporate Raider (C)
+ 'cr': { 'name': TTLocalizer.SuitCorporateRaider,
+ 'singularname': TTLocalizer.SuitCorporateRaiderS,
+ 'pluralname': TTLocalizer.SuitCorporateRaiderP,
+ 'level': 6,
+ 'hp': (72, 90, 110, 132, 156), # actual suit level * 4
+ 'def': (30, 35, 40, 45, 50),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('Canned', (6, 7, 8, 9, 10), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # struggle&slip-backward, sidestep-left/right
+ ('EvilEye', (12, 15, 18, 21, 24),
+ (60, 70, 75, 80, 90),
+ (35, 35, 35, 35, 35)),
+ # cringe&slip-backward, duck
+ ('PlayHardball', (7, 8, 12, 15, 16),
+ (60, 65, 70, 75, 80),
+ (30, 30, 30, 30, 30)),
+ # slip-backward, sidestep-l/r
+ ('PowerTie', (10, 12, 14, 16, 18),
+ (65, 75, 80, 85, 95),
+ (15, 15, 15, 15, 15))) },
+ # conked&neutral, sidestep-l/r
+
+ # The Big Cheese (A)
+ 'tbc': {'name': TTLocalizer.SuitTheBigCheese,
+ 'singularname': TTLocalizer.SuitTheBigCheeseS,
+ 'pluralname': TTLocalizer.SuitTheBigCheeseP,
+ 'level': 7,
+ 'hp': (90, 110, 132, 156, 200), # actual suit level * 4
+ 'def': (35, 40, 45, 50, 55),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('CigarSmoke', (10, 12, 15, 18, 20), # dmg
+ (55, 65, 75, 85, 95), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # not implemented defaults to glower power
+ # cringe, sidestep-left/right
+ ('FloodTheMarket', (14, 16, 18, 20, 22),
+ (70, 75, 85, 90, 95),
+ (10, 10, 10, 10, 10)),
+ # not implemented defaults to glower power
+ # slip-backward&jump, sidestep-l/r
+ ('SongAndDance', (14, 15, 17, 19, 20),
+ (60, 65, 70, 75, 80),
+ (20, 20, 20, 20, 20)),
+ # not implemented defaults to glower power
+ # bounce&slip-backward, sidestep-l/r
+ ('TeeOff', (8, 11, 14, 17, 20),
+ (55, 65, 70, 75, 80),
+ (50, 50, 50, 50, 50))) },
+ # conked&slip-backward, duck
+
+ #### Sales ####
+ # Cold Caller (C)
+ 'cc': { 'name': TTLocalizer.SuitColdCaller,
+ 'singularname': TTLocalizer.SuitColdCallerS,
+ 'pluralname': TTLocalizer.SuitColdCallerP,
+ 'level': 0,
+ 'hp': (6, 12, 20, 30, 42), # actual suit level * 4
+ 'def': (2, 5, 10, 12, 15),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('FreezeAssets', (1, 1, 1, 1, 1), # dmg
+ (90, 90, 90, 90, 90), # acc
+ (5, 10, 15, 20, 25)), # freq
+ # flatten, sidestep-left/right
+ ('PoundKey', (2, 2, 3, 4, 5),
+ (75, 80, 85, 90, 95),
+ (25, 25, 25, 25, 25)),
+ # conked&slip-backward, sidestep-l/r
+ ('DoubleTalk', (2, 3, 4, 6, 8),
+ (50, 55, 60, 65, 70),
+ (25, 25, 25, 25, 25)),
+ # conked&slip-backward, duck
+ ('HotAir', (3, 4, 6, 8, 10),
+ (50, 50, 50, 50, 50),
+ (45, 40, 35, 30, 25))) },
+ # cringe, sidestep-l/r
+ # Telemarketer (B)
+ 'tm': { 'name': TTLocalizer.SuitTelemarketer,
+ 'singularname': TTLocalizer.SuitTelemarketerS,
+ 'pluralname': TTLocalizer.SuitTelemarketerP,
+ 'level': 1,
+ 'hp': (12, 20, 30, 42, 56), # actual suit level * 4
+ 'def': (5, 10, 15, 20, 25),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (45, 50, 55, 60, 65),
+ 'attacks': (('ClipOnTie', (2, 2, 3, 3, 4), # dmg
+ (75, 75, 75, 75, 75), # acc
+ (15, 15, 15, 15, 15)), # freq
+ # flatten, sidestep-left/right
+ ('PickPocket', (1, 1, 1, 1, 1),
+ (75, 75, 75, 75, 75),
+ (15, 15, 15, 15, 15)),
+ # conked&slip-backward, sidestep-l/r
+ ('Rolodex', (4, 6, 7, 9, 12),
+ (50, 50, 50, 50, 50),
+ (30, 30, 30, 30, 30)),
+ # conked&slip-backward, sidestep-l/r
+ ('DoubleTalk', (4, 6, 7, 9, 12),
+ (75, 80, 85, 90, 95),
+ (40, 40, 40, 40, 40))) },
+ # Namedropper (A)
+ 'nd': { 'name': TTLocalizer.SuitNameDropper,
+ 'singularname': TTLocalizer.SuitNameDropperS,
+ 'pluralname': TTLocalizer.SuitNameDropperP,
+ 'level': 2,
+ 'hp': (20, 30, 42, 56, 72), # actual suit level * 4
+ 'def': (10, 15, 20, 25, 30),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (65, 70, 75, 80, 85),
+ 'attacks': (('RazzleDazzle', (4, 5, 6, 9, 12), # dmg
+ (75, 80, 85, 90, 95), # acc
+ (30, 30, 30, 30, 30)), # freq
+ # conked&slip-backward, sidestep-l/r
+ ('Rolodex', (5, 6, 7, 10, 14),
+ (95, 95, 95, 95, 95),
+ (40, 40, 40, 40, 40)),
+ # duck, sidestep-l/r
+ ('Synergy', (3, 4, 6, 9, 12),
+ (50, 50, 50, 50, 50),
+ (15, 15, 15, 15, 15)),
+ # slip-forward, jump
+ ('PickPocket', (2, 2, 2, 2, 2),
+ (95, 95, 95, 95, 95),
+ (15, 15, 15, 15, 15))) },
+ # cringe, sidestep-l/r
+ # Gladhander (C)
+ 'gh': { 'name': TTLocalizer.SuitGladHander,
+ 'singularname': TTLocalizer.SuitGladHanderS,
+ 'pluralname': TTLocalizer.SuitGladHanderP,
+ 'level': 3,
+ 'hp': (30, 42, 56, 72, 90), # actual suit level * 4
+ 'def': (15, 20, 25, 30, 35),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (70, 75, 80, 82, 85),
+ 'attacks': (('RubberStamp', (4, 3, 3, 2, 1), # dmg
+ (90, 70, 50, 30, 10), # acc
+ (40, 30, 20, 10, 5)), # freq
+ # flatten, sidestep-left/right
+ ('FountainPen', (3, 3, 2, 1, 1),
+ (70, 60, 50, 40, 30),
+ (40, 30, 20, 10, 5)),
+ # conked&slip-backward, duck
+ ('Filibuster', (4, 6, 9, 12, 15),
+ (30, 40, 50, 60, 70),
+ (10, 20, 30, 40, 45)),
+ # conked&slip-backward, duck
+ ('Schmooze', (5, 7, 11, 15, 20),
+ (55, 65, 75, 85, 95),
+ (10, 20, 30, 40, 45))) },
+
+ # Mover & Shaker (B)
+ 'ms': { 'name': TTLocalizer.SuitMoverShaker,
+ 'singularname': TTLocalizer.SuitMoverShakerS,
+ 'pluralname': TTLocalizer.SuitMoverShakerP,
+ 'level': 4,
+ 'hp': (42, 56, 72, 90, 110), # actual suit level * 4
+ 'def': (20, 25, 30, 35, 40),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('BrainStorm', (5, 6, 8, 10, 12), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (15, 15, 15, 15, 15)), # freq
+ # conked&slip-backward, duck
+ ('HalfWindsor', (6, 9, 11, 13, 16),
+ (50, 65, 70, 75, 80),
+ (20, 20, 20, 20, 20)),
+ # conked&neutral, sidestep-l/r
+ ('Quake', (9, 12, 15, 18, 21),
+ (60, 65, 75, 80, 85),
+ (20, 20, 20, 20, 20)),
+ # shake, sidestep-l/r
+ ('Shake', (6, 8, 10, 12, 14),
+ (70, 75, 80, 85, 90),
+ (25, 25, 25, 25, 25)),
+ # shake, jump
+ ('Tremor', (5, 6, 7, 8, 9),
+ (50, 50, 50, 50, 50),
+ (20, 20, 20, 20, 20))) },
+ # shake, sidestep-l/r
+
+ # Two-Face (A)
+ 'tf': { 'name': TTLocalizer.SuitTwoFace,
+ 'singularname': TTLocalizer.SuitTwoFaceS,
+ 'pluralname': TTLocalizer.SuitTwoFaceP,
+ 'level': 5,
+ 'hp': (56, 72, 90, 110, 132), # actual suit level * 4
+ 'def': (25, 30, 35, 40, 45),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('EvilEye', (10, 12, 14, 16, 18), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (30, 30, 30, 30, 30)), # freq
+ # cringe&slip-backward, duck
+ ('HangUp', (7, 8, 10, 12, 13),
+ (50, 60, 70, 80, 90),
+ (15, 15, 15, 15, 15)),
+ # slip-backward&neutral, sidestep-l/r
+ ('RazzleDazzle', (8, 10, 12, 14, 16),
+ (60, 65, 70, 75, 80),
+ (30, 30, 30, 30, 30)),
+ # conked&slip-backward, sidestep-l/r
+ ('RedTape', (6, 7, 8, 9, 10),
+ (60, 65, 75, 85, 90),
+ (25, 25, 25, 25, 25))) },
+ # bound&jump, sidestep-l/r
+
+ # The Mingler (A)
+ 'm': { 'name': TTLocalizer.SuitTheMingler,
+ 'singularname': TTLocalizer.SuitTheMinglerS,
+ 'pluralname': TTLocalizer.SuitTheMinglerP,
+ 'level': 6,
+ 'hp': (72, 90, 110, 132, 156), # actual suit level * 4
+ 'def': (30, 35, 40, 45, 50),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('BuzzWord', (10, 11, 13, 15, 16), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # cringe, sidestep-left/right
+ ('ParadigmShift', (12, 15, 18, 21, 24),
+ (60, 70, 75, 80, 90),
+ (25, 25, 25, 25, 25)),
+ # shift, sidestep-l/r
+ ('PowerTrip', (10, 13, 14, 15, 18),
+ (60, 65, 70, 75, 80),
+ (15, 15, 15, 15, 15)),
+ # slip-forward, jump
+ ('Schmooze', (7, 8, 12, 15, 16),
+ (55, 65, 75, 85, 95),
+ (30, 30, 30, 30, 30)),
+ # slip-backward, sidestep-l/r
+ ('TeeOff', (8, 9, 10, 11, 12),
+ (70, 75, 80, 85, 95),
+ (10, 10, 10, 10, 10))) },
+ # conked&slip-backward, duck
+
+ # Mr. Hollywood (A)
+ 'mh': {'name': TTLocalizer.SuitMrHollywood,
+ 'singularname': TTLocalizer.SuitMrHollywoodS,
+ 'pluralname': TTLocalizer.SuitMrHollywoodP,
+ 'level': 7,
+ 'hp': (90, 110, 132, 156, 200), # actual suit level * 4
+ 'def': (35, 40, 45, 50, 55),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('PowerTrip', (10, 12, 15, 18, 20), # dmg
+ (55, 65, 75, 85, 95), # acc
+ (50, 50, 50, 50, 50)), # freq
+ # slip-forward, jump
+ ('RazzleDazzle', (8, 11, 14, 17, 20),
+ (70, 75, 85, 90, 95),
+ (50, 50, 50, 50, 50)),
+ # conked&slip-backward, sidestep-l/r
+ #('SandTrap', (14, 15, 17, 19, 20),
+ # (60, 65, 70, 75, 80),
+ # (0, 0, 0, 0, 0)),
+ # not implemented defaults to razzle dazzle
+ # slip-forward, duck
+ #('SongAndDance', (12, 14, 16, 18, 20),
+ # (65, 75, 80, 85, 95),
+ # (0, 0, 0, 0, 0)),
+ # not implemented defaults to razzle dazzle
+ ) },
+ # bounce&slip-backward, sidestep-l/r
+
+ #### Money ####
+ # Short Change (C)
+ 'sc': { 'name': TTLocalizer.SuitShortChange,
+ 'singularname': TTLocalizer.SuitShortChangeS,
+ 'pluralname': TTLocalizer.SuitShortChangeP,
+ 'level': 0,
+ 'hp': (6, 12, 20, 30, 42), # actual suit level * 4
+ 'def': (2, 5, 10, 12, 15),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('Watercooler', (2, 2, 3, 4, 6), # dmg
+ (50, 50, 50, 50, 50), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # flatten, sidestep-left/right
+ ('BounceCheck', (3, 5, 7, 9, 11),
+ (75, 80, 85, 90, 95),
+ (15, 15, 15, 15, 15)),
+ # conked&slip-backward, duck
+ ('ClipOnTie', (1, 1, 2, 2, 3),
+ (50, 50, 50, 50, 50),
+ (25, 25, 25, 25, 25)),
+ # conked&slip-backward, duck
+ ('PickPocket', (2, 2, 3, 4, 6),
+ (95, 95, 95, 95, 95),
+ (40, 40, 40, 40, 40))) },
+ # Penny Pincher (A)
+ 'pp': { 'name': TTLocalizer.SuitPennyPincher,
+ 'singularname': TTLocalizer.SuitPennyPincherS,
+ 'pluralname': TTLocalizer.SuitPennyPincherP,
+ 'level': 1,
+ 'hp': (12, 20, 30, 42, 56), # actual suit level * 4
+ 'def': (5, 10, 15, 20, 25),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (45, 50, 55, 60, 65),
+ 'attacks': (('BounceCheck', (4, 5, 6, 8, 12), # dmg
+ (75, 75, 75, 75, 75), # acc
+ (45, 45, 45, 45, 45)), # freq
+ # flatten, sidestep-left/right
+ ('FreezeAssets', (2, 3, 4, 6, 9),
+ (75, 75, 75, 75, 75),
+ (20, 20, 20, 20, 20)),
+ # conked&slip-backward, sidestep-l/r
+ ('FingerWag', (1, 2, 3, 4, 6),
+ (50, 50, 50, 50, 50),
+ (35, 35, 35, 35, 35)),
+ # conked&slip-backward, sidestep-l/r
+ # PennyPinch not implemented
+ #('PennyPinch', (0, 0, 0, 0, 0),
+ # (75, 75, 75, 75, 75),
+ # (0, 0, 0, 0, 0)),
+ ) },
+ # Tightwad (C)
+ 'tw': { 'name': TTLocalizer.SuitTightwad,
+ 'singularname': TTLocalizer.SuitTightwadS,
+ 'pluralname': TTLocalizer.SuitTightwadP,
+ 'level': 2,
+ 'hp': (20, 30, 42, 56, 72), # actual suit level * 4
+ 'def': (10, 15, 20, 25, 30),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (65, 70, 75, 80, 85),
+ 'attacks': (('Fired', (3, 4, 5, 5, 6), # dmg
+ (75, 75, 75, 75, 75), # acc
+ (75, 5, 5, 5, 5)), # freq
+ # cringe, sidestep-l/r
+ ('GlowerPower', (3, 4, 6, 9, 12),
+ (95, 95, 95, 95, 95),
+ (10, 15, 20, 25, 30)),
+ # cringe&slip-backward, sidestep-l/r
+ ('FingerWag', (3, 3, 4, 4, 5),
+ (75, 75, 75, 75, 75),
+ (5, 70, 5, 5, 5)),
+ # slip-backward, sidestep-l/r
+ ('FreezeAssets', (3, 4, 6, 9, 12),
+ (75, 75, 75, 75, 75),
+ (5, 5, 65, 5, 30)),
+ # cringe, sidestep-l/r
+ ('BounceCheck', (5, 6, 9, 13, 18),
+ (75, 75, 75, 75, 75),
+ (5, 5, 5, 60, 30))) },
+ # conked, jump
+ # Bean Counter (B)
+ 'bc': { 'name': TTLocalizer.SuitBeanCounter,
+ 'singularname': TTLocalizer.SuitBeanCounterS,
+ 'pluralname': TTLocalizer.SuitBeanCounterP,
+ 'level': 3,
+ 'hp': (30, 42, 56, 72, 90), # actual suit level * 4
+ 'def': (15, 20, 25, 30, 35),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (70, 75, 80, 82, 85),
+ 'attacks': (('Audit', (4, 6, 9, 12, 15), # dmg
+ (95, 95, 95, 95, 95), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # flatten, sidestep-left/right
+ ('Calculate', (4, 6, 9, 12, 15),
+ (75, 75, 75, 75, 75),
+ (25, 25, 25, 25, 25)),
+ # conked&slip-backward, duck
+ ('Tabulate', (4, 6, 9, 12, 15),
+ (75, 75, 75, 75, 75),
+ (25, 25, 25, 25, 25)),
+ # conked&slip-backward, duck
+ ('WriteOff', (4, 6, 9, 12, 15),
+ (95, 95, 95, 95, 95),
+ (30, 30, 30, 30, 30))) },
+
+ # Number Cruncher (A)
+ 'nc': { 'name': TTLocalizer.SuitNumberCruncher,
+ 'singularname': TTLocalizer.SuitNumberCruncherS,
+ 'pluralname': TTLocalizer.SuitNumberCruncherP,
+ 'level': 4,
+ 'hp': (42, 56, 72, 90, 110), # actual suit level * 4
+ 'def': (20, 25, 30, 35, 40),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('Audit', (5, 6, 8, 10, 12), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (15, 15, 15, 15, 15)), # freq
+ # flatten, sidestep-left/right
+ ('Calculate', (6, 7, 9, 11, 13),
+ (50, 65, 70, 75, 80),
+ (30, 30, 30, 30, 30)),
+ # conked&slip-backward, duck
+ ('Crunch', (8, 9, 11, 13, 15),
+ (60, 65, 75, 80, 85),
+ (35, 35, 35, 35, 35)),
+ # slip-forward&flatten, sidestep-l/r
+ ('Tabulate', (5, 6, 7, 8, 9),
+ (50, 50, 50, 50, 50),
+ (20, 20, 20, 20, 20))) },
+ # conked&slip-backward, duck
+
+ # Money Bags (C)
+ 'mb': { 'name': TTLocalizer.SuitMoneyBags,
+ 'singularname': TTLocalizer.SuitMoneyBagsS,
+ 'pluralname': TTLocalizer.SuitMoneyBagsP,
+ 'level': 5,
+ 'hp': (56, 72, 90, 110, 132), # actual suit level * 4
+ 'def': (25, 30, 35, 40, 45),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('Liquidate', (10, 12, 14, 16, 18), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (30, 30, 30, 30, 30)), # freq
+ # melt&jump, sidestep-l/r
+ ('MarketCrash', (8, 10, 12, 14, 16),
+ (60, 65, 70, 75, 80),
+ (45, 45, 45, 45, 45)),
+ # flatten&jump, sidestep-l/r
+ ('PowerTie', (6, 7, 8, 9, 10),
+ (60, 65, 75, 85, 90),
+ (25, 25, 25, 25, 25))) },
+ # conked&neutral, sidestep-l/r
+
+ # Loan Shark (B)
+ 'ls': { 'name': TTLocalizer.SuitLoanShark,
+ 'singularname': TTLocalizer.SuitLoanSharkS,
+ 'pluralname': TTLocalizer.SuitLoanSharkP,
+ 'level': 6,
+ 'hp': (72, 90, 110, 132, 156), # actual suit level * 4
+ 'def': (30, 35, 40, 45, 50),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('Bite', (10, 11, 13, 15, 16), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (30, 30, 30, 30, 30)), # freq
+ # conked, duck
+ ('Chomp', (12, 15, 18, 21, 24),
+ (60, 70, 75, 80, 90),
+ (35, 35, 35, 35, 35)),
+ # slip-backward, sidestep-l/r
+ ('PlayHardball', (9, 11, 12, 13, 15),
+ (55, 65, 75, 85, 95),
+ (20, 20, 20, 20, 20)),
+ # slip-backward, sidestep-l/r
+ ('WriteOff', (6, 8, 10, 12, 14),
+ (70, 75, 80, 85, 95),
+ (15, 15, 15, 15, 15))) },
+ # slip-forward, sidestep-l/r
+
+ # Robber Baron (A)
+ 'rb': { 'name': TTLocalizer.SuitRobberBaron,
+ 'singularname': TTLocalizer.SuitRobberBaronS,
+ 'pluralname': TTLocalizer.SuitRobberBaronP,
+ 'level': 7,
+ 'hp': (90, 110, 132, 156, 200), # actual suit level * 4
+ 'def': (35, 40, 45, 50, 55),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (
+ #('FiveOClockShadow', (14, 17, 19, 21, 24), # dmg
+ # (55, 65, 75, 85, 95), # acc
+ # (0, 0, 0, 0, 0)), # freq
+ # not implemented defaults to tee off
+ # slip-forward, jump
+ #('FloodTheMarket', (12, 15, 18, 21, 24),
+ # (70, 75, 85, 90, 95),
+ # (0, 0, 0, 0, 0)),
+ # not implemented defaults to tee off
+ # slip-backward&jump, sidestep-l/r
+ ('PowerTrip', (11, 14, 16, 18, 21),
+ (60, 65, 70, 75, 80),
+ (50, 50, 50, 50, 50)),
+ # slip-forward, jump
+ ('TeeOff', (10, 12, 14, 16, 18),
+ (60, 65, 75, 85, 90),
+ (50, 50, 50, 50, 50))) },
+ # conked&slip-backward, duck
+
+ #### Legal ####
+ # Bottom Feeder (C)
+ 'bf': { 'name': TTLocalizer.SuitBottomFeeder,
+ 'singularname': TTLocalizer.SuitBottomFeederS,
+ 'pluralname': TTLocalizer.SuitBottomFeederP,
+ 'level': 0,
+ 'hp': (6, 12, 20, 30, 42), # actual suit level * 4
+ 'def': (2, 5, 10, 12, 15),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('RubberStamp', (2, 3, 4, 5, 6), # dmg
+ (75, 80, 85, 90, 95), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # flatten, sidestep-left/right
+ ('Shred', (2, 4, 6, 8, 10),
+ (50, 55, 60, 65, 70),
+ (20, 20, 20, 20, 20)),
+ # conked&slip-backward, duck
+ ('Watercooler', (3, 4, 5, 6, 7),
+ (95, 95, 95, 95, 95),
+ (10, 10, 10, 10, 10)),
+ # conked&slip-backward, duck
+ ('PickPocket', (1, 1, 2, 2, 3),
+ (25, 30, 35, 40, 45),
+ (50, 50, 50, 50, 50))) },
+ # Bloodsucker (B)
+ 'b': { 'name': TTLocalizer.SuitBloodsucker,
+ 'singularname': TTLocalizer.SuitBloodsuckerS,
+ 'pluralname': TTLocalizer.SuitBloodsuckerP,
+ 'level': 1,
+ 'hp': (12, 20, 30, 42, 56), # actual suit level * 4
+ 'def': (5, 10, 15, 20, 25),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (45, 50, 55, 60, 65),
+ 'attacks': (('EvictionNotice', (1, 2, 3, 3, 4), # dmg
+ (75, 75, 75, 75, 75), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # flatten, sidestep-left/right
+ ('RedTape', (2, 3, 4, 6, 9),
+ (75, 75, 75, 75, 75),
+ (20, 20, 20, 20, 20)),
+ # conked&slip-backward, sidestep-l/r
+ ('Withdrawal', (6, 8, 10, 12, 14),
+ (95, 95, 95, 95, 95),
+ (10, 10, 10, 10, 10)),
+ # conked&slip-backward, sidestep-l/r
+ ('Liquidate', (2, 3, 4, 6, 9),
+ (50, 60, 70, 80, 90),
+ (50, 50, 50, 50, 50))) },
+ # Double Talker (A)
+ 'dt': { 'name': TTLocalizer.SuitDoubleTalker,
+ 'singularname': TTLocalizer.SuitDoubleTalkerS,
+ 'pluralname': TTLocalizer.SuitDoubleTalkerP,
+ 'level': 2,
+ 'hp': (20, 30, 42, 56, 72), # actual suit level * 4
+ 'def': (10, 15, 20, 25, 30),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (65, 70, 75, 80, 85),
+ 'attacks': (('RubberStamp', (1, 1, 1, 1, 1), # dmg
+ (50, 60, 70, 80, 90), # acc
+ (5, 5, 5, 5, 5)), # freq
+ # cringe, sidestep-l/r
+ ('BounceCheck', (1, 1, 1, 1, 1),
+ (50, 60, 70, 80, 90),
+ (5, 5, 5, 5, 5)),
+ # conked, jump
+ ('BuzzWord', (1, 2, 3, 5, 6),
+ (50, 60, 70, 80, 90),
+ (20, 20, 20, 20, 20)),
+ # cringe, duck
+ ('DoubleTalk', (6, 6, 9, 13, 18),
+ (50, 60, 70, 80, 90),
+ (25, 25, 25, 25, 25)),
+ # cringe, sidestep-l/r
+ ('Jargon', (3, 4, 6, 9, 12),
+ (50, 60, 70, 80, 90),
+ (25, 25, 25, 25, 25)),
+ # cringe, sidestep-l/r
+ ('MumboJumbo', (3, 4, 6, 9, 12),
+ (50, 60, 70, 80, 90),
+ (20, 20, 20, 20, 20))) },
+ # cringe, sidestep-l/r
+ # Ambulance Chaser (B)
+ 'ac': { 'name': TTLocalizer.SuitAmbulanceChaser,
+ 'singularname': TTLocalizer.SuitAmbulanceChaserS,
+ 'pluralname': TTLocalizer.SuitAmbulanceChaserP,
+ 'level': 3,
+ 'hp': (30, 42, 56, 72, 90), # actual suit level * 4
+ 'def': (15, 20, 25, 30, 35),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (65, 70, 75, 80, 85),
+ 'attacks': (('Shake', (4, 6, 9, 12, 15), # dmg
+ (75, 75, 75, 75, 75), # acc
+ (15, 15, 15, 15, 15)), # freq
+ # shake, jump
+ ('RedTape', (6, 8, 12, 15, 19),
+ (75, 75, 75, 75, 75),
+ (30, 30, 30, 30, 30)),
+ # bound&jump, sidestep-l/r
+ ('Rolodex', (3, 4, 5, 6, 7),
+ (75, 75, 75, 75, 75),
+ (20, 20, 20, 20, 20)),
+ # duck, sidestep-l/r
+ ('HangUp', (2, 3, 4, 5, 6),
+ (75, 75, 75, 75, 75),
+ (35, 35, 35, 35, 35))) },
+ # slip-backward&neutral, sidestep-l/r
+
+ # Back Stabber (A)
+ 'bs': { 'name': TTLocalizer.SuitBackStabber,
+ 'singularname': TTLocalizer.SuitBackStabberS,
+ 'pluralname': TTLocalizer.SuitBackStabberP,
+ 'level': 4,
+ 'hp': (42, 56, 72, 90, 110), # actual suit level * 4
+ 'def': (20, 25, 30, 35, 40),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('GuiltTrip', (8, 11, 13, 15, 18), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (40, 40, 40, 40, 40)), # freq
+ # slip-forward&neutral, jump
+ ('RestrainingOrder', (6, 7, 9, 11, 13),
+ (50, 65, 70, 75, 90),
+ (25, 25, 25, 25, 25)),
+ # conked&bound, sidestep-l/r
+ ('FingerWag', (5, 6, 7, 8, 9),
+ (50, 55, 65, 75, 80),
+ (35, 35, 35, 35, 35))) },
+ # slip-backward, sidestep-l/r
+
+ # Spin Doctor (B)
+ 'sd': { 'name': TTLocalizer.SuitSpinDoctor,
+ 'singularname': TTLocalizer.SuitSpinDoctorS,
+ 'pluralname': TTLocalizer.SuitSpinDoctorP,
+ 'level': 5,
+ 'hp': (56, 72, 90, 110, 132), # actual suit level * 4
+ 'def': (25, 30, 35, 40, 45),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('ParadigmShift', (9, 10, 13, 16, 17), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (30, 30, 30, 30, 30)), # freq
+ # shift, sidestep-l/r
+ ('Quake', (8, 10, 12, 14, 16),
+ (60, 65, 70, 75, 80),
+ (20, 20, 20, 20, 20)),
+ # shake, sidestep-l/r
+ ('Spin', (10, 12, 15, 18, 20),
+ (70, 75, 80, 85, 90),
+ (35, 35, 35, 35, 35)),
+ # spin, sidestep-l/r
+ ('WriteOff', (6, 7, 8, 9, 10),
+ (60, 65, 75, 85, 90),
+ (15, 15, 15, 15, 15))) },
+ # slip-forward, sidestep-l/r
+
+ # Legal Eagle (A)
+ 'le': { 'name': TTLocalizer.SuitLegalEagle,
+ 'singularname': TTLocalizer.SuitLegalEagleS,
+ 'pluralname': TTLocalizer.SuitLegalEagleP,
+ 'level': 6,
+ 'hp': (72, 90, 110, 132, 156), # actual suit level * 4
+ 'def': (30, 35, 40, 45, 50),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (('EvilEye', (10, 11, 13, 15, 16), # dmg
+ (60, 75, 80, 85, 90), # acc
+ (20, 20, 20, 20, 20)), # freq
+ # cringe&slip-backward, duck
+ ('Jargon', (7, 9, 11, 13, 15),
+ (60, 70, 75, 80, 90),
+ (15, 15, 15, 15, 15)),
+ # cringe, sidestep-l/r
+ ('Legalese', (11, 13, 16, 19, 21),
+ (55, 65, 75, 85, 95),
+ (35, 35, 35, 35, 35)),
+ # cringe, sidestep-l/r
+ ('PeckingOrder', (12, 15, 17, 19, 22),
+ (70, 75, 80, 85, 95),
+ (30, 30, 30, 30, 30))) },
+ # duck, sidestep-l/r
+
+ # Big Wig (A)
+ 'bw': { 'name': TTLocalizer.SuitBigWig,
+ 'singularname': TTLocalizer.SuitBigWigS,
+ 'pluralname': TTLocalizer.SuitBigWigP,
+ 'level': 7,
+ 'hp': (90, 110, 132, 156, 200), # actual suit level * 4
+ 'def': (35, 40, 45, 50, 55),
+ 'freq': (50, 30, 10, 5, 5),
+ 'acc': (35, 40, 45, 50, 55),
+ 'attacks': (
+ #('CigarSmoke', (12, 14, 16, 18, 20), # dmg
+ # (65, 75, 85, 90, 95), # acc
+ # (0, 0, 0, 0, 0)), # freq
+ # not implemented defaults to finger wag
+ # cringe, sidestep-left/right
+ #('Gavel', (14, 16, 19, 22, 24),
+ # (70, 75, 85, 90, 95),
+ # (0, 0, 0, 0, 0)),
+ # not implemented defaults to finger wag
+ # slip-backward, sidestep-l/r
+ ('PowerTrip', (10, 11, 13, 15, 16),
+ (75, 80, 85, 90, 95),
+ (50, 50, 50, 50, 50)),
+ # slip-forward, jump
+ ('ThrowBook', (13, 15, 17, 19, 21),
+ (80, 85, 85, 85, 90),
+ (50, 50, 50, 50, 50))) },
+ # not implemented defaults to finger wag
+ # conked&neutral, sidestep-l/r
+
+ }
+
+ATK_TGT_UNKNOWN = 1
+ATK_TGT_SINGLE = 2
+ATK_TGT_GROUP = 3
+SuitAttacks = {
+ 'Audit': ('phone', ATK_TGT_SINGLE),
+ 'Bite': ('throw-paper', ATK_TGT_SINGLE),
+ 'BounceCheck': ('throw-paper', ATK_TGT_SINGLE),
+ 'BrainStorm': ('effort', ATK_TGT_SINGLE),
+ 'BuzzWord': ('speak', ATK_TGT_SINGLE),
+ 'Calculate': ('phone', ATK_TGT_SINGLE),
+ 'Canned': ('throw-paper', ATK_TGT_SINGLE),
+ 'Chomp': ('throw-paper', ATK_TGT_SINGLE),
+ 'CigarSmoke': ('cigar-smoke', ATK_TGT_SINGLE),
+ 'ClipOnTie': ('throw-paper', ATK_TGT_SINGLE),
+ 'Crunch': ('throw-object', ATK_TGT_SINGLE),
+ 'Demotion': ('magic1', ATK_TGT_SINGLE),
+ 'DoubleTalk': ('speak', ATK_TGT_SINGLE),
+ 'Downsize': ('magic2', ATK_TGT_SINGLE),
+ 'EvictionNotice': ('throw-paper', ATK_TGT_SINGLE),
+ 'EvilEye': ('glower', ATK_TGT_SINGLE),
+ 'Filibuster': ('speak', ATK_TGT_SINGLE),
+ 'FillWithLead': ('pencil-sharpener', ATK_TGT_SINGLE),
+ 'FingerWag': ('finger-wag', ATK_TGT_SINGLE),
+ 'Fired': ('magic2', ATK_TGT_SINGLE),
+ 'FiveOClockShadow': ('glower', ATK_TGT_SINGLE),
+ 'FloodTheMarket': ('glower', ATK_TGT_SINGLE),
+ 'FountainPen': ('pen-squirt', ATK_TGT_SINGLE),
+ 'FreezeAssets': ('glower', ATK_TGT_SINGLE),
+ 'Gavel': ('gavel', ATK_TGT_SINGLE),
+ 'GlowerPower': ('glower', ATK_TGT_SINGLE),
+ 'GuiltTrip': ('magic1', ATK_TGT_GROUP),
+ 'HalfWindsor': ('throw-paper', ATK_TGT_SINGLE),
+ 'HangUp': ('phone', ATK_TGT_SINGLE),
+ 'HeadShrink': ('magic1', ATK_TGT_SINGLE),
+ 'HotAir': ('speak', ATK_TGT_SINGLE),
+ 'Jargon': ('speak', ATK_TGT_SINGLE),
+ 'Legalese': ('speak', ATK_TGT_SINGLE),
+ 'Liquidate': ('magic1', ATK_TGT_SINGLE),
+ 'MarketCrash': ('throw-paper', ATK_TGT_SINGLE),
+ 'MumboJumbo': ('speak', ATK_TGT_SINGLE),
+ 'ParadigmShift': ('magic2', ATK_TGT_GROUP),
+ 'PeckingOrder': ('throw-object', ATK_TGT_SINGLE),
+ 'PickPocket': ('pickpocket', ATK_TGT_SINGLE),
+ 'PinkSlip': ('throw-paper', ATK_TGT_SINGLE),
+ 'PlayHardball': ('throw-paper', ATK_TGT_SINGLE),
+ 'PoundKey': ('phone', ATK_TGT_SINGLE),
+ 'PowerTie': ('throw-paper', ATK_TGT_SINGLE),
+ 'PowerTrip': ('magic1', ATK_TGT_GROUP),
+ 'Quake': ('quick-jump', ATK_TGT_GROUP),
+ 'RazzleDazzle': ('smile', ATK_TGT_SINGLE),
+ 'RedTape': ('throw-object', ATK_TGT_SINGLE),
+ 'ReOrg': ('magic3', ATK_TGT_SINGLE),
+ 'RestrainingOrder': ('throw-paper', ATK_TGT_SINGLE),
+ 'Rolodex': ('roll-o-dex', ATK_TGT_SINGLE),
+ 'RubberStamp': ('rubber-stamp', ATK_TGT_SINGLE),
+ 'RubOut': ('hold-eraser', ATK_TGT_SINGLE),
+ 'Sacked': ('throw-paper', ATK_TGT_SINGLE),
+ 'SandTrap': ('golf-club-swing', ATK_TGT_SINGLE),
+ 'Schmooze': ('speak', ATK_TGT_SINGLE),
+ 'Shake': ('stomp', ATK_TGT_GROUP),
+ 'Shred': ('shredder', ATK_TGT_SINGLE),
+ 'SongAndDance': ('song-and-dance', ATK_TGT_SINGLE),
+ 'Spin': ('magic3', ATK_TGT_SINGLE),
+ 'Synergy': ('magic3', ATK_TGT_GROUP),
+ 'Tabulate': ('phone', ATK_TGT_SINGLE),
+ 'TeeOff': ('golf-club-swing', ATK_TGT_SINGLE),
+ 'ThrowBook': ('throw-object', ATK_TGT_SINGLE),
+ 'Tremor': ('stomp', ATK_TGT_GROUP),
+ 'Watercooler': ('watercooler', ATK_TGT_SINGLE),
+ 'Withdrawal': ('magic1', ATK_TGT_SINGLE),
+ 'WriteOff': ('hold-pencil', ATK_TGT_SINGLE),
+ }
+
+AUDIT = SuitAttacks.keys().index('Audit')
+BITE = SuitAttacks.keys().index('Bite')
+BOUNCE_CHECK = SuitAttacks.keys().index('BounceCheck')
+BRAIN_STORM = SuitAttacks.keys().index('BrainStorm')
+BUZZ_WORD = SuitAttacks.keys().index('BuzzWord')
+CALCULATE = SuitAttacks.keys().index('Calculate')
+CANNED = SuitAttacks.keys().index('Canned')
+CHOMP = SuitAttacks.keys().index('Chomp')
+CIGAR_SMOKE = SuitAttacks.keys().index('CigarSmoke')
+CLIPON_TIE = SuitAttacks.keys().index('ClipOnTie')
+CRUNCH = SuitAttacks.keys().index('Crunch')
+DEMOTION = SuitAttacks.keys().index('Demotion')
+DOWNSIZE = SuitAttacks.keys().index('Downsize')
+DOUBLE_TALK = SuitAttacks.keys().index('DoubleTalk')
+EVICTION_NOTICE = SuitAttacks.keys().index('EvictionNotice')
+EVIL_EYE = SuitAttacks.keys().index('EvilEye')
+FILIBUSTER = SuitAttacks.keys().index('Filibuster')
+FILL_WITH_LEAD = SuitAttacks.keys().index('FillWithLead')
+FINGER_WAG = SuitAttacks.keys().index('FingerWag')
+FIRED = SuitAttacks.keys().index('Fired')
+FIVE_O_CLOCK_SHADOW = SuitAttacks.keys().index('FiveOClockShadow')
+FLOOD_THE_MARKET = SuitAttacks.keys().index('FloodTheMarket')
+FOUNTAIN_PEN = SuitAttacks.keys().index('FountainPen')
+FREEZE_ASSETS = SuitAttacks.keys().index('FreezeAssets')
+GAVEL = SuitAttacks.keys().index('Gavel')
+GLOWER_POWER = SuitAttacks.keys().index('GlowerPower')
+GUILT_TRIP = SuitAttacks.keys().index('GuiltTrip')
+HALF_WINDSOR = SuitAttacks.keys().index('HalfWindsor')
+HANG_UP = SuitAttacks.keys().index('HangUp')
+HEAD_SHRINK = SuitAttacks.keys().index('HeadShrink')
+HOT_AIR = SuitAttacks.keys().index('HotAir')
+JARGON = SuitAttacks.keys().index('Jargon')
+LEGALESE = SuitAttacks.keys().index('Legalese')
+LIQUIDATE = SuitAttacks.keys().index('Liquidate')
+MARKET_CRASH = SuitAttacks.keys().index('MarketCrash')
+MUMBO_JUMBO = SuitAttacks.keys().index('MumboJumbo')
+PARADIGM_SHIFT = SuitAttacks.keys().index('ParadigmShift')
+PECKING_ORDER = SuitAttacks.keys().index('PeckingOrder')
+PICK_POCKET = SuitAttacks.keys().index('PickPocket')
+PINK_SLIP = SuitAttacks.keys().index('PinkSlip')
+PLAY_HARDBALL = SuitAttacks.keys().index('PlayHardball')
+POUND_KEY = SuitAttacks.keys().index('PoundKey')
+POWER_TIE = SuitAttacks.keys().index('PowerTie')
+POWER_TRIP = SuitAttacks.keys().index('PowerTrip')
+QUAKE = SuitAttacks.keys().index('Quake')
+RAZZLE_DAZZLE = SuitAttacks.keys().index('RazzleDazzle')
+RED_TAPE = SuitAttacks.keys().index('RedTape')
+RE_ORG = SuitAttacks.keys().index('ReOrg')
+RESTRAINING_ORDER = SuitAttacks.keys().index('RestrainingOrder')
+ROLODEX = SuitAttacks.keys().index('Rolodex')
+RUBBER_STAMP = SuitAttacks.keys().index('RubberStamp')
+RUB_OUT = SuitAttacks.keys().index('RubOut')
+SACKED = SuitAttacks.keys().index('Sacked')
+SANDTRAP = SuitAttacks.keys().index('SandTrap')
+SCHMOOZE = SuitAttacks.keys().index('Schmooze')
+SHAKE = SuitAttacks.keys().index('Shake')
+SHRED = SuitAttacks.keys().index('Shred')
+SONG_AND_DANCE = SuitAttacks.keys().index('SongAndDance')
+SPIN = SuitAttacks.keys().index('Spin')
+SYNERGY = SuitAttacks.keys().index('Synergy')
+TABULATE = SuitAttacks.keys().index('Tabulate')
+TEE_OFF = SuitAttacks.keys().index('TeeOff')
+THROW_BOOK = SuitAttacks.keys().index('ThrowBook')
+TREMOR = SuitAttacks.keys().index('Tremor')
+WATERCOOLER = SuitAttacks.keys().index('Watercooler')
+WITHDRAWAL = SuitAttacks.keys().index('Withdrawal')
+WRITE_OFF = SuitAttacks.keys().index('WriteOff')
+
+def getFaceoffTaunt(suitName, doId):
+ if SuitFaceoffTaunts.has_key(suitName):
+ taunts = SuitFaceoffTaunts[suitName]
+ else:
+ taunts = TTLocalizer.SuitFaceoffDefaultTaunts
+ #return random.choice(taunts)
+ return taunts[doId % len(taunts)]
+
+SuitFaceoffTaunts = OTPLocalizer.SuitFaceoffTaunts
+
+
+
+def getAttackTauntIndexFromIndex(suit, attackIndex):
+ adict = getSuitAttack(suit.getStyleName(), suit.getLevel(), attackIndex)
+ return getAttackTauntIndex(adict['name'])
+
+def getAttackTauntIndex(attackName):
+ if (SuitAttackTaunts.has_key(attackName)):
+ taunts = SuitAttackTaunts[attackName]
+ return random.randint(0, len(taunts)-1)
+ else:
+ return 1
+
+def getAttackTaunt(attackName, index=None):
+ if (SuitAttackTaunts.has_key(attackName)):
+ taunts = SuitAttackTaunts[attackName]
+ else:
+ taunts = TTLocalizer.SuitAttackDefaultTaunts
+
+ if (index != None):
+ # If there's been a mistake, just use a default taunt
+ if (index >= len(taunts)):
+ notify.warning("index exceeds length of taunts list in getAttackTaunt")
+ return TTLocalizer.SuitAttackDefaultTaunts[0]
+
+ return taunts[index]
+ else:
+ return random.choice(taunts)
+
+
+
+SuitAttackTaunts = TTLocalizer.SuitAttackTaunts
+
diff --git a/toontown/src/battle/__init__.py b/toontown/src/battle/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/battle/bossCogFrontAttack.ptf b/toontown/src/battle/bossCogFrontAttack.ptf
new file mode 100644
index 0000000..0a20764
--- /dev/null
+++ b/toontown/src/battle/bossCogFrontAttack.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 4.600)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereSurfaceEmitter")
+p0.setPoolSize(200)
+p0.setBirthRate(0.0050)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.0000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/gear")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.150)
+p0.renderer.setFinalXScale(0.300)
+p0.renderer.setInitialYScale(0.150)
+p0.renderer.setFinalYScale(0.300)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, -10.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Surface parameters
+p0.emitter.setRadius(1.0000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 50.0000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/brainStorm.ptf b/toontown/src/battle/brainStorm.ptf
new file mode 100644
index 0000000..8caba42
--- /dev/null
+++ b/toontown/src/battle/brainStorm.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 0.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(70)
+p0.setBirthRate(0.4)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/brainstorm-box")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.600)
+p0.renderer.setFinalXScale(0.0400)
+p0.renderer.setInitialYScale(0.30)
+p0.renderer.setFinalYScale(0.0400 )
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 5.0000))
+# Disc parameters
+p0.emitter.setRadius(0.5000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(15.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/buzzWord.ptf b/toontown/src/battle/buzzWord.ptf
new file mode 100644
index 0000000..3c0c733
--- /dev/null
+++ b/toontown/src/battle/buzzWord.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 2.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(7)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.0000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/buzzwords-crash")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.300)
+p0.renderer.setFinalXScale(0.070)
+p0.renderer.setInitialYScale(0.200)
+p0.renderer.setFinalYScale(0.050)
+p0.renderer.setNonanimatedTheta(20.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(8.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 7.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -3.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.0010)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(64.5449, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/calculate.ptf b/toontown/src/battle/calculate.ptf
new file mode 100644
index 0000000..79794d9
--- /dev/null
+++ b/toontown/src/battle/calculate.ptf
@@ -0,0 +1,52 @@
+
+self.reset()
+self.setPos(0.000, 2.5, 3.5)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(30)
+p0.setBirthRate(0.4000)
+p0.setLitterSize(3)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.9000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.3000)
+p0.factory.setTerminalVelocityBase(8.0000)
+p0.factory.setTerminalVelocitySpread(4.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/audit-plus")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.0000)
+p0.renderer.setFinalXScale(0.400)
+p0.renderer.setInitialYScale(0.0000)
+p0.renderer.setFinalYScale(0.400)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(11.0000)
+p0.emitter.setAmplitudeSpread(2.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -2.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.5000)
+self.addParticles(p0)
diff --git a/toontown/src/battle/confetti.ptf b/toontown/src/battle/confetti.ptf
new file mode 100644
index 0000000..092627c
--- /dev/null
+++ b/toontown/src/battle/confetti.ptf
@@ -0,0 +1,66 @@
+self.reset()
+self.setPos(0.000, 0.000, 0.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(350)
+p0.setBirthRate(0.0200)
+p0.setLitterSize(5)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.7000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+#p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/spark")
+#p0.renderer.addTextureFromFile('confetti.png')
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.0070)
+p0.renderer.setFinalXScale(0.0500)
+p0.renderer.setInitialYScale(0.0070)
+p0.renderer.setFinalYScale(0.0500)
+p0.renderer.setNonanimatedTheta(145.0080)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+#p0.renderer.getColorInterpolationManager().addSinusoid(0.0,0.60000002384185791,Vec4(1.0,0.0,0.0,1.0),Vec4(0.0,1.0,0.0,1.0),0.30000001192092896,1)
+#p0.renderer.getColorInterpolationManager().addSinusoid(0.5,1.0,Vec4(0.0,0.0,1.0,1.0),Vec4(1.0,0.0,0.0,1.0),0.30000001192092896,1)
+#p0.renderer.getColorInterpolationManager().addSinusoid(0.0,1.0,Vec4(1.0,0.0,0.0,1.0),Vec4(0.0,1.0,0.0,1.0),0.5,0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Disc parameters
+p0.emitter.setRadius(0.0100)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('gravity')
+# Force parameters
+force0 = LinearJitterForce(5.0000, 0)
+force0.setVectorMasks(1, 1, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearSinkForce(Point3(0.0000, 0.0000, -0.8000), LinearDistanceForce.FTONEOVERRSQUARED, 0.5000, 1.0000, 1)
+force1.setVectorMasks(1, 1, 1)
+force1.setActive(1)
+f0.addForce(force1)
+self.addForceGroup(f0)
\ No newline at end of file
diff --git a/toontown/src/battle/demotionFreeze.ptf b/toontown/src/battle/demotionFreeze.ptf
new file mode 100644
index 0000000..2f20a80
--- /dev/null
+++ b/toontown/src/battle/demotionFreeze.ptf
@@ -0,0 +1,53 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(2.000, 2.000, 2.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+#p0.setRenderer("PointParticleRenderer")
+p0.setEmitter("SphereSurfaceEmitter")
+p0.setPoolSize(70)
+p0.setBirthRate(0.0200)
+p0.setLitterSize(10)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/roll-o-dex")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.04)
+p0.renderer.setFinalXScale(0.000)
+p0.renderer.setInitialYScale(0.04)
+p0.renderer.setFinalYScale(0.000)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(-1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Surface parameters
+p0.emitter.setRadius(1.0000)
+self.addParticles(p0)
diff --git a/toontown/src/battle/demotionSpray.ptf b/toontown/src/battle/demotionSpray.ptf
new file mode 100644
index 0000000..3f62d2d
--- /dev/null
+++ b/toontown/src/battle/demotionSpray.ptf
@@ -0,0 +1,53 @@
+
+self.reset()
+self.setPos(0.000, 4.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+#p0.setRenderer("PointParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(150)
+p0.setBirthRate(0.0500)
+p0.setLitterSize(7)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.8000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/roll-o-dex")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.04)
+p0.renderer.setFinalXScale(0.009)
+p0.renderer.setInitialYScale(0.04)
+p0.renderer.setFinalYScale(0.009)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 6.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.8900)
+self.addParticles(p0)
diff --git a/toontown/src/battle/demotionUnFreeze.ptf b/toontown/src/battle/demotionUnFreeze.ptf
new file mode 100644
index 0000000..9d79c25
--- /dev/null
+++ b/toontown/src/battle/demotionUnFreeze.ptf
@@ -0,0 +1,54 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(2.000, 2.000, 2.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+#p0.setRenderer("PointParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+#p0.setEmitter("SphereSurfaceEmitter")
+p0.setPoolSize(70)
+p0.setBirthRate(0.0200)
+p0.setLitterSize(10)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/roll-o-dex")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.04)
+p0.renderer.setFinalXScale(0.000)
+p0.renderer.setInitialYScale(0.04)
+p0.renderer.setFinalYScale(0.000)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(4.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Surface parameters
+p0.emitter.setRadius(0.6000)
+self.addParticles(p0)
diff --git a/toontown/src/battle/doubleTalkLeft.ptf b/toontown/src/battle/doubleTalkLeft.ptf
new file mode 100644
index 0000000..751f34e
--- /dev/null
+++ b/toontown/src/battle/doubleTalkLeft.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 3.000, 3.000)
+self.setHpr(55.000, 0.000, 0.000)
+self.setScale(3.000, 3.000, 3.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(2)
+p0.setBirthRate(0.7000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.7000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/doubletalk-double")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(1.50)
+p0.renderer.setFinalXScale(1.50)
+p0.renderer.setInitialYScale(1.50)
+p0.renderer.setFinalYScale(1.50)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(12.000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.6000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -8.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.0500)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(6.000, -3.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.5000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/doubleTalkRight.ptf b/toontown/src/battle/doubleTalkRight.ptf
new file mode 100644
index 0000000..1a2ec3f
--- /dev/null
+++ b/toontown/src/battle/doubleTalkRight.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 3.000, 3.000)
+self.setHpr(-55.000, 0.000, 0.000)
+self.setScale(3.000, 3.000, 3.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(2)
+p0.setBirthRate(0.7000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.7000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/doubletalk-good")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(1.5)
+p0.renderer.setFinalXScale(1.5)
+p0.renderer.setInitialYScale(1.5)
+p0.renderer.setFinalYScale(1.5)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(12.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.6000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -8.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.0500)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(-6.000, -3.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.5000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/downsizeCloud.ptf b/toontown/src/battle/downsizeCloud.ptf
new file mode 100644
index 0000000..d6d4fbe
--- /dev/null
+++ b/toontown/src/battle/downsizeCloud.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 0.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(12)
+p0.setLitterSpread(4)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.3000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/snow-particle")
+p0.renderer.setColor(Vec4(1.00, 1.00, 0.00, 0.80))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.015)
+p0.renderer.setFinalXScale(0.075)
+p0.renderer.setInitialYScale(0.0075)
+p0.renderer.setFinalYScale(0.055)
+p0.renderer.setNonanimatedTheta(20.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(-1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(2.70)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(14.5449, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/downsizeSpray.ptf b/toontown/src/battle/downsizeSpray.ptf
new file mode 100644
index 0000000..71ffc15
--- /dev/null
+++ b/toontown/src/battle/downsizeSpray.ptf
@@ -0,0 +1,64 @@
+
+self.reset()
+self.setPos(0.000, 2.900, 3.400)
+self.setHpr(0.000, 60.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(50)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(7)
+p0.setLitterSpread(2)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.3000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/snow-particle")
+p0.renderer.setColor(Vec4(1.00, 1.00, 0.00, 0.80))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.0750)
+p0.renderer.setFinalXScale(0.0375)
+p0.renderer.setInitialYScale(0.055)
+p0.renderer.setFinalYScale(0.024)
+p0.renderer.setNonanimatedTheta(20.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(4.9000)
+p0.emitter.setAmplitudeSpread(0.3000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 7.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -3.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.0010)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -5.3000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 2.5308, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearVectorForce(Vec3(0.0000, -7.0000, 0.0000), 1.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+force3 = LinearJitterForce(8.5449, 0)
+force3.setActive(1)
+f0.addForce(force3)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/filibusterSpray.ptf b/toontown/src/battle/filibusterSpray.ptf
new file mode 100644
index 0000000..e60381b
--- /dev/null
+++ b/toontown/src/battle/filibusterSpray.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 3.000, 4.000)
+self.setHpr(0.000, 55.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(1)
+p0.setBirthRate(0.400)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.2700)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/filibuster-cut")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.3*1.5)
+p0.renderer.setFinalXScale(0.75*1.5)
+p0.renderer.setInitialYScale(0.15*1.5)
+p0.renderer.setFinalYScale(0.25*1.5)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 8.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -1.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.1000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, -9.0000, -11.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.3661, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/fillWithLeadSmother.ptf b/toontown/src/battle/fillWithLeadSmother.ptf
new file mode 100644
index 0000000..e29d638
--- /dev/null
+++ b/toontown/src/battle/fillWithLeadSmother.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereSurfaceEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.0400)
+p0.setLitterSize(20)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.0300)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/mumbojumbo-iron")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.0100)
+p0.renderer.setFinalXScale(0.0100)
+p0.renderer.setInitialYScale(0.0100)
+p0.renderer.setFinalYScale(0.0100)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(-5.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Surface parameters
+p0.emitter.setRadius(1.1000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(37.2697, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/fillWithLeadSpray.ptf b/toontown/src/battle/fillWithLeadSpray.ptf
new file mode 100644
index 0000000..1ea5c09
--- /dev/null
+++ b/toontown/src/battle/fillWithLeadSpray.ptf
@@ -0,0 +1,67 @@
+
+self.reset()
+self.setPos(0.000, 2.000, 2.300)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(150)
+p0.setBirthRate(0.0400)
+p0.setLitterSize(45)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(2.9000)
+p0.factory.setLifespanSpread(0.4000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.2000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/roll-o-dex")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.010)
+p0.renderer.setFinalXScale(0.010)
+p0.renderer.setInitialYScale(0.010)
+p0.renderer.setFinalYScale(0.010)
+p0.renderer.setNonanimatedTheta(5.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0000)
+p0.emitter.setAmplitudeSpread(1.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 5.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -7.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.01000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearVectorForce(Vec3(0.0000, 0.0000, 5.0000), 1.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearSinkForce(Point3(0.0000, 0.0000, -8.0000), LinearDistanceForce.FTONEOVERRSQUARED, 14.5479, 155.9407, 1)
+force1.setActive(1)
+f0.addForce(force1)
+force2 = LinearNoiseForce(1.7000, 0)
+force2.setActive(1)
+f0.addForce(force2)
+force3 = LinearJitterForce(12.5698, 0)
+force3.setActive(1)
+f0.addForce(force3)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/fingerwag.ptf b/toontown/src/battle/fingerwag.ptf
new file mode 100644
index 0000000..2661b47
--- /dev/null
+++ b/toontown/src/battle/fingerwag.ptf
@@ -0,0 +1,64 @@
+
+self.reset()
+self.setPos(0.167, 0.692, 3.731)
+self.setHpr(90.000, -36.310, -0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("PointEmitter")
+p0.setPoolSize(250)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(2)
+p0.setLitterSpread(2)
+p0.setSystemLifespan(2.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.6000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(410.7267)
+p0.factory.setTerminalVelocitySpread(2.3816)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(0.86)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/blah")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.400)
+p0.renderer.setFinalXScale(0.0200)
+p0.renderer.setInitialYScale(0.200)
+p0.renderer.setFinalYScale(0.0200)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPNOBLEND)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETEXPLICIT)
+p0.emitter.setAmplitude(3.0000)
+p0.emitter.setAmplitudeSpread(2.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Point parameters
+p0.emitter.setLocation(Point3(0.0000, 0.0000, 0.0000))
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('jfo')
+# Force parameters
+force0 = LinearJitterForce(4.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearSourceForce(Point3(0.0000, 0.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 0.5000, 1.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+force2 = LinearSinkForce(Point3(0.0000, 1.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.0000, 1)
+force2.setActive(1)
+f0.addForce(force2)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/fingerwag2.ptf b/toontown/src/battle/fingerwag2.ptf
new file mode 100644
index 0000000..ac5c2b2
--- /dev/null
+++ b/toontown/src/battle/fingerwag2.ptf
@@ -0,0 +1,55 @@
+
+self.reset()
+self.setPos(0.228, 0.880, 4.314)
+self.setHpr(-2.862, -36.310, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("RingEmitter")
+p0.setPoolSize(250)
+p0.setBirthRate(0.3000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(2.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.6000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(410.7267)
+p0.factory.setTerminalVelocitySpread(2.3816)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(0.86)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/blah")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.400)
+p0.renderer.setFinalXScale(0.0200)
+p0.renderer.setInitialYScale(0.200)
+p0.renderer.setFinalYScale(0.0200)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPNOBLEND)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Ring parameters
+p0.emitter.setRadius(1.0000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('jfo')
+# Force parameters
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/firedBaseFlame.ptf b/toontown/src/battle/firedBaseFlame.ptf
new file mode 100644
index 0000000..f8feb0d
--- /dev/null
+++ b/toontown/src/battle/firedBaseFlame.ptf
@@ -0,0 +1,52 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 0.500)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(2.500, 4.500, 2.500)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(150)
+p0.setBirthRate(0.0200)
+p0.setLitterSize(10)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.100)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/fire")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.15)
+p0.renderer.setFinalXScale(0.50)
+p0.renderer.setInitialYScale(0.30)
+p0.renderer.setFinalYScale(0.50)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 2.200))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -30.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.4000)
+self.addParticles(p0)
diff --git a/toontown/src/battle/firedFlame.ptf b/toontown/src/battle/firedFlame.ptf
new file mode 100644
index 0000000..ce9da15
--- /dev/null
+++ b/toontown/src/battle/firedFlame.ptf
@@ -0,0 +1,52 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 0.500)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(2.500, 4.500, 2.500)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(60)
+p0.setBirthRate(0.0220)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.500)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/fire")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.15)
+p0.renderer.setFinalXScale(0.00025)
+p0.renderer.setInitialYScale(0.30)
+p0.renderer.setFinalYScale(0.00025)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 4.800))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -30.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.4000)
+self.addParticles(p0)
diff --git a/toontown/src/battle/freezeAssets.ptf b/toontown/src/battle/freezeAssets.ptf
new file mode 100644
index 0000000..a161b20
--- /dev/null
+++ b/toontown/src/battle/freezeAssets.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, -0.200)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(200)
+p0.setBirthRate(0.0800)
+p0.setLitterSize(7)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.7000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/snow-particle")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.064)
+p0.renderer.setFinalXScale(0.001)
+p0.renderer.setInitialYScale(0.064)
+p0.renderer.setFinalYScale(0.001)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(8.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 5.0000))
+# Disc parameters
+p0.emitter.setRadius(0.4500)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(15.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/gearExplosion.ptf b/toontown/src/battle/gearExplosion.ptf
new file mode 100644
index 0000000..5da1c6d
--- /dev/null
+++ b/toontown/src/battle/gearExplosion.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 4.600)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(40)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(40)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(4.2000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/gear")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.1600)
+p0.renderer.setFinalXScale(0.160)
+p0.renderer.setInitialYScale(0.1600)
+p0.renderer.setFinalYScale(0.160)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(9.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 9.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -2.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(3.2282)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0100, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/gearExplosionBig.ptf b/toontown/src/battle/gearExplosionBig.ptf
new file mode 100644
index 0000000..77dc50b
--- /dev/null
+++ b/toontown/src/battle/gearExplosionBig.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 4.600)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(40)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(40)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(4.2000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/gear")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.1600)
+p0.renderer.setFinalXScale(0.160)
+p0.renderer.setInitialYScale(0.1600)
+p0.renderer.setFinalYScale(0.160)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(15.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 18.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -2.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(1.6282)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0100, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/gearExplosionSmall.ptf b/toontown/src/battle/gearExplosionSmall.ptf
new file mode 100644
index 0000000..db2c54b
--- /dev/null
+++ b/toontown/src/battle/gearExplosionSmall.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 4.600)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(6)
+p0.setBirthRate(0.4000)
+p0.setLitterSize(2)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.5000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/gear")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.112)
+p0.renderer.setFinalXScale(0.112)
+p0.renderer.setInitialYScale(0.112)
+p0.renderer.setFinalYScale(0.112)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(9.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 9.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -2.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(3.2282)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0100, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/gearExplosionWide.ptf b/toontown/src/battle/gearExplosionWide.ptf
new file mode 100644
index 0000000..14b6b09
--- /dev/null
+++ b/toontown/src/battle/gearExplosionWide.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 0.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(40)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(40)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(4.2000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/gear")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.1600)
+p0.renderer.setFinalXScale(0.1600)
+p0.renderer.setInitialYScale(0.1600)
+p0.renderer.setFinalYScale(0.1600)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(15.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 10.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -0.5000))
+# Sphere Volume parameters
+p0.emitter.setRadius(1.7500)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/guiltTrip.ptf b/toontown/src/battle/guiltTrip.ptf
new file mode 100644
index 0000000..7d96c2c
--- /dev/null
+++ b/toontown/src/battle/guiltTrip.ptf
@@ -0,0 +1,51 @@
+
+self.reset()
+self.setPos(-2.000, 2.500, 2.200)
+self.setHpr(-90.000, 0.000, 0.000)
+self.setScale(4.800, 4.800, 4.800)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.0800)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.4000)
+p0.factory.setLifespanSpread(0.000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1.0, 0, 0, 0.9))
+p0.renderer.setEdgeColor(Vec4(0.8, 0.8, 0.8, 0.4))
+p0.renderer.setBirthRadius(0.1000)
+p0.renderer.setDeathRadius(15.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(2.4000)
+p0.emitter.setAmplitudeSpread(1.1000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 1.1000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.120)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(14.5449, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
\ No newline at end of file
diff --git a/toontown/src/battle/headShrinkCloud.ptf b/toontown/src/battle/headShrinkCloud.ptf
new file mode 100644
index 0000000..7afc3d7
--- /dev/null
+++ b/toontown/src/battle/headShrinkCloud.ptf
@@ -0,0 +1,50 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 8.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("SphereSurfaceEmitter")
+p0.setPoolSize(60)
+p0.setBirthRate(0.100)
+p0.setLitterSize(5)
+p0.setLitterSpread(3)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.3000)
+p0.factory.setLifespanSpread(0.100)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1, 0.84, 0, 1.00))
+p0.renderer.setEdgeColor(Vec4(1, 1, 1, 0.3))
+p0.renderer.setBirthRadius(0.1500)
+p0.renderer.setDeathRadius(0.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(4.0000)
+p0.emitter.setAmplitudeSpread(2.5000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Surface parameters
+p0.emitter.setRadius(0.0200)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(33.2697, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/headShrinkDrop.ptf b/toontown/src/battle/headShrinkDrop.ptf
new file mode 100644
index 0000000..9c1347e
--- /dev/null
+++ b/toontown/src/battle/headShrinkDrop.ptf
@@ -0,0 +1,50 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 7.500)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(2.000, 2.000, 2.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(60)
+p0.setBirthRate(0.1500)
+p0.setLitterSize(3)
+p0.setLitterSpread(2)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.2000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1, 0.84, 0, 1.00))
+p0.renderer.setEdgeColor(Vec4(1, 1, 1, 0.3))
+p0.renderer.setBirthRadius(0.0400)
+p0.renderer.setDeathRadius(0.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(2.300)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 4.0000))
+# Disc parameters
+p0.emitter.setRadius(0.2800)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(0.060, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/headShrinkSpray.ptf b/toontown/src/battle/headShrinkSpray.ptf
new file mode 100644
index 0000000..73c8775
--- /dev/null
+++ b/toontown/src/battle/headShrinkSpray.ptf
@@ -0,0 +1,53 @@
+
+self.reset()
+self.setPos(0.000, 2.900, 4.200)
+self.setHpr(0.000, 60.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(60) #60)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(4)
+p0.setLitterSpread(2)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.15) #1.1200)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1, 0.84, 0, 1.00))
+p0.renderer.setEdgeColor(Vec4(1, 1, 1, 0.3))
+p0.renderer.setBirthRadius(0.1500)
+p0.renderer.setDeathRadius(0.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(12.0000)
+p0.emitter.setAmplitudeSpread(0.9000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 5.1000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.4800)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -4.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 2.5308, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearVectorForce(Vec3(0.0000, -7.0000, 0.0000), 1.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/hotAirSpray.ptf b/toontown/src/battle/hotAirSpray.ptf
new file mode 100644
index 0000000..5668472
--- /dev/null
+++ b/toontown/src/battle/hotAirSpray.ptf
@@ -0,0 +1,61 @@
+
+self.reset()
+self.setPos(0.000, 2.500, 3.200) # originally (0,4,4)
+self.setHpr(-180.000, 80.000, -180.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(10)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.6000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/fire")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.6)
+p0.renderer.setFinalXScale(0.3)
+p0.renderer.setInitialYScale(0.6)
+p0.renderer.setFinalYScale(0.3)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(2.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 5.1000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.0200)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -4.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 2.5308, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearVectorForce(Vec3(0.0000, -10.0000, 0.0000), 1.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/icetnt.ptf b/toontown/src/battle/icetnt.ptf
new file mode 100644
index 0000000..92c2279
--- /dev/null
+++ b/toontown/src/battle/icetnt.ptf
@@ -0,0 +1,64 @@
+
+self.reset()
+self.setPos(0.000, 0.000, -0.000)
+self.setHpr(0.000, 10.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(40)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(2)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.2000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/spark")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.3)
+p0.renderer.setFinalXScale(0.3)
+p0.renderer.setInitialYScale(0.3)
+p0.renderer.setFinalYScale(0.03)
+p0.renderer.setNonanimatedTheta(20.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Sparkle parameters
+#p0.renderer.setCenterColor(Vec4(0.78, 0.78, 0, 1.00))
+#p0.renderer.setEdgeColor(Vec4(0.78, 0.78, 0, 1.00))
+#p0.renderer.setBirthRadius(0.0600)
+#p0.renderer.setDeathRadius(0.0600)
+#p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.5000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -2.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.2282)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -19.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0100, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/jargonSpray.ptf b/toontown/src/battle/jargonSpray.ptf
new file mode 100644
index 0000000..1df2725
--- /dev/null
+++ b/toontown/src/battle/jargonSpray.ptf
@@ -0,0 +1,59 @@
+
+self.reset()
+self.setPos(0.000, 3.000, 4.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("LineEmitter")
+p0.setPoolSize(4)
+p0.setBirthRate(0.200)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.0000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/jargon-brow")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.40)
+p0.renderer.setFinalXScale(1.60)
+p0.renderer.setInitialYScale(0.10)
+p0.renderer.setFinalYScale(0.40)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 4.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -9.0000, 0.0000))
+# Line parameters
+p0.emitter.setEndpoint1(Point3(0.0000, 0.0000, 0.0000))
+p0.emitter.setEndpoint2(Point3(0.0000, 0.0000, 0.0000))
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(2.1279, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/legaleseSpray.ptf b/toontown/src/battle/legaleseSpray.ptf
new file mode 100644
index 0000000..ebbb7c3
--- /dev/null
+++ b/toontown/src/battle/legaleseSpray.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 2.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(1)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(3.0000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/buzzwords-crash")
+p0.renderer.setColor(Vec4(0.00, 0.00, 0.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(1.0)
+p0.renderer.setFinalXScale(1.8)
+p0.renderer.setInitialYScale(0.5)
+p0.renderer.setFinalYScale(0.9)
+p0.renderer.setNonanimatedTheta(20.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(8.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 7.0000, -1.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -3.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.0010)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(19.5449, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/liquidate.ptf b/toontown/src/battle/liquidate.ptf
new file mode 100644
index 0000000..de55b8b
--- /dev/null
+++ b/toontown/src/battle/liquidate.ptf
@@ -0,0 +1,52 @@
+
+self.reset()
+self.setPos(0.000, 0.000, -0.200)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.0400)
+p0.setLitterSize(3)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.4000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/raindrop")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.06)
+p0.renderer.setFinalXScale(0.06)
+p0.renderer.setInitialYScale(0.225)
+p0.renderer.setFinalYScale(0.225)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(16.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 6.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.4500)
+self.addParticles(p0)
diff --git a/toontown/src/battle/mumboJumboSmother.ptf b/toontown/src/battle/mumboJumboSmother.ptf
new file mode 100644
index 0000000..55d0554
--- /dev/null
+++ b/toontown/src/battle/mumboJumboSmother.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereSurfaceEmitter")
+p0.setPoolSize(4)
+p0.setBirthRate(0.1100)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.0300)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/mumbojumbo-iron")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.40)
+p0.renderer.setFinalXScale(0.10)
+p0.renderer.setInitialYScale(0.20)
+p0.renderer.setFinalYScale(0.05)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(-5.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Surface parameters
+p0.emitter.setRadius(1.5000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(37.2697, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/mumboJumboSpray.ptf b/toontown/src/battle/mumboJumboSpray.ptf
new file mode 100644
index 0000000..2c56136
--- /dev/null
+++ b/toontown/src/battle/mumboJumboSpray.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 4.000, 4.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 4.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(3)
+p0.setBirthRate(0.3000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.900)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/mumbojumbo-iron")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.40)
+p0.renderer.setFinalXScale(0.40)
+p0.renderer.setInitialYScale(0.20)
+p0.renderer.setFinalYScale(0.20)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(6.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -9.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.7000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(20.4636, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/numberSpill.ptf b/toontown/src/battle/numberSpill.ptf
new file mode 100644
index 0000000..66f42fa
--- /dev/null
+++ b/toontown/src/battle/numberSpill.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.900, 2.100, 1.90)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.100, 1.100, 1.100)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(6)
+p0.setBirthRate(0.3000)
+p0.setLitterSize(2)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(5.8000)
+p0.factory.setLifespanSpread(0.4000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/raindrop")
+p0.renderer.setColor(Vec4(0, 0, 0, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.2)
+p0.renderer.setFinalXScale(0.03)
+p0.renderer.setInitialYScale(0.3)
+p0.renderer.setFinalYScale(0.05)
+p0.renderer.setNonanimatedTheta(90.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(2.0000)
+p0.emitter.setAmplitudeSpread(1.300)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -2.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.3282)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -33.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0100, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/numberSpray.ptf b/toontown/src/battle/numberSpray.ptf
new file mode 100644
index 0000000..906e047
--- /dev/null
+++ b/toontown/src/battle/numberSpray.ptf
@@ -0,0 +1,61 @@
+
+self.reset()
+self.setPos(0.000, 2.700, 3.900)
+self.setHpr(-180.000, 80.000, -180.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(1)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(2.1000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/fire")
+p0.renderer.setColor(Vec4(0.00, 0.00, 0.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.125)
+p0.renderer.setFinalXScale(0.5)
+p0.renderer.setInitialYScale(0.2)
+p0.renderer.setFinalYScale(1.0)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.1000)
+p0.emitter.setAmplitudeSpread(2.5000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 9.1000, -4.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.500)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -3.5000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 2.5308, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearVectorForce(Vec3(0.0000, -10.0000, 0.0000), 1.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/penSpill.ptf b/toontown/src/battle/penSpill.ptf
new file mode 100644
index 0000000..49e2edd
--- /dev/null
+++ b/toontown/src/battle/penSpill.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 0.000, -0.600)
+self.setHpr(0.000, 0.000, -90.000)
+self.setScale(1.100, 1.100, 1.100)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(70)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(2)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/raindrop")
+p0.renderer.setColor(Vec4(0, 0, 0, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.05)
+p0.renderer.setFinalXScale(0.000)
+p0.renderer.setInitialYScale(0.05)
+p0.renderer.setFinalYScale(0.000)
+p0.renderer.setNonanimatedTheta(90.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -2.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.2282)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -99.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0100, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/pixieDrop.ptf b/toontown/src/battle/pixieDrop.ptf
new file mode 100644
index 0000000..d78d548
--- /dev/null
+++ b/toontown/src/battle/pixieDrop.ptf
@@ -0,0 +1,50 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 6.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(2.000, 2.000, 2.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(150)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(7)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(2.2000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setEdgeColor(Vec4(0.00, 0.00, 1.00, 1.00))
+p0.renderer.setBirthRadius(0.0400)
+p0.renderer.setDeathRadius(0.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 4.0000))
+# Disc parameters
+p0.emitter.setRadius(0.3000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(3.6003, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/pixieExplode.ptf b/toontown/src/battle/pixieExplode.ptf
new file mode 100644
index 0000000..d2b05fb
--- /dev/null
+++ b/toontown/src/battle/pixieExplode.ptf
@@ -0,0 +1,50 @@
+
+self.reset()
+self.setPos(2.500, 0.000, 2.500)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(3.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(7)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.5000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setEdgeColor(Vec4(0.00, 0.00, 1.00, 1.00))
+p0.renderer.setBirthRadius(0.0400)
+p0.renderer.setDeathRadius(0.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETEXPLICIT)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0100)
+p0.emitter.setOffsetForce(Vec3(-0.1000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.5000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.1000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearJitterForce(2.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/pixiePoof.ptf b/toontown/src/battle/pixiePoof.ptf
new file mode 100644
index 0000000..b7d8317
--- /dev/null
+++ b/toontown/src/battle/pixiePoof.ptf
@@ -0,0 +1,45 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(200)
+p0.setBirthRate(0.0200)
+p0.setLitterSize(2)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.0000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setEdgeColor(Vec4(0.04, 0.04, 1.00, 1.00))
+p0.renderer.setBirthRadius(0.0272)
+p0.renderer.setDeathRadius(0.1872)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.200)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
diff --git a/toontown/src/battle/pixieSpray.ptf b/toontown/src/battle/pixieSpray.ptf
new file mode 100644
index 0000000..a1f877d
--- /dev/null
+++ b/toontown/src/battle/pixieSpray.ptf
@@ -0,0 +1,50 @@
+
+self.reset()
+self.setPos(2.00, 0.000, 4.00)
+self.setHpr(-90.000, 45.000, 0.000)
+self.setScale(4.000, 4.000, 4.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(50)
+p0.setBirthRate(0.0500)
+p0.setLitterSize(4)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.6000)
+p0.factory.setLifespanSpread(0.1000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setEdgeColor(Vec4(0.00, 0.00, 1.00, 1.00))
+p0.renderer.setBirthRadius(0.0200)
+p0.renderer.setDeathRadius(0.0500)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.5000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -3.0000, 0.0000))
+# Disc parameters
+p0.emitter.setRadius(0.100)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -30.0000), LinearDistanceForce.FTONEOVERRSQUARED, 3.0400, 1.5000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/pixieWall.ptf b/toontown/src/battle/pixieWall.ptf
new file mode 100644
index 0000000..0ea660a
--- /dev/null
+++ b/toontown/src/battle/pixieWall.ptf
@@ -0,0 +1,50 @@
+
+self.reset()
+self.setPos(2.500, 0.000, 2.500)
+self.setHpr(-90.000, 90.000, -180.000)
+self.setScale(1.50)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(100)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(4.0000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setEdgeColor(Vec4(0.00, 0.00, 1.00, 1.00))
+p0.renderer.setBirthRadius(0.0400)
+p0.renderer.setDeathRadius(0.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(2.5000)
+p0.emitter.setAmplitudeSpread(0.5000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(0.0000, 0.0000, 1.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -1.0000))
+# Disc parameters
+p0.emitter.setRadius(0.5000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearNoiseForce(0.0500, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/poundkey.ptf b/toontown/src/battle/poundkey.ptf
new file mode 100644
index 0000000..d0d4047
--- /dev/null
+++ b/toontown/src/battle/poundkey.ptf
@@ -0,0 +1,62 @@
+
+self.reset()
+self.setPos(-0.500, 1.000, 3.100)
+self.setHpr(-180.000, -0.000, 180.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(40)
+p0.setBirthRate(0.20)
+p0.setLitterSize(3)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.5000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(20.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/poundsign")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.0000)
+p0.renderer.setFinalXScale(0.600)
+p0.renderer.setInitialYScale(0.0000)
+p0.renderer.setFinalYScale(0.600)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(10.0000)
+p0.emitter.setAmplitudeSpread(3.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.200)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearVectorForce(Vec3(0.0000, 0.0000, 0.0000), 100.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
+force0 = LinearJitterForce(4.5449, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/powertrip.ptf b/toontown/src/battle/powertrip.ptf
new file mode 100644
index 0000000..4627432
--- /dev/null
+++ b/toontown/src/battle/powertrip.ptf
@@ -0,0 +1,57 @@
+
+self.reset()
+self.setPos(-2.000, 2.500, 2.200)
+self.setHpr(-90.000, 0.000, 0.000)
+self.setScale(4.800, 4.800, 4.800)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.0800)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.2500)
+p0.factory.setLifespanSpread(0.050)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(0.1, 0.95, 0.2, 1.00))
+p0.renderer.setEdgeColor(Vec4(0, 0, 0, 1.00))
+p0.renderer.setBirthRadius(0.1000)
+p0.renderer.setDeathRadius(15.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(2.4000)
+p0.emitter.setAmplitudeSpread(1.1000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 1.1000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.120)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(10.0000, 0.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 2.5308, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearVectorForce(Vec3(0.0000, 0.0000, 0.0000), 1.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+force2 = LinearJitterForce(4.5449, 0)
+force2.setActive(1)
+f0.addForce(force2)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/powertrip2.ptf b/toontown/src/battle/powertrip2.ptf
new file mode 100644
index 0000000..f5ecf64
--- /dev/null
+++ b/toontown/src/battle/powertrip2.ptf
@@ -0,0 +1,57 @@
+
+self.reset()
+self.setPos(-2.000, 2.500, 2.200)
+self.setHpr(-90.000, 0.000, 0.000)
+self.setScale(4.800, 4.800, 4.800)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.0800)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.2500)
+p0.factory.setLifespanSpread(0.050)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(0.1, 0.95, 0.2, 1.00))
+p0.renderer.setEdgeColor(Vec4(0, 0, 0, 1.00))
+p0.renderer.setBirthRadius(0.1000)
+p0.renderer.setDeathRadius(15.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(2.4000)
+p0.emitter.setAmplitudeSpread(1.1000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 1.1000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.120)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(-10.0000, 0.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 2.5308, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearVectorForce(Vec3(0.0000, 0.0000, 0.0000), 1.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+force2 = LinearJitterForce(4.5449, 0)
+force2.setActive(1)
+f0.addForce(force2)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/reorgCloud.ptf b/toontown/src/battle/reorgCloud.ptf
new file mode 100644
index 0000000..9eb1d9a
--- /dev/null
+++ b/toontown/src/battle/reorgCloud.ptf
@@ -0,0 +1,53 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 3.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(2.000, 2.000, 2.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+#p0.setRenderer("PointParticleRenderer")
+p0.setEmitter("SphereSurfaceEmitter")
+p0.setPoolSize(70)
+p0.setBirthRate(0.0200)
+p0.setLitterSize(10)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/snow-particle")
+p0.renderer.setColor(Vec4(1.00, 0.00, 0.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.003)
+p0.renderer.setFinalXScale(0.000)
+p0.renderer.setInitialYScale(0.003)
+p0.renderer.setFinalYScale(0.000)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(-1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Surface parameters
+p0.emitter.setRadius(1.0000)
+self.addParticles(p0)
diff --git a/toontown/src/battle/reorgSpray.ptf b/toontown/src/battle/reorgSpray.ptf
new file mode 100644
index 0000000..e7ae486
--- /dev/null
+++ b/toontown/src/battle/reorgSpray.ptf
@@ -0,0 +1,53 @@
+
+self.reset()
+self.setPos(0.000, 5.700, 2.700)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+#p0.setRenderer("PointParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(150)
+p0.setBirthRate(0.0500)
+p0.setLitterSize(7)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.8000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/snow-particle")
+p0.renderer.setColor(Vec4(1.00, 0.00, 0.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.003)
+p0.renderer.setFinalXScale(0.009)
+p0.renderer.setInitialYScale(0.003)
+p0.renderer.setFinalYScale(0.009)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 6.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.8900)
+self.addParticles(p0)
diff --git a/toontown/src/battle/restrainingOrderCloud.ptf b/toontown/src/battle/restrainingOrderCloud.ptf
new file mode 100644
index 0000000..60c581e
--- /dev/null
+++ b/toontown/src/battle/restrainingOrderCloud.ptf
@@ -0,0 +1,53 @@
+
+self.reset()
+self.setPos(0.000, 4.000, 3.000)
+self.setHpr(-180.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+#p0.setRenderer("PointParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(60)
+p0.setBirthRate(0.0001)
+p0.setLitterSize(60)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.2000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/roll-o-dex")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.02)
+p0.renderer.setFinalXScale(0.001)
+p0.renderer.setInitialYScale(0.02)
+p0.renderer.setFinalYScale(0.001)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 6.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -18.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.8900)
+self.addParticles(p0)
diff --git a/toontown/src/battle/rollodexStream.ptf b/toontown/src/battle/rollodexStream.ptf
new file mode 100644
index 0000000..4b265c1
--- /dev/null
+++ b/toontown/src/battle/rollodexStream.ptf
@@ -0,0 +1,61 @@
+
+self.reset()
+self.setPos(0.107, 2.799, 3.400)
+self.setHpr(89.908, -20.000, 179.476)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("PointEmitter")
+p0.setPoolSize(60)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(2)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(5.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.0000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(3.0000)
+p0.factory.setMassSpread(2.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/rollodex-card")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.4)
+p0.renderer.setFinalXScale(0.4)
+p0.renderer.setInitialYScale(0.3)
+p0.renderer.setFinalYScale(0.3)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETEXPLICIT)
+p0.emitter.setAmplitude(-15.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Point parameters
+p0.emitter.setLocation(Point3(0.0000, 0.0000, 0.0000))
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forward')
+# Force parameters
+force0 = LinearSourceForce(Point3(0.0000, 0.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.0000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearJitterForce(19.1346, 0)
+force1.setActive(1)
+f0.addForce(force1)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/rollodexVortex.ptf b/toontown/src/battle/rollodexVortex.ptf
new file mode 100644
index 0000000..c68aff1
--- /dev/null
+++ b/toontown/src/battle/rollodexVortex.ptf
@@ -0,0 +1,67 @@
+
+self.reset()
+self.setPos(-0.003, 2.465, 3.714)
+self.setHpr(84.924, 13.378, 56.334) #(70.004, -75.422, 35.756)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("TangentRingEmitter")
+p0.setPoolSize(250)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(5)
+p0.setLitterSpread(3)
+p0.setSystemLifespan(5.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.2500)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(40.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/rollodex-card")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.4)
+p0.renderer.setFinalXScale(0.4)
+p0.renderer.setInitialYScale(0.3)
+p0.renderer.setFinalYScale(0.3)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Tangent Ring parameters
+p0.emitter.setRadius(0.7500)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forward')
+# Force parameters
+force0 = LinearSourceForce(Point3(0.0000, 0.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.0000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearSinkForce(Point3(0.0000, 0.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 5.0000, 6.0000, 0)
+force1.setActive(0)
+f0.addForce(force1)
+force2 = LinearCylinderVortexForce(1.0000, 1.0000, 15.0000, 1.0000, 0)
+force2.setActive(1)
+f0.addForce(force2)
+force3 = LinearSourceForce(Point3(0.5000, 0.0000, 1.0000), LinearDistanceForce.FTONEOVERRCUBED, 4.0000, 4.0000, 1)
+force3.setActive(1)
+f0.addForce(force3)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/rollodexWaterfall.ptf b/toontown/src/battle/rollodexWaterfall.ptf
new file mode 100644
index 0000000..7213529
--- /dev/null
+++ b/toontown/src/battle/rollodexWaterfall.ptf
@@ -0,0 +1,61 @@
+
+self.reset()
+self.setPos(-0.160, 2.942, 3.400)
+self.setHpr(89.908, -20.000, 179.476)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereSurfaceEmitter")
+p0.setPoolSize(20)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(3)
+p0.setLitterSpread(2)
+p0.setSystemLifespan(5.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/rollodex-card")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.4)
+p0.renderer.setFinalXScale(0.4)
+p0.renderer.setInitialYScale(0.3)
+p0.renderer.setFinalYScale(0.3)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Sphere Surface parameters
+p0.emitter.setRadius(1.0000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forward')
+# Force parameters
+force0 = LinearSourceForce(Point3(0.0000, 0.0000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.0000, 1)
+force0.setActive(0)
+f0.addForce(force0)
+force1 = LinearSinkForce(Point3(0.0000, 0.0000, 10.0000), LinearDistanceForce.FTONEOVERRCUBED, 2.9550, 50.0000, 1)
+force1.setActive(1)
+f0.addForce(force1)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/schmoozeLowerSpray.ptf b/toontown/src/battle/schmoozeLowerSpray.ptf
new file mode 100644
index 0000000..38e18b0
--- /dev/null
+++ b/toontown/src/battle/schmoozeLowerSpray.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 6.600, 3.290)
+self.setHpr(0.000, -55.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(1)
+p0.setBirthRate(0.400)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.900)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/schmooze-master")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.7)
+p0.renderer.setFinalXScale(0.07)
+p0.renderer.setInitialYScale(0.35)
+p0.renderer.setFinalYScale(0.07)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 11.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -1.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.1000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, -23.0000, 9.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.3661, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/schmoozeUpperSpray.ptf b/toontown/src/battle/schmoozeUpperSpray.ptf
new file mode 100644
index 0000000..5827f71
--- /dev/null
+++ b/toontown/src/battle/schmoozeUpperSpray.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 3.000, 4.000)
+self.setHpr(0.000, 55.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(1)
+p0.setBirthRate(0.400)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.900)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/schmooze-master")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.7)
+p0.renderer.setFinalXScale(0.07)
+p0.renderer.setInitialYScale(0.35)
+p0.renderer.setFinalYScale(0.07)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 11.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -1.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.1000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, -23.0000, -9.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 1.3661, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/shiftSpray.ptf b/toontown/src/battle/shiftSpray.ptf
new file mode 100644
index 0000000..8e19313
--- /dev/null
+++ b/toontown/src/battle/shiftSpray.ptf
@@ -0,0 +1,48 @@
+
+self.reset()
+self.setPos(0.000, 5.000, 2.300)
+self.setHpr(0.000, -55.000, 0.000)
+self.setScale(9.000, 9.000, 9.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("LineEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.100)
+p0.setLitterSize(7)
+p0.setLitterSpread(2)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.3000)
+p0.factory.setLifespanSpread(0.1000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1.00, 1.00, 0.00, 0.9))
+p0.renderer.setEdgeColor(Vec4(1.00, 1.00, 0.00, 0.6))
+p0.renderer.setBirthRadius(0.0200)
+p0.renderer.setDeathRadius(0.0600)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.5000)
+p0.emitter.setAmplitudeSpread(0.5000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -3.0000, 0.0000))
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, 96.0000), LinearDistanceForce.FTONEOVERRSQUARED, 3.0400, 1.5000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/shred.ptf b/toontown/src/battle/shred.ptf
new file mode 100644
index 0000000..50a19da
--- /dev/null
+++ b/toontown/src/battle/shred.ptf
@@ -0,0 +1,67 @@
+
+self.reset()
+self.setPos(0.000, 3.000, 2.300)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(60)
+p0.setBirthRate(0.0600)
+p0.setLitterSize(3)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.9000)
+p0.factory.setLifespanSpread(0.4000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.2000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/roll-o-dex")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.0160)
+p0.renderer.setFinalXScale(0.0240)
+p0.renderer.setInitialYScale(0.3200)
+p0.renderer.setFinalYScale(0.0800)
+p0.renderer.setNonanimatedTheta(5.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0000)
+p0.emitter.setAmplitudeSpread(1.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 3.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -7.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.6000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearVectorForce(Vec3(0.0000, 0.0000, 5.0000), 1.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearSinkForce(Point3(0.0000, 0.0000, -8.0000), LinearDistanceForce.FTONEOVERRSQUARED, 14.5479, 155.9407, 1)
+force1.setActive(1)
+f0.addForce(force1)
+force2 = LinearNoiseForce(1.7000, 0)
+force2.setActive(1)
+f0.addForce(force2)
+force3 = LinearJitterForce(12.5698, 0)
+force3.setActive(1)
+f0.addForce(force3)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/smile.ptf b/toontown/src/battle/smile.ptf
new file mode 100644
index 0000000..0abfae5
--- /dev/null
+++ b/toontown/src/battle/smile.ptf
@@ -0,0 +1,44 @@
+
+self.reset()
+self.setPos(0.0, 0.0, 2.000)
+self.setHpr(85.000, 0.000, 90.000)
+#self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("RingEmitter")
+p0.setPoolSize(400)
+p0.setBirthRate(0.0200)
+p0.setLitterSize(10)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(1.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.0000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(200.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setEdgeColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setBirthRadius(0.1000)
+p0.renderer.setDeathRadius(0.0000)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Ring parameters
+p0.emitter.setRadius(1.0000)
+self.addParticles(p0)
diff --git a/toontown/src/battle/soundBreak.ptf b/toontown/src/battle/soundBreak.ptf
new file mode 100644
index 0000000..e877236
--- /dev/null
+++ b/toontown/src/battle/soundBreak.ptf
@@ -0,0 +1,57 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 0.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("ZSpinParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("PointEmitter")
+p0.setPoolSize(7)
+p0.setBirthRate(0.0500)
+p0.setLitterSize(3)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.5000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Z Spin factory parameters
+p0.factory.setInitialAngle(0.0000)
+p0.factory.setInitialAngleSpread(180.0000)
+p0.factory.enableAngularVelocity(1)
+p0.factory.setAngularVelocity(0.0000)
+p0.factory.setAngularVelocitySpread(0.0000)
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAINOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setTextureFromNode("phase_5/models/props/uberSoundEffects", "**/break")
+#p0.renderer.addTextureFromFile('maps/break.tif')
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(1)
+p0.renderer.setInitialXScale(1.5000)
+p0.renderer.setFinalXScale(1.5000)
+p0.renderer.setInitialYScale(0.0000)
+p0.renderer.setFinalYScale(9.0000)
+p0.renderer.setNonanimatedTheta(319.3987)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Point parameters
+p0.emitter.setLocation(Point3(0.0000, 0.0000, 0.0000))
+self.addParticles(p0)
diff --git a/toontown/src/battle/soundWave.ptf b/toontown/src/battle/soundWave.ptf
new file mode 100644
index 0000000..d820cce
--- /dev/null
+++ b/toontown/src/battle/soundWave.ptf
@@ -0,0 +1,54 @@
+
+
+self.reset()
+self.setPos(0.000, 0.000, 0.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(7.000, 7.000, 7.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("PointEmitter")
+p0.setPoolSize(128)
+p0.setBirthRate(0.4000)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(10.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(4.0000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(0.0010)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(0.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setTextureFromNode("phase_5/models/props/uberSoundEffects", "**/Circle")
+#p0.renderer.addTextureFromFile('maps/Circle.tif')
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.0000)
+p0.renderer.setFinalXScale(3.0000)
+p0.renderer.setInitialYScale(0.0000)
+p0.renderer.setFinalYScale(3.0000)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(1)
+p0.renderer.setColorBlendMode(ColorBlendAttrib.MAdd, ColorBlendAttrib.OIncomingAlpha, ColorBlendAttrib.OOne)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETEXPLICIT)
+p0.emitter.setAmplitude(0.0100)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
+# Point parameters
+p0.emitter.setLocation(Point3(0.0000, 0.0000, 0.0000))
+self.addParticles(p0)
diff --git a/toontown/src/battle/spinEffect.ptf b/toontown/src/battle/spinEffect.ptf
new file mode 100644
index 0000000..dd95970
--- /dev/null
+++ b/toontown/src/battle/spinEffect.ptf
@@ -0,0 +1,59 @@
+
+self.reset()
+self.setScale(0.040, 0.040, 0.040)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(100)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(6)
+p0.setLitterSpread(2)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.3000)
+p0.factory.setLifespanSpread(0.3000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/snow-particle")
+p0.renderer.setColor(Vec4(1.00, 0.00, 0.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.05)
+p0.renderer.setFinalXScale(0.05)
+p0.renderer.setInitialYScale(0.05)
+p0.renderer.setFinalYScale(0.05)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(4.000*1.2)
+p0.emitter.setAmplitudeSpread(1.000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -4.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.300)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 1.2000, 0.0000), LinearDistanceForce.FTONEOVERRSQUARED,1.0000, 20, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearJitterForce(5.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/spinSpray.ptf b/toontown/src/battle/spinSpray.ptf
new file mode 100644
index 0000000..612c751
--- /dev/null
+++ b/toontown/src/battle/spinSpray.ptf
@@ -0,0 +1,61 @@
+
+self.reset()
+self.setPos(0.000, 6.500, 3.200)
+self.setHpr(50.000, -0.000, -90.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(70)
+p0.setBirthRate(0.2000)
+p0.setLitterSize(9)
+p0.setLitterSpread(4)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.2000)
+p0.factory.setLifespanSpread(0.2000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/snow-particle")
+p0.renderer.setColor(Vec4(1.00, 0.00, 0.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.025)
+p0.renderer.setFinalXScale(0.05)
+p0.renderer.setInitialYScale(0.025)
+p0.renderer.setFinalYScale(0.05)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(6.0000)
+p0.emitter.setAmplitudeSpread(0.7000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -4.0000, 0.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.200)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -3.0000), LinearDistanceForce.FTONEOVERRSQUARED, 1.0000, 2.5308, 1)
+force0.setActive(1)
+f0.addForce(force0)
+force1 = LinearVectorForce(Vec3(0.0000, 0.0000, 0.0000), 1.0000, 0)
+force1.setActive(1)
+f0.addForce(force1)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/splashlines.ptf b/toontown/src/battle/splashlines.ptf
new file mode 100644
index 0000000..2438a31
--- /dev/null
+++ b/toontown/src/battle/splashlines.ptf
@@ -0,0 +1,47 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 0.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("LineParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(40)
+p0.setBirthRate(1000)
+p0.setLitterSize(40)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(2.0)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Line parameters
+p0.renderer.setHeadColor(Vec4(0.02, 0.67, 0.92, 1.00))
+p0.renderer.setTailColor(Vec4(1.00, 1.00, 1.00, 1.00))
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(9.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 9.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -2.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(3.2282)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -79.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0100, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/spriteFiredFlecks.ptf b/toontown/src/battle/spriteFiredFlecks.ptf
new file mode 100644
index 0000000..274499c
--- /dev/null
+++ b/toontown/src/battle/spriteFiredFlecks.ptf
@@ -0,0 +1,52 @@
+
+self.reset()
+self.setPos(0.000, 0.000, 2.000)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(60)
+p0.setBirthRate(0.200)
+p0.setLitterSize(2)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.100)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/roll-o-dex")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.025)
+p0.renderer.setFinalXScale(0.000)
+p0.renderer.setInitialYScale(0.025)
+p0.renderer.setFinalYScale(0.000)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 4.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -4.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(1.5000)
+self.addParticles(p0)
diff --git a/toontown/src/battle/synergy.ptf b/toontown/src/battle/synergy.ptf
new file mode 100644
index 0000000..e367cc8
--- /dev/null
+++ b/toontown/src/battle/synergy.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0, 7.8, 0.4)
+self.setHpr(90.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("RingEmitter")
+p0.setPoolSize(250)
+p0.setBirthRate(0.0100)
+p0.setLitterSize(1)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.6)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/dollar-sign")
+p0.renderer.setColor(Vec4(0.00, 1.00, 0.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.2)
+p0.renderer.setFinalXScale(0.2)
+p0.renderer.setInitialYScale(0.2)
+p0.renderer.setFinalYScale(0.2)
+p0.renderer.setNonanimatedTheta(20.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(5.0697)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(-4.0000, 0.0000, 0.0000))
+# Ring parameters
+p0.emitter.setRadius(1.8607)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('jfo')
+# Force parameters
+force0 = LinearJitterForce(1.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/synergyWaterfall.ptf b/toontown/src/battle/synergyWaterfall.ptf
new file mode 100644
index 0000000..ca2dd16
--- /dev/null
+++ b/toontown/src/battle/synergyWaterfall.ptf
@@ -0,0 +1,58 @@
+
+self.reset()
+self.setPos(0.000, 5.000, 2.300)
+self.setHpr(0.000, -45.000, 0.000)
+self.setScale(4.000, 4.000, 4.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(50)
+p0.setBirthRate(0.0500)
+p0.setLitterSize(4)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.1000)
+p0.factory.setLifespanSpread(0.1000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/dollar-sign")
+p0.renderer.setColor(Vec4(0.00, 1.00, 0.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.2)
+p0.renderer.setFinalXScale(0.2)
+p0.renderer.setInitialYScale(0.2)
+p0.renderer.setFinalYScale(0.2)
+p0.renderer.setNonanimatedTheta(20.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.5000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -3.0000, 0.0000))
+# Disc parameters
+p0.emitter.setRadius(0.2000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -15.0000), LinearDistanceForce.FTONEOVERRSQUARED, 3.0400, 1.5000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/tnt.ptf b/toontown/src/battle/tnt.ptf
new file mode 100644
index 0000000..7fca794
--- /dev/null
+++ b/toontown/src/battle/tnt.ptf
@@ -0,0 +1,64 @@
+
+self.reset()
+self.setPos(0.000, 0.000, -0.600)
+self.setHpr(0.000, 10.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(40)
+p0.setBirthRate(0.1000)
+p0.setLitterSize(2)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(1.2000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/spark")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.3)
+p0.renderer.setFinalXScale(0.3)
+p0.renderer.setInitialYScale(0.3)
+p0.renderer.setFinalYScale(0.03)
+p0.renderer.setNonanimatedTheta(20.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Sparkle parameters
+#p0.renderer.setCenterColor(Vec4(0.78, 0.78, 0, 1.00))
+#p0.renderer.setEdgeColor(Vec4(0.78, 0.78, 0, 1.00))
+#p0.renderer.setBirthRadius(0.0600)
+#p0.renderer.setDeathRadius(0.0600)
+#p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(1.5000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, -2.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.2282)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -19.0000), LinearDistanceForce.FTONEOVERRSQUARED, 15.9701, 95.0100, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/trickleLiquidate.ptf b/toontown/src/battle/trickleLiquidate.ptf
new file mode 100644
index 0000000..09398c3
--- /dev/null
+++ b/toontown/src/battle/trickleLiquidate.ptf
@@ -0,0 +1,52 @@
+
+self.reset()
+self.setPos(0.000, 0.000, -0.200)
+self.setHpr(0.000, 0.000, 0.000)
+self.setScale(1.000, 1.000, 1.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+p0.setEmitter("SphereVolumeEmitter")
+p0.setPoolSize(20)
+p0.setBirthRate(0.0800)
+p0.setLitterSize(3)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.4000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAUSER)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/raindrop")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(1)
+p0.renderer.setYScaleFlag(1)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.06)
+p0.renderer.setFinalXScale(0.06)
+p0.renderer.setInitialYScale(0.225)
+p0.renderer.setFinalYScale(0.225)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(16.0000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 6.0000))
+# Sphere Volume parameters
+p0.emitter.setRadius(0.4500)
+self.addParticles(p0)
diff --git a/toontown/src/battle/waterfall.ptf b/toontown/src/battle/waterfall.ptf
new file mode 100644
index 0000000..953d2ae
--- /dev/null
+++ b/toontown/src/battle/waterfall.ptf
@@ -0,0 +1,50 @@
+
+self.reset()
+self.setPos(0.000, 5.000, 2.300)
+self.setHpr(0.000, -45.000, 0.000)
+self.setScale(4.000, 4.000, 4.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SparkleParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(50)
+p0.setBirthRate(0.0500)
+p0.setLitterSize(4)
+p0.setLitterSpread(1)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.1000)
+p0.factory.setLifespanSpread(0.1000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHANONE)
+p0.renderer.setUserAlpha(1.00)
+# Sparkle parameters
+p0.renderer.setCenterColor(Vec4(0.1, 0.95, 0.2, 1.00))
+p0.renderer.setEdgeColor(Vec4(0.00, 0.00, 0.00, 1.00))
+p0.renderer.setBirthRadius(0.0200)
+p0.renderer.setDeathRadius(0.0600)
+p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(3.5000)
+p0.emitter.setAmplitudeSpread(0.0000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, -3.0000, 0.0000))
+# Disc parameters
+p0.emitter.setRadius(0.2000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearSinkForce(Point3(0.0000, 0.0000, -30.0000), LinearDistanceForce.FTONEOVERRSQUARED, 3.0400, 1.5000, 1)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/battle/withdrawal.ptf b/toontown/src/battle/withdrawal.ptf
new file mode 100644
index 0000000..f204200
--- /dev/null
+++ b/toontown/src/battle/withdrawal.ptf
@@ -0,0 +1,62 @@
+
+self.reset()
+self.setPos(0.000, 10.000, 2.500)
+self.setHpr(-180.000, 0.000, 0.000)
+self.setScale(4.000, 4.000, 4.000)
+p0 = Particles.Particles('particles-1')
+# Particles parameters
+p0.setFactory("PointParticleFactory")
+p0.setRenderer("SpriteParticleRenderer")
+#p0.setRenderer("PointParticleRenderer")
+p0.setEmitter("DiscEmitter")
+p0.setPoolSize(150)
+p0.setBirthRate(0.0200)
+p0.setLitterSize(10)
+p0.setLitterSpread(0)
+p0.setSystemLifespan(0.0000)
+p0.setLocalVelocityFlag(1)
+p0.setSystemGrowsOlderFlag(0)
+# Factory parameters
+p0.factory.setLifespanBase(0.4000)
+p0.factory.setLifespanSpread(0.0000)
+p0.factory.setMassBase(1.0000)
+p0.factory.setMassSpread(0.0000)
+p0.factory.setTerminalVelocityBase(400.0000)
+p0.factory.setTerminalVelocitySpread(0.0000)
+# Point factory parameters
+# Renderer parameters
+p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAIN)
+p0.renderer.setUserAlpha(1.00)
+# Sprite parameters
+p0.renderer.setIgnoreScale(1)
+p0.renderer.setTextureFromNode("phase_3.5/models/props/suit-particles", "**/snow-particle")
+p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
+p0.renderer.setXScaleFlag(0)
+p0.renderer.setYScaleFlag(0)
+p0.renderer.setAnimAngleFlag(0)
+p0.renderer.setInitialXScale(0.04)
+p0.renderer.setFinalXScale(0.3125)
+p0.renderer.setInitialYScale(0.03)
+p0.renderer.setFinalYScale(0.25)
+p0.renderer.setNonanimatedTheta(0.0000)
+p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR)
+p0.renderer.setAlphaDisable(0)
+# Line parameters
+#p0.renderer.setHeadColor(Vec4(1.00, 0.00, 0.00, 1.00))
+#p0.renderer.setTailColor(Vec4(1.00, 0.00, 0.00, 1.00))
+# Emitter parameters
+p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
+p0.emitter.setAmplitude(-0.4000)
+p0.emitter.setAmplitudeSpread(0.1000)
+p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 0.0000))
+p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
+p0.emitter.setRadiateOrigin(Point3(0.0000, 1.5000, 0.0000))
+# Disc parameters
+p0.emitter.setRadius(1.7000)
+self.addParticles(p0)
+f0 = ForceGroup.ForceGroup('forces')
+# Force parameters
+force0 = LinearVectorForce(Vec3(0.0000, 1.0000, 0.0000), 1.0000, 0)
+force0.setActive(1)
+f0.addForce(force0)
+self.addForceGroup(f0)
diff --git a/toontown/src/building/.cvsignore b/toontown/src/building/.cvsignore
new file mode 100644
index 0000000..985f113
--- /dev/null
+++ b/toontown/src/building/.cvsignore
@@ -0,0 +1,4 @@
+.cvsignore
+Makefile
+pp.dep
+*.pyc
diff --git a/toontown/src/building/BoardingGroupShow.py b/toontown/src/building/BoardingGroupShow.py
new file mode 100644
index 0000000..14b1cb5
--- /dev/null
+++ b/toontown/src/building/BoardingGroupShow.py
@@ -0,0 +1,365 @@
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals
+from direct.task.Task import Task
+from direct.interval.IntervalGlobal import *
+from toontown.toonbase import TTLocalizer
+from toontown.effects import DustCloud
+
+TRACK_TYPE_TELEPORT = 1
+TRACK_TYPE_RUN = 2
+TRACK_TYPE_POOF = 3
+
+class BoardingGroupShow:
+ notify = DirectNotifyGlobal.directNotify.newCategory('BoardingGroupShow')
+
+ thresholdRunDistance = 25.0
+
+ def __init__(self, toon):
+ self.toon = toon
+ self.avId = self.toon.doId
+ self.dustCloudIval = None
+ if __debug__:
+ base.bgs = self
+
+ def cleanup(self):
+ # Cleanup only for the local avatar.
+ if (localAvatar.doId == self.avId):
+ self.__stopTimer()
+ self.clock.removeNode()
+
+ def startTimer(self):
+## ts = globalClockDelta.localElapsedTime(timestamp)
+ # Start the countdown clock...
+ self.clockNode = TextNode("elevatorClock")
+ self.clockNode.setFont(ToontownGlobals.getSignFont())
+ self.clockNode.setAlign(TextNode.ACenter)
+ self.clockNode.setTextColor(0.5, 0.5, 0.5, 1)
+ self.clockNode.setText(str(int(self.countdownDuration)))
+ self.clock = aspect2d.attachNewNode(self.clockNode)
+
+ # TODO: Get the right coordinates for the elevator clock.
+ ## self.clock.setPosHprScale(0, 2.0, 7.5,
+ ## 0, 0, 0,
+ ## 2.0, 2.0, 2.0)
+
+ self.clock.setPos(0, 0, -0.6)
+ self.clock.setScale(0.15, 0.15, 0.15)
+
+## if ts < countdownTime:
+## self.__countdown(countdownTime - ts, self.__boardingElevatorTimerExpired)
+ self.__countdown(self.countdownDuration, self.__boardingElevatorTimerExpired)
+
+ def __countdown(self, duration, callback):
+ """
+ Spawn the timer task for duration seconds.
+ Calls callback when the timer is up
+ """
+ self.countdownTask = Task(self.__timerTask)
+ self.countdownTask.duration = duration
+ self.countdownTask.callback = callback
+
+ taskMgr.remove(self.uniqueName(self.avId))
+ return taskMgr.add(self.countdownTask, self.uniqueName(self.avId))
+
+ def __timerTask(self, task):
+ """
+ This is the task for the countdown.
+ """
+ countdownTime = int(task.duration - task.time)
+ timeStr = self.timeWarningText + str(countdownTime)
+
+ if self.clockNode.getText() != timeStr:
+ self.clockNode.setText(timeStr)
+
+ if task.time >= task.duration:
+ # Time is up, call the callback and return Task.done
+ if task.callback:
+ task.callback()
+ return Task.done
+ else:
+ return Task.cont
+
+ def __boardingElevatorTimerExpired(self):
+ """
+ This is where the control goes as soon as the countdown finishes.
+ """
+ self.notify.debug('__boardingElevatorTimerExpired')
+ self.clock.removeNode()
+
+ def __stopTimer(self):
+ """
+ Get rid of any countdowns
+ """
+ if self.countdownTask:
+ self.countdownTask.callback = None
+ taskMgr.remove(self.countdownTask)
+
+ def uniqueName(self, avId):
+ """
+ Here we're making our own uniqueName method, each avId's sequence should be unique.
+ """
+ uniqueName = "boardingElevatorTimerTask-" + str(avId)
+ return uniqueName
+
+ def getBoardingTrack(self, elevatorModel, offset, offsetWrtRender, wantToonRotation):
+ """
+ Return an interval of the toon teleporting/running to the front of the elevator.
+ This method is called from the elevator.
+ Note: The offset is to where the toon will teleport/run to. This offset has to be
+ calculated wrt the parent of the toon.
+ Eg: For the CogKart the offset should be computed wrt to the cogKart because the
+ toon is parented to the cogKart.
+ For the other elevators the offset should be computer wrt to render because the
+ toon is parented to render.
+ """
+ self.timeWarningText = TTLocalizer.BoardingTimeWarning
+ self.countdownDuration = 6
+ trackType = TRACK_TYPE_TELEPORT
+ boardingTrack = Sequence()
+ # Do anything only if the toon exists.
+ if self.toon:
+ # Do the whole timer only for the local avatar
+ if (self.avId == localAvatar.doId):
+ boardingTrack.append(Func(self.startTimer))
+
+ isInThresholdDist = self.__isInThresholdDist(elevatorModel, offset, self.thresholdRunDistance)
+ isRunPathClear = self.__isRunPathClear(elevatorModel, offsetWrtRender)
+
+ if isInThresholdDist and isRunPathClear:
+ boardingTrack.append(self.__getRunTrack(elevatorModel, offset, wantToonRotation))
+ trackType = TRACK_TYPE_RUN
+ else:
+ if self.toon.isDisguised:
+ boardingTrack.append(self.__getPoofTeleportTrack(elevatorModel, offset, wantToonRotation))
+ trackType = TRACK_TYPE_POOF
+ else:
+ boardingTrack.append(self.__getTeleportTrack(elevatorModel, offset, wantToonRotation))
+
+ # Else return an empty boarding track.
+ else:
+ pass
+
+ # Cleanup whatever you have created in this class at the end of the interval.
+ # We don't need this object any more.
+ boardingTrack.append(Func(self.cleanup))
+
+ return (boardingTrack, trackType)
+
+ def __getOffsetPos(self, elevatorModel, offset):
+ """
+ Get the offset position to where the toon might have to
+ teleport to or run to.
+ Note: This is the pos reletive to the elevator.
+ """
+ dest = elevatorModel.getPos(self.toon.getParent())
+ dest += Vec3(*offset)
+ return dest
+
+ def __getTeleportTrack(self, elevatorModel, offset, wantToonRotation):
+ """
+ We get the teleport track when the toon is outside the
+ threshold distance away from the elevator.
+ The Teleport Track is an interval of the toon teleporting to the
+ elevator seat's offset position. After it reaches the offset position
+ the boarding the elevator animation takes over.
+ """
+ teleportTrack = Sequence()
+ # Do anything only if the toon exists.
+ if self.toon:
+ if wantToonRotation:
+ teleportTrack.append(Func(self.toon.headsUp, elevatorModel, offset))
+ teleportTrack.append(Func(self.toon.setAnimState, 'TeleportOut'))
+ teleportTrack.append(Wait(3.5))
+ teleportTrack.append(Func(self.toon.setPos, Point3(offset)))
+ teleportTrack.append(Func(self.toon.setAnimState, 'TeleportIn'))
+ teleportTrack.append(Wait(1))
+
+ # Else return an empty teleport track.
+ else:
+ pass
+ return teleportTrack
+
+ def __getPoofTeleportTrack(self, elevatorModel, offset, wantToonRotation):
+ """
+ We get the poof teleport track when the toon is outside the
+ threshold distance away from the elevator
+ and when the toon is disguised in a cog suit.
+ The Poof Teleport Track is an interval of the toon poofing out
+ and poofing into the elevator seat's offset position.
+ After it reaches the offset position
+ the boarding the elevator animation takes over.
+ """
+ teleportTrack = Sequence()
+
+ if wantToonRotation:
+ teleportTrack.append(Func(self.toon.headsUp, elevatorModel, offset))
+
+ def getDustCloudPos():
+ toonPos = self.toon.getPos(render)
+ return Point3(toonPos.getX(), toonPos.getY(), toonPos.getZ() + 3)
+
+ def cleanupDustCloudIval():
+ if self.dustCloudIval:
+ self.dustCloudIval.finish()
+ self.dustCloudIval = None
+
+ def getDustCloudIval():
+ # Clean up any dust cloud before starting another one
+ cleanupDustCloudIval()
+
+ dustCloud = DustCloud.DustCloud(fBillboard = 0,wantSound = 1)
+ dustCloud.setBillboardAxis(2.)
+ dustCloud.setZ(3)
+ dustCloud.setScale(0.4)
+ dustCloud.createTrack()
+
+ self.dustCloudIval = Sequence(Func(dustCloud.reparentTo, render),
+ Func(dustCloud.setPos, getDustCloudPos()),
+ dustCloud.track,
+ Func(dustCloud.detachNode),
+ Func(dustCloud.destroy),
+ name = 'dustCloadIval'
+ )
+ self.dustCloudIval.start()
+
+ # Do anything only if the toon exists.
+ if self.toon:
+ teleportTrack.append(Func(self.toon.setAnimState, 'neutral'))
+ teleportTrack.append(Wait(0.5))
+ teleportTrack.append(Func(getDustCloudIval))
+ teleportTrack.append(Wait(0.25))
+ teleportTrack.append(Func(self.toon.hide))
+ teleportTrack.append(Wait(1.5))
+ teleportTrack.append(Func(self.toon.setPos, Point3(offset)))
+ teleportTrack.append(Func(getDustCloudIval))
+ teleportTrack.append(Wait(0.25))
+ teleportTrack.append(Func(self.toon.show))
+ teleportTrack.append(Wait(0.5))
+ # Clean up the dust cloud interval once it is done.
+ teleportTrack.append(Func(cleanupDustCloudIval))
+
+ # Else return an empty teleport track.
+ else:
+ pass
+ return teleportTrack
+
+ def __getRunTrack(self, elevatorModel, offset, wantToonRotation):
+ """
+ We get the run track when the toon is within the threshold distance
+ away from the elevator.
+ The Run Track is an interval of the toon running to the
+ elevator seat's offset position. After it reaches the offset position
+ the boarding the elevator animation takes over.
+ """
+ runTrack = Sequence()
+ # Do anything only if the toon exists.
+ if self.toon:
+ if wantToonRotation:
+ runTrack.append(Func(self.toon.headsUp, elevatorModel, offset))
+
+ if self.toon.isDisguised:
+ runTrack.append(Func(self.toon.suit.loop, "walk"))
+ else:
+ runTrack.append(Func(self.toon.setAnimState, 'run'))
+ runTrack.append(LerpPosInterval(self.toon, 1, Point3(offset)))
+
+ # Else return an empty run track.
+ else:
+ pass
+
+ return runTrack
+
+ def __isInThresholdDist(self, elevatorModel, offset, thresholdDist):
+ """
+ Checks to see if the toon is within the threshold distance
+ from the elevator.
+ """
+ diff = Point3(offset) - self.toon.getPos()
+
+ if (diff.length() > thresholdDist):
+ return False
+ else:
+ return True
+
+ def __isRunPathClear(self, elevatorModel, offsetWrtRender):
+ pathClear = True
+ source = self.toon.getPos(render)
+ dest = offsetWrtRender
+
+ # Shoot collision ray from toon to elevator
+ collSegment = CollisionSegment(source[0], source[1], source[2],
+ dest[0], dest[1], dest[2])
+ fromObject = render.attachNewNode(CollisionNode('runCollSegment'))
+ fromObject.node().addSolid(collSegment)
+ fromObject.node().setFromCollideMask(ToontownGlobals.WallBitmask)
+ fromObject.node().setIntoCollideMask(BitMask32.allOff())
+
+ queue = CollisionHandlerQueue()
+ base.cTrav.addCollider(fromObject, queue)
+ base.cTrav.traverse(render)
+ queue.sortEntries()
+ if queue.getNumEntries():
+ # Go through all the collision entries
+ for entryNum in xrange(queue.getNumEntries()):
+ entry = queue.getEntry(entryNum)
+ hitObject = entry.getIntoNodePath()
+ # This collision ray might collide against another toon standing in front of it
+ # Ignore any toon, including self toon
+ # Every toon has a netTag('pieCode') = 3
+ if (hitObject.getNetTag('pieCode') != '3'):
+ # This must be a something with a wall bit mask that is not a toon
+ pathClear = False
+
+ base.cTrav.removeCollider(fromObject)
+ fromObject.removeNode()
+ return pathClear
+
+ def getGoButtonShow(self, elevatorName):
+ """
+ Return an interval of the toon teleporting out with the time.
+ This method is called from DistributedBoardingParty.
+ """
+ self.elevatorName = elevatorName
+ self.timeWarningText = TTLocalizer.BoardingGoShow %self.elevatorName
+ self.countdownDuration = 4
+ goButtonShow = Sequence()
+ # Do anything only if the toon exists.
+ if self.toon:
+ # Do the whole timer only for the local avatar
+ if (self.avId == localAvatar.doId):
+ goButtonShow.append(Func(self.startTimer))
+ goButtonShow.append(self.__getTeleportOutTrack())
+ goButtonShow.append(Wait(3))
+ # Cleanup whatever you have created in this class at the end of the interval.
+ # We don't need this object any more.
+ goButtonShow.append(Func(self.cleanup))
+ return goButtonShow
+
+ def __getTeleportOutTrack(self):
+ """
+ Return an interval of the toon teleporting out.
+ """
+ teleportOutTrack = Sequence()
+ # Do anything only if the toon exists.
+ if self.toon and not self.toon.isDisguised:
+ teleportOutTrack.append(Func(self.toon.b_setAnimState, 'TeleportOut'))
+ return teleportOutTrack
+
+ def getGoButtonPreShow(self):
+ """
+ Return an interval showing time left for the pre show.
+ """
+ self.timeWarningText = TTLocalizer.BoardingGoPreShow
+ self.countdownDuration = 4
+ goButtonPreShow = Sequence()
+ # Do anything only if the toon exists.
+ if self.toon:
+ # Do the whole timer only for the local avatar
+ if (self.avId == localAvatar.doId):
+ goButtonPreShow.append(Func(self.startTimer))
+ goButtonPreShow.append(Wait(3))
+ # Cleanup whatever you have created in this class at the end of the interval.
+ # We don't need this object any more.
+ goButtonPreShow.append(Func(self.cleanup))
+ return goButtonPreShow
\ No newline at end of file
diff --git a/toontown/src/building/BoardingPartyBase.py b/toontown/src/building/BoardingPartyBase.py
new file mode 100644
index 0000000..823ebd2
--- /dev/null
+++ b/toontown/src/building/BoardingPartyBase.py
@@ -0,0 +1,131 @@
+from otp.otpbase import OTPGlobals
+from toontown.toonbase import ToontownGlobals
+import copy
+
+# elevator boarding codes
+
+BOARDCODE_OKAY = 1
+BOARDCODE_MISSING = 0
+BOARDCODE_MINLAFF = -1
+BOARDCODE_PROMOTION = -2
+BOARDCODE_BATTLE = -3
+BOARDCODE_SPACE = -4
+BOARDCODE_NOT_PAID = -5
+BOARDCODE_DIFF_GROUP = -6
+BOARDCODE_PENDING_INVITE = -7
+BOARDCODE_IN_ELEVATOR = -8
+
+INVITE_ACCEPT_FAIL_GROUP_FULL = -1
+
+class BoardingPartyBase:
+
+ def __init__(self):
+ self.groupListDict = {} #key->leaderId : [members],[invitees],[banned]
+ self.avIdDict = {} #pointer from each member to a leaderId
+
+ def cleanup(self):
+ del self.groupListDict
+ del self.avIdDict
+
+ def getGroupSize(self):
+ return self.maxSize
+
+ def setGroupSize(self, groupSize):
+ self.maxSize = groupSize
+
+ def getGroupLeader(self, avatarId):
+ if self.avIdDict.has_key(avatarId):
+ leaderId = self.avIdDict[avatarId]
+ return leaderId
+ else:
+ return None
+
+ def isGroupLeader(self, avatarId):
+ leaderId = self.getGroupLeader(avatarId)
+ if (avatarId == leaderId):
+ return True
+ else:
+ return False
+
+ def getGroupMemberList(self, avatarId):
+ """
+ returns the memberlist with the leader at index 0
+ """
+ if self.avIdDict.has_key(avatarId):
+ leaderId = self.avIdDict[avatarId]
+ group = self.groupListDict.get(leaderId)
+ if group:
+ returnList = copy.copy(group[0])
+ if 0 in returnList:
+ returnList.remove(0)
+ return returnList
+ return []
+
+ def getGroupInviteList(self, avatarId):
+ if self.avIdDict.has_key(avatarId):
+ leaderId = self.avIdDict[avatarId]
+ group = self.groupListDict.get(leaderId)
+ if group:
+ returnList = copy.copy(group[1])
+ if 0 in returnList:
+ returnList.remove(0)
+ return returnList
+ return []
+
+ def getGroupKickList(self, avatarId):
+ if self.avIdDict.has_key(avatarId):
+ leaderId = self.avIdDict[avatarId]
+ group = self.groupListDict.get(leaderId)
+ if group:
+ returnList = copy.copy(group[2])
+ if 0 in returnList:
+ returnList.remove(0)
+ return returnList
+ return []
+
+ def hasActiveGroup(self, avatarId):
+ """
+ Returns True if the avatar has an active boarding group.
+ """
+ memberList = self.getGroupMemberList(avatarId)
+ if avatarId in memberList:
+ if (len(memberList) > 1):
+ return True
+ return False
+
+ def hasPendingInvite(self, avatarId):
+ """
+ This is a two-stage check:
+ If the avatar is a leader just check if there is anyone in the leader's invite list.
+ If the avatar is a non-leader just check if the avatar is there in it's leader's invite list.
+ """
+ pendingInvite = False
+ if self.avIdDict.has_key(avatarId):
+ leaderId = self.avIdDict[avatarId]
+ leaderInviteList = self.getGroupInviteList(leaderId)
+ if (leaderId == avatarId):
+ # The avatar is the leader, just check if there is anybody in the invite list.
+ if len(leaderInviteList):
+ pendingInvite = True
+ else:
+ pendingInvite = False
+ else:
+ # The avatar is a non-leader, check if the avatar is there is the leader's invite list.
+ if avatarId in leaderInviteList:
+ pendingInvite = True
+ else:
+ pendingInvite = False
+ if pendingInvite:
+ return True
+ else:
+ return False
+
+ def isInGroup(self, memberId, leaderId):
+ """
+ Returns True if the member is in the leader's member list or invite list.
+ Else returns False.
+ """
+ if (memberId in self.getGroupMemberList(leaderId)) or (memberId in self.getGroupInviteList(leaderId)):
+ return True
+ else:
+ return False
\ No newline at end of file
diff --git a/toontown/src/building/DistributedAnimBuilding.py b/toontown/src/building/DistributedAnimBuilding.py
new file mode 100644
index 0000000..71ea854
--- /dev/null
+++ b/toontown/src/building/DistributedAnimBuilding.py
@@ -0,0 +1,66 @@
+from pandac.PandaModules import DecalEffect, DepthWriteAttrib
+from direct.directnotify import DirectNotifyGlobal
+from toontown.building import DistributedBuilding
+
+
+class DistributedAnimBuilding(DistributedBuilding.DistributedBuilding):
+ """
+ DistributedAnimBuilding class: The client side representation of a
+ single ANIMATED building.
+ """
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedAnimBuilding')
+
+ def __init__(self, cr):
+ """blockNumber: the landmark building number (from the name)"""
+ DistributedBuilding.DistributedBuilding.__init__(self,cr)
+
+ def enterToon(self,ts):
+ """Clear out the decal effect as quick hack."""
+ DistributedBuilding.DistributedBuilding.enterToon(self,ts)
+ self.fixEffects()
+
+ def fixEffects(self):
+ """Fix our attribs and effects to show up properly."""
+ nodes=self.getNodePaths()
+ for curNode in nodes:
+ mf = curNode.find('**/*mesh_front*')
+ sign_joint = curNode.find('**/sign_origin_joint')
+ if not sign_joint.isEmpty():
+ self.notify.debug("I found sign_origin_joint 1")
+ if not mf.isEmpty():
+ sign = mf.find('**/sign')
+ mf.clearEffect(DecalEffect.getClassType())
+ if not sign.isEmpty():
+ sign.setDepthWrite(1,1)
+ sign.setEffect(DecalEffect.make())
+ sign_joint = curNode.find('**/sign_origin_joint')
+ allSignJoints = curNode.findAllMatches('**/sign_origin_joint')
+ num = allSignJoints.getNumPaths()
+ if num:
+ sign_joint = allSignJoints.getPath(num-1)
+ if not sign_joint.isEmpty():
+ self.notify.debug("I found sign_origin_joint 2")
+ sign.wrtReparentTo(sign_joint)
+
+ def setupNametag(self):
+ assert(self.debugPrint("setupNametag()"))
+ if not self.wantsNametag():
+ return
+ DistributedBuilding.DistributedBuilding.setupNametag(self)
+
+ def getSbSearchString(self):
+ """Return a string to use when looking for the suit building nodepath."""
+ result = "landmarkBlocks/sb" + str(self.block) + \
+ ":*animated_building_*_DNARoot"
+ return result
+
+ def adjustSbNodepathScale(self, nodePath):
+ """Animated buildings needs a scale hack, this does nothing for reg bldg."""
+ nodePath.setScale(0.543667, 1, 1)
+ pass
+
+ def animToToon(self, timeStamp):
+ DistributedBuilding.DistributedBuilding.animToToon(self,timeStamp)
+ # the doors don't look right do the fix effects
+ self.fixEffects()
diff --git a/toontown/src/building/DistributedAnimBuildingAI.py b/toontown/src/building/DistributedAnimBuildingAI.py
new file mode 100644
index 0000000..b71b213
--- /dev/null
+++ b/toontown/src/building/DistributedAnimBuildingAI.py
@@ -0,0 +1,27 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.building import DistributedBuildingAI
+from toontown.building import DistributedAnimDoorAI
+from toontown.building import DoorTypes
+
+class DistributedAnimBuildingAI(DistributedBuildingAI.DistributedBuildingAI):
+ """
+ DistributedAnimBuildingAI class: The server side representation of a
+ single ANIMATED building. This is the object that remember who 'owns' the
+ associated building (either the bad guys or the toons). The child
+ of this object, the DistributedBuilding object, is the client side
+ version and updates the display that client's display based on who
+ 'owns' the building.
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedAnimBuildingAI')
+
+ def __init__(self, air, blockNumber, zoneId, trophyMgr):
+ """blockNumber: the landmark building number (from the name)"""
+ DistributedBuildingAI.DistributedBuildingAI.__init__(self, air, blockNumber, zoneId, trophyMgr)
+
+ def createExteriorDoor(self):
+ """Return the DistributedDoor for the exterior, with correct door type set"""
+ result = DistributedAnimDoorAI.DistributedAnimDoorAI(self.air, self.block,
+ DoorTypes.EXT_ANIM_STANDARD)
+ return result
diff --git a/toontown/src/building/DistributedAnimDoor.py b/toontown/src/building/DistributedAnimDoor.py
new file mode 100644
index 0000000..22ae594
--- /dev/null
+++ b/toontown/src/building/DistributedAnimDoor.py
@@ -0,0 +1,266 @@
+""" DistributedAnimDoor module: contains the DistributedAnimDoor
+ class, the client side representation of a 'animated landmark door'."""
+
+from pandac.PandaModules import NodePath, VBase3
+from direct.directnotify import DirectNotifyGlobal
+from direct.interval.IntervalGlobal import Parallel, Sequence, Wait, \
+ HprInterval, LerpHprInterval, SoundInterval
+from toontown.building import DistributedDoor
+from toontown.building import DoorTypes
+
+if( __debug__ ):
+ import pdb
+
+class DistributedAnimDoor(DistributedDoor.DistributedDoor):
+ """
+ DistributedAnimDoor class: The client side representation of a
+ animated 'landmark door'.
+ Too much stuff has changed to put them in DistributedDoor.py
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedAnimDoor')
+ #notify.setDebug(True)
+
+ def __init__(self, cr):
+ """constructor for the DistributedDoor"""
+ DistributedDoor.DistributedDoor.__init__(self, cr)
+ # WARNING debug only
+ base.animDoor = self
+
+ def getBuilding(self):
+ """Return only a nodepath to our parent building.
+ See also getAnimBuilding.
+ """
+ if (not self.__dict__.has_key('building')):
+ if self.doorType == DoorTypes.EXT_ANIM_STANDARD:
+ searchStr = "**/??"+str(self.block)+":animated_building_*_DNARoot;+s"
+ self.notify.debug("searchStr=%s" % searchStr)
+ self.building = self.cr.playGame.hood.loader.geom.find(searchStr)
+ else:
+ self.notify.error("DistributedAnimDoor.getBuiding with doorType=%s"% self.doorType)
+
+ assert(not self.building.isEmpty())
+ return self.building
+
+ def getDoorNodePath(self):
+ """Return the nodepath to door origin."""
+ if self.doorType == DoorTypes.EXT_ANIM_STANDARD:
+ if hasattr(self, "tempDoorNodePath"):
+ return self.tempDoorNodePath
+ else:
+ # tempDoorNodePath gets removed in disable
+ # ...exterior door.
+ assert(self.debugPrint("getDoorNodePath() -- exterior"))
+ building = self.getBuilding()
+ doorNP = building.find("**/door_origin")
+ self.notify.debug("creating doorOrigin at %s %s" % (str(doorNP.getPos()),
+ str(doorNP.getHpr())))
+ otherNP=NodePath("doorOrigin")
+ otherNP.setPos(doorNP.getPos())
+ otherNP.setHpr(doorNP.getHpr()) #
+ otherNP.reparentTo(doorNP.getParent())
+ assert(not otherNP.isEmpty())
+ # Store this for clean up later
+ self.tempDoorNodePath=otherNP
+ else:
+ self.notify.error("DistributedAnimDoor.getDoorNodePath with doorType=%s"% self.doorType)
+ return otherNP
+
+ def setTriggerName(self):
+ """Find and rename the collision trigger."""
+ if self.doorType == DoorTypes.EXT_ANIM_STANDARD:
+ building = self.getBuilding()
+ if not building.isEmpty():
+ doorTrigger = building.find("**/door_0_door_trigger")
+ if not doorTrigger.isEmpty():
+ doorTrigger.node().setName(self.getTriggerName())
+ else:
+ # we could have already set it
+ pass
+ else:
+ self.notify.warning("setTriggerName failed no building")
+ else:
+ self.notify.error("setTriggerName doorTYpe=%s" % self.doorType)
+
+ def getAnimBuilding(self):
+ """Return a handle on the animated building prop."""
+ # Once we find it, we store it, so we don't have to find it again.
+ if (not self.__dict__.has_key('animBuilding')):
+ if self.doorType == DoorTypes.EXT_ANIM_STANDARD:
+ #self.building = self.cr.playGame.hood.loader.geom.find(
+ # "**/??"+str(self.block)+":animated_building_*_DNARoot;+s")
+ bldg= self.getBuilding()
+ key = bldg.getParent().getParent()
+ animPropList = self.cr.playGame.hood.loader.animPropDict.get(key)
+
+ if animPropList:
+ for prop in animPropList:
+ # TODO string matching is such a hack, maybe test paths?
+ if bldg == prop.getActor().getParent():
+ self.animBuilding = prop
+ break
+ else:
+ self.notify.error("could not find" + str(key))
+ else:
+ self.notify.error("No such door type as " + str(self.doorType))
+
+ assert(not self.animBuilding.getActor().isEmpty())
+ return self.animBuilding
+
+ def getBuildingActor(self):
+ """Return the animated building actor."""
+ result = self.getAnimBuilding().getActor()
+ return result
+
+ ##### opening state #####
+ def enterOpening(self, ts):
+ #if( __debug__ ):
+ # import pdb
+ # pdb.set_trace()
+ assert(self.debugPrint("enterOpening()"))
+
+ # Right door:
+ bldgActor = self.getBuildingActor()
+ rightDoor = bldgActor.controlJoint(None, "modelRoot", "def_right_door")
+ if (rightDoor.isEmpty()):
+ self.notify.warning("enterOpening(): did not find rightDoor")
+ return
+ # Open the door:
+ otherNP=self.getDoorNodePath()
+ trackName = "doorOpen-%d" % (self.doId)
+ if self.rightSwing:
+ h = 100
+ else:
+ h = -100
+ # Stop animation:
+ self.finishDoorTrack()
+ self.doorTrack=Parallel(
+ SoundInterval(self.openSfx, node=rightDoor),
+ Sequence(
+ HprInterval(
+ rightDoor,
+ VBase3(0, 0, 0),
+ #other=otherNP,
+ ),
+ Wait(0.4),
+ #Func(rightDoor.show),
+ #Func(doorFrameHoleRight.show),
+ LerpHprInterval(
+ nodePath=rightDoor,
+ duration=0.6,
+ hpr=VBase3(h, 0, 0),
+ startHpr=VBase3(0, 0, 0),
+ #other=otherNP,
+ blendType="easeInOut")),
+ name = trackName)
+ # Start the tracks:
+ self.doorTrack.start(ts)
+
+ ##### closing state #####
+
+ def enterClosing(self, ts):
+ assert(self.debugPrint("enterClosing()"))
+ # Right door:
+ bldgActor = self.getBuildingActor()
+ rightDoor = bldgActor.controlJoint(None, "modelRoot", "def_right_door")
+ if (rightDoor.isEmpty()):
+ self.notify.warning("enterClosing(): did not find rightDoor")
+ return
+
+ # Close the door:
+ otherNP=self.getDoorNodePath()
+ trackName = "doorClose-%d" % (self.doId)
+ if self.rightSwing:
+ h = 100
+ else:
+ h = -100
+ # Stop animation:
+ self.finishDoorTrack()
+ self.doorTrack=Sequence(
+ LerpHprInterval(
+ nodePath=rightDoor,
+ duration=1.0,
+ hpr=VBase3(0, 0, 0),
+ startHpr=VBase3(h, 0, 0),
+ #other=otherNP,
+ blendType="easeInOut"),
+ #Func(doorFrameHoleRight.hide),
+ #Func(self.hideIfHasFlat, rightDoor),
+ SoundInterval(self.closeSfx, node=rightDoor),
+ name = trackName)
+ self.doorTrack.start(ts)
+ if hasattr(self, "done"):
+ request = self.getRequestStatus()
+ messenger.send("doorDoneEvent", [request])
+
+ ##### Exit Door opening state #####
+
+ def exitDoorEnterOpening(self, ts):
+ assert(self.debugPrint("exitDoorEnterOpening()"))
+ bldgActor = self.getBuildingActor()
+ leftDoor = bldgActor.controlJoint(None, "modelRoot", "def_left_door")
+ if self.leftSwing:
+ h = -100
+ else:
+ h = 100
+ if (not leftDoor.isEmpty()):
+ # Open the door:
+ otherNP=self.getDoorNodePath()
+ trackName = "doorDoorExitTrack-%d" % (self.doId)
+ self.finishDoorExitTrack()
+ self.doorExitTrack = Parallel(
+ SoundInterval(self.openSfx, node=leftDoor),
+ Sequence(
+ #Func(leftDoor.show),
+ #Func(doorFrameHoleLeft.show),
+ LerpHprInterval(nodePath=leftDoor,
+ duration=0.6,
+ hpr=VBase3(h, 0, 0),
+ startHpr=VBase3(0, 0, 0),
+ #other=otherNP,
+ blendType="easeInOut")),
+ name = trackName)
+ # Start the tracks:
+ self.doorExitTrack.start(ts)
+ else:
+ self.notify.warning("exitDoorEnterOpening(): did not find leftDoor")
+
+ ##### Exit Door closing state #####
+
+ def exitDoorEnterClosing(self, ts):
+ assert(self.debugPrint("exitDoorEnterClosing()"))
+ # Start animation:
+
+ bldgActor = self.getBuildingActor()
+ leftDoor = bldgActor.controlJoint(None, "modelRoot", "def_left_door")
+
+ #if ZoneUtil.isInterior(self.zoneId):
+ # doorFrameHoleLeft.setColor(1., 1., 1., 1.)
+ # Left door:
+ if self.leftSwing:
+ h = -100
+ else:
+ h = 100
+
+ if (not leftDoor.isEmpty()):
+ # Close the door:
+ otherNP=self.getDoorNodePath()
+ trackName = "doorExitTrack-%d" % (self.doId)
+ self.finishDoorExitTrack()
+ self.doorExitTrack = Sequence(
+ LerpHprInterval(
+ nodePath=leftDoor,
+ duration=1.0,
+ hpr=VBase3(0, 0, 0),
+ startHpr=VBase3(h, 0, 0),
+ #other=otherNP,
+ blendType="easeInOut"),
+ #Func(doorFrameHoleLeft.hide),
+ #Func(self.hideIfHasFlat, leftDoor),
+ SoundInterval(self.closeSfx, node=leftDoor),
+ name = trackName)
+ self.doorExitTrack.start(ts)
+ #else:
+ # self.notify.error("enterOpening(): did not find leftDoor")
+
diff --git a/toontown/src/building/DistributedAnimDoorAI.py b/toontown/src/building/DistributedAnimDoorAI.py
new file mode 100644
index 0000000..f565d7e
--- /dev/null
+++ b/toontown/src/building/DistributedAnimDoorAI.py
@@ -0,0 +1,29 @@
+""" DistributedDoorAI module: contains the DistributedDoorAI
+ class, the server side representation of a 'landmark door'."""
+from direct.directnotify import DirectNotifyGlobal
+from toontown.building import DistributedDoorAI
+
+class DistributedAnimDoorAI(DistributedDoorAI.DistributedDoorAI):
+ """
+ The server side representation of a single anim door of an animated landmark
+ building.
+ Too many things have changed to stuff it in regular DistributedDoor.py
+ """
+
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedAnimDoorAI')
+
+ def __init__(self, air, blockNumber, doorType, doorIndex=0,
+ lockValue=0, swing=3):
+ """
+ blockNumber: the landmark building number (from the name)
+ doorIndex: Each door must have a unique index.
+ """
+ #import pdb; pdb.set_trace()
+ assert(self.notify.debug(str(blockNumber)+" DistributedAnimDoorAI("
+ "%s, %s)" % ("the air", str(blockNumber))))
+ DistributedDoorAI.DistributedDoorAI.__init__(self, air,
+ blockNumber, doorType,
+ doorIndex, lockValue,
+ swing)
+
diff --git a/toontown/src/building/DistributedAnimatedProp.py b/toontown/src/building/DistributedAnimatedProp.py
new file mode 100644
index 0000000..8effe24
--- /dev/null
+++ b/toontown/src/building/DistributedAnimatedProp.py
@@ -0,0 +1,168 @@
+""" DistributedAnimatedProp module: contains the DistributedAnimatedProp
+ class, the client side representation of a 'landmark door'."""
+
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObject
+
+class DistributedAnimatedProp(DistributedObject.DistributedObject):
+ """
+ DistributedAnimatedProp class: The client side representation of any
+ simple animated prop.
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedAnimatedProp')
+
+ def __init__(self, cr):
+ """constructor for the DistributedAnimatedProp"""
+ DistributedObject.DistributedObject.__init__(self, cr)
+ assert self.debugPrint("__init()")
+
+ self.fsm = ClassicFSM.ClassicFSM('DistributedAnimatedProp',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['playing',
+ 'attract']),
+ State.State('attract',
+ self.enterAttract,
+ self.exitAttract,
+ ['playing']),
+ State.State('playing',
+ self.enterPlaying,
+ self.exitPlaying,
+ ['attract'])],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ # self.generate will be called automatically.
+
+ def generate(self):
+ """
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ assert self.debugPrint("generate()")
+ DistributedObject.DistributedObject.generate(self)
+
+ def announceGenerate(self):
+ """
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ assert self.debugPrint("announceGenerate()")
+ DistributedObject.DistributedObject.announceGenerate(self)
+ self.setState(self.initialState, self.initialStateTimestamp)
+ del self.initialState
+ del self.initialStateTimestamp
+
+ def disable(self):
+ assert self.debugPrint("disable()")
+ # Go to the off state when the object is put in the cache
+ self.fsm.request("off")
+ DistributedObject.DistributedObject.disable(self)
+ # self.delete() will automatically be called.
+
+ def delete(self):
+ assert self.debugPrint("delete()")
+ del self.fsm
+ DistributedObject.DistributedObject.delete(self)
+
+ def setPropId(self, propId):
+ """
+ required dc field.
+ """
+ assert self.debugPrint("setPropId(%s)"%(propId,))
+ assert not self.__dict__.has_key("propId")
+ self.propId=propId
+
+ def setAvatarInteract(self, avatarId):
+ """
+ required dc field.
+ """
+ assert self.debugPrint("setAvatarInteract(%s)"%(avatarId,))
+ assert not self.__dict__.has_key(avatarId)
+ self.avatarId=avatarId
+
+ def setOwnerDoId(self, ownerDoId):
+ """
+ required dc field.
+ """
+ assert self.debugPrint("setOwnerDoId(%s)"%(ownerDoId,))
+ assert not self.__dict__.has_key("ownerDoId")
+ self.ownerDoId=ownerDoId
+
+ def setState(self, state, timestamp):
+ """
+ required dc field.
+ """
+ assert self.debugPrint("setState(%s, %d)" % (state, timestamp))
+ if self.isGenerated():
+ self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+ else:
+ self.initialState = state
+ self.initialStateTimestamp = timestamp
+
+ #def __getPropNodePath(self):
+ # assert self.debugPrint("__getPropNodePath()")
+ # if (not self.__dict__.has_key('propNodePath')):
+ # self.propNodePath=self.cr.playGame.hood.loader.geom.find(
+ # "**/prop"+self.propID+":*_DNARoot")
+ # return self.propNodePath
+
+ def enterTrigger(self, args=None):
+ assert self.debugPrint("enterTrigger(args="+str(args)+")")
+ messenger.send("DistributedAnimatedProp_enterTrigger")
+ self.sendUpdate("requestInteract")
+ # the AI server will reply with toonInteract or rejectInteract.
+
+ def exitTrigger(self, args=None):
+ assert self.debugPrint("exitTrigger(args="+str(args)+")")
+ messenger.send("DistributedAnimatedProp_exitTrigger")
+ self.sendUpdate("requestExit")
+ # the AI server will reply with avatarExit.
+
+ def rejectInteract(self):
+ """Server doesn't let the avatar interact with prop"""
+ assert self.debugPrint("rejectInteract()")
+ self.cr.playGame.getPlace().setState('walk')
+
+ def avatarExit(self, avatarId):
+ assert self.debugPrint("avatarExit(avatarId=%s)"%(avatarId,))
+
+ ##### off state #####
+
+ def enterOff(self):
+ assert self.debugPrint("enterOff()")
+
+ def exitOff(self):
+ assert self.debugPrint("exitOff()")
+
+ ##### attract state #####
+
+ def enterAttract(self, ts):
+ assert self.debugPrint("enterAttract()")
+
+ def exitAttract(self):
+ assert self.debugPrint("exitAttract()")
+
+ ##### playing state #####
+
+ def enterPlaying(self, ts):
+ assert self.debugPrint("enterPlaying()")
+
+ def exitPlaying(self):
+ assert self.debugPrint("exitPlaying()")
+
+ if __debug__:
+ def debugPrint(self, message):
+ """for debugging"""
+ return self.notify.debug(
+ str(self.__dict__.get('propId', '?'))+' '+message)
diff --git a/toontown/src/building/DistributedAnimatedPropAI.py b/toontown/src/building/DistributedAnimatedPropAI.py
new file mode 100644
index 0000000..c4385ae
--- /dev/null
+++ b/toontown/src/building/DistributedAnimatedPropAI.py
@@ -0,0 +1,149 @@
+""" DistributedAnimatedPropAI module: contains the DistributedAnimatedPropAI
+ class, the server side representation of a simple, animated, interactive
+ prop."""
+
+
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObjectAI
+from direct.fsm import State
+
+
+class DistributedAnimatedPropAI(DistributedObjectAI.DistributedObjectAI):
+ """
+ DistributedAnimatedPropAI class: The server side representation of
+ an animated prop. This is the object that remembers what the
+ prop is doing. The child of this object, the DistributedAnimatedProp
+ object, is the client side version and updates the display that
+ client's display based on the state of the prop.
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedAnimatedPropAI')
+
+ def __init__(self, air, propId):
+ """propId: a unique identifier for this prop."""
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ assert(self.debugPrint(
+ "DistributedAnimatedPropAI(air=%s, propId=%s)"
+ %("the air", propId)))
+ self.fsm = ClassicFSM.ClassicFSM('DistributedAnimatedPropAI',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['playing']),
+ # Attract is an idle mode. It is named attract
+ # because the prop is not interacting with an
+ # avatar, and is therefore trying to attract an
+ # avatar.
+ State.State('attract',
+ self.enterAttract,
+ self.exitAttract,
+ ['playing']),
+ # Playing is for when an avatar is interacting
+ # with the prop.
+ State.State('playing',
+ self.enterPlaying,
+ self.exitPlaying,
+ ['attract'])],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ self.propId=propId
+ self.avatarId=0
+
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ del self.fsm
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def getPropId(self):
+ assert self.debugPrint("getPropId() returning: %s"%(self.propId,))
+ return self.propId
+
+ def getAvatarInteract(self):
+ assert self.debugPrint("getAvatarInteract() returning: %s"%(self.avatarId,))
+ return self.avatarId
+
+ def getInitialState(self):
+ assert self.debugPrint("getInitialState()")
+ return [self.fsm.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+
+ def getOwnerDoId(self):
+ assert self.debugPrint("getOwnerDoId() returning: %s"%(self.ownerDoId,))
+ return self.ownerDoId
+
+ def requestInteract(self):
+ assert self.debugPrint("requestInteract()")
+ avatarId = self.air.getAvatarIdFromSender()
+ assert self.notify.debug(" avatarId:%s"%(avatarId,))
+ stateName = self.fsm.getCurrentState().getName()
+ if stateName != 'playing':
+ self.sendUpdate("setAvatarInteract", [avatarId])
+ self.avatarId=avatarId
+ self.fsm.request('playing')
+ else:
+ self.sendUpdateToAvatarId(avatarId, "rejectInteract", [])
+
+ def requestExit(self):
+ assert self.debugPrint("requestExit()")
+ avatarId = self.air.getAvatarIdFromSender()
+ assert self.notify.debug(" avatarId:%s"%(avatarId,))
+ if avatarId==self.avatarId:
+ stateName = self.fsm.getCurrentState().getName()
+ if stateName == 'playing':
+ self.sendUpdate("avatarExit", [avatarId])
+ self.fsm.request('attract')
+ else:
+ assert self.notify.debug(" requestExit: invalid avatarId")
+
+ def getState(self):
+ assert self.debugPrint("getState()")
+ return [self.fsm.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+
+ def d_setState(self, state):
+ assert self.debugPrint("d_setState(state=%s)"%(state,))
+ self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()])
+
+ ##### off state #####
+
+ def enterOff(self):
+ assert self.debugPrint("enterOff()")
+ #self.d_setState('off')
+
+ def exitOff(self):
+ assert self.debugPrint("exitOff()")
+
+ ##### attract state #####
+
+ def enterAttract(self):
+ assert self.debugPrint("enterAttract()")
+ self.d_setState('attract')
+
+ def exitAttract(self):
+ assert self.debugPrint("exitAttract()")
+
+ ##### open state #####
+
+ def enterPlaying(self):
+ assert self.debugPrint("enterPlaying()")
+ self.d_setState('playing')
+
+ def exitPlaying(self):
+ assert self.debugPrint("exitPlaying()")
+
+ if __debug__:
+ def debugPrint(self, message):
+ """for debugging"""
+ return self.notify.debug(
+ str(self.__dict__.get('propId', '?'))+' '+message)
+
diff --git a/toontown/src/building/DistributedBBElevator.py b/toontown/src/building/DistributedBBElevator.py
new file mode 100644
index 0000000..9dafb1a
--- /dev/null
+++ b/toontown/src/building/DistributedBBElevator.py
@@ -0,0 +1,41 @@
+import DistributedElevator
+import DistributedBossElevator
+from ElevatorConstants import *
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import TTLocalizer
+
+class DistributedBBElevator(DistributedBossElevator.DistributedBossElevator):
+
+ def __init__(self, cr):
+ DistributedBossElevator.DistributedBossElevator.__init__(self, cr)
+ self.type = ELEVATOR_BB
+ self.countdownTime = ElevatorData[self.type]['countdown']
+ self.elevatorPoints = BossbotElevatorPoints
+
+ def setupElevator(self):
+ """setupElevator(self)
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+ # TODO: place this on a node indexed by the entraceId
+ geom = base.cr.playGame.hood.loader.geom
+ self.elevatorModel = loader.loadModel(
+ "phase_12/models/bossbotHQ/BB_Elevator")
+
+ self.leftDoor = self.elevatorModel.find("**/left-door")
+ if self.leftDoor.isEmpty():
+ self.leftDoor = self.elevatorModel.find("**/left_door")
+
+ self.rightDoor = self.elevatorModel.find("**/right-door")
+ if self.rightDoor.isEmpty():
+ self.rightDoor = self.elevatorModel.find("**/right_door")
+
+
+ locator = geom.find('**/elevator_locator')
+ self.elevatorModel.reparentTo(locator)
+
+ DistributedElevator.DistributedElevator.setupElevator(self)
+
+ def getDestName(self):
+ return TTLocalizer.ElevatorBossBotBoss
+
diff --git a/toontown/src/building/DistributedBBElevatorAI.py b/toontown/src/building/DistributedBBElevatorAI.py
new file mode 100644
index 0000000..051865c
--- /dev/null
+++ b/toontown/src/building/DistributedBBElevatorAI.py
@@ -0,0 +1,19 @@
+from ElevatorConstants import *
+import DistributedBossElevatorAI
+
+class DistributedBBElevatorAI(DistributedBossElevatorAI.DistributedBossElevatorAI):
+
+ def __init__(self, air, bldg, zone, antiShuffle = 0, minLaff = 0):
+ """__init__(air)
+ """
+ DistributedBossElevatorAI.DistributedBossElevatorAI.__init__(self, air, bldg, zone, antiShuffle = antiShuffle, minLaff = 0)
+ self.type = ELEVATOR_BB
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
+ def checkBoard(self, av):
+ result = 0
+ if simbase.config.GetBool('allow-ceo-elevator',1):
+ result = DistributedBossElevatorAI.DistributedBossElevatorAI.checkBoard(self,av)
+ else:
+ result = REJECT_NOT_YET_AVAILABLE
+ return result
diff --git a/toontown/src/building/DistributedBoardingParty.py b/toontown/src/building/DistributedBoardingParty.py
new file mode 100644
index 0000000..bb26023
--- /dev/null
+++ b/toontown/src/building/DistributedBoardingParty.py
@@ -0,0 +1,775 @@
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownGlobals
+from direct.distributed import DistributedObject
+from toontown.toon import GroupInvitee
+from toontown.toon import GroupPanel
+from toontown.toon import BoardingGroupInviterPanels
+from toontown.building import BoardingPartyBase
+from direct.gui.DirectGui import *
+from toontown.toontowngui import TTDialog
+from toontown.hood import ZoneUtil
+from toontown.toontowngui import TeaserPanel
+from direct.interval.IntervalGlobal import *
+import BoardingGroupShow
+
+class DistributedBoardingParty(DistributedObject.DistributedObject, BoardingPartyBase.BoardingPartyBase):
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBoardingParty')
+
+ # This is the length of time that should elapse before we allow
+ # another InvitationFailed message to be displayed from the same avatar.
+ InvitationFailedTimeout = 60.0
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+ BoardingPartyBase.BoardingPartyBase.__init__(self)
+ if __debug__:
+ base.bparty = self
+ self.groupInviteePanel = None
+ self.groupPanel = None
+ self.inviterPanels = BoardingGroupInviterPanels.BoardingGroupInviterPanels()
+ self.lastInvitationFailedMessage = {} #key->avatarId : time
+ self.goToPreShowTrack = None
+ self.goToShowTrack = None
+
+ def generate(self):
+ self.load()
+ DistributedObject.DistributedObject.generate(self)
+ localAvatar.boardingParty = self
+
+ def announceGenerate(self):
+ canonicalZoneId = ZoneUtil.getCanonicalZoneId(self.zoneId)
+ self.notify.debug('canonicalZoneId = %s' %canonicalZoneId)
+ localAvatar.chatMgr.chatInputSpeedChat.addBoardingGroupMenu(canonicalZoneId)
+
+ #REMOVE THIS from here and put it in the right place:
+ if base.config.GetBool('want-singing', 0):
+ localAvatar.chatMgr.chatInputSpeedChat.addSingingGroupMenu()
+
+ def delete(self):
+ DistributedObject.DistributedObject.delete(self)
+
+ def disable(self):
+ #self.demandDrop()
+ self.finishGoToPreShowTrack()
+ self.finishGoToShowTrack()
+ self.forceCleanupInviteePanel()
+ self.forceCleanupInviterPanels()
+ self.inviterPanels = None
+ if self.groupPanel:
+ self.groupPanel.cleanup()
+ self.groupPanel = None
+ DistributedObject.DistributedObject.disable(self)
+ BoardingPartyBase.BoardingPartyBase.cleanup(self)
+ localAvatar.boardingParty = None
+ localAvatar.chatMgr.chatInputSpeedChat.removeBoardingGroupMenu()
+ self.lastInvitationFailedMessage = {}
+
+ def getElevatorIdList(self):
+ return self.elevatorIdList
+
+ def setElevatorIdList(self, elevatorIdList):
+ self.notify.debug("setElevatorIdList")
+ self.elevatorIdList = elevatorIdList
+
+ def load(self):
+ """
+ Load any assets here if required.
+ """
+ pass
+
+ def postGroupInfo(self, leaderId, memberList, inviteeList, kickedList):
+ """
+ A group has changed so the AI is sending us new information on it
+ """
+ self.notify.debug("postgroupInfo")
+ isMyGroup = 0
+ removedMemberIdList = []
+ if self.groupListDict.has_key(leaderId):
+ oldGroupEntry = self.groupListDict[leaderId]
+ else:
+ oldGroupEntry = [[],[],[]]
+ oldMemberList = oldGroupEntry[0]
+
+ newGroupEntry = [memberList, inviteeList, kickedList]
+ self.groupListDict[leaderId] = newGroupEntry
+
+ # Comb the avIdDict if there is a change in the number of members in the group.
+ # Remove any avId that has left the group or has been kicked out.
+ if not (len(oldMemberList) == len(memberList)):
+ for oldMember in oldMemberList:
+ if not oldMember in memberList:
+ # This member has been removed.
+ if oldMember in self.avIdDict:
+ # Remove this member from the avIdDict ONLY if we doesn't have a different leader.
+ # This means he hasn't joined a different group.
+ if (self.avIdDict[oldMember] == leaderId):
+ self.avIdDict.pop(oldMember)
+ removedMemberIdList.append(oldMember)
+
+ self.avIdDict[leaderId] = leaderId
+ if leaderId == localAvatar.doId:
+ isMyGroup = 1
+ for memberId in memberList:
+ self.avIdDict[memberId] = leaderId
+ if memberId == localAvatar.doId:
+ isMyGroup = 1
+
+ if (newGroupEntry[0]) == [0] or (not newGroupEntry[0]): # if the new memberList is empty
+ dgroup = self.groupListDict.pop(leaderId)
+ for memberId in dgroup[0]:
+ if self.avIdDict.has_key(memberId):
+ self.avIdDict.pop(memberId)
+
+ if isMyGroup:
+ self.notify.debug("new info posted on my group")
+ if not self.groupPanel:
+ self.groupPanel = GroupPanel.GroupPanel(self)
+ messenger.send('updateGroupStatus')
+ for removedMemberId in removedMemberIdList:
+ removedMember = base.cr.doId2do.get(removedMemberId)
+ # Send the message to the members only if you can find the removedMember.
+ # This means that this would work only if the removedMember left the
+ # group using the leave button. It won't work if the removedMember Alt+F4ed out.
+ if not removedMember:
+ # Try second time, just in case removedMemberId is a friend.
+ removedMember = base.cr.identifyFriend(removedMemberId)
+ if removedMember:
+ removedMemberName = removedMember.name
+ # Message to the members in the group that the removedMember has left the Boarding Group.
+ messageText = TTLocalizer.BoardingMessageLeftGroup %(removedMemberName)
+ localAvatar.setSystemMessage(0, messageText, WhisperPopup.WTToontownBoardingGroup)
+
+ elif (localAvatar.doId in oldMemberList) and not (localAvatar.doId in memberList):
+ # localAvatar's doId was there in the old list but is not there in the new list
+ # This must mean that he has either left the group or kicked out.
+ messenger.send('updateGroupStatus')
+ # Cleanup the GroupPanel.
+ if self.groupPanel:
+ self.groupPanel.cleanup()
+ self.groupPanel = None
+ else:
+ self.notify.debug("new info posted on some other group")
+
+ def postInvite(self, leaderId, inviterId):
+ """
+ The AI tells us someone has been invited into a group
+ """
+ self.notify.debug("post Invite")
+ if not base.cr.avatarFriendsManager.checkIgnored(inviterId):
+ inviter = base.cr.doId2do.get(inviterId)
+ if inviter:
+ if self.inviterPanels.isInvitingPanelUp() or self.inviterPanels.isInvitationRejectedPanelUp():
+ self.inviterPanels.forceCleanup()
+ self.groupInviteePanel = GroupInvitee.GroupInvitee()
+ self.groupInviteePanel.make(self, inviter, leaderId)
+
+ def postKick(self, leaderId):
+ self.notify.debug("%s was kicked out of the Boarding Group by %s" % (localAvatar.doId, leaderId))
+ # Message to the kicked player that the leader kicked him out.
+ localAvatar.setSystemMessage(0, TTLocalizer.BoardingMessageKickedOut, WhisperPopup.WTToontownBoardingGroup)
+
+ def postSizeReject(self, leaderId, inviterId, inviteeId):
+ self.notify.debug("%s was not invited because the group is full" % (inviteeId))
+
+ def postKickReject(self, leaderId, inviterId, inviteeId):
+ self.notify.debug("%s was not invited because %s has kicked them from the group" % (inviteeId, leaderId))
+
+ def postInviteDelcined(self, inviteeId):
+ self.notify.debug("%s delinced %s's Boarding Group invitation." %(inviteeId, localAvatar.doId))
+ # Show Boarding Group Invitation Rejected Panel
+ invitee = base.cr.doId2do.get(inviteeId)
+ if invitee:
+ self.inviterPanels.createInvitationRejectedPanel(self, inviteeId)
+
+ def postInviteAccepted(self, inviteeId):
+ self.notify.debug("%s accepted %s's Boarding Group invitation." %(inviteeId, localAvatar.doId))
+ # Remove the Boarding Group Inviting Panel if it has the same inviteeId
+ if self.inviterPanels.isInvitingPanelIdCorrect(inviteeId):
+ self.inviterPanels.destroyInvitingPanel()
+
+ def postInviteCanceled(self):
+ self.notify.debug("The invitation to the Boarding Group was cancelled")
+ # Remove the Boarding Group Invitation Panel
+ if self.isInviteePanelUp():
+ self.groupInviteePanel.cleanup()
+ self.groupInviteePanel = None
+
+ def postInviteNotQualify(self, avId, reason, elevatorId):
+ messenger.send('updateGroupStatus')
+ rejectText = ''
+ minLaff = TTLocalizer.BoardingMore
+
+ if elevatorId:
+ elevator = base.cr.doId2do.get(elevatorId)
+ if elevator:
+ minLaff = elevator.minLaff
+
+ if (avId == localAvatar.doId):
+ if (reason == BoardingPartyBase.BOARDCODE_MINLAFF):
+ rejectText = TTLocalizer.BoardingInviteMinLaffInviter %(minLaff)
+ if (reason == BoardingPartyBase.BOARDCODE_PROMOTION):
+ rejectText = TTLocalizer.BoardingInvitePromotionInviter
+ else:
+ avatar = base.cr.doId2do.get(avId)
+ if avatar:
+ avatarNameText = avatar.name
+ else:
+ avatarNameText = ''
+ if (reason == BoardingPartyBase.BOARDCODE_MINLAFF):
+ rejectText = TTLocalizer.BoardingInviteMinLaffInvitee %(avatarNameText, minLaff)
+ if (reason == BoardingPartyBase.BOARDCODE_PROMOTION):
+ rejectText = TTLocalizer.BoardingInvitePromotionInvitee %(avatarNameText)
+ if (reason == BoardingPartyBase.BOARDCODE_BATTLE):
+ rejectText = TTLocalizer.TeleportPanelNotAvailable %(avatarNameText)
+ if (reason == BoardingPartyBase.BOARDCODE_NOT_PAID):
+ rejectText = TTLocalizer.BoardingInviteNotPaidInvitee %(avatarNameText)
+ if (reason == BoardingPartyBase.BOARDCODE_DIFF_GROUP):
+ rejectText = TTLocalizer.BoardingInviteeInDiffGroup %(avatarNameText)
+ if (reason == BoardingPartyBase.BOARDCODE_PENDING_INVITE):
+ rejectText = TTLocalizer.BoardingInviteePendingIvite %(avatarNameText)
+ if (reason == BoardingPartyBase.BOARDCODE_IN_ELEVATOR):
+ rejectText = TTLocalizer.BoardingInviteeInElevator %(avatarNameText)
+ if self.inviterPanels.isInvitingPanelIdCorrect(avId) or (avId == localAvatar.doId):
+ self.inviterPanels.destroyInvitingPanel()
+ self.showMe(rejectText)
+
+ def postAlreadyInGroup(self):
+ """
+ The invitee is already part of a group and cannot accept another invitation.
+ """
+ self.showMe(TTLocalizer.BoardingAlreadyInGroup)
+
+ def postGroupAlreadyFull(self):
+ """
+ The invitee cannot accept the invitation because the group is already full.
+ """
+ self.showMe(TTLocalizer.BoardingGroupAlreadyFull)
+
+ def postSomethingMissing(self):
+ """
+ The AI determines that something is wrong and this cannot be accepted.
+ Eg 1: The leader of the group is not there in the avIdDict.
+ """
+ self.showMe(TTLocalizer.BoardcodeMissing)
+
+ def postRejectBoard(self, elevatorId, reason, avatarsFailingRequirements, avatarsInBattle):
+ """
+ Problem when the leader tried to enter the elevator detected on AI
+ """
+ self.showRejectMessage(elevatorId, reason, avatarsFailingRequirements, avatarsInBattle)
+ # Enable the Group Panel GO Button here.
+ self.enableGoButton()
+
+ def postRejectGoto(self, elevatorId, reason, avatarsFailingRequirements, avatarsInBattle):
+ """
+ Problem when the leader tried to use a GO Button detected on AI.
+ """
+ self.showRejectMessage(elevatorId, reason, avatarsFailingRequirements, avatarsInBattle)
+
+ def postMessageInvited(self, inviteeId, inviterId):
+ """
+ The AI tells all the members (except the inviter) in the Boarding Group that
+ the inviter has invited the invitee.
+ """
+ inviterName = ''
+ inviteeName = ''
+ inviter = base.cr.doId2do.get(inviterId)
+ if inviter:
+ inviterName = inviter.name
+ invitee = base.cr.doId2do.get(inviteeId)
+ if invitee:
+ inviteeName = invitee.name
+ # Message to the members in the group that the inviter has invited the invitee.
+ messageText = TTLocalizer.BoardingMessageInvited %(inviterName, inviteeName)
+ localAvatar.setSystemMessage(0, messageText, WhisperPopup.WTToontownBoardingGroup)
+
+ def postMessageInvitationFailed(self, inviterId):
+ """
+ The AI tells the invitee that an inviter had tried to invite him to their
+ Boarding Group, but failed for some reason.
+ """
+ inviterName = ''
+ inviter = base.cr.doId2do.get(inviterId)
+ if inviter:
+ inviterName = inviter.name
+ # Don't generate the message more than once a minute, so that the inviter can't spam the invitee.
+ if self.invitationFailedMessageOk(inviterId):
+ # Message to the invitee that the inviter had tried to invite the invitee.
+ messageText = TTLocalizer.BoardingMessageInvitationFailed %(inviterName)
+ localAvatar.setSystemMessage(0, messageText, WhisperPopup.WTToontownBoardingGroup)
+
+ def postMessageAcceptanceFailed(self, inviteeId, reason):
+ '''
+ The AI tells the inviter that the invitee tried to accept the invitation
+ because of the following reason.
+ '''
+ inviteeName = ''
+ messageText = ''
+ invitee = base.cr.doId2do.get(inviteeId)
+ if invitee:
+ inviteeName = invitee.name
+
+ if (reason == BoardingPartyBase.INVITE_ACCEPT_FAIL_GROUP_FULL):
+ messageText = TTLocalizer.BoardingMessageGroupFull %inviteeName
+
+ # Message to the inviter that the invitee failed to accept the invitation.
+ localAvatar.setSystemMessage(0, messageText, WhisperPopup.WTToontownBoardingGroup)
+
+ # Remove the Boarding Group Inviting Panel if it has the same inviteeId
+ if self.inviterPanels.isInvitingPanelIdCorrect(inviteeId):
+ self.inviterPanels.destroyInvitingPanel()
+
+ def invitationFailedMessageOk(self, inviterId):
+ """
+ Returns True if it is OK to display this message from this inviter.
+ Returns False if this message should be suppressed because we just
+ recently displaced this message from this inviter.
+ This check is added so that the inviter can't spam the invitee.
+ """
+ now = globalClock.getFrameTime()
+ lastTime = self.lastInvitationFailedMessage.get(inviterId, None)
+ if lastTime:
+ elapsedTime = now - lastTime
+ if elapsedTime < self.InvitationFailedTimeout:
+ return False
+
+ self.lastInvitationFailedMessage[inviterId] = now
+ return True
+
+ def showRejectMessage(self, elevatorId, reason, avatarsFailingRequirements, avatarsInBattle):
+ leaderId = localAvatar.doId
+ rejectText = ''
+ def getAvatarText(avIdList):
+ """
+ This function takes a list of avIds and returns a string of names.
+ If there is only 1 person it returns "avatarOneName"
+ If there are 2 people it returns "avatarOneName and avatarTwoName"
+ If there are 3 or more people it returns "avatarOneName, avatarTwoName and avatarThreeName"
+ """
+ avatarText = ''
+ nameList = []
+ for avId in avIdList:
+ avatar = base.cr.doId2do.get(avId)
+ if avatar:
+ nameList.append(avatar.name)
+ if (len(nameList) > 0):
+ lastName = nameList.pop()
+ avatarText = lastName
+ if (len(nameList) > 0):
+ secondLastName = nameList.pop()
+ for name in nameList:
+ avatarText = name + ', '
+ avatarText += secondLastName + ' ' + TTLocalizer.And + ' ' + lastName
+ return avatarText
+
+ if (reason == BoardingPartyBase.BOARDCODE_MINLAFF):
+ self.notify.debug("%s 's group cannot board because it does not have enough laff points." % (leaderId))
+
+ elevator = base.cr.doId2do.get(elevatorId)
+ if elevator:
+ minLaffPoints = elevator.minLaff
+ else:
+ minLaffPoints = TTLocalizer.BoardingMore
+
+ if (leaderId in avatarsFailingRequirements):
+ rejectText = TTLocalizer.BoardcodeMinLaffLeader %(minLaffPoints)
+ else:
+ avatarNameText = getAvatarText(avatarsFailingRequirements)
+ if (len(avatarsFailingRequirements) == 1):
+ rejectText = TTLocalizer.BoardcodeMinLaffNonLeaderSingular %(avatarNameText, minLaffPoints)
+ else:
+ rejectText = TTLocalizer.BoardcodeMinLaffNonLeaderPlural %(avatarNameText, minLaffPoints)
+
+ elif (reason == BoardingPartyBase.BOARDCODE_PROMOTION):
+ self.notify.debug("%s 's group cannot board because it does not have enough promotion merits." % (leaderId))
+ if (leaderId in avatarsFailingRequirements):
+ rejectText = TTLocalizer.BoardcodePromotionLeader
+ else:
+ avatarNameText = getAvatarText(avatarsFailingRequirements)
+ if (len(avatarsFailingRequirements) == 1):
+ rejectText = TTLocalizer.BoardcodePromotionNonLeaderSingular %(avatarNameText)
+ else:
+ rejectText = TTLocalizer.BoardcodePromotionNonLeaderPlural %(avatarNameText)
+
+ elif (reason == BoardingPartyBase.BOARDCODE_BATTLE):
+ self.notify.debug("%s 's group cannot board because it is in a battle" % (leaderId))
+ if (leaderId in avatarsInBattle):
+ rejectText = TTLocalizer.BoardcodeBattleLeader
+ else:
+ avatarNameText = getAvatarText(avatarsInBattle)
+ if (len(avatarsInBattle) == 1):
+ rejectText = TTLocalizer.BoardcodeBattleNonLeaderSingular %(avatarNameText)
+ else:
+ rejectText = TTLocalizer.BoardcodeBattleNonLeaderPlural %(avatarNameText)
+
+ elif (reason == BoardingPartyBase.BOARDCODE_SPACE):
+ self.notify.debug("%s 's group cannot board there was not enough room" % (leaderId))
+ rejectText = TTLocalizer.BoardcodeSpace
+
+ elif (reason == BoardingPartyBase.BOARDCODE_MISSING):
+ self.notify.debug("%s 's group cannot board because something was missing" % (leaderId))
+ rejectText = TTLocalizer.BoardcodeMissing
+ base.localAvatar.elevatorNotifier.showMe(rejectText)
+
+ def postGroupDissolve(self, quitterId, leaderId, memberList, kick):
+ self.notify.debug("%s group has dissolved" % (leaderId))
+
+ isMyGroup = 0
+ if (localAvatar.doId == quitterId) or (localAvatar.doId == leaderId):
+ isMyGroup = 1
+ if self.groupListDict.has_key(leaderId):
+ if leaderId == localAvatar.doId:
+ isMyGroup = 1
+ if self.avIdDict.has_key(leaderId):
+ self.avIdDict.pop(leaderId)
+ dgroup = self.groupListDict.pop(leaderId)
+
+ for memberId in memberList:
+ if memberId == localAvatar.doId:
+ isMyGroup = 1
+ if self.avIdDict.has_key(memberId):
+ self.avIdDict.pop(memberId)
+
+ if isMyGroup:
+ self.notify.debug("new info posted on my group")
+ messenger.send('updateGroupStatus')
+ groupFormed = False
+ if self.groupPanel:
+ groupFormed = True
+ self.groupPanel.cleanup()
+ self.groupPanel = None
+
+ # Cheap Hack: If the group panel was not there, I'm assuming that the group
+ # wasn't formed. So don't show any of the whisper messages.
+ if groupFormed:
+ # We can get here in 2 ways:
+ # leader has disbanded the group OR
+ # non-leader(quitterId) has left a 2 member group.
+ # If the leader is the quitter.
+ if (leaderId == quitterId):
+ # Don't message the leader because he already knows.
+ if not (localAvatar.doId == leaderId):
+ # Message to the members in the group that the group was dissolved by the leader.
+ localAvatar.setSystemMessage(0, TTLocalizer.BoardingMessageGroupDissolved, WhisperPopup.WTToontownBoardingGroup)
+ # If the non-leader is the quitter.
+ else:
+ # kick = 1 means leader kicked out the quitter. Don't show any message to the leader.
+ if not kick:
+ # Don't message the quitter because he already knows.
+ if not (localAvatar.doId == quitterId):
+ quitter = base.cr.doId2do.get(quitterId)
+ if quitter:
+ # If we can find the quitter, message saying quitter has left the group.
+ quitterName = quitter.name
+ # Message to the members in the group that the quitter has left the Boarding Group.
+ messageText = TTLocalizer.BoardingMessageLeftGroup %(quitterName)
+ localAvatar.setSystemMessage(0, messageText, WhisperPopup.WTToontownBoardingGroup)
+ else:
+ # If we can't find the quitter write a generic message saying your group was disbanded.
+ messageText = TTLocalizer.BoardingMessageGroupDisbandedGeneric
+ localAvatar.setSystemMessage(0, messageText, WhisperPopup.WTToontownBoardingGroup)
+
+ def requestInvite(self, inviteeId):
+ self.notify.debug("requestInvite %s" % (inviteeId))
+ # Check for trial toon
+ elevator = base.cr.doId2do.get(self.getElevatorIdList()[0])
+ if elevator:
+ if elevator.allowedToEnter():
+ # Check if the inviteeId is in the kick out list
+ if inviteeId in self.getGroupKickList(localAvatar.doId):
+ if not self.isGroupLeader(localAvatar.doId):
+ # You cannot invite a toon if he is in the kick out list
+ # and if you are not the group leader
+ avatar = base.cr.doId2do.get(inviteeId)
+ if avatar:
+ avatarNameText = avatar.name
+ else:
+ avatarNameText = ''
+ rejectText = TTLocalizer.BoardingInviteeInKickOutList %(avatarNameText)
+ self.showMe(rejectText)
+ return
+
+ # Check if any Boarding Group Inviting Panel is up
+ if self.inviterPanels.isInvitingPanelUp():
+ self.showMe(TTLocalizer.BoardingPendingInvite, pos = (0, 0, 0))
+ elif (len(self.getGroupMemberList(localAvatar.doId)) >= self.maxSize):
+ # The Boarding Group is full, so you can't send any more invites.
+ self.showMe(TTLocalizer.BoardingInviteGroupFull)
+ else:
+ # Show BoardingGroupInviter panel
+ invitee = base.cr.doId2do.get(inviteeId)
+ if invitee:
+ self.inviterPanels.createInvitingPanel(self, inviteeId)
+ # TODO: Add invitee to the groupListDict
+ # Sending the invite
+ self.sendUpdate("requestInvite", [inviteeId])
+ else:
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.fsm.request('stopped')
+ self.teaserDialog = TeaserPanel.TeaserPanel(pageName='cogHQ', doneFunc = self.handleOkTeaser)
+
+ def handleOkTeaser(self):
+ """Handle the user clicking ok on the teaser panel."""
+ self.teaserDialog.destroy()
+ del self.teaserDialog
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.fsm.request('walk')
+
+ def requestCancelInvite(self, inviteeId):
+ self.sendUpdate("requestCancelInvite", [inviteeId])
+
+ def requestAcceptInvite(self, leaderId, inviterId):
+ self.notify.debug("requestAcceptInvite %s %s" % (leaderId, inviterId))
+ self.sendUpdate("requestAcceptInvite", [leaderId, inviterId])
+
+ def requestRejectInvite(self, leaderId, inviterId):
+ self.sendUpdate("requestRejectInvite", [leaderId, inviterId])
+
+ def requestKick(self, kickId):
+ self.sendUpdate("requestKick", [kickId])
+
+ def requestLeave(self):
+ # Don't request to leave if the toon is doing the goToShowTrack.
+ if self.goToShowTrack and self.goToShowTrack.isPlaying():
+ return
+
+ # Don't request to leave if the toon is already in the elevator.
+ place = base.cr.playGame.getPlace()
+ if place:
+ if not (place.getState() == 'elevator'):
+ if self.avIdDict.has_key(localAvatar.doId):
+ leaderId = self.avIdDict[localAvatar.doId]
+ self.sendUpdate("requestLeave", [leaderId])
+
+ def handleEnterElevator(self, elevator):
+ """
+ If you are the leader of the boarding group, do it the boarding group way.
+ We come into this function only if the player is the leader of the boarding group.
+ """
+ if self.getGroupLeader(localAvatar.doId) == localAvatar.doId:
+ if base.localAvatar.hp > 0:
+ # Put localToon into requestBoard mode.
+ self.cr.playGame.getPlace().detectedElevatorCollision(elevator)
+ self.sendUpdate("requestBoard", [elevator.doId])
+ # Change the destination name on everybody's GroupPanel
+ elevatorId = elevator.doId
+ if elevatorId in self.elevatorIdList:
+ offset = self.elevatorIdList.index(elevatorId)
+ if self.groupPanel:
+ self.groupPanel.scrollToDestination(offset)
+ self.informDestChange(offset)
+ # Disable the Group Panel GO Button and Destination Scrolled List here.
+ self.disableGoButton()
+
+ def informDestChange(self, offset):
+ """
+ This function is called from Group Panel when the leader changes the destination.
+ """
+ self.sendUpdate('informDestinationInfo', [offset])
+
+ def postDestinationInfo(self, offset):
+ """
+ The destination has been changed by the leader. Change the GroupPanel of
+ this client to reflect the change.
+ """
+ if self.groupPanel:
+ self.groupPanel.changeDestination(offset)
+
+ def enableGoButton(self):
+ """
+ Enables the GO Button in the Group Panel.
+ """
+ if self.groupPanel:
+ self.groupPanel.enableGoButton()
+ self.groupPanel.enableDestinationScrolledList()
+
+ def disableGoButton(self):
+ """
+ Disables the GO Button in the Group Panel.
+ """
+ if self.groupPanel:
+ self.groupPanel.disableGoButton()
+ self.groupPanel.disableDestinationScrolledList()
+
+ def isInviteePanelUp(self):
+ """
+ Helper function to determine whether any Group Invitee panel is up or not.
+ """
+ if self.groupInviteePanel:
+ if not self.groupInviteePanel.isEmpty():
+ return True
+ self.groupInviteePanel = None
+ return False
+
+ def requestGoToFirstTime(self, elevatorId):
+ """
+ Request the AI if the leader and all the members can go directly to the elevator destination.
+ This is the first request the leader makes to the AI.
+ The AI responds back only to the leader with a rejectGoToRequest if anything goes wrong or
+ responds back only to the leader with a acceptGoToFirstTime if everything goes well.
+ """
+ self.waitingForFirstResponse = True
+ self.firstRequestAccepted = False
+ self.sendUpdate("requestGoToFirstTime", [elevatorId])
+ # Start the Go To pre show immediately, we'll stop it if we hear a reject from the AI.
+ self.startGoToPreShow(elevatorId)
+
+ def acceptGoToFirstTime(self, elevatorId):
+ """
+ The AI's response back to the leader's first request saying that everybody was accepted.
+ Flag this response and use this response flag before we requestGoToSecondTime.
+ """
+ self.waitingForFirstResponse = False
+ self.firstRequestAccepted = True
+
+ def requestGoToSecondTime(self, elevatorId):
+ """
+ Request the AI if the leader and all the members can go directly to the elevator destination.
+ This is the first request the leader makes to the AI.
+ The AI responds back only to the leader with a rejectGoToRequest if anything goes wrong or
+ responds back to all the members with a acceptGoToSecondTime if everything goes well.
+ """
+ if not self.waitingForFirstResponse:
+ if self.firstRequestAccepted:
+ self.firstRequestAccepted = False
+ self.disableGoButton()
+ self.sendUpdate("requestGoToSecondTime", [elevatorId])
+ else:
+ # 3 seconds have passed and we haven't heard back from the AI yet.
+ # Post a generic reject message.
+ self.postRejectGoto(elevatorId, BoardingPartyBase.BOARDCODE_MISSING, [], [])
+ self.cancelGoToElvatorDest()
+
+ def acceptGoToSecondTime(self, elevatorId):
+ """
+ The AI's response to all the members of the group that everybody was accepted to
+ Go directly to the elevator destination.
+ Now all the members can start the GoToShow.
+ """
+ self.startGoToShow(elevatorId)
+
+ def rejectGoToRequest(self, elevatorId, reason, avatarsFailingRequirements, avatarsInBattle):
+ """
+ The AI's response back to the leader's first request saying that something went wrong.
+ Now the leader should stop GoToPreShow.
+ """
+ self.firstRequestAccepted = False
+ self.waitingForFirstResponse = False
+ self.cancelGoToElvatorDest()
+ self.postRejectGoto(elevatorId, reason, avatarsFailingRequirements, avatarsInBattle)
+
+ def startGoToPreShow(self, elevatorId):
+ """
+ This is where the first 3 seconds of GO Pre show happens only on the leader's client.
+ GO Button becomes Cancel GO Button and the leader can't move.
+ """
+ self.notify.debug('Starting Go Pre Show.')
+
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.setState('stopped')
+
+ goButtonPreShow = BoardingGroupShow.BoardingGroupShow(localAvatar)
+ goButtonPreShowTrack = goButtonPreShow.getGoButtonPreShow()
+
+ if self.groupPanel:
+ self.groupPanel.changeGoToCancel()
+ self.groupPanel.disableQuitButton()
+ self.groupPanel.disableDestinationScrolledList()
+
+ self.finishGoToPreShowTrack()
+ self.goToPreShowTrack = Sequence()
+ self.goToPreShowTrack.append(goButtonPreShowTrack)
+ # Request to the AI the second time using requestGoToSecondTime
+ self.goToPreShowTrack.append(Func(self.requestGoToSecondTime, elevatorId))
+ self.goToPreShowTrack.start()
+
+ def finishGoToPreShowTrack(self):
+ """
+ Finish the goToPreShowTrack, if it is still going on.
+ """
+ if self.goToPreShowTrack:
+ self.goToPreShowTrack.finish()
+ self.goToPreShowTrack = None
+
+ def startGoToShow(self, elevatorId):
+ """
+ This is where the 3 seconds of GO show happens. This is essentially the teleport track
+ and then the client is taken to the elevator destination.
+ """
+ self.notify.debug('Starting Go Show.')
+
+ # Retract any pending invitations.
+ localAvatar.boardingParty.forceCleanupInviterPanels()
+
+ elevatorName = self.__getDestName(elevatorId)
+ if self.groupPanel:
+ self.groupPanel.disableQuitButton()
+ goButtonShow = BoardingGroupShow.BoardingGroupShow(localAvatar)
+
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.setState('stopped')
+
+ self.goToShowTrack = goButtonShow.getGoButtonShow(elevatorName)
+ self.goToShowTrack.start()
+
+ def finishGoToShowTrack(self):
+ """
+ Finish the goToShowTrack, if it is still going on.
+ """
+ if self.goToShowTrack:
+ self.goToShowTrack.finish()
+ self.goToShowTrack = None
+
+ def cancelGoToElvatorDest(self):
+ """
+ The leader has decided to Cancel going to the elevator destination
+ using the Cancel Go Button.
+ """
+ self.notify.debug('%s cancelled the GoTo Button.' %(localAvatar.doId))
+ self.firstRequestAccepted = False
+ self.waitingForFirstResponse = False
+ self.finishGoToPreShowTrack()
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.setState('walk')
+ if self.groupPanel:
+ self.groupPanel.changeCancelToGo()
+ self.groupPanel.enableGoButton()
+ self.groupPanel.enableQuitButton()
+ self.groupPanel.enableDestinationScrolledList()
+
+ def __getDestName(self, elevatorId):
+ elevator = base.cr.doId2do.get(elevatorId)
+ destName = ''
+ if elevator:
+ destName = elevator.getDestName()
+ return destName
+
+ def showMe(self, message, pos = None):
+ """
+ Making a version of elevatorNotifier.showMe. This version doesn't put the toon
+ into stopped state while displaying the panel. At the same time, the OK button
+ in the panel doesn't put the toon in the walk state.
+ """
+ base.localAvatar.elevatorNotifier.showMeWithoutStopping(message, pos)
+
+ def forceCleanupInviteePanel(self):
+ """
+ Forcibly close the group invitee panel, with a reject as the default answer.
+ """
+ if self.isInviteePanelUp():
+ self.groupInviteePanel.forceCleanup()
+ self.groupInviteePanel = None
+
+ def forceCleanupInviterPanels(self):
+ """
+ Forcibly cleanup the inviter panels, with a reject as the default answer.
+ """
+ if self.inviterPanels:
+ self.inviterPanels.forceCleanup()
\ No newline at end of file
diff --git a/toontown/src/building/DistributedBoardingPartyAI.py b/toontown/src/building/DistributedBoardingPartyAI.py
new file mode 100644
index 0000000..8f8cef9
--- /dev/null
+++ b/toontown/src/building/DistributedBoardingPartyAI.py
@@ -0,0 +1,589 @@
+from otp.otpbase import OTPGlobals
+from otp.ai.AIBase import *
+from toontown.toonbase import ToontownGlobals
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+from direct.distributed import DistributedObjectAI
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from toontown.building import BoardingPartyBase
+##from direct.showbase.PythonUtil import StackTrace
+
+# these are array indexs
+# since there are no structs
+GROUPMEMBER = 0
+GROUPINVITE = 1
+
+class DistributedBoardingPartyAI(DistributedObjectAI.DistributedObjectAI, BoardingPartyBase.BoardingPartyBase):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedBoardingPartyAI")
+
+ def __init__(self, air, elevatorList, maxSize = 4):
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ BoardingPartyBase.BoardingPartyBase.__init__(self)
+ self.setGroupSize(maxSize)
+ self.elevatorIdList = elevatorList # the elevators this boardingParty can send groups to
+ if __debug__:
+ simbase.bp = self #REMOVE ME
+ self.visibleZones = [] #tells us which zones this works in, usually it's just one
+
+ def delete(self):
+ self.cleanup()
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def generate(self):
+ DistributedObjectAI.DistributedObjectAI.generate(self)
+ for elevatorId in self.elevatorIdList:
+ elevator = simbase.air.doId2do.get(elevatorId)
+ elevator.setBoardingParty(self)
+
+ store = simbase.air.dnaStoreMap.get(self.zoneId)
+ if store:
+ numVisGroups = store.getNumDNAVisGroupsAI()
+ myVisGroup = None
+ for index in range(numVisGroups):
+ if store.getDNAVisGroupAI(index).getName() == str(self.zoneId):
+ myVisGroup = store.getDNAVisGroupAI(index)
+
+ if myVisGroup:
+ numVisibles = myVisGroup.getNumVisibles()
+ for index in range(numVisibles):
+ newVisible = myVisGroup.getVisibleName(index)
+ self.visibleZones.append(int(newVisible))
+ else:
+ self.visibleZones = [self.zoneId]
+ else:
+ self.visibleZones = [self.zoneId]
+
+ def cleanup(self):
+ BoardingPartyBase.BoardingPartyBase.cleanup(self)
+ del self.elevatorIdList
+ del self.visibleZones
+
+ def getElevatorIdList(self):
+ return self.elevatorIdList
+
+ def setElevatorIdList(self, elevatorIdList):
+ self.elevatorIdList = elevatorIdList
+
+ def addWacthAvStatus(self, avId):
+ """
+ tells us when a toon has entered a battle, has changed
+ zones or disconnects. Anything that would cause them to
+ leave the boardingParty, or keep the boardingParty from
+ going to a destination
+ """
+ self.acceptOnce(self.air.getAvatarExitEvent(avId),
+ self.handleAvatarDisco, extraArgs=[avId])
+ self.accept(self.staticGetLogicalZoneChangeEvent(avId),
+ self.handleAvatarZoneChange, extraArgs=[avId])
+
+
+ messageToonAdded = ("Battle adding toon %s" % (avId))
+ self.accept(messageToonAdded, self.handleToonJoinedBattle)
+ messageToonReleased = ("Battle releasing toon %s" % (avId))
+ self.accept(messageToonReleased, self.handleToonLeftBattle)
+
+ def handleToonJoinedBattle(self, avId):
+ self.notify.debug("handleToonJoinedBattle %s" % (avId))
+
+ def handleToonLeftBattle(self, avId):
+ self.notify.debug("handleToonLeftBattle %s" % (avId))
+
+ def removeWacthAvStatus(self, avId):
+ self.ignore(self.air.getAvatarExitEvent(avId))
+ self.ignore(self.staticGetLogicalZoneChangeEvent(avId))
+
+ def requestInvite(self, inviteeId):
+ self.notify.debug("requestInvite %s" % (inviteeId))
+ inviterId = self.air.getAvatarIdFromSender()
+
+ invitee = simbase.air.doId2do.get(inviteeId)
+ # Send a reject to the inviter if the invitee is in a battle.
+ if invitee and (invitee.battleId != 0):
+ reason = BoardingPartyBase.BOARDCODE_BATTLE
+ self.sendUpdateToAvatarId(inviterId, "postInviteNotQualify", [inviteeId, reason, 0])
+ # Send a message to the invitee saying that inviter had tried to invite him.
+ self.sendUpdateToAvatarId(inviteeId, "postMessageInvitationFailed", [inviterId])
+ return
+
+ # Send a reject to the inviter if the invitee is already in a boarding group.
+ if self.hasActiveGroup(inviteeId):
+ reason = BoardingPartyBase.BOARDCODE_DIFF_GROUP
+ self.sendUpdateToAvatarId(inviterId, "postInviteNotQualify", [inviteeId, reason, 0])
+ # Send a message to the invitee saying that inviter had tried to invite him.
+ self.sendUpdateToAvatarId(inviteeId, "postMessageInvitationFailed", [inviterId])
+ return
+
+ # Send a reject to the inviter if the invitee has a pending invite.
+ if self.hasPendingInvite(inviteeId):
+ reason = BoardingPartyBase.BOARDCODE_PENDING_INVITE
+ self.sendUpdateToAvatarId(inviterId, "postInviteNotQualify", [inviteeId, reason, 0])
+ # Send a message to the invitee saying that inviter had tried to invite him.
+ self.sendUpdateToAvatarId(inviteeId, "postMessageInvitationFailed", [inviterId])
+ return
+
+ # Send a reject to the inviter if the invitee has already boarded an elevator.
+ if self.__isInElevator(inviteeId):
+ reason = BoardingPartyBase.BOARDCODE_IN_ELEVATOR
+ self.sendUpdateToAvatarId(inviterId, "postInviteNotQualify", [inviteeId, reason, 0])
+ # Send a message to the invitee saying that inviter had tried to invite him.
+ self.sendUpdateToAvatarId(inviteeId, "postMessageInvitationFailed", [inviterId])
+ return
+
+ inviteeOkay = self.checkBoard(inviteeId, self.elevatorIdList[0])
+ reason = 0
+ # Send a reject to the inviter if the invitee is not a paid member.
+ if (inviteeOkay == REJECT_NOTPAID):
+ reason = BoardingPartyBase.BOARDCODE_NOT_PAID
+ self.sendUpdateToAvatarId(inviterId, "postInviteNotQualify", [inviteeId, reason, 0])
+ return
+
+ if len(self.elevatorIdList) == 1:
+ if inviteeOkay:
+ if (inviteeOkay == REJECT_MINLAFF):
+ reason = BoardingPartyBase.BOARDCODE_MINLAFF
+ elif (inviteeOkay == REJECT_PROMOTION):
+ reason = BoardingPartyBase.BOARDCODE_PROMOTION
+ # Send a reject to the inviter if the invitee does not qualify for the only elevator.
+ self.sendUpdateToAvatarId(inviterId, "postInviteNotQualify", [inviteeId, reason, self.elevatorIdList[0]])
+ return
+ else:
+ inviterOkay = self.checkBoard(inviterId, self.elevatorIdList[0])
+ if inviterOkay:
+ if (inviterOkay == REJECT_MINLAFF):
+ reason = BoardingPartyBase.BOARDCODE_MINLAFF
+ elif (inviterOkay == REJECT_PROMOTION):
+ reason = BoardingPartyBase.BOARDCODE_PROMOTION
+ # Send a reject to the inviter if the inviter does not qualify for the only elevator.
+ self.sendUpdateToAvatarId(inviterId, "postInviteNotQualify", [inviterId, reason, self.elevatorIdList[0]])
+ return
+
+ if self.avIdDict.has_key(inviterId): #existing group
+ self.notify.debug("old group")
+ leaderId = self.avIdDict[inviterId]
+ groupList = self.groupListDict.get(leaderId)
+ if groupList:
+ self.notify.debug("got group list")
+ if inviterId == leaderId: #leader can unban players
+ if inviteeId in groupList[2]:
+ groupList[2].remove(inviteeId)
+
+ if (len(self.getGroupMemberList(leaderId))) >= self.maxSize:
+ self.sendUpdate("postSizeReject", [leaderId, inviterId, inviteeId])
+ elif (not (inviterId in groupList[1])) and (not (inviterId in groupList[2])):
+ if not inviteeId in groupList[1]:
+ groupList[1].append(inviteeId)
+ self.groupListDict[leaderId] = groupList
+
+ if self.avIdDict.has_key(inviteeId):
+ self.notify.warning('inviter %s tried to invite %s who already exists in the avIdDict.' %(inviterId, inviteeId))
+ self.air.writeServerEvent('suspicious: inviter', inviterId,' tried to invite %s who already exists in the avIdDict.' %inviteeId)
+ self.avIdDict[inviteeId] = leaderId
+ self.sendUpdateToAvatarId(inviteeId, "postInvite", [leaderId, inviterId])
+ # Send a message to all members in the group except the inviter that an invitation was sent out.
+ # Inviter doesn't need to get this message, because he knows that he sent out the invitation.
+ for memberId in groupList[0]:
+ if not (memberId == inviterId):
+ self.sendUpdateToAvatarId(memberId, "postMessageInvited", [inviteeId, inviterId])
+ elif inviterId in groupList[2]:
+ self.sendUpdate("postKickReject", [leaderId, inviterId, inviteeId])
+ else: #creating new Group with inviter as leader
+ if self.avIdDict.has_key(inviteeId):
+ self.notify.warning('inviter %s tried to invite %s who already exists in avIdDict.' %(inviterId, inviteeId))
+ self.air.writeServerEvent('suspicious: inviter', inviterId,' tried to invite %s who already exists in the avIdDict.' %inviteeId)
+
+ self.notify.debug("new group")
+ leaderId = inviterId
+ self.avIdDict[inviterId] = inviterId
+ self.avIdDict[inviteeId] = inviterId
+ self.groupListDict[leaderId] = [[leaderId],[inviteeId],[]]
+ self.addWacthAvStatus(leaderId)
+ self.sendUpdateToAvatarId(inviteeId, "postInvite", [leaderId, inviterId])
+
+ def requestCancelInvite(self, inviteeId):
+ inviterId = self.air.getAvatarIdFromSender()
+ if self.avIdDict.has_key(inviterId):
+ leaderId = self.avIdDict[inviterId]
+ groupList = self.groupListDict.get(leaderId)
+ if groupList:
+ self.removeFromGroup(leaderId, inviteeId)
+ self.sendUpdateToAvatarId(inviteeId, "postInviteCanceled", [])
+
+ def requestAcceptInvite(self, leaderId, inviterId):
+ inviteeId = self.air.getAvatarIdFromSender()
+ self.notify.debug("requestAcceptInvite leader%s inviter%s invitee%s" % (leaderId, inviterId, inviteeId))
+
+ if self.avIdDict.has_key(inviteeId):
+ # Send reject to the invitee if the invitee is already in a boarding group
+ if self.hasActiveGroup(inviteeId):
+ self.sendUpdateToAvatarId(inviteeId, "postAlreadyInGroup", [])
+ return
+
+ # Reject this acceptance if the leader is not part of the group.
+ if not self.avIdDict.has_key(leaderId) or not self.isInGroup(inviteeId, leaderId):
+ self.sendUpdateToAvatarId(inviteeId, "postSomethingMissing", [])
+ return
+
+ memberList = self.getGroupMemberList(leaderId)
+ if self.avIdDict[inviteeId]:
+ # Don't do anything if the invitee already exists in the current leader's group.
+ if self.avIdDict[inviteeId] == leaderId:
+ if inviteeId in memberList:
+ self.notify.debug("invitee already in group, aborting requestAcceptInvite")
+ return
+ # If the invitee's leader is different, remove him from his old leader's group.
+ else:
+ self.air.writeServerEvent('suspicious: ', inviteeId, " accepted a second invite from %s, in %s's group, while he was in alredy in %s's group." %(inviterId, leaderId, self.avIdDict[inviteeId]))
+ self.removeFromGroup(self.avIdDict[inviteeId], inviteeId, post = 0)
+
+ # If the group has already become full, auto reject the invite
+ if len(memberList) >= self.maxSize:
+ self.removeFromGroup(leaderId, inviteeId)
+ self.sendUpdateToAvatarId(inviterId, "postMessageAcceptanceFailed", [inviteeId, BoardingPartyBase.INVITE_ACCEPT_FAIL_GROUP_FULL])
+ self.sendUpdateToAvatarId(inviteeId, "postGroupAlreadyFull", [])
+ return
+
+ self.sendUpdateToAvatarId(inviterId, "postInviteAccepted", [inviteeId])
+ self.addToGroup(leaderId, inviteeId)
+
+ else:
+ self.air.writeServerEvent('suspicious: ', inviteeId, " was invited to %s's group by %s, but the invitee didn't have an entry in the avIdDict." %(leaderId, inviterId))
+
+ def requestRejectInvite(self, leaderId, inviterId):
+ inviteeId = self.air.getAvatarIdFromSender()
+ self.removeFromGroup(leaderId, inviteeId)
+ self.sendUpdateToAvatarId(inviterId, "postInviteDelcined", [inviteeId])
+
+ def requestKick(self, kickId):
+ leaderId = self.air.getAvatarIdFromSender()
+ if self.avIdDict.has_key(kickId):
+ if self.avIdDict[kickId] == leaderId:
+ self.removeFromGroup(leaderId, kickId, kick = 1)
+ self.sendUpdateToAvatarId(kickId, "postKick", [leaderId])
+
+ def requestLeave(self, leaderId):
+ # player is leaving a group voluntarly
+ memberId = self.air.getAvatarIdFromSender()
+ if self.avIdDict.has_key(memberId):
+ if leaderId == self.avIdDict[memberId]:
+ self.removeFromGroup(leaderId, memberId)
+## else:
+## stack = StackTrace()
+## self.notify.warning('boarding_requestLeave: StackTrace: %s' %stack.compact())
+
+ def checkBoard(self, avId, elevatorId):
+ """
+ Checks boarding for elevator requirements and paid status.
+ """
+ elevator = simbase.air.doId2do.get(elevatorId)
+ avatar = simbase.air.doId2do.get(avId)
+ if avatar:
+ if not (avatar.getGameAccess() == OTPGlobals.AccessFull):
+ return REJECT_NOTPAID
+ elif elevator:
+ # Returns 0 if everything is OK.
+ return elevator.checkBoard(avatar)
+ # It'll come here only if either avatar or elevator was not found.
+ return REJECT_BOARDINGPARTY
+
+ def testBoard(self, leaderId, elevatorId, needSpace = 0):
+ """
+ tests boarding against everything, space optional depending on whether
+ the leader pressed a goto button, or just got on the elevator
+ """
+ elevator = None
+ boardOkay = BoardingPartyBase.BOARDCODE_MISSING
+ avatarsFailingRequirements = []
+ avatarsInBattle = []
+ if elevatorId in self.elevatorIdList:
+ elevator = simbase.air.doId2do.get(elevatorId)
+ if elevator:
+ if self.avIdDict.has_key(leaderId):
+ if leaderId == self.avIdDict[leaderId]:
+ boardOkay = BoardingPartyBase.BOARDCODE_OKAY
+ for avId in self.getGroupMemberList(leaderId):
+ avatar = simbase.air.doId2do.get(avId)
+ if avatar:
+ if (elevator.checkBoard(avatar) != 0):
+ if (elevator.checkBoard(avatar) == REJECT_MINLAFF):
+ boardOkay = BoardingPartyBase.BOARDCODE_MINLAFF
+ elif (elevator.checkBoard(avatar) == REJECT_PROMOTION):
+ boardOkay = BoardingPartyBase.BOARDCODE_PROMOTION
+ avatarsFailingRequirements.append(avId)
+ elif (avatar.battleId != 0):
+ boardOkay = BoardingPartyBase.BOARDCODE_BATTLE
+ avatarsInBattle.append(avId)
+ groupSize = len(self.getGroupMemberList(leaderId))
+ if (groupSize > self.maxSize):
+ boardOkay = BoardingPartyBase.BOARDCODE_SPACE
+ if needSpace:
+ if (groupSize > elevator.countOpenSeats()):
+ boardOkay = BoardingPartyBase.BOARDCODE_SPACE
+
+ if (boardOkay != BoardingPartyBase.BOARDCODE_OKAY):
+ self.notify.debug("Something is wrong with the group board request")
+ if (boardOkay == BoardingPartyBase.BOARDCODE_MINLAFF):
+ self.notify.debug("An avatar did not meet the elevator laff requirements")
+ if (boardOkay == BoardingPartyBase.BOARDCODE_PROMOTION):
+ self.notify.debug("An avatar did not meet the elevator promotion requirements")
+ elif (boardOkay == BoardingPartyBase.BOARDCODE_BATTLE):
+ self.notify.debug("An avatar is in battle")
+
+ return (boardOkay, avatarsFailingRequirements, avatarsInBattle)
+
+ def requestBoard(self, elevatorId):
+ """
+ The group leader has tried to enter the elevator, thereby requesting that
+ their group board.
+ """
+ wantDisableGoButton = False
+ leaderId = self.air.getAvatarIdFromSender()
+ elevator = None
+ if elevatorId in self.elevatorIdList:
+ elevator = simbase.air.doId2do.get(elevatorId)
+ if elevator:
+ if self.avIdDict.has_key(leaderId):
+ if leaderId == self.avIdDict[leaderId]:
+ group = self.groupListDict.get(leaderId)
+ if group:
+ boardOkay, avatarsFailingRequirements, avatarsInBattle = self.testBoard(leaderId, elevatorId, needSpace = 1)
+ if boardOkay == BoardingPartyBase.BOARDCODE_OKAY:
+ # Board the leader.
+ leader = simbase.air.doId2do.get(leaderId)
+ if leader:
+ elevator.partyAvatarBoard(leader)
+ wantDisableGoButton = True
+ # Board all the members except the leader.
+ for avId in group[0]:
+ if not (avId == leaderId):
+ avatar = simbase.air.doId2do.get(avId)
+ if avatar:
+ elevator.partyAvatarBoard(avatar, wantBoardingShow = 1)
+ self.air.writeServerEvent('boarding_elevator', self.zoneId, '%s; Sending avatars %s' %(elevatorId, group[0]))
+ else:
+ self.sendUpdateToAvatarId(leaderId, "postRejectBoard", [elevatorId, boardOkay, avatarsFailingRequirements, avatarsInBattle])
+ return
+ if not wantDisableGoButton:
+ self.sendUpdateToAvatarId(leaderId, "postRejectBoard", [elevatorId, BoardingPartyBase.BOARDCODE_MISSING, [], []])
+
+ def testGoButtonRequirements(self, leaderId, elevatorId):
+ """
+ Test if the leader can take his memebers to the elevator destination.
+ Return True if everything looks good.
+ Send a reject to the leader and return False if somebody doesn't qualify.
+ """
+ if self.avIdDict.has_key(leaderId):
+ if leaderId == self.avIdDict[leaderId]:
+ if elevatorId in self.elevatorIdList:
+ elevator = simbase.air.doId2do.get(elevatorId)
+ if elevator:
+ boardOkay, avatarsFailingRequirements, avatarsInBattle = self.testBoard(leaderId, elevatorId, needSpace = 0)
+ if boardOkay == BoardingPartyBase.BOARDCODE_OKAY:
+ avList = self.getGroupMemberList(leaderId)
+ if 0 in avList:
+ avList.remove(0)
+ if not (leaderId in elevator.seats):
+ return True
+ else:
+ self.notify.warning('avId: %s has hacked his/her client.' %leaderId)
+ self.air.writeServerEvent('suspicious: ', leaderId, ' pressed the GO Button while inside the elevator.')
+ else:
+ self.sendUpdateToAvatarId(leaderId, "rejectGoToRequest", [elevatorId, boardOkay, avatarsFailingRequirements, avatarsInBattle])
+ return False
+
+ def requestGoToFirstTime(self, elevatorId):
+ """
+ The leader has pushed a GO Button. Do the initial check and respond only to the
+ leader if everything is OK.
+ """
+ callerId = self.air.getAvatarIdFromSender()
+ if self.testGoButtonRequirements(callerId, elevatorId):
+ self.sendUpdateToAvatarId(callerId, "acceptGoToFirstTime", [elevatorId])
+
+ def requestGoToSecondTime(self, elevatorId):
+ """
+ The leader has finished his GO Pre Show. Do the initial check and respond to
+ all the members of the group to start the GO show.
+ """
+ callerId = self.air.getAvatarIdFromSender()
+ avList = self.getGroupMemberList(callerId)
+ if self.testGoButtonRequirements(callerId, elevatorId):
+ for avId in avList:
+ self.sendUpdateToAvatarId(avId, "acceptGoToSecondTime", [elevatorId])
+ THREE_SECONDS = 3.0
+ taskMgr.doMethodLater(THREE_SECONDS, self.sendAvatarsToDestinationTask,
+ self.uniqueName('sendAvatarsToDestinationTask'),
+ extraArgs = [elevatorId, avList],
+ appendTask = True)
+
+ def sendAvatarsToDestinationTask(self, elevatorId, avList, task):
+ """
+ This is the task that sends the avatars in the Boarding Group to go
+ directly to the elevator destinvations after 3 seconds.
+ """
+ self.notify.debug('entering sendAvatarsToDestinationTask')
+ if len(avList):
+ if elevatorId in self.elevatorIdList:
+ elevator = simbase.air.doId2do.get(elevatorId)
+ if elevator:
+ self.notify.warning('Sending avatars %s' %avList)
+ boardOkay, avatarsFailingRequirements, avatarsInBattle = self.testBoard(avList[0], elevatorId, needSpace = 0)
+ if not (boardOkay == BoardingPartyBase.BOARDCODE_OKAY):
+ for avId in avatarsFailingRequirements:
+ self.air.writeServerEvent('suspicious: ', avId, ' failed requirements after the second go button request.')
+ for avId in avatarsInBattle:
+ self.air.writeServerEvent('suspicious: ', avId, ' joined battle after the second go button request.')
+
+ self.air.writeServerEvent('boarding_go', self.zoneId, '%s; Sending avatars %s' %(elevatorId, avList))
+ elevator.sendAvatarsToDestination(avList)
+ return Task.done
+
+ def handleAvatarDisco(self, avId):
+ self.notify.debug("handleAvatarDisco %s" % (avId))
+ if self.avIdDict.has_key(avId):
+ leaderId = self.avIdDict[avId]
+ self.removeFromGroup(leaderId, avId)
+
+ def handleAvatarZoneChange(self, avId, zoneNew, zoneOld):
+ self.notify.debug("handleAvatarZoneChange %s new%s old%s bp%s" % (avId, zoneNew, zoneOld, self.zoneId))
+ if zoneNew in self.visibleZones:#== self.zoneId:
+ self.toonInZone(avId)
+ elif self.avIdDict.has_key(avId):
+ leaderId = self.avIdDict[avId]
+ self.removeFromGroup(leaderId, avId)
+
+ def toonInZone(self, avId):
+ """
+ This should never be called, but if an avatar leaves and for some reason is
+ still in the boarding system (due to a bug), this tries to put them back in the
+ right group
+ """
+ #callerId = self.air.getAvatarIdFromSender()
+ if self.avIdDict.has_key(avId):
+ leaderId = self.avIdDict[avId]
+ group = self.groupListDict.get(leaderId)
+ if leaderId and group:
+ self.notify.debug('Calling postGroupInfo from toonInZone')
+## self.sendUpdate("postGroupInfo", [leaderId, group[0], group[1], group[2]])
+
+ def addToGroup(self, leaderId, inviteeId, post = 1):
+ group = self.groupListDict.get(leaderId)
+ if group:
+ self.avIdDict[inviteeId] = leaderId
+ if inviteeId in group[1]:
+ group[1].remove(inviteeId)
+ if not(inviteeId in group[0]):
+ group[0].append(inviteeId)
+
+ self.groupListDict[leaderId] = group
+
+ if post:
+ self.notify.debug('Calling postGroupInfo from addToGroup')
+ self.sendUpdate("postGroupInfo", [leaderId, group[0], group[1], group[2]])
+ self.addWacthAvStatus(inviteeId)
+ else:
+ #if the group wasn't found something has gone wrong
+ #to mitigate this we post a group dissolve trying
+ #to let the clients know that the group doesn't exist
+ self.sendUpdate("postGroupDissolve",[leaderId, leaderId, [], 0])
+
+ def removeFromGroup(self, leaderId, memberId, kick = 0, post = 1):
+ self.notify.debug ("")
+ self.notify.debug ("removeFromGroup leaderId %s memberId %s" % (leaderId, memberId))
+ self.notify.debug ("Groups %s" % (self.groupListDict))
+ self.notify.debug ("avDict %s" % (self.avIdDict))
+
+ if not (self.avIdDict.has_key(leaderId)):
+ # Dissolve the group if you can't find the leader.
+ self.sendUpdate("postGroupDissolve",[memberId, leaderId, [], kick])
+ if self.avIdDict.has_key(memberId):
+ self.avIdDict.pop(memberId)
+ return
+ self.removeWacthAvStatus(memberId)
+ group = self.groupListDict.get(leaderId)
+ if group:
+ if memberId in group[0]:
+ group[0].remove(memberId)
+ if memberId in group[1]:
+ group[1].remove(memberId)
+ if memberId in group[2]:
+ group[2].remove(memberId)
+
+ if kick:
+ group[2].append(memberId)
+ else:
+## # The group doesn't exist, something strange must have gone wrong.
+## # Remove this memberId from the avIdDict.
+## if post:
+## self.notify.debug("postGroupDissolve")
+## self.sendUpdate("postGroupDissolve",[memberId, leaderId, kick])
+## if self.avIdDict.has_key(memberId):
+## self.avIdDict.pop(memberId)
+ return
+
+ # Either the leader wants to leave or the group has only 1 member, so dissolve the group.
+ if (memberId == leaderId) or (len(group[0]) < 2):
+ if self.avIdDict.has_key(leaderId):
+ self.avIdDict.pop(leaderId)
+ # Remove all the pending invitees of this group from avIdDict
+ for inviteeId in group[1]:
+ if self.avIdDict.has_key(inviteeId):
+ self.avIdDict.pop(inviteeId)
+ # Send a message to the invitee to remove the invitee message.
+ self.sendUpdateToAvatarId(inviteeId, "postInviteCanceled", [])
+
+ dgroup = self.groupListDict.pop(leaderId)
+ for dMemberId in dgroup[0]:
+ if self.avIdDict.has_key(dMemberId):
+ self.avIdDict.pop(dMemberId)
+ self.notify.debug("postGroupDissolve")
+ # Adding the removed member to the list so that we can send this list to the clients.
+ # This is done so that even the removed member is removed from the avIdDicts in all the clients.
+ dgroup[0].insert(0, memberId)
+ self.sendUpdate("postGroupDissolve",[memberId, leaderId, dgroup[0], kick])
+ else:
+ self.groupListDict[leaderId] = group
+ if post:
+ self.notify.debug('Calling postGroupInfo from removeFromGroup')
+ self.sendUpdate("postGroupInfo", [leaderId, group[0], group[1], group[2]])
+
+ if self.avIdDict.has_key(memberId):
+ self.avIdDict.pop(memberId)
+
+ self.notify.debug("Remove from group END")
+ self.notify.debug ("Groups %s" % (self.groupListDict))
+ self.notify.debug ("avDict %s" % (self.avIdDict))
+ self.notify.debug("")
+
+ def informDestinationInfo(self, offset):
+ '''
+ This function is called from DistributedBoardingParty informing that a
+ destination has been changed.
+ '''
+ leaderId = self.air.getAvatarIdFromSender()
+ if (offset > len(self.elevatorIdList)):
+ self.air.writeServerEvent('suspicious: ', leaderId, 'has requested to go to %s elevator which does not exist' %offset)
+ return
+ memberList = self.getGroupMemberList(leaderId)
+ # Post this info to all the members.
+ for avId in memberList:
+ # No need to post it back to the leader.
+ if (avId != leaderId):
+ self.sendUpdateToAvatarId(avId, "postDestinationInfo", [offset])
+
+ def __isInElevator(self, avId):
+ """
+ Returns True if the avatar is found in any elevator in that zone.
+ Else returns False.
+ """
+ inElevator = False
+ for elevatorId in self.elevatorIdList:
+ elevator = simbase.air.doId2do.get(elevatorId)
+ if elevator:
+ if avId in elevator.seats:
+ inElevator = True
+ return inElevator
\ No newline at end of file
diff --git a/toontown/src/building/DistributedBossElevator.py b/toontown/src/building/DistributedBossElevator.py
new file mode 100644
index 0000000..91afd91
--- /dev/null
+++ b/toontown/src/building/DistributedBossElevator.py
@@ -0,0 +1,114 @@
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+from direct.interval.IntervalGlobal import *
+from ElevatorConstants import *
+from ElevatorUtils import *
+import DistributedElevator
+import DistributedElevatorExt
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from toontown.hood import ZoneUtil
+from toontown.toonbase import TTLocalizer
+from toontown.toontowngui import TTDialog
+
+class DistributedBossElevator(DistributedElevatorExt.DistributedElevatorExt):
+
+ def __init__(self, cr):
+ DistributedElevatorExt.DistributedElevatorExt.__init__(self, cr)
+ self.elevatorPoints = BigElevatorPoints
+ self.openSfx = base.loadSfx(
+ "phase_9/audio/sfx/CHQ_FACT_door_open_sliding.mp3")
+ self.finalOpenSfx = base.loadSfx(
+ "phase_9/audio/sfx/CHQ_FACT_door_open_final.mp3")
+ self.closeSfx = base.loadSfx(
+ "phase_9/audio/sfx/CHQ_FACT_door_open_sliding.mp3")
+ self.finalCloseSfx = base.loadSfx(
+ "phase_9/audio/sfx/CHQ_FACT_door_open_final.mp3")
+ self.type = ELEVATOR_VP
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
+
+ def disable(self):
+ DistributedElevator.DistributedElevator.disable(self)
+
+ def generate(self):
+ DistributedElevatorExt.DistributedElevatorExt.generate(self)
+
+ def delete(self):
+ self.elevatorModel.removeNode()
+ del self.elevatorModel
+ DistributedElevatorExt.DistributedElevatorExt.delete(self)
+
+ def setupElevator(self):
+ """setupElevator(self)
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+ # TODO: place this on a node indexed by the entraceId
+ self.elevatorModel = loader.loadModel(
+ "phase_9/models/cogHQ/cogHQ_elevator")
+
+ # The big cog icon on the top is only visible at the BossRoom.
+ icon = self.elevatorModel.find('**/big_frame/')
+ icon.hide()
+
+ self.leftDoor = self.elevatorModel.find("**/left-door")
+ self.rightDoor = self.elevatorModel.find("**/right-door")
+
+ geom = base.cr.playGame.hood.loader.geom
+ locator = geom.find('**/elevator_locator')
+ self.elevatorModel.reparentTo(locator)
+ self.elevatorModel.setH(180)
+
+ DistributedElevator.DistributedElevator.setupElevator(self)
+
+ def getElevatorModel(self):
+ return self.elevatorModel
+
+ def gotBldg(self, buildingList):
+ # Do not call elevator ext here because it wants a suit door origin
+ # Instead call all the way up to DistributedElevator
+ return DistributedElevator.DistributedElevator.gotBldg(
+ self, buildingList)
+
+ def getZoneId(self):
+ return 0
+
+ def __doorsClosed(self, zoneId):
+ return
+
+ def setBossOfficeZone(self, zoneId):
+ if (self.localToonOnBoard):
+ hoodId = self.cr.playGame.hood.hoodId
+ doneStatus = {
+ 'loader' : "cogHQLoader",
+ 'where' : "cogHQBossBattle",
+ 'how' : "movie",
+ 'zoneId' : zoneId,
+ 'hoodId' : hoodId,
+ }
+ self.cr.playGame.getPlace().elevator.signalDone(doneStatus)
+
+ def setBossOfficeZoneForce(self, zoneId):
+ place = self.cr.playGame.getPlace()
+ if place:
+ place.fsm.request("elevator", [self, 1])
+ hoodId = self.cr.playGame.hood.hoodId
+ doneStatus = {
+ 'loader' : "cogHQLoader",
+ 'where' : "cogHQBossBattle",
+ 'how' : "movie",
+ 'zoneId' : zoneId,
+ 'hoodId' : hoodId,
+ }
+ if hasattr(place, 'elevator') and place.elevator:
+ place.elevator.signalDone(doneStatus)
+ else:
+ self.notify.warning("setMintInteriorZoneForce: Couldn't find playGame.getPlace().elevator, zoneId: %s" %zoneId)
+ else:
+ self.notify.warning("setBossOfficeZoneForce: Couldn't find playGame.getPlace(), zoneId: %s" %zoneId)
+
+ def getDestName(self):
+ return TTLocalizer.ElevatorSellBotBoss
diff --git a/toontown/src/building/DistributedBossElevatorAI.py b/toontown/src/building/DistributedBossElevatorAI.py
new file mode 100644
index 0000000..48163a0
--- /dev/null
+++ b/toontown/src/building/DistributedBossElevatorAI.py
@@ -0,0 +1,118 @@
+from otp.ai.AIBase import *
+from toontown.toonbase import ToontownGlobals
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+import DistributedElevatorAI
+import DistributedElevatorExtAI
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from toontown.suit import DistributedSellbotBossAI
+
+
+class DistributedBossElevatorAI(DistributedElevatorExtAI.DistributedElevatorExtAI):
+
+ def __init__(self, air, bldg, zone, antiShuffle = 0, minLaff = 0):
+ DistributedElevatorExtAI.DistributedElevatorExtAI.__init__(self, air, bldg, numSeats = 8, antiShuffle = antiShuffle, minLaff = minLaff)
+ self.zone = zone
+ self.type = ELEVATOR_VP
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
+ def elevatorClosed(self):
+ numPlayers = self.countFullSeats()
+
+ # It is possible the players exited the district
+ if (numPlayers > 0):
+
+ bossZone = self.bldg.createBossOffice(self.seats)
+
+ for seatIndex in range(len(self.seats)):
+ avId = self.seats[seatIndex]
+ if avId:
+ # Tell each player on the elevator that they should enter
+ # the factory
+ # And which zone it is in
+ self.sendUpdateToAvatarId(avId, 'setBossOfficeZone',
+ [bossZone])
+ # Clear the fill slot
+ self.clearFullNow(seatIndex)
+ else:
+ self.notify.warning("The elevator left, but was empty.")
+ self.fsm.request("closed")
+
+ def sendAvatarsToDestination(self, avIdList):
+ if (len(avIdList) > 0):
+ bossZone = self.bldg.createBossOffice(avIdList)
+ for avId in avIdList:
+ if avId:
+ # Tell each player on the elevator that they should enter
+ # the factory
+ # And which zone it is in
+ self.sendUpdateToAvatarId(avId, 'setBossOfficeZoneForce',
+ [bossZone])
+
+
+
+ def enterClosing(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterClosing(self)
+ taskMgr.doMethodLater(ElevatorData[self.type]['closeTime'],
+ self.elevatorClosedTask,
+ self.uniqueName('closing-timer'))
+
+ def enterClosed(self):
+ DistributedElevatorExtAI.DistributedElevatorExtAI.enterClosed(self)
+ # Switch back into opening mode since we allow other Toons onboard
+ self.fsm.request('opening')
+
+ def enterOpening(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterOpening(self)
+ taskMgr.doMethodLater(ElevatorData[self.type]['openTime'],
+ self.waitEmptyTask,
+ self.uniqueName('opening-timer'))
+ def checkBoard(self, av):
+ dept = ToontownGlobals.cogHQZoneId2deptIndex(self.zone)
+ if (av.hp < self.minLaff):
+ return REJECT_MINLAFF
+ if not av.readyForPromotion(dept):
+ return REJECT_PROMOTION
+ return 0
+
+ def requestBoard(self, *args):
+ self.notify.debug("requestBoard")
+ avId = self.air.getAvatarIdFromSender()
+ av = self.air.doId2do.get(avId)
+ if av:
+ # Only toons with hp greater than the minLaff may board the elevator.
+ boardResponse = self.checkBoard(av)
+ newArgs = (avId,) + args + (boardResponse,)
+ if boardResponse == 0:
+ self.acceptingBoardersHandler(*newArgs)
+ else:
+ self.rejectingBoardersHandler(*newArgs)
+ else:
+ self.notify.warning(
+ "avid: %s does not exist, but tried to board an elevator"
+ % avId
+ )
+ return
+
+# def partyAvatarBoard(self, avatar):
+# av = avatar
+# avId = avatar.doId
+# if av:
+# # Only toons with hp greater than the minLaff may board the elevator.
+# boardResponse = self.checkBoard(av)
+# newArgs = (avId,) + (boardResponse,)
+# if boardResponse == 0:
+# self.acceptingBoardersHandler(*newArgs)
+# else:
+# self.rejectingBoardersHandler(*newArgs)
+# else:
+# self.notify.warning(
+# "avid: %s does not exist, but tried to board an elevator"
+# % avId
+# )
+# return
+
diff --git a/toontown/src/building/DistributedBuilding.py b/toontown/src/building/DistributedBuilding.py
new file mode 100644
index 0000000..ec704eb
--- /dev/null
+++ b/toontown/src/building/DistributedBuilding.py
@@ -0,0 +1,1691 @@
+""" DistributedBuilding module: contains the DistributedBuilding
+ class, the client side representation of a 'building'."""
+
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+from direct.interval.IntervalGlobal import *
+from direct.directtools.DirectGeometry import *
+from ElevatorConstants import *
+from ElevatorUtils import *
+from SuitBuildingGlobals import *
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObject
+import random
+from toontown.suit import SuitDNA
+from toontown.toonbase import TTLocalizer
+from toontown.distributed import DelayDelete
+from toontown.toon import TTEmote
+from otp.avatar import Emote
+from toontown.hood import ZoneUtil
+
+class DistributedBuilding(DistributedObject.DistributedObject):
+ """
+ DistributedBuilding class: The client side representation of a
+ 'building' which can be taken over by bad guys and toons. Each
+ of these buildings can also be 'entered' by bad guys and toons.
+ This object also has a server side representation of it,
+ DistributedBuildingAI. This object has to worry about updating
+ the display of the building and all of its components on the
+ client's machine. The display of the building is either 'toon
+ owned' or 'bad guy owned'.
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBuilding')
+
+ # the initial height of suit buildings when a toon building is taken over
+ #
+ SUIT_INIT_HEIGHT = 125
+
+ # Where to load the takeover sound effects from
+ TAKEOVER_SFX_PREFIX = "phase_5/audio/sfx/"
+
+ def __init__(self, cr):
+ """constructor for the DistributedBuilding"""
+ DistributedObject.DistributedObject.__init__(self, cr)
+ assert(self.debugPrint("__init()"))
+ self.interactiveProp = None
+
+ self.suitDoorOrigin = None
+ self.elevatorModel = None
+
+ self.fsm = ClassicFSM.ClassicFSM('DistributedBuilding',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['waitForVictors',
+ 'waitForVictorsFromCogdo',
+ 'becomingToon',
+ 'becomingToonFromCogdo',
+ 'toon',
+ 'clearOutToonInterior',
+ 'becomingSuit',
+ 'suit',
+ 'clearOutToonInteriorForCogdo',
+ 'becomingCogdo',
+ 'cogdo']),
+ State.State('waitForVictors',
+ self.enterWaitForVictors,
+ self.exitWaitForVictors,
+ ['becomingToon',
+ ]),
+ State.State('waitForVictorsFromCogdo',
+ self.enterWaitForVictorsFromCogdo,
+ self.exitWaitForVictorsFromCogdo,
+ ['becomingToonFromCogdo',
+ ]),
+ State.State('becomingToon',
+ self.enterBecomingToon,
+ self.exitBecomingToon,
+ ['toon']),
+ State.State('becomingToonFromCogdo',
+ self.enterBecomingToonFromCogdo,
+ self.exitBecomingToonFromCogdo,
+ ['toon']),
+ State.State('toon',
+ self.enterToon,
+ self.exitToon,
+ ['clearOutToonInterior', 'clearOutToonInteriorForCogdo']),
+ State.State('clearOutToonInterior',
+ self.enterClearOutToonInterior,
+ self.exitClearOutToonInterior,
+ ['becomingSuit']),
+ State.State('becomingSuit',
+ self.enterBecomingSuit,
+ self.exitBecomingSuit,
+ ['suit']),
+ State.State('suit',
+ self.enterSuit,
+ self.exitSuit,
+ ['waitForVictors',
+ 'becomingToon', # debug only
+ ]),
+ State.State('clearOutToonInteriorForCogdo',
+ self.enterClearOutToonInteriorForCogdo,
+ self.exitClearOutToonInteriorForCogdo,
+ ['becomingCogdo']),
+ State.State('becomingCogdo',
+ self.enterBecomingCogdo,
+ self.exitBecomingCogdo,
+ ['cogdo']),
+ State.State('cogdo',
+ self.enterCogdo,
+ self.exitCogdo,
+ ['waitForVictorsFromCogdo',
+ 'becomingToonFromCogdo', # debug only
+ ])],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ # self.generate will be called automatically.
+
+ # TODO: Eventually, bossLevel will be one of the
+ # required fields.
+ self.bossLevel = 0
+
+ # multitrack used to animate the transitions between suit and toon
+ # buildings
+ #
+ self.transitionTrack = None
+
+ # reference to the elevator created when a building is a suit type
+ #
+ self.elevatorNodePath = None
+
+ # The list of toons who just won the building back.
+ self.victorList = [0, 0, 0, 0]
+
+ # This becomes a Label for the text that is displayed while
+ # we are waiting for the elevator doors to open.
+ self.waitingMessage = None
+
+ # Placeholders for sound effects.
+ self.cogDropSound = None
+ self.cogLandSound = None
+ self.cogSettleSound = None
+ self.cogWeakenSound = None
+ self.toonGrowSound = None
+ self.toonSettleSound = None
+
+ def generate(self):
+ """generate(self)
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ assert(self.debugPrint("generate()"))
+ DistributedObject.DistributedObject.generate(self)
+ self.mode = 'toon'
+ # (the default is to be a toon building, so toonTakeOver()
+ # can ignore the first call for toon take over).
+ self.townTopLevel=self.cr.playGame.hood.loader.geom
+ assert(not self.townTopLevel.isEmpty())
+
+ def disable(self):
+ assert(self.debugPrint("disable()"))
+ # Go to the off state when the object is put in the cache
+ self.fsm.request("off")
+ del self.townTopLevel
+ self.stopTransition()
+ DistributedObject.DistributedObject.disable(self)
+ # self.delete() will automatically be called.
+
+ def delete(self):
+ assert(self.debugPrint("delete()"))
+ if self.elevatorNodePath:
+ self.elevatorNodePath.removeNode()
+ del self.elevatorNodePath
+ del self.elevatorModel
+ if hasattr(self, 'cab'):
+ del self.cab
+ del self.leftDoor
+ del self.rightDoor
+ del self.suitDoorOrigin
+ self.cleanupSuitBuilding()
+ self.unloadSfx()
+ del self.fsm
+ DistributedObject.DistributedObject.delete(self)
+
+ def setBlock(self, block, interiorZoneId):
+ self.block = block
+ self.interiorZoneId = interiorZoneId
+
+ def setSuitData(self, suitTrack, difficulty, numFloors):
+ assert(self.debugPrint("setSuitData(%s, %d, %d)" %(suitTrack, difficulty, numFloors)))
+ self.track=suitTrack
+ self.difficulty=difficulty
+ self.numFloors=numFloors
+
+ def setState(self, state, timestamp):
+ assert(self.debugPrint("setState(%s, %d)" % (state, timestamp)))
+ self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+
+ def getSuitElevatorNodePath(self):
+ """getElevatorNodePath(self)
+
+ Returns the elevator node path associated with the suit
+ building. This assumes the building is in suit mode. If the
+ building is not in suit mode (in particular, if it hasn't had
+ any mode at all set yet), assumes the building is meant to be
+ in suit mode and switches it immediately.
+ """
+ if self.mode != 'suit':
+ self.setToSuit()
+
+ assert(self.elevatorNodePath != None)
+ return self.elevatorNodePath
+
+ def getCogdoElevatorNodePath(self):
+ if self.mode != 'cogdo':
+ self.setToCogdo()
+
+ assert(self.elevatorNodePath != None)
+ return self.elevatorNodePath
+
+ def getSuitDoorOrigin(self):
+ if self.mode != 'suit':
+ self.setToSuit()
+
+ return self.suitDoorOrigin
+
+ def getCogdoDoorOrigin(self):
+ if self.mode != 'cogdo':
+ self.setToCogdo()
+
+ return self.suitDoorOrigin
+
+ def getBossLevel(self):
+ return self.bossLevel
+
+ def setBossLevel(self, bossLevel):
+ self.bossLevel = bossLevel
+
+ def setVictorList(self, victorList):
+ self.victorList = victorList
+
+ ##### off state #####
+
+ def enterOff(self):
+ assert(self.debugPrint("enterOff()"))
+
+ def exitOff(self):
+ assert(self.debugPrint("exitOff()"))
+
+ ##### waitForVictors state #####
+ def enterWaitForVictors(self, ts):
+ if self.mode != 'suit':
+ self.setToSuit()
+ victorCount = self.victorList.count(base.localAvatar.doId)
+ if victorCount == 1:
+ self.acceptOnce("insideVictorElevator", self.handleInsideVictorElevator)
+
+ # Since the localToon is on the elevator, we should
+ # position the camera in front of the building so we have
+ # something to look at while we're waiting for everyone to
+ # load up the zone. This duplicates the camera setup in
+ # the beginning of walkOutCameraTrack().
+ camera.reparentTo(render)
+ camera.setPosHpr(self.elevatorNodePath,
+ 0, -32.5, 9.4, 0, 348, 0)
+ base.camLens.setFov(52.0)
+
+ # Are we waiting for any other players to come out?
+ anyOthers = 0
+ for v in self.victorList:
+ if v != 0 and v != base.localAvatar.doId:
+ anyOthers = 1
+
+ if anyOthers:
+ self.waitingMessage = DirectLabel(
+ text = TTLocalizer.BuildingWaitingForVictors,
+ text_fg = VBase4(1,1,1,1),
+ text_align = TextNode.ACenter,
+ relief = None,
+ pos = (0, 0, 0.35),
+ scale = 0.1)
+
+ elif victorCount == 0:
+ pass
+ else:
+ self.error("localToon is on the victorList %d times" % victorCount)
+
+ # Make sure the elevator doors are still closed in this state.
+ closeDoors(self.leftDoor, self.rightDoor)
+
+ # And turn off the elevator light.
+ for light in self.floorIndicator:
+ if light != None:
+ light.setColor(LIGHT_OFF_COLOR)
+
+ return
+
+ def handleInsideVictorElevator(self):
+ self.sendUpdate("setVictorReady", [])
+ return
+
+ def exitWaitForVictors(self):
+ self.ignore("insideVictorElevator")
+ if self.waitingMessage != None:
+ self.waitingMessage.destroy()
+ self.waitingMessage = None
+ return
+
+ ##### waitForVictorsFromCogdo state #####
+ def enterWaitForVictorsFromCogdo(self, ts):
+ if self.mode != 'cogdo':
+ self.setToCogdo()
+ victorCount = self.victorList.count(base.localAvatar.doId)
+ if victorCount == 1:
+ self.acceptOnce("insideVictorElevator", self.handleInsideVictorElevatorFromCogdo)
+
+ # Since the localToon is on the elevator, we should
+ # position the camera in front of the building so we have
+ # something to look at while we're waiting for everyone to
+ # load up the zone. This duplicates the camera setup in
+ # the beginning of walkOutCameraTrack().
+ camera.reparentTo(render)
+ camera.setPosHpr(self.elevatorNodePath,
+ 0, -32.5, 9.4, 0, 348, 0)
+ base.camLens.setFov(52.0)
+
+ # Are we waiting for any other players to come out?
+ anyOthers = 0
+ for v in self.victorList:
+ if v != 0 and v != base.localAvatar.doId:
+ anyOthers = 1
+
+ if anyOthers:
+ self.waitingMessage = DirectLabel(
+ text = TTLocalizer.BuildingWaitingForVictors,
+ text_fg = VBase4(1,1,1,1),
+ text_align = TextNode.ACenter,
+ relief = None,
+ pos = (0, 0, 0.35),
+ scale = 0.1)
+
+ elif victorCount == 0:
+ pass
+ else:
+ self.error("localToon is on the victorList %d times" % victorCount)
+
+ # Make sure the elevator doors are still closed in this state.
+ closeDoors(self.leftDoor, self.rightDoor)
+
+ # And turn off the elevator light.
+ for light in self.floorIndicator:
+ if light != None:
+ light.setColor(LIGHT_OFF_COLOR)
+
+ return
+
+ def handleInsideVictorElevatorFromCogdo(self):
+ self.sendUpdate("setVictorReady", [])
+ return
+
+ def exitWaitForVictorsFromCogdo(self):
+ self.ignore("insideVictorElevator")
+ if self.waitingMessage != None:
+ self.waitingMessage.destroy()
+ self.waitingMessage = None
+ return
+
+ ##### becomingToon state #####
+
+ def enterBecomingToon(self, ts):
+ assert(self.debugPrint("enterBecomingToon() %s" %(str(self.getDoId()))))
+ # Start animation:
+ self.animToToon(ts)
+
+ def exitBecomingToon(self):
+ assert(self.debugPrint("exitBecomingToon()"))
+ # Stop animation:
+
+ ##### becomingToonFromCogdo state #####
+
+ def enterBecomingToonFromCogdo(self, ts):
+ assert(self.debugPrint("enterBecomingToonFromCogdo() %s" %(str(self.getDoId()))))
+ # Start animation:
+ self.animToToonFromCogdo(ts)
+
+ def exitBecomingToonFromCogdo(self):
+ assert(self.debugPrint("exitBecomingToonFromCogdo()"))
+ # Stop animation:
+
+ ##### toon state #####
+
+ def enterToon(self, ts):
+ assert(self.debugPrint("enterToon()"))
+ if self.getInteractiveProp():
+ self.getInteractiveProp().buildingLiberated(self.doId)
+ self.setToToon()
+
+ def exitToon(self):
+ assert(self.debugPrint("exitToon()"))
+
+ ##### ClearOutToonInterior state #####
+
+ def enterClearOutToonInterior(self, ts):
+ assert(self.debugPrint("enterClearOutToonInterior()"))
+
+ def exitClearOutToonInterior(self):
+ assert(self.debugPrint("exitClearOutToonInterior()"))
+
+ ##### becomingSuit state #####
+
+ def enterBecomingSuit(self, ts):
+ assert(self.debugPrint("enterBecomingSuit()"))
+ # Start animation:
+ #print "enterBecomingSuit %s" %(str(self.getDoId()))
+ self.animToSuit(ts)
+
+ def exitBecomingSuit(self):
+ assert(self.debugPrint("exitBecomingSuit()"))
+ # Stop animation:
+ pass
+
+ ##### suit state #####
+
+ def enterSuit(self, ts):
+ assert(self.debugPrint("enterSuit()"))
+ #print "enterSuit %s" %(str(self.getDoId()))
+ self.makePropSad()
+ self.setToSuit()
+
+ def exitSuit(self):
+ assert(self.debugPrint("exitSuit()"))
+
+ ##### ClearOutToonInterior state #####
+
+ def enterClearOutToonInteriorForCogdo(self, ts):
+ assert(self.debugPrint("enterClearOutToonInteriorForCogdo()"))
+
+ def exitClearOutToonInteriorForCogdo(self):
+ assert(self.debugPrint("exitClearOutToonInteriorForCogdo()"))
+
+ ##### becomingCogdo state #####
+
+ def enterBecomingCogdo(self, ts):
+ assert(self.debugPrint("enterBecomingCogdo()"))
+ # Start animation:
+ #print "enterBecomingCogdo %s" %(str(self.getDoId()))
+ self.animToCogdo(ts)
+
+ def exitBecomingCogdo(self):
+ assert(self.debugPrint("exitBecomingCogdo()"))
+ # Stop animation:
+ pass
+
+ ##### cogdo state #####
+
+ def enterCogdo(self, ts):
+ assert(self.debugPrint("enterCogdo()"))
+ #print "enterCogdo %s" %(str(self.getDoId()))
+ self.setToCogdo()
+
+ def exitCogdo(self):
+ assert(self.debugPrint("exitCogdo()"))
+
+ #####
+
+ def getNodePaths(self):
+ assert(self.debugPrint("getNodePaths()"))
+ # Toon flat buildings:
+ nodePath=[]
+ # Find all tb or sb of this block, even stashed (";+s") ones:
+ npc = self.townTopLevel.findAllMatches(
+ "**/?b" + str(self.block) + ":*_DNARoot;+s")
+ assert(npc.getNumPaths()>0)
+ for i in range(npc.getNumPaths()):
+ nodePath.append(npc.getPath(i))
+ return nodePath
+
+ def loadElevator(self, newNP):
+ assert(self.debugPrint("loadElevator(newNP=%s)"%(newNP,)))
+ # Load up an elevator
+ self.elevatorNodePath = hidden.attachNewNode("elevatorNodePath")
+ self.elevatorModel = loader.loadModel(
+ "phase_4/models/modules/elevator")
+
+ # Put up a display to show the current floor of the elevator
+ self.floorIndicator=[None, None, None, None, None]
+ npc=self.elevatorModel.findAllMatches("**/floor_light_?;+s")
+ for i in range(npc.getNumPaths()):
+ np=npc.getPath(i)
+ # Get the last character, and make it zero based:
+ floor=int(np.getName()[-1:])-1
+ self.floorIndicator[floor]=np
+ if floor < self.numFloors:
+ np.setColor(LIGHT_OFF_COLOR)
+ else:
+ np.hide()
+
+ self.elevatorModel.reparentTo(self.elevatorNodePath)
+
+ if self.mode == 'suit':
+ # Add in a corporate icon
+ self.cab = self.elevatorModel.find('**/elevator')
+ cogIcons = loader.loadModel('phase_3/models/gui/cog_icons')
+ dept = chr(self.track)
+ if dept == 'c':
+ corpIcon = cogIcons.find('**/CorpIcon').copyTo(self.cab)
+ elif dept == 's':
+ corpIcon = cogIcons.find('**/SalesIcon').copyTo(self.cab)
+ elif dept == 'l':
+ corpIcon = cogIcons.find('**/LegalIcon').copyTo(self.cab)
+ elif dept == 'm':
+ corpIcon = cogIcons.find('**/MoneyIcon').copyTo(self.cab)
+ corpIcon.setPos(0,6.79,6.8)
+ corpIcon.setScale(3)
+ from toontown.suit import Suit
+ corpIcon.setColor(Suit.Suit.medallionColors[dept])
+ cogIcons.removeNode()
+
+ self.leftDoor = self.elevatorModel.find("**/left-door")
+ self.rightDoor = self.elevatorModel.find("**/right-door")
+
+ # Find the door origin
+ self.suitDoorOrigin = newNP.find("**/*_door_origin")
+ assert(not self.suitDoorOrigin.isEmpty())
+
+ # Put the elevator under the door origin
+ self.elevatorNodePath.reparentTo(self.suitDoorOrigin)
+ self.normalizeElevator()
+ return
+
+ def loadAnimToSuitSfx(self):
+ """loadAnimToSuitSfx(self)
+ Loads up the sound effects necessary for the animToSuit effect.
+ """
+ if self.cogDropSound == None:
+ self.cogDropSound = base.loadSfx(self.TAKEOVER_SFX_PREFIX + "cogbldg_drop.mp3")
+ self.cogLandSound = base.loadSfx(self.TAKEOVER_SFX_PREFIX + "cogbldg_land.mp3")
+ self.cogSettleSound = base.loadSfx(self.TAKEOVER_SFX_PREFIX + "cogbldg_settle.mp3")
+ self.openSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_open.mp3")
+
+ def loadAnimToToonSfx(self):
+ """loadAnimToToonSfx(self)
+ Loads up the sound effects necessary for the animToToon effect.
+ """
+ if self.cogWeakenSound == None:
+ self.cogWeakenSound = base.loadSfx(self.TAKEOVER_SFX_PREFIX + "cogbldg_weaken.mp3")
+ self.toonGrowSound = base.loadSfx(self.TAKEOVER_SFX_PREFIX + "toonbldg_grow.mp3")
+ self.toonSettleSound = base.loadSfx(self.TAKEOVER_SFX_PREFIX + "toonbldg_settle.mp3")
+ self.openSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_open.mp3")
+
+ def unloadSfx(self):
+ """unloadSfx(self)
+ Unloads any sound effects that may have been loaded.
+ """
+ if self.cogDropSound != None:
+ self.cogDropSound = None
+ self.cogLandSound = None
+ self.cogSettleSound = None
+ self.openSfx = None
+
+ if self.cogWeakenSound != None:
+ self.cogWeakenSound = None
+ self.toonGrowSound = None
+ self.toonSettleSound = None
+ self.openSfx = None
+
+ def _deleteTransitionTrack(self):
+ if self.transitionTrack:
+ DelayDelete.cleanupDelayDeletes(self.transitionTrack)
+ self.transitionTrack = None
+
+ def animToSuit(self, timeStamp):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: create the multitrack that contains the animation
+ // sequence to transition this building from a toon to
+ // suit building
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ assert(self.debugPrint("animToSuit(timeStamp=%s)"%(timeStamp,)))
+ self.stopTransition()
+ if self.mode != 'toon':
+ self.setToToon()
+ self.loadAnimToSuitSfx()
+
+ # first find the side building portions
+ #
+ sideBldgNodes = self.getNodePaths()
+
+ # now find and position the landmark portion of the suit building
+ # Copy the suit landmark building, based on suit track & difficulty:
+ #
+ nodePath=hidden.find(self.getSbSearchString())
+ assert(not nodePath.isEmpty())
+ newNP = self.setupSuitBuilding(nodePath)
+ # Make sure the doors are closed for now.
+ closeDoors(self.leftDoor, self.rightDoor)
+ newNP.stash()
+ sideBldgNodes.append(newNP)
+
+ # create intervals to position and/or hide/stash the building parts
+ # depending if it is part of the toon or suit version
+ #
+ soundPlayed = 0 # don't want to play sound for every part
+ tracks = Parallel(name = self.taskName('toSuitTrack'))
+ for i in sideBldgNodes:
+ name=i.getName()
+ timeForDrop = TO_SUIT_BLDG_TIME*0.85
+ if (name[0]=='s'):
+ #print 'anim2suit: suit flat scale: %s' % repr(i.getScale())
+ # set the position of the node, then unstash it to show it
+ showTrack = Sequence(
+ name = self.taskName('ToSuitFlatsTrack') +
+ '-' + str(sideBldgNodes.index(i)))
+ initPos = Point3(0, 0, self.SUIT_INIT_HEIGHT) + \
+ i.getPos()
+ showTrack.append(Func(i.setPos, initPos))
+ showTrack.append(Func(i.unstash))
+
+ # Assumption: The last item on sideBldgNodes is actually
+ # the landmark bldg itself.
+ if i == sideBldgNodes[len(sideBldgNodes) - 1]:
+ showTrack.append(Func(self.normalizeElevator))
+ #print "moving suit bldg part from %s to %s"%(str(initPos),
+ # str(i.getPos()))
+ if not soundPlayed:
+ showTrack.append(Func(
+ base.playSfx, self.cogDropSound, 0, 1, None, 0.))
+ showTrack.append(LerpPosInterval(
+ i, timeForDrop,
+ i.getPos(), name = self.taskName('ToSuitAnim') + '-' +
+ str(sideBldgNodes.index(i))))
+ if not soundPlayed:
+ showTrack.append(Func(
+ base.playSfx, self.cogLandSound, 0, 1, None, 0.))
+ showTrack.append(self.createBounceTrack(
+ i, 2, 0.65,
+ TO_SUIT_BLDG_TIME-timeForDrop,
+ slowInitBounce=1.0))
+ if not soundPlayed:
+ showTrack.append(Func(
+ base.playSfx, self.cogSettleSound, 0, 1, None, 0.))
+ tracks.append(showTrack)
+
+ if not soundPlayed:
+ soundPlayed = 1
+ #print "moving suit flat from %s to %s"%(str(initPos),
+ # str(i.getPos()))
+ # lerp the alpha in for the building part, making sure to
+ # remove the transparency transition when the fade is done
+ # CCC is it ok if we have this other track also manipulate
+ # the same building flat as the previously created track?
+ # I know this can be an issue if the flat is removed, but
+ # that should not happen in this multi-track
+ #
+ #showTrack = Sequence()
+ #showTrack.append(Func(i.setTransparency, 1))
+ #showTrack.append(LerpFunctionInterval(
+ # i.setAlphaScale, fromData=0, toData=1,
+ # duration=TO_SUIT_BLDG_TIME*0.20))
+ #showTrack.append(FunctionInterval(i.clearTransparency))
+ #tracks.append(showTrack)
+
+ elif (name[0]=='t'):
+ hideTrack = Sequence(
+ name = self.taskName('ToSuitToonFlatsTrack'))
+ # figure how long till the toon building will start to be
+ # compressed by the suit building coming down on it
+ #
+ timeTillSquish = (self.SUIT_INIT_HEIGHT - 20.0) / \
+ self.SUIT_INIT_HEIGHT
+ timeTillSquish *= timeForDrop
+ #hideTrack.append(Wait(timeTillSquish))
+ hideTrack.append(LerpFunctionInterval(
+ self.adjustColorScale, fromData=1,
+ toData=0.25,
+ duration=timeTillSquish,
+ extraArgs=[i]))
+ hideTrack.append(LerpScaleInterval(
+ i, timeForDrop-timeTillSquish,
+ Vec3(1, 1, 0.01)))
+ hideTrack.append(Func(i.stash))
+ hideTrack.append(Func(i.setScale, Vec3(1)))
+ hideTrack.append(Func(i.clearColorScale))
+ tracks.append(hideTrack)
+
+ # bundle up all of our tracks for the entire transition and start
+ # playing
+ #
+ self.stopTransition()
+ self._deleteTransitionTrack()
+ self.transitionTrack = tracks
+
+ #print "transitionTrack: %s" % self.transitionTrack
+ #print "starting track at %s" % globalClock.getFrameTime()
+ self.transitionTrack.start(timeStamp)
+
+ def setupSuitBuilding(self, nodePath):
+ assert(self.debugPrint("setupSuitBuilding(nodePath=%s)"%(nodePath,)))
+ dnaStore=self.cr.playGame.dnaStore
+ level = int(self.difficulty / 2) + 1
+ suitNP=dnaStore.findNode("suit_landmark_"
+ +chr(self.track)+str(level))
+
+ # If you want to make the suit buildings visible from a
+ # distance, uncomment the following line, and comment out
+ # the three lines under it.
+ #suitBuildingNP=suitNP.copyTo(self.townTopLevel)
+ zoneId = dnaStore.getZoneFromBlockNumber(self.block)
+ zoneId = ZoneUtil.getTrueZoneId(zoneId, self.interiorZoneId)
+ newParentNP = base.cr.playGame.hood.loader.zoneDict[zoneId]
+ suitBuildingNP = suitNP.copyTo(newParentNP)
+
+ # Setup the sign:
+ buildingTitle = dnaStore.getTitleFromBlockNumber(self.block)
+ if not buildingTitle:
+ buildingTitle = TTLocalizer.CogsInc
+ else:
+ buildingTitle += TTLocalizer.CogsIncExt
+ buildingTitle += ("\n%s" % SuitDNA.getDeptFullname(chr(self.track)))
+
+ # Try to find this signText in the node map
+ textNode = TextNode("sign")
+ textNode.setTextColor(1.0, 1.0, 1.0, 1.0)
+ textNode.setFont(ToontownGlobals.getSuitFont())
+ textNode.setAlign(TextNode.ACenter)
+ textNode.setWordwrap(17.0)
+ textNode.setText(buildingTitle)
+
+ # Since the text is wordwrapped, it may flow over more
+ # than one line. Try to adjust the scale and position of
+ # the sign accordingly.
+ textHeight = textNode.getHeight()
+ zScale = (textHeight + 2) / 3.0
+
+ # Determine where the sign should go:
+ signOrigin=suitBuildingNP.find("**/sign_origin;+s")
+ assert(not signOrigin.isEmpty())
+ # Get the background:
+ backgroundNP=loader.loadModel("phase_5/models/modules/suit_sign")
+ assert(not backgroundNP.isEmpty())
+ backgroundNP.reparentTo(signOrigin)
+ backgroundNP.setPosHprScale(0.0, 0.0, textHeight * 0.8 / zScale,
+ 0.0, 0.0, 0.0,
+ 8.0, 8.0, 8.0 * zScale)
+ backgroundNP.node().setEffect(DecalEffect.make())
+ # Get the text node path:
+ signTextNodePath = backgroundNP.attachNewNode(textNode.generate())
+ assert(not signTextNodePath.isEmpty())
+ # Scale the text:
+ signTextNodePath.setPosHprScale(0.0, 0.0, -0.21 + textHeight * 0.1 / zScale,
+ 0.0, 0.0, 0.0,
+ 0.1, 0.1, 0.1 / zScale)
+ # Clear parent color higher in the hierarchy
+ signTextNodePath.setColor(1.0, 1.0, 1.0, 1.0)
+ # Decal sign onto the front of the building:
+ frontNP = suitBuildingNP.find("**/*_front/+GeomNode;+s")
+ assert(not frontNP.isEmpty())
+ backgroundNP.wrtReparentTo(frontNP)
+ frontNP.node().setEffect(DecalEffect.make())
+
+ # Rename the building:
+ suitBuildingNP.setName("sb"+str(self.block)+":_landmark__DNARoot")
+ suitBuildingNP.setPosHprScale(nodePath,
+ 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0,
+ 1.0, 1.0, 1.0)
+ # Get rid of any transitions and extra nodes
+ suitBuildingNP.flattenMedium()
+ self.loadElevator(suitBuildingNP)
+ return suitBuildingNP
+
+ def cleanupSuitBuilding(self):
+ if hasattr(self, "floorIndicator"):
+ del self.floorIndicator
+
+ def adjustColorScale(self, scale, node):
+ node.setColorScale(scale, scale, scale, 1)
+
+ def animToCogdo(self, timeStamp):
+ assert(self.debugPrint("animToCogdo(timeStamp=%s)"%(timeStamp,)))
+ self.stopTransition()
+ if self.mode != 'toon':
+ self.setToToon()
+ self.loadAnimToSuitSfx()
+
+ # first find the side building portions
+ #
+ sideBldgNodes = self.getNodePaths()
+
+ # now find and position the landmark portion of the cogdo building
+ # Copy the cogdo landmark building, based on difficulty:
+ #
+ nodePath=hidden.find(self.getSbSearchString())
+ assert(not nodePath.isEmpty())
+ newNP = self.setupCogdo(nodePath)
+ # Make sure the doors are closed for now.
+ closeDoors(self.leftDoor, self.rightDoor)
+ newNP.stash()
+ sideBldgNodes.append(newNP)
+
+ for np in sideBldgNodes:
+ if not np.isEmpty():
+ np.setColorScale(.6,.6,.6,1.)
+
+ # create intervals to position and/or hide/stash the building parts
+ # depending if it is part of the toon or cogdo version
+ #
+ soundPlayed = 0 # don't want to play sound for every part
+ tracks = Parallel(name = self.taskName('toCogdoTrack'))
+ for i in sideBldgNodes:
+ name=i.getName()
+ timeForDrop = TO_SUIT_BLDG_TIME*0.85
+ if (name[0]=='s'):
+ #print 'anim2suit: suit flat scale: %s' % repr(i.getScale())
+ # set the position of the node, then unstash it to show it
+ showTrack = Sequence(
+ name = self.taskName('ToCogdoFlatsTrack') +
+ '-' + str(sideBldgNodes.index(i)))
+ initPos = Point3(0, 0, self.SUIT_INIT_HEIGHT) + \
+ i.getPos()
+ showTrack.append(Func(i.setPos, initPos))
+ showTrack.append(Func(i.unstash))
+
+ # Assumption: The last item on sideBldgNodes is actually
+ # the landmark bldg itself.
+ if i == sideBldgNodes[len(sideBldgNodes) - 1]:
+ showTrack.append(Func(self.normalizeElevator))
+ #print "moving suit bldg part from %s to %s"%(str(initPos),
+ # str(i.getPos()))
+ if not soundPlayed:
+ showTrack.append(Func(
+ base.playSfx, self.cogDropSound, 0, 1, None, 0.))
+ showTrack.append(LerpPosInterval(
+ i, timeForDrop,
+ i.getPos(), name = self.taskName('ToCogdoAnim') + '-' +
+ str(sideBldgNodes.index(i))))
+ if not soundPlayed:
+ showTrack.append(Func(
+ base.playSfx, self.cogLandSound, 0, 1, None, 0.))
+ showTrack.append(self.createBounceTrack(
+ i, 2, 0.65,
+ TO_SUIT_BLDG_TIME-timeForDrop,
+ slowInitBounce=1.0))
+ if not soundPlayed:
+ showTrack.append(Func(
+ base.playSfx, self.cogSettleSound, 0, 1, None, 0.))
+ tracks.append(showTrack)
+
+ if not soundPlayed:
+ soundPlayed = 1
+ #print "moving suit flat from %s to %s"%(str(initPos),
+ # str(i.getPos()))
+ # lerp the alpha in for the building part, making sure to
+ # remove the transparency transition when the fade is done
+ # CCC is it ok if we have this other track also manipulate
+ # the same building flat as the previously created track?
+ # I know this can be an issue if the flat is removed, but
+ # that should not happen in this multi-track
+ #
+ #showTrack = Sequence()
+ #showTrack.append(Func(i.setTransparency, 1))
+ #showTrack.append(LerpFunctionInterval(
+ # i.setAlphaScale, fromData=0, toData=1,
+ # duration=TO_SUIT_BLDG_TIME*0.20))
+ #showTrack.append(FunctionInterval(i.clearTransparency))
+ #tracks.append(showTrack)
+
+ elif (name[0]=='t'):
+ hideTrack = Sequence(
+ name = self.taskName('ToCogdoToonFlatsTrack'))
+ # figure how long till the toon building will start to be
+ # compressed by the cogdo coming down on it
+ #
+ timeTillSquish = (self.SUIT_INIT_HEIGHT - 20.0) / \
+ self.SUIT_INIT_HEIGHT
+ timeTillSquish *= timeForDrop
+ #hideTrack.append(Wait(timeTillSquish))
+ hideTrack.append(LerpFunctionInterval(
+ self.adjustColorScale, fromData=1,
+ toData=0.25,
+ duration=timeTillSquish,
+ extraArgs=[i]))
+ hideTrack.append(LerpScaleInterval(
+ i, timeForDrop-timeTillSquish,
+ Vec3(1, 1, 0.01)))
+ hideTrack.append(Func(i.stash))
+ hideTrack.append(Func(i.setScale, Vec3(1)))
+ hideTrack.append(Func(i.clearColorScale))
+ tracks.append(hideTrack)
+
+ # bundle up all of our tracks for the entire transition and start
+ # playing
+ #
+ self.stopTransition()
+ self._deleteTransitionTrack()
+ self.transitionTrack = tracks
+
+ #print "transitionTrack: %s" % self.transitionTrack
+ #print "starting track at %s" % globalClock.getFrameTime()
+ self.transitionTrack.start(timeStamp)
+
+ def setupCogdo(self, nodePath):
+ assert(self.debugPrint("setupCogdo(nodePath=%s)"%(nodePath,)))
+ dnaStore=self.cr.playGame.dnaStore
+ level = int(self.difficulty / 2) + 1
+ suitNP=dnaStore.findNode("suit_landmark_"
+ +'s'+str(level))
+
+ # If you want to make the suit buildings visible from a
+ # distance, uncomment the following line, and comment out
+ # the three lines under it.
+ #suitBuildingNP=suitNP.copyTo(self.townTopLevel)
+ zoneId = dnaStore.getZoneFromBlockNumber(self.block)
+ zoneId = ZoneUtil.getTrueZoneId(zoneId, self.interiorZoneId)
+ newParentNP = base.cr.playGame.hood.loader.zoneDict[zoneId]
+ suitBuildingNP = suitNP.copyTo(newParentNP)
+
+ # Setup the sign:
+ buildingTitle = dnaStore.getTitleFromBlockNumber(self.block)
+ if not buildingTitle:
+ buildingTitle = TTLocalizer.Cogdominiums
+ else:
+ buildingTitle += TTLocalizer.CogdominiumsExt
+
+ # Try to find this signText in the node map
+ textNode = TextNode("sign")
+ textNode.setTextColor(1.0, 1.0, 1.0, 1.0)
+ textNode.setFont(ToontownGlobals.getSuitFont())
+ textNode.setAlign(TextNode.ACenter)
+ textNode.setWordwrap(17.0)
+ textNode.setText(buildingTitle)
+
+ # Since the text is wordwrapped, it may flow over more
+ # than one line. Try to adjust the scale and position of
+ # the sign accordingly.
+ textHeight = textNode.getHeight()
+ zScale = (textHeight + 2) / 3.0
+
+ # Determine where the sign should go:
+ signOrigin=suitBuildingNP.find("**/sign_origin;+s")
+ assert(not signOrigin.isEmpty())
+ # Get the background:
+ backgroundNP=loader.loadModel("phase_5/models/modules/suit_sign")
+ assert(not backgroundNP.isEmpty())
+ backgroundNP.reparentTo(signOrigin)
+ backgroundNP.setPosHprScale(0.0, 0.0, textHeight * 0.8 / zScale,
+ 0.0, 0.0, 0.0,
+ 8.0, 8.0, 8.0 * zScale)
+ backgroundNP.node().setEffect(DecalEffect.make())
+ # Get the text node path:
+ signTextNodePath = backgroundNP.attachNewNode(textNode.generate())
+ assert(not signTextNodePath.isEmpty())
+ # Scale the text:
+ signTextNodePath.setPosHprScale(0.0, 0.0, -0.21 + textHeight * 0.1 / zScale,
+ 0.0, 0.0, 0.0,
+ 0.1, 0.1, 0.1 / zScale)
+ # Clear parent color higher in the hierarchy
+ signTextNodePath.setColor(1.0, 1.0, 1.0, 1.0)
+ # Decal sign onto the front of the building:
+ frontNP = suitBuildingNP.find("**/*_front/+GeomNode;+s")
+ assert(not frontNP.isEmpty())
+ backgroundNP.wrtReparentTo(frontNP)
+ frontNP.node().setEffect(DecalEffect.make())
+
+ # Rename the building:
+ suitBuildingNP.setName("sb"+str(self.block)+":_landmark__DNARoot")
+ suitBuildingNP.setPosHprScale(nodePath,
+ 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0,
+ 1.0, 1.0, 1.0)
+ # Get rid of any transitions and extra nodes
+ suitBuildingNP.flattenMedium()
+ suitBuildingNP.setColorScale(.6,.6,.6,1.)
+ self.loadElevator(suitBuildingNP)
+ return suitBuildingNP
+
+ def animToToon(self, timeStamp):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: create the multitrack that contains the animation
+ // sequence to transition this building from a suit to
+ // toon building
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # TODO: Incorporate the toons coming out of the elevator into
+ # this multitrack. There will also need to be something like
+ # exitCompleted...
+ self.stopTransition()
+ if self.mode != 'suit':
+ self.setToSuit()
+
+ self.loadAnimToToonSfx()
+
+ # create intervals to position and/or hide/stash the building parts
+ # depending if it is part of the toon or suit version
+ #
+ suitSoundPlayed = 0 # don't want to play sound for every part
+ toonSoundPlayed = 0 # don't want to play sound for every part
+ bldgNodes = self.getNodePaths()
+ tracks = Parallel()
+ for i in bldgNodes:
+ name=i.getName()
+ if (name[0]=='s'):
+ hideTrack = Sequence(
+ name = self.taskName('ToToonSuitFlatsTrack'))
+ # have the suit building scale away
+ #
+ #origPos = i.getPos()
+ #print "sidebldgpos: %s" % str(origPos)
+ #tgtPos = Point3(
+ # Point3(0,0,-self.SUIT_INIT_HEIGHT) + \
+ # origPos)
+ # shake the suit building on the x and y axis as it goes down
+ #
+ landmark = name.find("_landmark_") != -1
+ #hideTrack.append(LerpPosInterval(
+ # i, TO_TOON_BLDG_TIME,
+ # tgtPos, name = self.taskName('ToToonAnim')))
+ #hideTrack.append(LerpFunctionInterval(
+ # self.shakePart,
+ # extraArgs=[i,landmark,
+ # origPos.getX(),origPos.getY()]))
+ #print 'anim2toon: suit flat scale: %s' % repr(i.getScale())
+ if not suitSoundPlayed:
+ hideTrack.append(Func(
+ base.playSfx, self.cogWeakenSound, 0, 1, None, 0.))
+ hideTrack.append(self.createBounceTrack(
+ i, 3, 1.2, TO_TOON_BLDG_TIME*0.05,
+ slowInitBounce=0.0))
+ hideTrack.append(self.createBounceTrack(
+ i, 5, 0.8, TO_TOON_BLDG_TIME*0.10,
+ slowInitBounce=0.0))
+ hideTrack.append(self.createBounceTrack(
+ i, 7, 1.2, TO_TOON_BLDG_TIME*0.17,
+ slowInitBounce=0.0))
+ hideTrack.append(self.createBounceTrack(
+ i, 9, 1.2, TO_TOON_BLDG_TIME*0.18,
+ slowInitBounce=0.0))
+ realScale = i.getScale()
+ hideTrack.append(LerpScaleInterval(
+ i, TO_TOON_BLDG_TIME*0.10,
+ Vec3(realScale[0], realScale[1], 0.01)))
+ if landmark:
+ # the landmark portion is recreated each time a suit
+ # building is generated, so we can just completely remove
+ # the node
+ #
+ hideTrack.append(Func(i.removeNode))
+ else:
+ # make sure to relocate the suit building part to its
+ # original position above the ground so when it is shown
+ # again it it in its original, correct location (or set
+ # the scale to 1, depending on how the toToon transition
+ # is implemented)
+ #
+ hideTrack.append(Func(i.stash))
+ #hideTrack.append(FunctionInterval(
+ # i.setPos, extraArgs = [origPos]))
+ hideTrack.append(Func(i.setScale, Vec3(1)))
+ if not suitSoundPlayed:
+ suitSoundPlayed = 1
+ tracks.append(hideTrack)
+ elif (name[0]=='t'):
+ hideTrack = Sequence(
+ name = self.taskName('ToToonFlatsTrack'))
+ # show the toon portion of the building and set up the
+ # transparency transition so we can slowly fade it in
+ #
+ hideTrack.append(Wait(TO_TOON_BLDG_TIME*0.5))
+ if not toonSoundPlayed:
+ hideTrack.append(Func(
+ base.playSfx, self.toonGrowSound, 0, 1, None, 0.))
+ hideTrack.append(Func(i.unstash))
+ hideTrack.append(Func(i.setScale, Vec3(1,1,0.01)))
+ #hideTrack.append(Func(i.setTransparency, 1))
+ # lerp the alpha in for the building part, making sure to
+ # remove the transparency transition when the fade is done
+ #
+ #hideTrack.append(LerpFunctionInterval(
+ # i.setAlphaScale, fromData=0.15, toData=1,
+ # duration=TO_TOON_BLDG_TIME*0.25))
+ #hideTrack.append(Func(i.clearTransparency))
+ if not toonSoundPlayed:
+ hideTrack.append(Func(
+ base.playSfx, self.toonSettleSound, 0, 1, None, 0.))
+ hideTrack.append(self.createBounceTrack(
+ i, 11, 1.2, TO_TOON_BLDG_TIME*0.5,
+ slowInitBounce=4.0))
+ tracks.append(hideTrack)
+ if not toonSoundPlayed:
+ toonSoundPlayed = 1
+
+ # bundle up all of our tracks for the entire transition and start
+ # playing
+ #
+ self.stopTransition()
+ #self.transitionTrack = Parallel(tracks, self.taskName(
+ # 'ToToonMTrack'))
+ bldgMTrack = tracks
+
+ #print "transitionTrack: %s" % self.transitionTrack
+ #print "starting track at %s" % globalClock.getFrameTime()
+
+ # TODO: integrate the toons running out of the building into
+ # the multitrack. For now, Just plant them outside the elevator.
+ localToonIsVictor = self.localToonIsVictor()
+
+ if localToonIsVictor:
+ camTrack = self.walkOutCameraTrack()
+
+ victoryRunTrack, delayDeletes = self.getVictoryRunTrack()
+
+ trackName = self.taskName('toToonTrack')
+ self._deleteTransitionTrack()
+ if localToonIsVictor:
+ freedomTrack1 = Func(
+ self.cr.playGame.getPlace().setState,
+ "walk")
+ freedomTrack2 = Func(
+ base.localAvatar.d_setParent,
+ ToontownGlobals.SPRender)
+
+ self.transitionTrack = Parallel(camTrack,
+ Sequence(victoryRunTrack,
+ bldgMTrack,
+ freedomTrack1,
+ freedomTrack2,
+ ),
+ name = trackName)
+ else:
+ self.transitionTrack = Sequence(victoryRunTrack,
+ bldgMTrack,
+ name = trackName
+ )
+
+ self.transitionTrack.delayDeletes = delayDeletes
+
+ if localToonIsVictor:
+ self.transitionTrack.start(0)
+ else:
+ self.transitionTrack.start(timeStamp)
+
+ return
+
+ def animToToonFromCogdo(self, timeStamp):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: create the multitrack that contains the animation
+ // sequence to transition this building from a cogdo to
+ // toon building
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # TODO: Incorporate the toons coming out of the elevator into
+ # this multitrack. There will also need to be something like
+ # exitCompleted...
+ self.stopTransition()
+ if self.mode != 'cogdo':
+ self.setToCogdo()
+
+ self.loadAnimToToonSfx()
+
+ # create intervals to position and/or hide/stash the building parts
+ # depending if it is part of the toon or suit version
+ #
+ suitSoundPlayed = 0 # don't want to play sound for every part
+ toonSoundPlayed = 0 # don't want to play sound for every part
+ bldgNodes = self.getNodePaths()
+ tracks = Parallel()
+ for i in bldgNodes:
+ i.clearColorScale()
+ name=i.getName()
+ if (name[0]=='s'):
+ hideTrack = Sequence(
+ name = self.taskName('ToToonCogdoFlatsTrack'))
+ # have the suit building scale away
+ #
+ #origPos = i.getPos()
+ #print "sidebldgpos: %s" % str(origPos)
+ #tgtPos = Point3(
+ # Point3(0,0,-self.SUIT_INIT_HEIGHT) + \
+ # origPos)
+ # shake the suit building on the x and y axis as it goes down
+ #
+ landmark = name.find("_landmark_") != -1
+ #hideTrack.append(LerpPosInterval(
+ # i, TO_TOON_BLDG_TIME,
+ # tgtPos, name = self.taskName('ToToonAnim')))
+ #hideTrack.append(LerpFunctionInterval(
+ # self.shakePart,
+ # extraArgs=[i,landmark,
+ # origPos.getX(),origPos.getY()]))
+ #print 'anim2toon: suit flat scale: %s' % repr(i.getScale())
+ if not suitSoundPlayed:
+ hideTrack.append(Func(
+ base.playSfx, self.cogWeakenSound, 0, 1, None, 0.))
+ hideTrack.append(self.createBounceTrack(
+ i, 3, 1.2, TO_TOON_BLDG_TIME*0.05,
+ slowInitBounce=0.0))
+ hideTrack.append(self.createBounceTrack(
+ i, 5, 0.8, TO_TOON_BLDG_TIME*0.10,
+ slowInitBounce=0.0))
+ hideTrack.append(self.createBounceTrack(
+ i, 7, 1.2, TO_TOON_BLDG_TIME*0.17,
+ slowInitBounce=0.0))
+ hideTrack.append(self.createBounceTrack(
+ i, 9, 1.2, TO_TOON_BLDG_TIME*0.18,
+ slowInitBounce=0.0))
+ realScale = i.getScale()
+ hideTrack.append(LerpScaleInterval(
+ i, TO_TOON_BLDG_TIME*0.10,
+ Vec3(realScale[0], realScale[1], 0.01)))
+ if landmark:
+ # the landmark portion is recreated each time a suit
+ # building is generated, so we can just completely remove
+ # the node
+ #
+ hideTrack.append(Func(i.removeNode))
+ else:
+ # make sure to relocate the suit building part to its
+ # original position above the ground so when it is shown
+ # again it it in its original, correct location (or set
+ # the scale to 1, depending on how the toToon transition
+ # is implemented)
+ #
+ hideTrack.append(Func(i.stash))
+ #hideTrack.append(FunctionInterval(
+ # i.setPos, extraArgs = [origPos]))
+ hideTrack.append(Func(i.setScale, Vec3(1)))
+ if not suitSoundPlayed:
+ suitSoundPlayed = 1
+ tracks.append(hideTrack)
+ elif (name[0]=='t'):
+ hideTrack = Sequence(
+ name = self.taskName('ToToonFromCogdoFlatsTrack'))
+ # show the toon portion of the building and set up the
+ # transparency transition so we can slowly fade it in
+ #
+ hideTrack.append(Wait(TO_TOON_BLDG_TIME*0.5))
+ if not toonSoundPlayed:
+ hideTrack.append(Func(
+ base.playSfx, self.toonGrowSound, 0, 1, None, 0.))
+ hideTrack.append(Func(i.unstash))
+ hideTrack.append(Func(i.setScale, Vec3(1,1,0.01)))
+ #hideTrack.append(Func(i.setTransparency, 1))
+ # lerp the alpha in for the building part, making sure to
+ # remove the transparency transition when the fade is done
+ #
+ #hideTrack.append(LerpFunctionInterval(
+ # i.setAlphaScale, fromData=0.15, toData=1,
+ # duration=TO_TOON_BLDG_TIME*0.25))
+ #hideTrack.append(Func(i.clearTransparency))
+ if not toonSoundPlayed:
+ hideTrack.append(Func(
+ base.playSfx, self.toonSettleSound, 0, 1, None, 0.))
+ hideTrack.append(self.createBounceTrack(
+ i, 11, 1.2, TO_TOON_BLDG_TIME*0.5,
+ slowInitBounce=4.0))
+ tracks.append(hideTrack)
+ if not toonSoundPlayed:
+ toonSoundPlayed = 1
+
+ # bundle up all of our tracks for the entire transition and start
+ # playing
+ #
+ self.stopTransition()
+ #self.transitionTrack = Parallel(tracks, self.taskName(
+ # 'ToToonMTrack'))
+ bldgMTrack = tracks
+
+ #print "transitionTrack: %s" % self.transitionTrack
+ #print "starting track at %s" % globalClock.getFrameTime()
+
+ # TODO: integrate the toons running out of the building into
+ # the multitrack. For now, Just plant them outside the elevator.
+ localToonIsVictor = self.localToonIsVictor()
+
+ if localToonIsVictor:
+ camTrack = self.walkOutCameraTrack()
+
+ victoryRunTrack, delayDeletes = self.getVictoryRunTrack()
+
+ trackName = self.taskName('toToonFromCogdoTrack')
+ self._deleteTransitionTrack()
+ if localToonIsVictor:
+ freedomTrack1 = Func(
+ self.cr.playGame.getPlace().setState,
+ "walk")
+ freedomTrack2 = Func(
+ base.localAvatar.d_setParent,
+ ToontownGlobals.SPRender)
+
+ self.transitionTrack = Parallel(camTrack,
+ Sequence(victoryRunTrack,
+ bldgMTrack,
+ freedomTrack1,
+ freedomTrack2,
+ ),
+ name = trackName)
+ else:
+ self.transitionTrack = Sequence(victoryRunTrack,
+ bldgMTrack,
+ name = trackName
+ )
+
+ self.transitionTrack.delayDeletes = delayDeletes
+
+ if localToonIsVictor:
+ self.transitionTrack.start(0)
+ else:
+ self.transitionTrack.start(timeStamp)
+
+ return
+
+ def walkOutCameraTrack(self):
+ track = Sequence(
+ # Put the camera under render
+ Func(camera.reparentTo, render),
+ # Watch the toons come out of the door
+ Func(camera.setPosHpr,
+ self.elevatorNodePath,
+ 0, -32.5, 9.4, 0, 348, 0),
+ Func(base.camLens.setFov, 52.0),
+ Wait(VICTORY_RUN_TIME),
+ # Watch the building transform
+ Func(camera.setPosHpr,
+ self.elevatorNodePath,
+ 0, -32.5, 17, 0, 347, 0),
+ Func(base.camLens.setFov, 75.0),
+ Wait(TO_TOON_BLDG_TIME),
+ # Put the camera fov back to normal
+ Func(base.camLens.setFov, 52.0),
+ )
+ return track
+
+ def plantVictorsOutsideBldg(self):
+ #print "planting Victors %s !" % self.victorList
+ retVal = 0
+ for victor in self.victorList:
+ if victor != 0 and self.cr.doId2do.has_key(victor):
+ toon = self.cr.doId2do[victor]
+ toon.setPosHpr(self.elevatorModel, 0, -10, 0, 0, 0, 0)
+ toon.startSmooth()
+ if victor == base.localAvatar.getDoId():
+ retVal = 1
+ self.cr.playGame.getPlace().setState('walk')
+ return retVal
+
+ def getVictoryRunTrack(self):
+ # Put each toon in the elevator
+ origPosTrack = Sequence()
+ delayDeletes = []
+ i = 0
+ for victor in self.victorList:
+ if victor != 0 and self.cr.doId2do.has_key(victor):
+ toon = self.cr.doId2do[victor]
+ delayDeletes.append(DelayDelete.DelayDelete(toon, 'getVictoryRunTrack'))
+ toon.stopSmooth()
+ toon.setParent(ToontownGlobals.SPHidden)
+ origPosTrack.append(Func(toon.setPosHpr,
+ self.elevatorNodePath,
+ apply(Point3, ElevatorPoints[i]),
+ Point3(180, 0, 0)))
+ origPosTrack.append(Func(toon.setParent,
+ ToontownGlobals.SPRender))
+ i += 1
+
+ # Open the elevator doors
+ openDoors = getOpenInterval(self, self.leftDoor, self.rightDoor,
+ self.openSfx, None)
+
+ # Run the toons out of the elevator
+ runOutAll = Parallel()
+ i = 0
+ for victor in self.victorList:
+ if victor != 0 and self.cr.doId2do.has_key(victor):
+ toon = self.cr.doId2do[victor]
+ p0 = Point3(0, 0, 0)
+ p1 = Point3(ElevatorPoints[i][0],
+ ElevatorPoints[i][1] - 5.0,
+ ElevatorPoints[i][2])
+ p2 = Point3(ElevatorOutPoints[i][0],
+ ElevatorOutPoints[i][1],
+ ElevatorOutPoints[i][2])
+
+ runOutSingle = Sequence(
+ # Disallow body emotes so we don't slide
+ Func(Emote.globalEmote.disableBody, toon, "getVictory"),
+ # Start the run animation
+ Func(toon.animFSM.request, "run"),
+ # Move the toon out of the elevator
+ LerpPosInterval(toon, TOON_VICTORY_EXIT_TIME * 0.25,
+ p1, other=self.elevatorNodePath),
+ # Run him from there to his observation point
+ Func(toon.headsUp, self.elevatorNodePath, p2),
+ LerpPosInterval(toon, TOON_VICTORY_EXIT_TIME * 0.50,
+ p2, other=self.elevatorNodePath),
+ # Turn the toon around to face the building
+ LerpHprInterval(toon, TOON_VICTORY_EXIT_TIME * 0.25,
+ Point3(0, 0, 0),
+ other=self.elevatorNodePath),
+ # Stop the toon from running
+ Func(toon.animFSM.request, "neutral"),
+ # Free the toon up to walk around on his own again
+ Func(toon.startSmooth),
+ Func(Emote.globalEmote.releaseBody, toon, "getVictory"),
+ )
+ runOutAll.append(runOutSingle)
+ i += 1
+
+ victoryRunTrack = Sequence(origPosTrack,
+ openDoors,
+ runOutAll,
+ )
+
+ return (victoryRunTrack, delayDeletes)
+
+ def localToonIsVictor(self):
+ retVal = 0
+ for victor in self.victorList:
+ if victor == base.localAvatar.getDoId():
+ retVal = 1
+ return retVal
+
+ def createBounceTrack(self, nodeObj, numBounces, startScale,
+ totalTime, slowInitBounce=0.0):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function:
+ // Parameters:
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ if not nodeObj or numBounces < 1 or \
+ startScale == 0.0 or totalTime == 0:
+ self.notify.warning(
+ "createBounceTrack called with invalid parameter")
+ return
+
+ # add an extra bounce to make sure the object
+ # is properly scaled to 1 on the last lerpScaleInterval
+ #
+ result = Sequence()
+ numBounces+=1
+
+ # calculate how long, in seconds, each bounce should last, make
+ # the time of each bounce smaller if we want to extend the length
+ # the initial bounce
+ #
+ if slowInitBounce:
+ bounceTime = totalTime/(numBounces + slowInitBounce - 1.0)
+ else:
+ bounceTime = totalTime/float(numBounces)
+
+ # if specified, the first bounce lasts the length of 3 bounces,
+ # useful for when initially appearing, the first bounce of the
+ # object is more pronounced than the others
+ #
+ if slowInitBounce:
+ currTime = bounceTime * float(slowInitBounce)
+ else:
+ currTime = bounceTime
+
+ # determine the how much of a change in scale the first bounce
+ # will produce based on the node's base scale (current scale)
+ # and the given start scale
+ #
+ realScale = nodeObj.getScale()
+ currScaleDiff = startScale - realScale[2]
+
+ # create a lerpScaleInterval for each bounce, making sure to
+ # figure out the new scale, which progressively gets closer
+ # to our base scale
+ #
+ for currBounceScale in range(numBounces):
+ # determine the direction that this scale should go,
+ # alternating for each lerpScaleInterval to simulate
+ # a spring effect
+ #
+ if currBounceScale == numBounces-1:
+ currScale = realScale[2]
+ elif currBounceScale%2:
+ currScale = realScale[2] - currScaleDiff
+ else:
+ currScale = realScale[2] + currScaleDiff
+ result.append(LerpScaleInterval(
+ nodeObj, currTime,
+ Vec3(realScale[0], realScale[1], currScale),
+ blendType='easeInOut'))
+
+ # the scale diff from the base gets smaller for each
+ # consecutive bounce, and make sure to update for
+ # possibly a new amount of time the next bounce will
+ # take
+ #
+ currScaleDiff *= 0.5
+ currTime = bounceTime
+
+ return result
+
+ def stopTransition(self):
+ if self.transitionTrack:
+ self.transitionTrack.finish()
+ self._deleteTransitionTrack()
+
+ def setToSuit(self):
+ assert(self.debugPrint("setToSuit()"))
+ self.stopTransition()
+ if self.mode == 'suit':
+ return
+ self.mode = 'suit'
+
+ nodes=self.getNodePaths()
+ for i in nodes:
+ name=i.getName()
+ if (name[0]=='s'):
+ if (name.find("_landmark_") != -1):
+ # an old suit landmark instance.
+ i.removeNode()
+ else:
+ # Suit flat buildings:
+ # i.show()
+ i.unstash()
+ elif (name[0]=='t'):
+ if (name.find("_landmark_") != -1):
+ # Toon landmark buildings:
+ i.stash()
+ else:
+ # Toon flat buildings:
+ # i.hide()
+ i.stash()
+
+ # Copy the suit landmark building, based on the suit track and
+ # difficulty:
+ npc=hidden.findAllMatches(self.getSbSearchString())
+
+ assert(npc.getNumPaths()>0)
+ for i in range(npc.getNumPaths()):
+ nodePath=npc.getPath(i)
+ self.adjustSbNodepathScale(nodePath)
+ self.notify.debug("net transform = %s" % str(nodePath.getNetTransform()))
+ self.setupSuitBuilding(nodePath)
+
+ def setToCogdo(self):
+ assert(self.debugPrint("setToCogdo()"))
+ self.stopTransition()
+ if self.mode == 'cogdo':
+ return
+ self.mode = 'cogdo'
+
+ nodes=self.getNodePaths()
+ for i in nodes:
+ name=i.getName()
+ if (name[0]=='s'):
+ if (name.find("_landmark_") != -1):
+ # an old suit landmark instance.
+ i.removeNode()
+ else:
+ # Suit flat buildings:
+ # i.show()
+ i.unstash()
+ elif (name[0]=='t'):
+ if (name.find("_landmark_") != -1):
+ # Toon landmark buildings:
+ i.stash()
+ else:
+ # Toon flat buildings:
+ # i.hide()
+ i.stash()
+
+ for np in nodes:
+ if not np.isEmpty():
+ np.setColorScale(.6,.6,.6,1.)
+
+ # Copy the suit landmark building, based on the suit track and
+ # difficulty:
+ npc=hidden.findAllMatches(self.getSbSearchString())
+
+ assert(npc.getNumPaths()>0)
+ for i in range(npc.getNumPaths()):
+ nodePath=npc.getPath(i)
+ self.adjustSbNodepathScale(nodePath)
+ self.notify.debug("net transform = %s" % str(nodePath.getNetTransform()))
+ self.setupCogdo(nodePath)
+
+ def setToToon(self):
+ assert(self.debugPrint("setToToon() mode=%s" % (self.mode)))
+ self.stopTransition()
+ if self.mode == 'toon':
+ return
+ self.mode = 'toon'
+
+ # Clear reference to the suit door.
+ self.suitDoorOrigin = None
+ # Go through nodes, and do the right thing.
+ nodes=self.getNodePaths()
+ for i in nodes:
+ i.clearColorScale()
+ name=i.getName()
+ if (name[0]=='s'):
+ if (name.find("_landmark_") != -1):
+ i.removeNode()
+ else:
+ # Suit flat buildings:
+ # i.hide()
+ i.stash()
+ elif (name[0]=='t'):
+ if (name.find("_landmark_") != -1):
+ # Toon landmark buildings:
+ i.unstash()
+ else:
+ # Toon flat buildings:
+ # i.show()
+ i.unstash()
+
+ def normalizeElevator(self):
+ # Normalize the size of the elevator
+ # The suit building probably has a funny scale on it,
+ # but by doing this, we normalize the scale on the elevator.
+ self.elevatorNodePath.setScale(render, Vec3(1, 1, 1))
+ self.elevatorNodePath.setPosHpr(0, 0, 0, 0, 0, 0)
+ return
+
+ if __debug__:
+ def debugPrint(self, message):
+ """for debugging"""
+ return self.notify.debug(
+ str(self.__dict__.get('block', '?'))+' '+message)
+
+ def getSbSearchString(self):
+ """Return a string to use when looking for the suit building nodepath."""
+ result = "landmarkBlocks/sb" + str(self.block) + \
+ ":*_landmark_*_DNARoot"
+ return result
+
+ def adjustSbNodepathScale(self, nodePath):
+ """Animated buildings needs a scale hack, this does nothing for reg bldg."""
+ pass
+
+ def getVisZoneId(self):
+ """Retur our visibible Zone Id."""
+ # this computation is taken from DistributedBuildingMgrAI
+ exteriorZoneId = base.cr.playGame.hood.dnaStore.getZoneFromBlockNumber(self.block)
+ visZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.zoneId)
+ return visZoneId
+
+ def getInteractiveProp(self):
+ """Get our associated interactive prop, if any."""
+ result = None
+ if self.interactiveProp:
+ result = self.interactiveProp
+ else:
+ visZoneId = self.getVisZoneId()
+ if base.cr.playGame.hood:
+ loader = base.cr.playGame.hood.loader
+ if hasattr(loader,"getInteractiveProp"):
+ self.interactiveProp = loader.getInteractiveProp(visZoneId)
+ result = self.interactiveProp
+ self.notify.debug("self.interactiveProp = %s" % self.interactiveProp)
+ else:
+ self.notify.warning("no loader.getInteractiveProp self.interactiveProp is None")
+ else:
+ self.notify.warning("no hood self.interactiveProp is None")
+ return result
+
+
+ def makePropSad(self):
+ """Make an interactive prop near us be sad when we're a cog building."""
+ self.notify.debug("makePropSad")
+ if self.getInteractiveProp():
+ if self.getInteractiveProp().state == "Sad":
+ #import pdb; pdb.set_trace()
+ pass
+ self.getInteractiveProp().gotoSad(self.doId)
diff --git a/toontown/src/building/DistributedBuildingAI.py b/toontown/src/building/DistributedBuildingAI.py
new file mode 100644
index 0000000..3bf3c63
--- /dev/null
+++ b/toontown/src/building/DistributedBuildingAI.py
@@ -0,0 +1,914 @@
+""" DistributedBuildingAI module: contains the DistributedBuildingAI
+ class, the server side representation of a 'building'."""
+
+
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+
+import types
+from direct.task.Task import Task
+from direct.directnotify import DirectNotifyGlobal
+from direct.distributed import DistributedObjectAI
+from direct.fsm import State
+from direct.fsm import ClassicFSM, State
+from toontown.toonbase.ToontownGlobals import ToonHall
+import DistributedToonInteriorAI
+import DistributedToonHallInteriorAI
+import DistributedSuitInteriorAI
+import DistributedDoorAI
+import DoorTypes
+import DistributedElevatorExtAI
+import DistributedKnockKnockDoorAI
+import SuitPlannerInteriorAI
+import SuitBuildingGlobals
+import FADoorCodes
+from toontown.hood import ZoneUtil
+import random
+import time
+from toontown.cogdominium.DistributedCogdoInteriorAI import DistributedCogdoInteriorAI
+from toontown.cogdominium.SuitPlannerCogdoInteriorAI import SuitPlannerCogdoInteriorAI
+from toontown.cogdominium.CogdoLayout import CogdoLayout
+from toontown.cogdominium.DistributedCogdoElevatorExtAI import DistributedCogdoElevatorExtAI
+
+class DistributedBuildingAI(DistributedObjectAI.DistributedObjectAI):
+ """
+ DistributedBuildingAI class: The server side representation of a
+ single building. This is the object that remember who 'owns' the
+ associated building (either the bad guys or the toons). The child
+ of this object, the DistributedBuilding object, is the client side
+ version and updates the display that client's display based on who
+ 'owns' the building.
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBuildingAI')
+
+ def __init__(self, air, blockNumber, zoneId, trophyMgr):
+ """blockNumber: the landmark building number (from the name)"""
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.block = blockNumber
+ assert(self.debugPrint("DistributedBuildingAI(%s, %s)" % ("the air", str(blockNumber))))
+ self.zoneId = zoneId
+ self.canonicalZoneId = ZoneUtil.getCanonicalZoneId(zoneId)
+ self.trophyMgr = trophyMgr
+ self.victorResponses = None
+ self.fsm = ClassicFSM.ClassicFSM('DistributedBuildingAI',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['waitForVictors',
+ 'becomingToon',
+ 'toon',
+ 'clearOutToonInterior',
+ 'becomingSuit',
+ 'suit',
+ 'clearOutToonInteriorForCogdo',
+ 'becomingCogdo',
+ 'cogdo',
+ ]),
+ State.State('waitForVictors',
+ self.enterWaitForVictors,
+ self.exitWaitForVictors,
+ ['becomingToon',
+ ]),
+ State.State('waitForVictorsFromCogdo',
+ self.enterWaitForVictorsFromCogdo,
+ self.exitWaitForVictorsFromCogdo,
+ ['becomingToonFromCogdo',
+ ]),
+ State.State('becomingToon',
+ self.enterBecomingToon,
+ self.exitBecomingToon,
+ ['toon']),
+ State.State('becomingToonFromCogdo',
+ self.enterBecomingToonFromCogdo,
+ self.exitBecomingToonFromCogdo,
+ ['toon']),
+ State.State('toon',
+ self.enterToon,
+ self.exitToon,
+ ['clearOutToonInterior', 'clearOutToonInteriorForCogdo']),
+ State.State('clearOutToonInterior',
+ self.enterClearOutToonInterior,
+ self.exitClearOutToonInterior,
+ ['becomingSuit']),
+ State.State('becomingSuit',
+ self.enterBecomingSuit,
+ self.exitBecomingSuit,
+ ['suit']),
+ State.State('suit',
+ self.enterSuit,
+ self.exitSuit,
+ ['waitForVictors',
+ 'becomingToon', # debug only
+ ]),
+ State.State('clearOutToonInteriorForCogdo',
+ self.enterClearOutToonInteriorForCogdo,
+ self.exitClearOutToonInteriorForCogdo,
+ ['becomingCogdo']),
+ State.State('becomingCogdo',
+ self.enterBecomingCogdo,
+ self.exitBecomingCogdo,
+ ['cogdo']),
+ State.State('cogdo',
+ self.enterCogdo,
+ self.exitCogdo,
+ ['waitForVictorsFromCogdo',
+ 'becomingToonFromCogdo', # debug only
+ ])],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ self.track='c'
+ self.difficulty=1
+ self.numFloors=0
+ self.savedBy=None
+ self.becameSuitTime=0
+ self.frontDoorPoint=None
+ self.suitPlannerExt=None
+
+ def cleanup(self):
+ if self.isDeleted():
+ return
+
+ self.fsm.requestFinalState()
+
+ if hasattr(self, "interior"):
+ self.interior.requestDelete()
+ del self.interior
+
+ if hasattr(self, "door"):
+ self.door.requestDelete()
+ del self.door
+ self.insideDoor.requestDelete()
+ del self.insideDoor
+ self.knockKnock.requestDelete()
+ del self.knockKnock
+
+ if hasattr(self, "elevator"):
+ self.elevator.requestDelete()
+ del self.elevator
+
+ self.requestDelete()
+
+
+ def delete( self ):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: clean up tasks that might still be running for this
+ // building
+ // Parameters:
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # make sure to remove any tasks we might have created
+ taskMgr.remove(self.taskName('suitbldg-time-out'))
+
+ # remove the doLater associated with the state transition of
+ # a suit building becoming a toon building
+ taskMgr.remove(self.taskName(str(self.block) + '_becomingToon-timer'))
+
+ # remove the doLater associated with the state transition of
+ # a toon building becoming a suit building
+ taskMgr.remove(self.taskName(str(self.block) + '_becomingSuit-timer'))
+
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ del self.fsm
+
+ def getPickleData(self):
+ assert(self.debugPrint("getPickleData()"))
+ pickleData={
+ 'state': str(self.fsm.getCurrentState().getName()),
+ 'block': str(self.block),
+ 'track': str(self.track),
+ 'difficulty': str(self.difficulty),
+ 'numFloors': str(self.numFloors),
+ 'savedBy': self.savedBy,
+ 'becameSuitTime' : self.becameSuitTime,
+ }
+ return pickleData
+
+ def _getMinMaxFloors(self, difficulty):
+ return SuitBuildingGlobals.SuitBuildingInfo[difficulty][0]
+
+ def suitTakeOver(self, suitTrack, difficulty, buildingHeight):
+ """Switch from toon to suit building
+ suitTrack: one of 'c', 'l', 'm', or 's'
+ difficulty: 0+
+ buildingHeight: 0..4, or None to choose based on the difficulty.
+ """
+ if not self.isToonBlock():
+ return
+ assert(suitTrack in ['c', 'l', 'm', 's'])
+
+ # Remove the old saved by credit with the old number of floors
+ self.updateSavedBy(None)
+
+ difficulty = min(difficulty, len(SuitBuildingGlobals.SuitBuildingInfo) - 1)
+ minFloors, maxFloors = self._getMinMaxFloors(difficulty)
+ if buildingHeight == None:
+ # Pick a random floor number from the appropriate range.
+ numFloors = random.randint(minFloors, maxFloors)
+ else:
+ # The number of floors is specified.
+ numFloors = buildingHeight + 1
+
+ if (numFloors < minFloors or numFloors > maxFloors):
+ # Hmm, the number of floors is out of range for this
+ # suit. There must be an invasion in effect. In that
+ # case, go ahead and make a building of any height
+ # appropriate to the suit.
+ numFloors = random.randint(minFloors, maxFloors)
+
+ assert(self.debugPrint("suitTakeOver(%s, %s, %s)" % (suitTrack, difficulty, numFloors - 1)))
+
+ self.track=suitTrack
+ self.difficulty=difficulty
+ self.numFloors=numFloors
+ self.becameSuitTime = time.time()
+ self.fsm.request('clearOutToonInterior')
+
+ def cogdoTakeOver(self, difficulty, buildingHeight):
+ if not self.isToonBlock():
+ return
+
+ # Remove the old saved by credit with the old number of floors
+ self.updateSavedBy(None)
+
+ minFloors, maxFloors = self._getMinMaxFloors(difficulty)
+ if buildingHeight == None:
+ # Pick a random floor number from the appropriate range.
+ numFloors = random.randint(minFloors, maxFloors)
+ else:
+ # The number of floors is specified.
+ numFloors = buildingHeight + 1
+
+ if (numFloors < minFloors or numFloors > maxFloors):
+ # Hmm, the number of floors is out of range for this
+ # suit. There must be an invasion in effect. In that
+ # case, go ahead and make a building of any height
+ # appropriate to the suit.
+ numFloors = random.randint(minFloors, maxFloors)
+
+ assert(self.debugPrint("cogdoTakeOver(%s, %s)" % (difficulty, numFloors - 1)))
+
+ self.track='c'
+ self.difficulty=difficulty
+ self.numFloors=numFloors
+ self.becameSuitTime = time.time()
+ self.fsm.request('clearOutToonInteriorForCogdo')
+
+ def toonTakeOver(self):
+ """Switch from suit to toon building
+ savedBy: a list of 1 to 4 avatar [name, style] lists."""
+ assert(self.debugPrint("toonTakeOver(savedBy=%s)"%(self.savedBy)))
+ if 'cogdo' in self.fsm.getCurrentState().getName().lower():
+ self.fsm.request('becomingToonFromCogdo')
+ else:
+ self.fsm.request('becomingToon')
+ if self.suitPlannerExt:
+ self.suitPlannerExt.recycleBuilding()
+ if hasattr(self, "interior"):
+ self.interior.requestDelete()
+ del self.interior
+
+ def getFrontDoorPoint(self):
+ """get any associated path point for this building, useful for
+ suits to know where to go when exiting from a building"""
+ assert(self.debugPrint("getFrontDoorPoint()"))
+ return self.frontDoorPoint
+
+ def setFrontDoorPoint(self, point):
+ """set the associated front door point with this building"""
+ assert(self.debugPrint("setFrontDoorPoint(%s)" % (str(point))))
+ self.frontDoorPoint = point
+
+ def getBlock(self):
+ assert(self.debugPrint("getBlock()"))
+ dummy, interiorZoneId = self.getExteriorAndInteriorZoneId()
+ return [self.block, interiorZoneId]
+
+ def getSuitData(self):
+ assert(self.debugPrint("getSuitData()"))
+ return [ord(self.track), self.difficulty, self.numFloors]
+
+ def getState(self):
+ assert(self.debugPrint("getState()"))
+ return [self.fsm.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+
+ def setState(self, state, timestamp=0):
+ assert(self.notify.debug(str(self.block)+" setState(state="+str(state)+")"))
+ self.fsm.request(state)
+
+ def isSuitBuilding(self):
+ """return true if that block is a suit building"""
+ assert(self.debugPrint("isSuitBlock()"))
+ state=self.fsm.getCurrentState().getName()
+ return state=='suit' or state=='becomingSuit' or \
+ state=='clearOutToonInterior'
+
+ def isCogdo(self):
+ """return true if that block is a cogdo"""
+ assert(self.debugPrint("isSuitBlock()"))
+ state=self.fsm.getCurrentState().getName()
+ return state=='cogdo' or state=='becomingCogdo' or \
+ state=='clearOutToonInteriorForCogdo'
+
+ def isSuitBlock(self):
+ """return true if that block is a suit block/building/cogdo"""
+ assert(self.debugPrint("isSuitBlock()"))
+ state=self.fsm.getCurrentState().getName()
+ return self.isSuitBuilding() or self.isCogdo()
+
+ def isEstablishedSuitBlock(self):
+ """return true if that block is a fully established suit building"""
+ assert(self.debugPrint("isEstablishedSuitBlock()"))
+ state=self.fsm.getCurrentState().getName()
+ return state=='suit'
+
+ def isToonBlock(self):
+ """return true if that block is a toon block/building"""
+ assert(self.debugPrint("isToonBlock()"))
+ state=self.fsm.getCurrentState().getName()
+ return state in ('toon', 'becomingToon', 'becomingToonFromCogdo', )
+
+ def getExteriorAndInteriorZoneId(self):
+ assert(self.notify.debug(str(self.block)+" getInteriorZoneId()"))
+ blockNumber = self.block
+ assert(blockNumber<100) # this may cause trouble for the interiorZoneId,
+ # it may bump into the next higher zone range.
+ dnaStore = self.air.dnaStoreMap[self.canonicalZoneId]
+ zoneId = dnaStore.getZoneFromBlockNumber(blockNumber)
+ zoneId = ZoneUtil.getTrueZoneId(zoneId, self.zoneId)
+ interiorZoneId = (zoneId - zoneId % 100) + 500 + blockNumber
+ assert(self.notify.debug(str(self.block)+" getInteriorZoneId() returning"
+ +str(interiorZoneId)))
+ return zoneId, interiorZoneId
+
+ def d_setState(self, state):
+ assert(self.notify.debug(str(self.block)+" d_setState(state="+str(state)+")"))
+ self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()])
+
+ def b_setVictorList(self, victorList):
+ self.setVictorList(victorList)
+ self.d_setVictorList(victorList)
+ return
+
+ def d_setVictorList(self, victorList):
+ self.sendUpdate("setVictorList", [victorList])
+ return
+
+ def setVictorList(self, victorList):
+ self.victorList = victorList
+ return
+
+ def findVictorIndex(self, avId):
+ for i in range(len(self.victorList)):
+ if self.victorList[i] == avId:
+ return i
+ return None
+
+ def recordVictorResponse(self, avId):
+ index = self.findVictorIndex(avId)
+ if index == None:
+ self.air.writeServerEvent('suspicious', avId, 'DistributedBuildingAI.setVictorReady from toon not in %s.' % (self.victorList))
+ return
+
+ assert(self.victorResponses[index] == 0 or self.victorResponses[index] == avId)
+ self.victorResponses[index] = avId
+
+ def allVictorsResponded(self):
+ if self.victorResponses == self.victorList:
+ return 1
+ else:
+ return 0
+
+ def setVictorReady(self):
+ avId = self.air.getAvatarIdFromSender()
+ if self.victorResponses == None:
+ self.air.writeServerEvent('suspicious', avId, 'DistributedBuildingAI.setVictorReady in state %s.' % (self.fsm.getCurrentState().getName()))
+ return
+
+ assert(self.notify.debug("victor %d is ready for bldg %d" % (avId, self.doId)))
+ self.recordVictorResponse(avId)
+
+ # Don't tell us about this avatar exiting any more.
+ event = self.air.getAvatarExitEvent(avId)
+ self.ignore(event)
+
+ if self.allVictorsResponded():
+ self.toonTakeOver()
+
+ def setVictorExited(self, avId):
+ print "victor %d exited unexpectedly for bldg %d" % (avId, self.doId)
+ self.recordVictorResponse(avId)
+ if self.allVictorsResponded():
+ self.toonTakeOver()
+
+ ##### off state #####
+
+ def enterOff(self):
+ assert(self.debugPrint("enterOff()"))
+
+ def exitOff(self):
+ assert(self.debugPrint("exitOff()"))
+
+ ##### waitForVictors state #####
+
+ def getToon(self, toonId):
+ if (self.air.doId2do.has_key(toonId)):
+ return self.air.doId2do[toonId]
+ else:
+ self.notify.warning('getToon() - toon: %d not in repository!' \
+ % toonId)
+ return None
+
+ def updateSavedBy(self, savedBy):
+ # Clear the old savedBy from the trophy manager
+ if self.savedBy:
+ for avId, name, dna in self.savedBy:
+ # Don't change building take over score when the toon is in the welcome valley.
+ if not ZoneUtil.isWelcomeValley(self.zoneId):
+ self.trophyMgr.removeTrophy(avId, self.numFloors)
+ # Update the new saved by list
+ self.savedBy = savedBy
+ if self.savedBy:
+ for avId, name, dna in self.savedBy:
+ # Don't change building take over score when the toon is in the welcome valley.
+ if not ZoneUtil.isWelcomeValley(self.zoneId):
+ self.trophyMgr.addTrophy(avId, name, self.numFloors)
+
+ def enterWaitForVictors(self, victorList, savedBy):
+ assert(len(victorList) == 4)
+
+ # Grab the list of active toons to pass in for each toon
+ # (this is used by the quest system)
+ activeToons = []
+ for t in victorList:
+ toon = None
+ if (t):
+ toon = self.getToon(t)
+ if (toon != None):
+ activeToons.append(toon)
+ # Tell the quest manager that these toons defeated this building
+ for t in victorList:
+ toon = None
+ if t:
+ toon = self.getToon(t)
+ self.air.writeServerEvent(
+ 'buildingDefeated', t, "%s|%s|%s|%s" % (self.track, self.numFloors, self.zoneId, victorList))
+
+ if toon != None:
+ self.air.questManager.toonKilledBuilding(
+ toon, self.track, self.difficulty,
+ self.numFloors, self.zoneId, activeToons)
+
+ # Convert the list to all ints. 0 means no one is there.
+ # Also, if a toon has disconnected, remove him from the list.
+ for i in range(0, 4):
+ victor = victorList[i]
+ if victor == None or not self.air.doId2do.has_key(victor):
+ victorList[i] = 0
+
+ else:
+ # Handle unexpected exit messages for everyone else.
+ event = self.air.getAvatarExitEvent(victor)
+ self.accept(event, self.setVictorExited, extraArgs=[victor])
+
+ # Save the list and also tell it to all the clients.
+ self.b_setVictorList(victorList)
+ self.updateSavedBy(savedBy)
+ # List of victor responses
+ self.victorResponses = [0, 0, 0, 0]
+ # Tell the client to go into waitForVictors state
+ self.d_setState("waitForVictors")
+ return
+
+ def exitWaitForVictors(self):
+ # Stop waiting for unexpected exits.
+ self.victorResponses = None
+ for victor in self.victorList:
+ event = simbase.air.getAvatarExitEvent(victor)
+ self.ignore(event)
+ return
+
+ def enterWaitForVictorsFromCogdo(self, victorList, savedBy):
+ assert(len(victorList) == 4)
+
+ # Grab the list of active toons to pass in for each toon
+ # (this is used by the quest system)
+ activeToons = []
+ for t in victorList:
+ toon = None
+ if (t):
+ toon = self.getToon(t)
+ if (toon != None):
+ activeToons.append(toon)
+ # Tell the quest manager that these toons defeated this building
+ for t in victorList:
+ toon = None
+ if t:
+ toon = self.getToon(t)
+ self.air.writeServerEvent(
+ 'buildingDefeated', t, "%s|%s|%s|%s" % (self.track, self.numFloors, self.zoneId, victorList))
+
+ if toon != None:
+ self.air.questManager.toonKilledCogdo(
+ toon, self.difficulty,
+ self.numFloors, self.zoneId, activeToons)
+
+ # Convert the list to all ints. 0 means no one is there.
+ # Also, if a toon has disconnected, remove him from the list.
+ for i in range(0, 4):
+ victor = victorList[i]
+ if victor == None or not self.air.doId2do.has_key(victor):
+ victorList[i] = 0
+
+ else:
+ # Handle unexpected exit messages for everyone else.
+ event = self.air.getAvatarExitEvent(victor)
+ self.accept(event, self.setVictorExited, extraArgs=[victor])
+
+ # Save the list and also tell it to all the clients.
+ self.b_setVictorList(victorList)
+ self.updateSavedBy(savedBy)
+ # List of victor responses
+ self.victorResponses = [0, 0, 0, 0]
+ # Tell the client to go into waitForVictors state
+ self.d_setState("waitForVictorsFromCogdo")
+ return
+
+ def exitWaitForVictorsFromCogdo(self):
+ # Stop waiting for unexpected exits.
+ self.victorResponses = None
+ for victor in self.victorList:
+ event = simbase.air.getAvatarExitEvent(victor)
+ self.ignore(event)
+ return
+
+ ##### becomingToon state #####
+
+ def enterBecomingToon(self):
+ assert(self.debugPrint("enterBecomingToon()"))
+ self.d_setState('becomingToon')
+ name = self.taskName(str(self.block)+'_becomingToon-timer')
+ taskMgr.doMethodLater(
+ SuitBuildingGlobals.VICTORY_SEQUENCE_TIME,
+ self.becomingToonTask,
+ name)
+
+ def exitBecomingToon(self):
+ assert(self.debugPrint("exitBecomingToon()"))
+ name = self.taskName(str(self.block)+'_becomingToon-timer')
+ taskMgr.remove(name)
+
+ ##### becomingToonFromCogdo state #####
+
+ def enterBecomingToonFromCogdo(self):
+ assert(self.debugPrint("enterBecomingToonFromCogdo()"))
+ self.d_setState('becomingToonFromCogdo')
+ name = self.taskName(str(self.block)+'_becomingToonFromCogdo-timer')
+ taskMgr.doMethodLater(
+ SuitBuildingGlobals.VICTORY_SEQUENCE_TIME,
+ self.becomingToonTask,
+ name)
+
+ def exitBecomingToonFromCogdo(self):
+ assert(self.debugPrint("exitBecomingToonFromCogdo()"))
+ name = self.taskName(str(self.block)+'_becomingToonFromCogdo-timer')
+ taskMgr.remove(name)
+
+ ##### toon state #####
+
+ def becomingToonTask(self, task):
+ assert(self.debugPrint("becomingToonTask()"))
+ self.fsm.request("toon")
+
+ # Save the building state whenever we convert a building to
+ # toonness.
+ self.suitPlannerExt.buildingMgr.save()
+
+ return Task.done
+
+ def enterToon(self):
+ assert(self.debugPrint("enterToon()"))
+ self.d_setState('toon')
+ # Create the DistributedDoor:
+ exteriorZoneId, interiorZoneId=self.getExteriorAndInteriorZoneId()
+ # Toon interior:
+ if simbase.config.GetBool("want-new-toonhall",1) and \
+ ZoneUtil.getCanonicalZoneId(interiorZoneId)== ToonHall:
+ self.interior=DistributedToonHallInteriorAI.DistributedToonHallInteriorAI(
+ self.block, self.air, interiorZoneId, self)
+ else:
+ self.interior=DistributedToonInteriorAI.DistributedToonInteriorAI(
+ self.block, self.air, interiorZoneId, self)
+ self.interior.generateWithRequired(interiorZoneId)
+
+ # Outside door:
+ door=self.createExteriorDoor()
+ # Inside of the same door (different zone, and different distributed object):
+ insideDoor=DistributedDoorAI.DistributedDoorAI(self.air, self.block,
+ DoorTypes.INT_STANDARD)
+ # Tell them about each other:
+ door.setOtherDoor(insideDoor)
+ insideDoor.setOtherDoor(door)
+ door.zoneId=exteriorZoneId
+ insideDoor.zoneId=interiorZoneId
+ # Now that they both now about each other, generate them:
+ door.generateWithRequired(exteriorZoneId)
+ insideDoor.generateWithRequired(interiorZoneId)
+ # keep track of them:
+ self.door=door
+ self.insideDoor=insideDoor
+ self.becameSuitTime = 0
+
+ self.knockKnock=DistributedKnockKnockDoorAI.DistributedKnockKnockDoorAI(
+ self.air, self.block)
+ self.knockKnock.generateWithRequired(exteriorZoneId)
+
+ self.air.writeServerEvent(
+ 'building-toon', self.doId,
+ "%s|%s" % (self.zoneId, self.block))
+
+ def createExteriorDoor(self):
+ """Return the DistributedDoor for the exterior, with correct door type set"""
+ # Created so animated buildings can over ride this function
+ result = DistributedDoorAI.DistributedDoorAI(self.air, self.block,
+ DoorTypes.EXT_STANDARD)
+ return result
+
+ def exitToon(self):
+ assert(self.debugPrint("exitToon()"))
+ self.door.setDoorLock(FADoorCodes.BUILDING_TAKEOVER)
+ # The door doesn't get unlocked, because
+ # it will be distroyed and recreated.
+
+ ##### clearOutToonInterior state #####
+
+ def enterClearOutToonInterior(self):
+ assert(self.debugPrint("enterClearOutToonInterior()"))
+ self.d_setState('clearOutToonInterior')
+ if hasattr(self, "interior"):
+ self.interior.setState("beingTakenOver")
+ name = self.taskName(str(self.block)+'_clearOutToonInterior-timer')
+ taskMgr.doMethodLater(
+ SuitBuildingGlobals.CLEAR_OUT_TOON_BLDG_TIME,
+ self.clearOutToonInteriorTask,
+ name)
+
+ def exitClearOutToonInterior(self):
+ assert(self.debugPrint("exitClearOutToonInterior()"))
+ name = self.taskName(str(self.block)+'_clearOutToonInterior-timer')
+ taskMgr.remove(name)
+
+ ##### becomingSuit state #####
+
+ def clearOutToonInteriorTask(self, task):
+ assert(self.debugPrint("clearOutToonInteriorTask()"))
+ self.fsm.request("becomingSuit")
+ return Task.done
+
+ def enterBecomingSuit(self):
+ assert(self.debugPrint("enterBecomingSuit()"))
+
+ # We have to send this message before we send the distributed
+ # update to becomingSuit state, because the clients depend on
+ # knowing what kind of suit building we're becoming.
+ self.sendUpdate('setSuitData',
+ [ord(self.track), self.difficulty, self.numFloors])
+
+ self.d_setState('becomingSuit')
+ name = self.taskName(str(self.block)+'_becomingSuit-timer')
+ taskMgr.doMethodLater(
+ SuitBuildingGlobals.TO_SUIT_BLDG_TIME,
+ self.becomingSuitTask,
+ name)
+
+ def exitBecomingSuit(self):
+ assert(self.debugPrint("exitBecomingSuit()"))
+ name = self.taskName(str(self.block)+'_becomingSuit-timer')
+ taskMgr.remove(name)
+ # Clean up the toon distributed objects:
+ if hasattr(self, "interior"):
+ self.interior.requestDelete()
+ del self.interior
+ self.door.requestDelete()
+ del self.door
+ self.insideDoor.requestDelete()
+ del self.insideDoor
+ self.knockKnock.requestDelete()
+ del self.knockKnock
+
+ ##### suit state #####
+
+ def becomingSuitTask(self, task):
+ assert(self.debugPrint("becomingSuitTask()"))
+ self.fsm.request("suit")
+
+ # Save the building state whenever we convert a building to
+ # suitness.
+ self.suitPlannerExt.buildingMgr.save()
+
+ return Task.done
+
+ def enterSuit(self):
+ assert(self.debugPrint("enterSuit()"))
+
+ # We have to send this message again, even though we've
+ # already sent it in becomingSuit, because we might have come
+ # to this state directly on startup.
+ self.sendUpdate('setSuitData',
+ [ord(self.track), self.difficulty, self.numFloors])
+
+ # Create the suit planner for the interior
+ zoneId, interiorZoneId = self.getExteriorAndInteriorZoneId()
+ self.planner = SuitPlannerInteriorAI.SuitPlannerInteriorAI(
+ self.numFloors, self.difficulty, self.track, interiorZoneId)
+
+ self.d_setState('suit')
+ # Create the DistributedDoor:
+ exteriorZoneId, interiorZoneId=self.getExteriorAndInteriorZoneId()
+ #todo: ...create the elevator.
+ self.elevator = DistributedElevatorExtAI.DistributedElevatorExtAI(
+ self.air,
+ self)
+ self.elevator.generateWithRequired(exteriorZoneId)
+
+ self.air.writeServerEvent(
+ 'building-cog', self.doId,
+ "%s|%s|%s|%s" % (self.zoneId, self.block, self.track, self.numFloors))
+
+ def exitSuit(self):
+ assert(self.debugPrint("exitSuit()"))
+ del self.planner
+ # Clean up the suit distributed objects:
+ if hasattr(self, "elevator"):
+ self.elevator.requestDelete()
+ del self.elevator
+
+ ##### clearOutToonInteriorForCogdo state #####
+
+ def enterClearOutToonInteriorForCogdo(self):
+ assert(self.debugPrint("enterClearOutToonInteriorForCogdo()"))
+ self.d_setState('clearOutToonInteriorForCogdo')
+ if hasattr(self, "interior"):
+ self.interior.setState("beingTakenOver")
+ name = self.taskName(str(self.block)+'_clearOutToonInteriorForCogdo-timer')
+ taskMgr.doMethodLater(
+ SuitBuildingGlobals.CLEAR_OUT_TOON_BLDG_TIME,
+ self.clearOutToonInteriorForCogdoTask,
+ name)
+
+ def exitClearOutToonInteriorForCogdo(self):
+ assert(self.debugPrint("exitClearOutToonInteriorForCogdo()"))
+ name = self.taskName(str(self.block)+'_clearOutToonInteriorForCogdo-timer')
+ taskMgr.remove(name)
+
+ ##### becomingCogdo state #####
+
+ def clearOutToonInteriorForCogdoTask(self, task):
+ assert(self.debugPrint("clearOutToonInteriorForCogdoTask()"))
+ self.fsm.request("becomingCogdo")
+ return Task.done
+
+ def enterBecomingCogdo(self):
+ assert(self.debugPrint("enterBecomingCogdo()"))
+
+ # We have to send this message before we send the distributed
+ # update to becomingCogdo state, because the clients depend on
+ # knowing what kind of cogdo building we're becoming.
+ self.sendUpdate('setSuitData',
+ [ord(self.track), self.difficulty, self.numFloors])
+
+ self.d_setState('becomingCogdo')
+ name = self.taskName(str(self.block)+'_becomingCogdo-timer')
+ taskMgr.doMethodLater(
+ SuitBuildingGlobals.TO_SUIT_BLDG_TIME,
+ self.becomingCogdoTask,
+ name)
+
+ def exitBecomingCogdo(self):
+ assert(self.debugPrint("exitBecomingCogdo()"))
+ name = self.taskName(str(self.block)+'_becomingCogdo-timer')
+ taskMgr.remove(name)
+ # Clean up the toon distributed objects:
+ if hasattr(self, "interior"):
+ self.interior.requestDelete()
+ del self.interior
+ self.door.requestDelete()
+ del self.door
+ self.insideDoor.requestDelete()
+ del self.insideDoor
+ self.knockKnock.requestDelete()
+ del self.knockKnock
+
+ ##### cogdo state #####
+
+ def becomingCogdoTask(self, task):
+ assert(self.debugPrint("becomingCogdoTask()"))
+ self.fsm.request("cogdo")
+
+ # Save the building state whenever we convert a building to
+ # cogdoness.
+ self.suitPlannerExt.buildingMgr.save()
+
+ return Task.done
+
+ def enterCogdo(self):
+ assert(self.debugPrint("enterCogdo()"))
+
+ # We have to send this message again, even though we've
+ # already sent it in becomingCogdo, because we might have come
+ # to this state directly on startup.
+ self.sendUpdate('setSuitData',
+ [ord(self.track), self.difficulty, self.numFloors])
+
+ # Create the suit planner for the interior
+ zoneId, interiorZoneId = self.getExteriorAndInteriorZoneId()
+ self._cogdoLayout = CogdoLayout(self.numFloors)
+ self.planner = SuitPlannerCogdoInteriorAI(
+ self._cogdoLayout, self.difficulty, self.track, interiorZoneId)
+
+ self.d_setState('cogdo')
+ # Create the DistributedDoor:
+ exteriorZoneId, interiorZoneId=self.getExteriorAndInteriorZoneId()
+ #todo: ...create the elevator.
+ self.elevator = DistributedCogdoElevatorExtAI(
+ self.air,
+ self)
+ self.elevator.generateWithRequired(exteriorZoneId)
+
+ self.air.writeServerEvent(
+ 'building-cogdo', self.doId,
+ "%s|%s|%s" % (self.zoneId, self.block, self.numFloors))
+
+ def exitCogdo(self):
+ assert(self.debugPrint("exitCogdo()"))
+ del self.planner
+ # Clean up the cogdo distributed objects:
+ if hasattr(self, "elevator"):
+ self.elevator.requestDelete()
+ del self.elevator
+
+ def setSuitPlannerExt( self, planner ):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: let the building know which suit planner contains
+ // its building manager
+ // Parameters: planner, the governing suit planner for this bldg
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ self.suitPlannerExt = planner
+
+ def _createSuitInterior(self):
+ return DistributedSuitInteriorAI.DistributedSuitInteriorAI(self.air, self.elevator)
+
+ def _createCogdoInterior(self):
+ return DistributedCogdoInteriorAI(self.air, self.elevator)
+
+ def createSuitInterior(self):
+ # Create a building interior in the new (interior) zone
+ self.interior = self._createSuitInterior()
+ dummy, interiorZoneId = self.getExteriorAndInteriorZoneId()
+ self.interior.fsm.request('WaitForAllToonsInside')
+ self.interior.generateWithRequired(interiorZoneId)
+
+ def createCogdoInterior(self):
+ # Create a building interior in the new (interior) zone
+ self.interior = self._createCogdoInterior()
+ dummy, interiorZoneId = self.getExteriorAndInteriorZoneId()
+ self.interior.fsm.request('WaitForAllToonsInside')
+ self.interior.generateWithRequired(interiorZoneId)
+
+ def deleteSuitInterior(self):
+ if hasattr(self, "interior"):
+ self.interior.requestDelete()
+ del self.interior
+ if hasattr(self, "elevator"):
+ # -1 means the lobby.
+ self.elevator.d_setFloor(-1)
+ self.elevator.open()
+
+ def deleteCogdoInterior(self):
+ self.deleteSuitInterior()
+
+ if __debug__:
+ def debugPrint(self, message):
+ """for debugging"""
+ return self.notify.debug(
+ str(self.__dict__.get('block', '?'))+' '+message)
+
+# history
+#
+# 10May01 jlbutler added frontDoorPoint to the building so a suit or
+# the suit planner can get from a building to a suit
+# path point which is in front of the building
+#
+
diff --git a/toontown/src/building/DistributedBuildingMgrAI.py b/toontown/src/building/DistributedBuildingMgrAI.py
new file mode 100644
index 0000000..24dfcf5
--- /dev/null
+++ b/toontown/src/building/DistributedBuildingMgrAI.py
@@ -0,0 +1,446 @@
+""" DistributedBuildingMgrAI module: contains the DistributedBuildingMgrAI
+ class, the server side handler of all buildings in a neighborhood."""
+
+# AI code should not import ShowBaseGlobal because it creates a graphics window
+# Use AIBaseGlobal instead
+# from ShowBaseGlobal import *
+
+import os
+from direct.task.Task import Task
+import cPickle
+from otp.ai.AIBaseGlobal import *
+import DistributedBuildingAI
+import HQBuildingAI
+import GagshopBuildingAI
+import PetshopBuildingAI
+from toontown.building.KartShopBuildingAI import KartShopBuildingAI
+from toontown.building import DistributedAnimBuildingAI
+#import DistributedDoorAI
+from direct.directnotify import DirectNotifyGlobal
+from toontown.hood import ZoneUtil
+import time
+import random
+
+
+class DistributedBuildingMgrAI:
+ """
+ DistributedBuildingMgrAI class: a server side object, keeps track of
+ all buildings within a single neighborhood (street), handles
+ converting them from good to bad, and hands out information about
+ buildings to whoever asks.
+
+ Landmark data will be saved to an AI Server local file.
+
+ *How landmark building info gets loaded:
+ load list from dna;
+
+ look for backup .buildings file;
+ if present:
+ load from backup buildings file;
+ #if buildings file is present:
+ # remove buildings file;
+ else:
+ load .buildings file;
+
+ compare dna list with saved list;
+ if they are different:
+ make reasonable matches for suit blocks;
+
+ create the building AI dictionary
+
+ *Saving building data:
+ check for backup buildings file;
+ if present:
+ remove buildings file;
+ else:
+ move buildings file to backup file;
+ write new buildings file;
+ remove backup buildings file;
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBuildingMgrAI')
+ serverDatafolder = simbase.config.GetString('server-data-folder', "")
+
+ def __init__(self, air, branchID, dnaStore, trophyMgr):
+ """
+ branchID: The street number. Such as 2200.
+ """
+ self.branchID = branchID
+ self.canonicalBranchID = ZoneUtil.getCanonicalZoneId(branchID)
+ assert(self.debugPrint("__init__(air, branchID, dnaStore, trophyMgr)"))
+ self.air = air
+ self.__buildings = {}
+ self.dnaStore = dnaStore
+ self.trophyMgr = trophyMgr
+ self.shard = str(air.districtId)
+ self.backupExtension = '.bu'
+ self.findAllLandmarkBuildings()
+ self.doLaterTask = None
+
+
+ def cleanup(self):
+ taskMgr.remove(str(self.branchID)+'_delayed_save-timer')
+
+ for building in self.__buildings.values():
+ building.cleanup()
+ self.__buildings = {}
+
+ def isValidBlockNumber(self, blockNumber):
+ """return true if that block refers to a real block"""
+ assert(self.debugPrint("isValidBlockNumber(blockNumber="+str(blockNumber)+")"))
+ return self.__buildings.has_key(blockNumber)
+
+ def delayedSaveTask(self, task):
+ assert(self.debugPrint("delayedSaveTask()"))
+ self.save()
+ self.doLaterTask=None
+ return Task.done
+
+ def isSuitBlock(self, blockNumber):
+ """return true if that block is a suit block/building"""
+ assert(self.debugPrint("isSuitBlock(blockNumber="+str(blockNumber)+")"))
+ assert(self.__buildings.has_key(blockNumber))
+ return self.__buildings[blockNumber].isSuitBlock()
+
+ def getSuitBlocks(self):
+ assert(self.debugPrint("getSuitBlocks()"))
+ blocks=[]
+ for i in self.__buildings.values():
+ if i.isSuitBlock():
+ blocks.append(i.getBlock()[0])
+ return blocks
+
+ def getEstablishedSuitBlocks(self):
+ assert(self.debugPrint("getEstablishedSuitBlocks()"))
+ blocks=[]
+ for i in self.__buildings.values():
+ if i.isEstablishedSuitBlock():
+ blocks.append(i.getBlock()[0])
+ return blocks
+
+ def getToonBlocks(self):
+ assert(self.debugPrint("getToonBlocks()"))
+ blocks=[]
+ for i in self.__buildings.values():
+ if isinstance(i, HQBuildingAI.HQBuildingAI):
+ continue
+ if not i.isSuitBlock():
+ blocks.append(i.getBlock()[0])
+ return blocks
+
+ def getBuildings(self):
+ return self.__buildings.values()
+
+ def getFrontDoorPoint(self, blockNumber):
+ """get any associated path point for the specified building,
+ useful for suits to know where to go when exiting from a
+ building"""
+ assert(self.debugPrint("getFrontDoorPoint(blockNumber="+str(blockNumber)+")"))
+ assert(self.__buildings.has_key(blockNumber))
+ return self.__buildings[blockNumber].getFrontDoorPoint()
+
+ def getBuildingTrack(self, blockNumber):
+ """get any associated path point for the specified building,
+ useful for suits to know where to go when exiting from a
+ building"""
+ assert(self.debugPrint("getBuildingTrack(blockNumber="+str(blockNumber)+")"))
+ assert(self.__buildings.has_key(blockNumber))
+ return self.__buildings[blockNumber].track
+
+ def getBuilding( self, blockNumber ):
+ assert(self.debugPrint("getBuilding(%s)" %(str(blockNumber),)))
+ assert(self.__buildings.has_key(blockNumber))
+ return self.__buildings[blockNumber]
+
+ def setFrontDoorPoint(self, blockNumber, point):
+ """get any associated path point for the specified building,
+ useful for suits to know where to go when exiting from a
+ building"""
+ assert(self.debugPrint("setFrontDoorPoint(blockNumber="+str(blockNumber)
+ +", point="+str(point)+")"))
+ assert(self.__buildings.has_key(blockNumber))
+ return self.__buildings[blockNumber].setFrontDoorPoint(point)
+
+ def getDNABlockLists(self):
+ blocks=[]
+ hqBlocks=[]
+ gagshopBlocks=[]
+ petshopBlocks=[]
+ kartshopBlocks = []
+ animBldgBlocks = []
+ for i in range(self.dnaStore.getNumBlockNumbers()):
+ blockNumber = self.dnaStore.getBlockNumberAt(i)
+ buildingType = self.dnaStore.getBlockBuildingType(blockNumber)
+ if (buildingType == 'hq'):
+ hqBlocks.append(blockNumber)
+ elif (buildingType == 'gagshop'):
+ gagshopBlocks.append(blockNumber)
+ elif (buildingType == 'petshop'):
+ petshopBlocks.append(blockNumber)
+ elif( buildingType == 'kartshop' ):
+ kartshopBlocks.append( blockNumber )
+ elif( buildingType == 'animbldg' ):
+ animBldgBlocks.append( blockNumber )
+ else:
+ blocks.append(blockNumber)
+ return blocks, hqBlocks, gagshopBlocks, petshopBlocks, kartshopBlocks, animBldgBlocks
+
+ def findAllLandmarkBuildings(self):
+ assert(self.debugPrint("findAllLandmarkBuildings()"))
+ # Load the saved buildings:
+ buildings=self.load()
+ # Create the distributed buildings:
+ blocks, hqBlocks, gagshopBlocks, petshopBlocks, kartshopBlocks, animBldgBlocks = self.getDNABlockLists()
+ for block in blocks:
+ # Used saved data, if appropriate:
+ self.newBuilding(block, buildings.get(block, None))
+ for block in animBldgBlocks:
+ # Used saved data, if appropriate:
+ self.newAnimBuilding(block, buildings.get(block, None))
+ for block in hqBlocks:
+ self.newHQBuilding(block)
+ for block in gagshopBlocks:
+ self.newGagshopBuilding(block)
+
+ if simbase.wantPets:
+ for block in petshopBlocks:
+ self.newPetshopBuilding(block)
+
+ if( simbase.wantKarts ):
+ for block in kartshopBlocks:
+ self.newKartShopBuilding( block )
+
+ def newBuilding(self, blockNumber, blockData=None):
+ """Create a new building and keep track of it."""
+ assert(self.debugPrint("newBuilding(blockNumber="+str(blockNumber)
+ +", blockData="+str(blockData)+")"))
+ assert(not self.__buildings.has_key(blockNumber))
+
+ building=DistributedBuildingAI.DistributedBuildingAI(
+ self.air, blockNumber, self.branchID, self.trophyMgr)
+ building.generateWithRequired(self.branchID)
+ if blockData:
+ building.track = blockData.get("track", "c")
+ building.difficulty = int(blockData.get("difficulty", 1))
+ building.numFloors = int(blockData.get("numFloors", 1))
+ building.numFloors = max(1, min(5, building.numFloors))
+ if not ZoneUtil.isWelcomeValley(building.zoneId):
+ building.updateSavedBy(blockData.get("savedBy"))
+ else:
+ self.notify.warning('we had a cog building in welcome valley %d' % building.zoneId)
+ building.becameSuitTime = blockData.get("becameSuitTime", time.time())
+
+ # Double check the state becuase we have seen the building
+ # saved out with other states (like waitForVictor). If we
+ # get one of these weird states, just make it a toon bldg
+ if blockData["state"] == "suit":
+ building.setState("suit")
+ elif blockData['state'] == 'cogdo':
+ if simbase.air.wantCogdominiums:
+ building.setState("cogdo")
+ else:
+ building.setState("toon")
+ else:
+ building.setState("toon")
+ self.__buildings[blockNumber] = building
+ return building
+
+ def newAnimBuilding(self, blockNumber, blockData=None):
+ """Create a new building and keep track of it."""
+ assert(self.debugPrint("newBuilding(blockNumber="+str(blockNumber)
+ +", blockData="+str(blockData)+")"))
+ assert(not self.__buildings.has_key(blockNumber))
+
+ building=DistributedAnimBuildingAI.DistributedAnimBuildingAI(
+ self.air, blockNumber, self.branchID, self.trophyMgr)
+ building.generateWithRequired(self.branchID)
+ if blockData:
+ building.track = blockData.get("track", "c")
+ building.difficulty = int(blockData.get("difficulty", 1))
+ building.numFloors = int(blockData.get("numFloors", 1))
+ if not ZoneUtil.isWelcomeValley(building.zoneId):
+ building.updateSavedBy(blockData.get("savedBy"))
+ else:
+ self.notify.warning('we had a cog building in welcome valley %d' % building.zoneId)
+ building.becameSuitTime = blockData.get("becameSuitTime", time.time())
+
+ # Double check the state becuase we have seen the building
+ # saved out with other states (like waitForVictor). If we
+ # get one of these weird states, just make it a toon bldg
+ if blockData["state"] == "suit":
+ building.setState("suit")
+ else:
+ building.setState("toon")
+ else:
+ building.setState("toon")
+ self.__buildings[blockNumber] = building
+ return building
+
+ def newHQBuilding(self, blockNumber):
+ """Create a new HQ building and keep track of it."""
+ assert(not self.__buildings.has_key(blockNumber))
+ dnaStore = self.air.dnaStoreMap[self.canonicalBranchID]
+ exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber)
+ exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID)
+ interiorZoneId = (self.branchID-self.branchID%100)+500+blockNumber
+ assert(self.debugPrint("newHQBuilding(blockNumber=%s exteriorZoneId=%s interiorZoneId=%s" %
+ (blockNumber, exteriorZoneId, interiorZoneId)))
+ building=HQBuildingAI.HQBuildingAI(self.air, exteriorZoneId, interiorZoneId, blockNumber)
+ self.__buildings[blockNumber] = building
+ return building
+
+ def newGagshopBuilding(self, blockNumber):
+ """Create a new Gagshop building and keep track of it."""
+ assert(self.debugPrint("newGagshopBuilding(blockNumber="+str(blockNumber)+")"))
+ assert(not self.__buildings.has_key(blockNumber))
+ dnaStore = self.air.dnaStoreMap[self.canonicalBranchID]
+ exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber)
+ exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID)
+ interiorZoneId = (self.branchID-self.branchID%100)+500+blockNumber
+ building=GagshopBuildingAI.GagshopBuildingAI(self.air, exteriorZoneId, interiorZoneId, blockNumber)
+ self.__buildings[blockNumber] = building
+ return building
+
+ def newPetshopBuilding(self, blockNumber):
+ """Create a new Petshop building and keep track of it."""
+ assert(self.debugPrint("newPetshopBuilding(blockNumber="+str(blockNumber)+")"))
+ assert(not self.__buildings.has_key(blockNumber))
+ dnaStore = self.air.dnaStoreMap[self.canonicalBranchID]
+ exteriorZoneId = dnaStore.getZoneFromBlockNumber(blockNumber)
+ exteriorZoneId = ZoneUtil.getTrueZoneId(exteriorZoneId, self.branchID)
+ interiorZoneId = (self.branchID-self.branchID%100)+500+blockNumber
+ building=PetshopBuildingAI.PetshopBuildingAI(self.air, exteriorZoneId, interiorZoneId, blockNumber)
+ self.__buildings[blockNumber] = building
+ return building
+
+ def newKartShopBuilding( self, blockNumber ):
+ """
+ Purpose: The newKartShopBuilding Method creates a new KartShop
+ building and keeps track of it.
+
+ Params: blockNumber - block that the shop is on.
+ Return: None
+ """
+ assert( self.debugPrint( "newKartShopBuilding(blockNumber=" + str( blockNumber ) + ")" ) )
+ assert( not self.__buildings.has_key( blockNumber ) )
+
+ dnaStore = self.air.dnaStoreMap[ self.canonicalBranchID ]
+
+ # Retrieve the Exterior and Interior ZoneIds
+ exteriorZoneId = dnaStore.getZoneFromBlockNumber( blockNumber )
+ exteriorZoneId = ZoneUtil.getTrueZoneId( exteriorZoneId, self.branchID )
+ interiorZoneId = ( self.branchID - self.branchID%100 ) + 500 + blockNumber
+
+ building = KartShopBuildingAI( self.air, exteriorZoneId, interiorZoneId, blockNumber )
+ self.__buildings[ blockNumber ] = building
+
+ return building
+
+ def getFileName(self):
+ """Figure out the path to the saved state"""
+ f = "%s%s_%d.buildings" % (self.serverDatafolder, self.shard, self.branchID)
+ assert(self.debugPrint("getFileName() returning \""+str(f)+"\""))
+ return f
+
+ def saveTo(self, file, block=None):
+ """Save data to specified file"""
+ assert(self.debugPrint("saveTo(file="+str(file)+", block="+str(block)+")"))
+ if block:
+ # Save just this one block to the file:
+ pickleData=block.getPickleData()
+ cPickle.dump(pickleData, file)
+ else:
+ # Save them all:
+ for i in self.__buildings.values():
+ # HQs do not need to be saved
+ if isinstance(i, HQBuildingAI.HQBuildingAI):
+ continue
+ pickleData=i.getPickleData()
+ cPickle.dump(pickleData, file)
+
+ def fastSave(self, block):
+ """Save data to default location"""
+ return
+ # This code has not been tested or connected. If the normal save takes
+ # too long on the AI server, this fastSave should be considered.
+ assert(0)
+ assert(self.debugPrint("fastSave(block="+str(block)+")"))
+ try:
+ fileName=self.getFileName()+'.delta'
+ working=fileName+'.temp'
+ # Change the name to flag the work in progress:
+ if os.path.exists(working):
+ os.remove(working)
+ os.rename(fileName, working)
+ file=open(working, 'w')
+ file.seek(0, 2)
+ self.saveTo(file, block)
+ file.close()
+ # Change the name to flag the work complete:
+ os.rename(working, fileName)
+ except IOError:
+ self.notify.error(str(sys.exc_info()[1]))
+ # Even if it's just the rename that failed, we don't want to
+ # clobber the prior file.
+
+ def save(self):
+ """Save data to default location"""
+ assert(self.debugPrint("save()"))
+ try:
+ fileName=self.getFileName()
+ backup=fileName+self.backupExtension
+ # Move current file as the backup file:
+ if os.path.exists(fileName):
+ os.rename(fileName, backup)
+ file=open(fileName, 'w')
+ file.seek(0)
+ self.saveTo(file)
+ file.close()
+ if os.path.exists(backup):
+ os.remove(backup)
+ except EnvironmentError:
+ self.notify.warning(str(sys.exc_info()[1]))
+ # Even if it's just the rename that failed, we don't want to
+ # clobber the prior file.
+
+ def loadFrom(self, file):
+ """Load data from specified file"""
+ assert(self.debugPrint("loadFrom(file="+str(file)+")"))
+ blocks={}
+ try:
+ while 1:
+ pickleData=cPickle.load(file)
+ blocks[int(pickleData['block'])]=pickleData
+ except EOFError:
+ pass
+ return blocks
+
+ def load(self):
+ """Load data from default location"""
+ assert(self.debugPrint("load()"))
+ fileName=self.getFileName()
+ try:
+ # Try to open the backup file:
+ file=open(fileName+self.backupExtension, 'r')
+ # Remove the (assumed) broken file:
+ if os.path.exists(fileName):
+ os.remove(fileName)
+ except IOError:
+ # OK, there's no backup file, good.
+ try:
+ # Open the real file:
+ file=open(fileName, 'r')
+ except IOError:
+ # OK, there's no file. Start new list:
+ return {}
+ file.seek(0)
+ blocks=self.loadFrom(file)
+ file.close()
+ return blocks
+
+ if __debug__:
+ def debugPrint(self, message):
+ """for debugging"""
+ return self.notify.debug(
+ str(self.__dict__.get('branchID', '?'))+' '+message)
+
diff --git a/toontown/src/building/DistributedCFOElevator.py b/toontown/src/building/DistributedCFOElevator.py
new file mode 100644
index 0000000..5f11fa4
--- /dev/null
+++ b/toontown/src/building/DistributedCFOElevator.py
@@ -0,0 +1,33 @@
+import DistributedElevator
+import DistributedBossElevator
+from ElevatorConstants import *
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import TTLocalizer
+
+class DistributedCFOElevator(DistributedBossElevator.DistributedBossElevator):
+
+ def __init__(self, cr):
+ DistributedBossElevator.DistributedBossElevator.__init__(self, cr)
+ self.type = ELEVATOR_CFO
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
+ def setupElevator(self):
+ """setupElevator(self)
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+ # TODO: place this on a node indexed by the entraceId
+ self.elevatorModel = loader.loadModel("phase_10/models/cogHQ/CFOElevator")
+
+ self.leftDoor = self.elevatorModel.find("**/left_door")
+ self.rightDoor = self.elevatorModel.find("**/right_door")
+
+ geom = base.cr.playGame.hood.loader.geom
+ locator = geom.find('**/elevator_locator')
+ self.elevatorModel.reparentTo(locator)
+ #self.elevatorModel.setH(180)
+
+ DistributedElevator.DistributedElevator.setupElevator(self)
+
+ def getDestName(self):
+ return TTLocalizer.ElevatorCashBotBoss
diff --git a/toontown/src/building/DistributedCFOElevatorAI.py b/toontown/src/building/DistributedCFOElevatorAI.py
new file mode 100644
index 0000000..24e2ebb
--- /dev/null
+++ b/toontown/src/building/DistributedCFOElevatorAI.py
@@ -0,0 +1,12 @@
+from ElevatorConstants import *
+import DistributedBossElevatorAI
+
+class DistributedCFOElevatorAI(DistributedBossElevatorAI.DistributedBossElevatorAI):
+
+ def __init__(self, air, bldg, zone, antiShuffle = 0, minLaff = 0):
+ """__init__(air)
+ """
+ DistributedBossElevatorAI.DistributedBossElevatorAI.__init__(self, air, bldg, zone, antiShuffle = antiShuffle, minLaff = minLaff)
+ self.type = ELEVATOR_CFO
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
diff --git a/toontown/src/building/DistributedCJElevator.py b/toontown/src/building/DistributedCJElevator.py
new file mode 100644
index 0000000..27cd7a5
--- /dev/null
+++ b/toontown/src/building/DistributedCJElevator.py
@@ -0,0 +1,45 @@
+import DistributedElevator
+import DistributedBossElevator
+from ElevatorConstants import *
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import TTLocalizer
+
+class DistributedCJElevator(DistributedBossElevator.DistributedBossElevator):
+
+ def __init__(self, cr):
+ DistributedBossElevator.DistributedBossElevator.__init__(self, cr)
+ self.type = ELEVATOR_CJ
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
+ def setupElevator(self):
+ """setupElevator(self)
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+ # TODO: place this on a node indexed by the entraceId
+ self.elevatorModel = loader.loadModel(
+ "phase_11/models/lawbotHQ/LB_Elevator")
+
+ # The big cog icon on the top is only visible at the BossRoom.
+ #icon = self.elevatorModel.find('**/big_frame/')
+ #if not icon.isEmpty():
+ # icon.hide()
+
+ self.leftDoor = self.elevatorModel.find("**/left-door")
+ if self.leftDoor.isEmpty():
+ self.leftDoor = self.elevatorModel.find("**/left_door")
+
+ self.rightDoor = self.elevatorModel.find("**/right-door")
+ if self.rightDoor.isEmpty():
+ self.rightDoor = self.elevatorModel.find("**/right_door")
+
+ geom = base.cr.playGame.hood.loader.geom
+ locator = geom.find('**/elevator_locator')
+ self.elevatorModel.reparentTo(locator)
+ #self.elevatorModel.setH(180)
+
+ DistributedElevator.DistributedElevator.setupElevator(self)
+
+ def getDestName(self):
+ return TTLocalizer.ElevatorLawBotBoss
+
diff --git a/toontown/src/building/DistributedCJElevatorAI.py b/toontown/src/building/DistributedCJElevatorAI.py
new file mode 100644
index 0000000..c19c9f8
--- /dev/null
+++ b/toontown/src/building/DistributedCJElevatorAI.py
@@ -0,0 +1,12 @@
+from ElevatorConstants import *
+import DistributedBossElevatorAI
+
+class DistributedCJElevatorAI(DistributedBossElevatorAI.DistributedBossElevatorAI):
+
+ def __init__(self, air, bldg, zone, antiShuffle = 0, minLaff = 0):
+ """__init__(air)
+ """
+ DistributedBossElevatorAI.DistributedBossElevatorAI.__init__(self, air, bldg, zone, antiShuffle = antiShuffle, minLaff = 0)
+ self.type = ELEVATOR_CJ
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
diff --git a/toontown/src/building/DistributedClubElevator.py b/toontown/src/building/DistributedClubElevator.py
new file mode 100644
index 0000000..9052c15
--- /dev/null
+++ b/toontown/src/building/DistributedClubElevator.py
@@ -0,0 +1,781 @@
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+from direct.interval.IntervalGlobal import *
+from toontown.building import ElevatorConstants
+from toontown.building import ElevatorUtils
+from toontown.building import DistributedElevatorFSM
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from toontown.hood import ZoneUtil
+from toontown.toonbase import TTLocalizer
+from direct.fsm.FSM import FSM
+from direct.task import Task
+from toontown.distributed import DelayDelete
+from direct.showbase import PythonUtil
+
+class DistributedClubElevator(DistributedElevatorFSM.DistributedElevatorFSM):
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedClubElevator')
+
+ JumpOutOffsets = ((3, 5, 0), (1.5, 4, 0), (-1.5, 4, 0), (-3, 4, 0))
+
+ defaultTransitions = {
+ 'Off' : [ 'Opening', 'Closed'],
+ 'Opening' : [ 'WaitEmpty', 'WaitCountdown', 'Opening', 'Closing' ],
+ 'WaitEmpty' : [ 'WaitCountdown', "Closing", "Off" ],
+ 'WaitCountdown' : [ 'WaitEmpty', 'AllAboard', "Closing", 'WaitCountdown' ],
+ 'AllAboard' : [ 'WaitEmpty', "Closing" ],
+ 'Closing' : [ 'Closed', 'WaitEmpty', 'Closing', 'Opening' ],
+ 'Closed' : [ 'Opening' ],
+ }
+ id = 0
+
+ def __init__(self, cr):
+ DistributedElevatorFSM.DistributedElevatorFSM.__init__(self, cr)
+ FSM.__init__( self, "ElevatorClub_%s_FSM" % ( self.id ) )
+ # When we get enough information about the elevator, we can
+ # set up its nametag. The nametag points out nearby buildings
+ # to the player.
+ # self.type = ElevatorConstants.ELEVATOR_STAGE
+ self.type = ElevatorConstants.ELEVATOR_COUNTRY_CLUB
+ self.countdownTime = ElevatorConstants.ElevatorData[self.type]['countdown']
+ self.nametag = None
+ self.currentFloor = -1
+ self.isLocked = 0
+ self.isEntering = 0
+ self.doorOpeningFlag = 0 #for when the door is forced closed while opening
+ self.doorsNeedToClose = 0 #for when the door is forced closed while opening
+ self.wantState = 0
+ #self.latchRoom = None
+ self.latch = None
+
+ self.lastState = self.state
+
+ self.kartModelPath = 'phase_12/models/bossbotHQ/Coggolf_cart3.bam'
+ self.leftDoor = None
+ self.rightDoor = None
+
+ # Tracks on toons, for starting and stopping
+ # stored by avId : track. There is only a need for one at a time,
+ # in fact the point of the dict is to ensure only one is playing at a time
+ self.__toonTracks = {}
+
+
+ def setupElevator(self):
+ """setupElevator(self)
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+
+ # TODO: place this on a node indexed by the entraceId
+ self.elevatorModel = loader.loadModel(
+ "phase_11/models/lawbotHQ/LB_ElevatorScaled")
+ if not self.elevatorModel:
+ self.notify.error("No Elevator Model in DistributedElevatorFloor.setupElevator. Please inform JML. Fool!")
+
+ # The big cog icon on the top is only visible at the BossRoom.
+ #icon = self.elevatorModel.find('**/big_frame/')
+ #if not icon.isEmpty():
+ # icon.hide()
+
+ self.leftDoor = self.elevatorModel.find("**/left-door")
+ if self.leftDoor.isEmpty():
+ self.leftDoor = self.elevatorModel.find("**/left_door")
+
+ self.rightDoor = self.elevatorModel.find("**/right-door")
+ if self.rightDoor.isEmpty():
+ self.rightDoor = self.elevatorModel.find("**/right_door")
+
+ #self.elevatorModel.setH(180)
+
+ DistributedElevatorFSM.DistributedElevatorFSM.setupElevator(self)
+
+ def generate(self):
+ DistributedElevatorFSM.DistributedElevatorFSM.generate(self)
+ #self.accept("LawOffice_Spec_Loaded", self.__placeElevator)
+
+ # Get the state machine stuff for playGame
+ self.loader = self.cr.playGame.hood.loader
+ self.golfKart = render.attachNewNode('golfKartNode')
+ self.kart = loader.loadModel(self.kartModelPath)
+ self.kart.setPos(0, 0, 0)
+ self.kart.setScale(1)
+ self.kart.reparentTo(self.golfKart)
+ #self.golfKart.reparentTo(self.loader.geom)
+
+ # Wheels
+ self.wheels = self.kart.findAllMatches('**/wheelNode*')
+ self.numWheels = self.wheels.getNumPaths()
+
+ # temp only
+ self.setPosHpr(0,0,0,0,0,0)
+
+ def announceGenerate(self):
+ DistributedElevatorFSM.DistributedElevatorFSM.announceGenerate(self)
+ if self.latch:
+ self.notify.info("Setting latch in announce generate")
+ self.setLatch(self.latch)
+
+ angle = self.startingHpr[0]
+ angle -= 90
+ radAngle = deg2Rad(angle)
+ unitVec = Vec3( math.cos(radAngle), math.sin(radAngle), 0)
+ unitVec *= 45.0
+ self.endPos = self.startingPos + unitVec
+ self.endPos.setZ(0.5)
+
+ dist = Vec3(self.endPos - self.enteringPos).length()
+ wheelAngle = (dist / (4.8 * 1.4 * math.pi)) * 360
+
+ self.kartEnterAnimateInterval = Parallel(
+ # start a lerp HPR for each wheel
+ LerpHprInterval(self.wheels[0], 5.0, Vec3(self.wheels[0].getH(), wheelAngle, self.wheels[0].getR())),
+ LerpHprInterval(self.wheels[1], 5.0, Vec3(self.wheels[1].getH(), wheelAngle, self.wheels[1].getR())),
+ LerpHprInterval(self.wheels[2], 5.0, Vec3(self.wheels[2].getH(), wheelAngle, self.wheels[2].getR())),
+ LerpHprInterval(self.wheels[3], 5.0, Vec3(self.wheels[3].getH(), wheelAngle, self.wheels[3].getR())),
+ name = "CogKartAnimate")
+
+ trolleyExitTrack1 = Parallel(
+ LerpPosInterval(self.golfKart, 5.0, self.endPos),
+ self.kartEnterAnimateInterval,
+ name = "CogKartExitTrack")
+ self.trolleyExitTrack = Sequence(
+ trolleyExitTrack1,
+ # Func(self.hideSittingToons), # we may not need this
+ )
+
+ self.trolleyEnterTrack = Sequence(
+ LerpPosInterval(self.golfKart, 5.0, self.startingPos, startPos = self.enteringPos))
+
+ self.closeDoors = Sequence(
+ self.trolleyExitTrack,
+ Func(self.onDoorCloseFinish))
+ self.openDoors = Sequence(
+ self.trolleyEnterTrack
+ )
+
+ self.setPos(0,0,0)
+
+ def __placeElevator(self):
+ self.notify.debug("PLACING ELEVATOR FOOL!!")
+ if self.isEntering:
+ elevatorNode = render.find("**/elevator_origin")
+ if not elevatorNode.isEmpty():
+ self.elevatorModel.setPos(0,0, 0)
+ self.elevatorModel.reparentTo(elevatorNode)
+ else:
+ #explode
+ self.notify.debug("NO NODE elevator_origin!!")
+ else:
+ elevatorNode = render.find("**/SlidingDoor")
+ if not elevatorNode.isEmpty():
+ self.elevatorModel.setPos(0,10,-15)
+ self.elevatorModel.setH(180)
+ self.elevatorModel.reparentTo(elevatorNode)
+ else:
+ #explode
+ self.notify.debug("NO NODE SlidingDoor!!")
+
+ def setLatch(self, markerId):
+ self.notify.info("Setting latch")
+ #room = self.cr.doId2do.get(roomId)
+ marker = self.cr.doId2do.get(markerId)
+ self.latchRequest = self.cr.relatedObjectMgr.requestObjects(
+ [markerId], allCallback = self.set2Latch, timeout = 5)
+ self.latch = markerId
+
+
+ def set2Latch(self, taskMgrFooler = None):
+ self.latchRequest = None
+ if hasattr(self, "cr"): #might callback to dead object
+ marker = self.cr.doId2do.get(self.latch)
+ if marker:
+ #self.elevatorModel.reparentTo(marker)
+ self.getElevatorModel().reparentTo(marker)
+ return
+ taskMgr.doMethodLater(10.0, self._repart2Marker, "elevatorfloor-markerReparent")
+ self.notify.warning("Using backup, do method later version of latch")
+
+ def _repart2Marker(self, taskFoolio = 0):
+ if hasattr(self, "cr") and self.cr: #might call to dead object
+ marker = self.cr.doId2do.get(self.latch)
+ if marker:
+ #self.elevatorModel.reparentTo(marker)
+ self.getElevatorModel().reparentTo(marker)
+ else:
+ self.notify.error("could not find latch even in defered try")
+
+ def setPos(self, x, y, z):
+ self.getElevatorModel().setPos(x, y, z)
+
+ def setH(self, H):
+ self.getElevatorModel().setH(H)
+
+ def delete(self):
+ self.request('Off')
+ DistributedElevatorFSM.DistributedElevatorFSM.delete(self)
+ self.getElevatorModel().removeNode()
+ del self.golfKart
+ self.ignore("LawOffice_Spec_Loaded")
+ self.ignoreAll()
+
+ def disable(self):
+ #self.clearNametag()
+ self.request('Off')
+ self.clearToonTracks()
+ DistributedElevatorFSM.DistributedElevatorFSM.disable(self)
+
+ def setEntranceId(self, entranceId):
+ self.entranceId = entranceId
+
+ # These hard coded poshprs should be replaced with nodes in the model
+ if self.entranceId == 0:
+ # Front of the factory (south entrance)
+ self.elevatorModel.setPosHpr(62.74, -85.31, 0.00, 2.00, 0.00, 0.00)
+ elif self.entranceId == 1:
+ # Side of the factory (west entrance)
+ self.elevatorModel.setPosHpr(-162.25, 26.43, 0.00, 269.00, 0.00, 0.00)
+ else:
+ self.notify.error("Invalid entranceId: %s" % entranceId)
+
+
+
+ def gotBldg(self, buildingList):
+ return
+
+ def setFloor(self, floorNumber):
+ # Darken the old light:
+ if self.currentFloor >= 0:
+ self.bldg.floorIndicator[self.currentFloor].setColor(LIGHT_OFF_COLOR)
+
+ # Brighten the new light:
+ if floorNumber >= 0:
+ self.bldg.floorIndicator[floorNumber].setColor(LIGHT_ON_COLOR)
+
+ # Remember the floor:
+ self.currentFloor = floorNumber
+
+ def handleEnterSphere(self, collEntry):
+ #print("Entering Elevator Sphere....")
+ # Tell localToon we are considering entering the elevator
+ self.cr.playGame.getPlace().detectedElevatorCollision(self)
+
+ def handleEnterElevator(self):
+ #print("Entering Elevator....")
+ # Only toons with hp can board the elevator.
+ if base.localAvatar.hp > 0:
+ # Tell the server that this avatar wants to board.
+ toon = base.localAvatar
+ self.sendUpdate("requestBoard",[])
+ else:
+ self.notify.warning("Tried to board elevator with hp: %d" %
+ base.localAvatar.hp)
+
+ ##### WaitEmpty state #####
+
+ def enterWaitEmpty(self, ts):
+ self.lastState = self.state
+ #print("Entering WaitEmpty %s" % (self.doId))
+ self.elevatorSphereNodePath.unstash()
+ self.forceDoorsOpen()
+ # Toons may now try to board the elevator
+ self.accept(self.uniqueName('enterelevatorSphere'),
+ self.handleEnterSphere)
+ self.accept(self.uniqueName('enterElevatorOK'),
+ self.handleEnterElevator)
+ DistributedElevatorFSM.DistributedElevatorFSM.enterWaitEmpty(self, ts)
+
+ def exitWaitEmpty(self):
+ self.lastState = self.state
+ #print("Exiting WaitEmpty")
+ self.elevatorSphereNodePath.stash()
+ # Toons may not attempt to board the elevator if it isn't waiting
+ self.ignore(self.uniqueName('enterelevatorSphere'))
+ self.ignore(self.uniqueName('enterElevatorOK'))
+ DistributedElevatorFSM.DistributedElevatorFSM.exitWaitEmpty(self)
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self, ts):
+ self.lastState = self.state
+ #print("Entering WaitCountdown")
+ DistributedElevatorFSM.DistributedElevatorFSM.enterWaitCountdown(self, ts)
+ self.forceDoorsOpen()
+ self.accept(self.uniqueName('enterElevatorOK'),
+ self.handleEnterElevator)
+ self.startCountdownClock(self.countdownTime, ts)
+
+ def exitWaitCountdown(self):
+ self.lastState = self.state
+ #print("Exiting WaitCountdown")
+ self.ignore(self.uniqueName('enterElevatorOK'))
+ DistributedElevatorFSM.DistributedElevatorFSM.exitWaitCountdown(self)
+
+ def enterClosing(self, ts):
+ self.lastState = self.state
+ #print("Entering Closing")
+ #base.transitions.irisOut(2.0)
+ taskMgr.doMethodLater(1.00, self._delayIris, "delayedIris")
+ DistributedElevatorFSM.DistributedElevatorFSM.enterClosing(self, ts)
+
+ def _delayIris(self, tskfooler = 0):
+ base.transitions.irisOut(1.0)
+ base.localAvatar.pauseGlitchKiller()
+ return Task.done
+
+ def kickToonsOut(self):
+ #print"TOONS BEING KICKED OUT"
+ if not self.localToonOnBoard:
+ zoneId = self.cr.playGame.hood.hoodId
+ self.cr.playGame.getPlace().fsm.request('teleportOut', [{
+ "loader": ZoneUtil.getLoaderName(zoneId),
+ "where": ZoneUtil.getToonWhereName(zoneId),
+ "how": "teleportIn",
+ "hoodId": zoneId,
+ "zoneId": zoneId,
+ "shardId": None,
+ "avId": -1,
+ }])
+
+
+ def exitClosing(self):
+ self.lastState = self.state
+ #print("Exiting Closing")
+ DistributedElevatorFSM.DistributedElevatorFSM.exitClosing(self)
+
+ def enterClosed(self, ts):
+ self.lastState = self.state
+ #print("Entering Closed")
+ self.forceDoorsClosed()
+ self.__doorsClosed(self.getZoneId())
+ return
+
+ def exitClosed(self):
+ self.lastState = self.state
+ #print("Exiting Closed")
+ DistributedElevatorFSM.DistributedElevatorFSM.exitClosed(self)
+
+ def enterOff(self):
+ self.lastState = self.state
+ #print("Entering Off")
+ if self.wantState == 'closed':
+ self.demand('Closing')
+ elif self.wantState == 'waitEmpty':
+ self.demand('WaitEmpty')
+
+ DistributedElevatorFSM.DistributedElevatorFSM.enterOff(self)
+
+ def exitOff(self):
+ self.lastState = self.state
+ #print("Exiting Off")
+ DistributedElevatorFSM.DistributedElevatorFSM.exitOff(self)
+
+ def enterOpening(self, ts):
+ self.lastState = self.state
+ #print("Entering Opening")
+ DistributedElevatorFSM.DistributedElevatorFSM.enterOpening(self,ts)
+
+ def exitOpening(self):
+ #print("Exiting Opening")
+ #print("WE ARE ACTAULLY CALLING exitOpening!!!!")
+ #import pdb; pdb.set_trace()
+ DistributedElevatorFSM.DistributedElevatorFSM.exitOpening(self)
+ self.kickEveryoneOut()
+
+ return
+
+ def getZoneId(self):
+ return 0
+
+ def setBldgDoId(self, bldgDoId):
+ # The doId is junk, there is no building object for the factory
+ # exterior elevators. Do the appropriate things that
+ # DistributedElevator.gotBldg does.
+ #import pdb; pdb.set_trace()
+ self.bldg = None
+ self.setupElevatorKart()
+
+ def setupElevatorKart(self):
+ """Setup elevator related fields."""
+ # Establish a collision sphere. There must be an easier way!
+ collisionRadius = ElevatorConstants.ElevatorData[self.type]['collRadius']
+ self.elevatorSphere = CollisionSphere(0, 0, 0, collisionRadius)
+ self.elevatorSphere.setTangible(1)
+ self.elevatorSphereNode = CollisionNode(self.uniqueName("elevatorSphere"))
+ self.elevatorSphereNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
+ self.elevatorSphereNode.addSolid(self.elevatorSphere)
+ self.elevatorSphereNodePath = self.getElevatorModel().attachNewNode(
+ self.elevatorSphereNode)
+ self.elevatorSphereNodePath.hide()
+ self.elevatorSphereNodePath.reparentTo(self.getElevatorModel())
+ self.elevatorSphereNodePath.stash()
+
+ self.boardedAvIds = {}
+ self.finishSetup()
+
+
+ def getElevatorModel(self):
+ return self.elevatorModel
+
+ def kickEveryoneOut(self):
+ #makes the toons leave the elevator
+ bailFlag = 0
+ #print self.boardedAvIds
+ for avId, slot in self.boardedAvIds.items():
+ #print("Kicking toon out! avId %s Slot %s" % (avId, slot))
+ self.emptySlot(slot, avId, bailFlag, globalClockDelta.getRealNetworkTime())
+ if avId == base.localAvatar.doId:
+ pass
+
+
+
+ def __doorsClosed(self, zoneId):
+ return
+
+ def onDoorCloseFinish(self):
+ """this is called when the elevator doors finish closing on the client
+ """
+
+ def setLocked(self, locked):
+ self.isLocked = locked
+ if locked:
+ if self.state == 'WaitEmpty':
+ self.request('Closing')
+ if self.countFullSeats() == 0:
+ self.wantState = 'closed'
+ else:
+ self.wantState = 'opening'
+ else:
+ self.wantState = 'waitEmpty'
+ if self.state == 'Closed':
+ self.request('Opening')
+
+ def getLocked(self):
+ return self.isLocked
+
+ def setEntering(self, entering):
+ self.isEntering = entering
+
+ def getEntering(self):
+ return self.isEntering
+
+
+ def forceDoorsOpen(self):
+ """Deliberately do nothing."""
+ pass
+
+ def forceDoorsClosed(self):
+ """Deliberately do nothing."""
+ pass
+
+ def enterOff(self):
+ self.lastState = self.state
+ return
+
+ def exitOff(self):
+ return
+
+
+ def setLawOfficeInteriorZone(self, zoneId):
+ if (self.localToonOnBoard):
+ hoodId = self.cr.playGame.hood.hoodId
+ doneStatus = {
+ 'loader' : "cogHQLoader",
+ 'where' : 'factoryInterior', #should be lawOffice
+ 'how' : "teleportIn",
+ 'zoneId' : zoneId,
+ 'hoodId' : hoodId,
+ }
+ self.cr.playGame.getPlace().elevator.signalDone(doneStatus)
+
+# def emptySlot(self, index, avId, bailFlag, timestamp):
+# pass
+
+
+ def getElevatorModel(self):
+ return self.golfKart
+
+
+ def setPosHpr(self, x, y, z, h, p ,r):
+ """Set the pos hpr as dictated by the AI."""
+ self.startingPos = Vec3(x, y, z)
+ self.enteringPos = Vec3(x, y, z - 10)
+ self.startingHpr = Vec3(h, 0, 0)
+ self.golfKart.setPosHpr( x, y, z, h, 0, 0 )
+
+
+
+ def fillSlot(self, index, avId):
+ """Put someone in the kart, as dictated by the AI."""
+ self.notify.debug("%s.fillSlot(%s, %s, ...)" % (self.doId, index, avId))
+ request = self.toonRequests.get(index)
+ if request:
+ self.cr.relatedObjectMgr.abortRequest(request)
+ del self.toonRequests[index]
+
+ if avId == 0:
+ # This means that the slot is now empty, and no action should
+ # be taken.
+ pass
+
+ elif not self.cr.doId2do.has_key(avId):
+ # It's someone who hasn't been generated yet.
+ func = PythonUtil.Functor(
+ self.gotToon, index, avId)
+
+ assert not self.toonRequests.has_key(index)
+ self.toonRequests[index] = self.cr.relatedObjectMgr.requestObjects(
+ [avId], allCallback = func)
+
+ elif not self.isSetup:
+ # We haven't set up the elevator yet.
+ self.deferredSlots.append((index, avId))
+
+ else:
+ # If localToon is boarding, he needs to change state
+ if avId == base.localAvatar.getDoId():
+ self.localToonOnBoard = 1
+ elevator = self.getPlaceElevator()
+ elevator.fsm.request("boarding", [self.getElevatorModel()])
+ elevator.fsm.request("boarded")
+
+ toon = self.cr.doId2do[avId]
+ # Parent it to the elevator
+ toon.stopSmooth()
+ toon.wrtReparentTo(self.golfKart )
+
+ sitStartDuration = toon.getDuration("sit-start")
+ jumpTrack = self.generateToonJumpTrack(toon, index)
+
+ track = Sequence(
+ jumpTrack,
+ Func(toon.setAnimState, "Sit", 1.0),
+ Func(self.clearToonTrack, avId),
+ name = toon.uniqueName("fillElevator"),
+ autoPause = 1)
+ track.delayDelete = DelayDelete.DelayDelete(toon, 'fillSlot')
+ self.storeToonTrack(avId, track)
+ track.start()
+
+ assert avId not in self.boardedAvIds
+ self.boardedAvIds[avId] = None
+
+
+ def generateToonJumpTrack( self, av, seatIndex ):
+ """Return an interval of the toon jumping into the golf kart."""
+ av.pose('sit', 47)
+ hipOffset = av.getHipsParts()[2].getPos(av)
+
+ def getToonJumpTrack( av, seatIndex ):
+ # using a local func allows the ProjectileInterval to
+ # calculate this pos at run-time
+ def getJumpDest(av = av, node = self.golfKart):
+ dest = Point3(0,0,0)
+ if hasattr(self, 'golfKart') and self.golfKart:
+ dest = Vec3(self.golfKart.getPos(av.getParent()))
+ seatNode = self.golfKart.find("**/seat" + str(seatIndex + 1))
+ dest += seatNode.getPos(self.golfKart)
+ dna = av.getStyle()
+ dest -= hipOffset
+ if(seatIndex < 2):
+ dest.setY( dest.getY() + 2 * hipOffset.getY())
+ dest.setZ(dest.getZ() + 0.1)
+ else:
+ self.notify.warning('getJumpDestinvalid golfKart, returning (0,0,0)')
+ return dest
+
+ def getJumpHpr(av = av, node = self.golfKart):
+ hpr = Point3(0,0,0)
+ if hasattr(self, 'golfKart') and self.golfKart:
+ hpr = self.golfKart.getHpr(av.getParent())
+ if(seatIndex < 2):
+ hpr.setX( hpr.getX() + 180)
+ else:
+ hpr.setX( hpr.getX() )
+ angle = PythonUtil.fitDestAngle2Src(av.getH(), hpr.getX())
+ hpr.setX(angle)
+ else:
+ self.notify.warning('getJumpHpr invalid golfKart, returning (0,0,0)')
+ return hpr
+
+ toonJumpTrack = Parallel(
+ ActorInterval( av, 'jump' ),
+ Sequence(
+ Wait( 0.43 ),
+ Parallel( LerpHprInterval( av,
+ hpr = getJumpHpr,
+ duration = .9 ),
+ ProjectileInterval( av,
+ endPos = getJumpDest,
+ duration = .9 )
+ ),
+ )
+ )
+ return toonJumpTrack
+
+ def getToonSitTrack( av ):
+ toonSitTrack = Sequence(
+
+ ActorInterval( av, 'sit-start' ),
+ Func( av.loop, 'sit' )
+ )
+ return toonSitTrack
+
+ toonJumpTrack = getToonJumpTrack( av, seatIndex )
+ toonSitTrack = getToonSitTrack( av )
+
+ jumpTrack = Sequence(
+ Parallel(
+ toonJumpTrack,
+ Sequence( Wait(1),
+ toonSitTrack,
+ ),
+ ),
+ Func( av.wrtReparentTo, self.golfKart ),
+ )
+
+ return jumpTrack
+
+
+ def emptySlot(self, index, avId, bailFlag, timestamp):
+ """Remove someone as dictated by the AI."""
+ # If localToon is exiting, he needs to change state
+ if avId == 0:
+ # This means that no one is currently exiting, and no action
+ # should be taken
+ pass
+
+ elif not self.isSetup:
+ # We haven't set up the elevator yet. Remove the toon
+ # from the deferredSlots list, if it is there.
+ newSlots = []
+ for slot in self.deferredSlots:
+ if slot[0] != index:
+ newSlots.append(slot)
+
+ self.deferredSlots = newSlots
+
+ else:
+ if self.cr.doId2do.has_key(avId):
+ # See if we need to reset the clock
+ # (countdown assumes we've created a clockNode already)
+ if (bailFlag == 1 and hasattr(self, 'clockNode')):
+ if (timestamp < self.countdownTime and
+ timestamp >= 0):
+ self.countdown(self.countdownTime - timestamp)
+ else:
+ self.countdown(self.countdownTime)
+ # If the toon exists, look it up
+ toon = self.cr.doId2do[avId]
+ # avoid wrtReparent so that we don't muck with the toon's scale
+ ## Parent it to render
+ #toon.wrtReparentTo(render)
+ toon.stopSmooth()
+
+ sitStartDuration = toon.getDuration("sit-start")
+ jumpOutTrack = self.generateToonReverseJumpTrack(toon, index)
+
+ # Place it on the appropriate spot relative to the
+ # elevator
+
+ track = Sequence(
+ # TODO: Find the right coords for the elevator
+ jumpOutTrack,
+ Func(self.clearToonTrack, avId),
+ # Tell the toon he is free to roam now
+ Func(self.notifyToonOffElevator, toon),
+ name = toon.uniqueName("emptyElevator"),
+ autoPause = 1)
+ track.delayDelete = DelayDelete.DelayDelete(toon, 'ClubElevator.emptySlot')
+ self.storeToonTrack(avId, track)
+ track.start()
+
+ # Tell localToon he is exiting (if localToon is on board)
+ if avId == base.localAvatar.getDoId():
+ messenger.send("exitElevator")
+
+ # if the elevator is generated as a toon is leaving it,
+ # we will not have gotten a corresponding 'fillSlot' message
+ # for that toon, hence the toon will not be found in
+ # boardedAvIds
+ if avId in self.boardedAvIds:
+ del self.boardedAvIds[avId]
+
+ else:
+ self.notify.warning("toon: " + str(avId) +
+ " doesn't exist, and" +
+ " cannot exit the elevator!")
+
+ def generateToonReverseJumpTrack( self, av, seatIndex ):
+ """Return an interval of the toon jumping out of the golf kart."""
+ self.notify.debug("av.getH() = %s" % av.getH())
+ def getToonJumpTrack( av, destNode ):
+ # using a local func allows the ProjectileInterval to
+ # calculate this pos at run-time
+ def getJumpDest(av = av, node = destNode):
+ dest = node.getPos(av.getParent())
+ dest += Vec3(*self.JumpOutOffsets[seatIndex])
+ return dest
+
+ def getJumpHpr(av = av, node = destNode):
+ hpr = node.getHpr(av.getParent())
+ hpr.setX( hpr.getX() + 180)
+ angle = PythonUtil.fitDestAngle2Src(av.getH(), hpr.getX())
+ hpr.setX(angle)
+ return hpr
+
+ toonJumpTrack = Parallel(
+ ActorInterval( av, 'jump' ),
+ Sequence(
+ Wait( 0.1), #43 ),
+ Parallel( #LerpHprInterval( av,
+ # hpr = getJumpHpr,
+ # duration = .9 ),
+ ProjectileInterval( av,
+ endPos = getJumpDest,
+ duration = .9 ) )
+ )
+ )
+ return toonJumpTrack
+
+ toonJumpTrack = getToonJumpTrack( av, self.golfKart)
+ jumpTrack = Sequence(
+ toonJumpTrack,
+ Func( av.loop, 'neutral' ),
+ Func( av.wrtReparentTo, render ),
+ #Func( self.av.setPosHpr, self.exitMovieNode, 0,0,0,0,0,0 ),
+ )
+ return jumpTrack
+
+ def startCountdownClock(self, countdownTime, ts):
+ """Start the countdown clock."""
+ # just reverse the text counter
+ DistributedElevatorFSM.DistributedElevatorFSM.startCountdownClock(self, countdownTime, ts)
+ self.clock.setH(self.clock.getH() + 180)
+
+
+ def storeToonTrack(self, avId, track):
+ # Clear out any currently playing tracks on this toon
+ self.clearToonTrack(avId)
+ # Store this new one
+ self.__toonTracks[avId] = track
+
+ def clearToonTrack(self, avId):
+ # Clear out any currently playing tracks on this toon
+ oldTrack = self.__toonTracks.get(avId)
+ if oldTrack:
+ oldTrack.pause()
+ if self.__toonTracks.get(avId):
+ DelayDelete.cleanupDelayDeletes(self.__toonTracks[avId])
+ del self.__toonTracks[avId]
+
+ def clearToonTracks(self):
+ #We can't use an iter because we are deleting keys
+ keyList = []
+ for key in self.__toonTracks:
+ keyList.append(key)
+
+ for key in keyList:
+ if self.__toonTracks.has_key(key):
+ self.clearToonTrack(key)
diff --git a/toontown/src/building/DistributedClubElevatorAI.py b/toontown/src/building/DistributedClubElevatorAI.py
new file mode 100644
index 0000000..df5001a
--- /dev/null
+++ b/toontown/src/building/DistributedClubElevatorAI.py
@@ -0,0 +1,406 @@
+from otp.ai.AIBase import *
+from toontown.toonbase import ToontownGlobals
+from direct.distributed.ClockDelta import *
+from toontown.building import ElevatorConstants
+from toontown.building import DistributedElevatorFSMAI
+#from direct.fsm import ClassicFSM
+#from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm.FSM import FSM
+
+class DistributedClubElevatorAI(DistributedElevatorFSMAI.DistributedElevatorFSMAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedElevatorFloorAI")
+ #"""
+ defaultTransitions = {
+ 'Off' : [ 'Opening', 'Closed'],
+ 'Opening' : [ 'WaitEmpty', 'WaitCountdown', 'Opening', 'Closing' ],
+ 'WaitEmpty' : [ 'WaitCountdown', "Closing", 'WaitEmpty'],
+ 'WaitCountdown' : [ 'WaitEmpty', 'AllAboard', "Closing", 'WaitCountdown' ],
+ 'AllAboard' : [ 'WaitEmpty', "Closing" ],
+ 'Closing' : [ 'Closed', 'WaitEmpty', 'Closing', 'Opening' ],
+ 'Closed' : [ 'Opening' ],
+ }
+ #"""
+ id = 0
+ DoBlockedRoomCheck = simbase.config.GetBool("elevator-blocked-rooms-check",1)
+
+ def __init__(self, air, lawOfficeId,bldg, avIds, markerId = None, numSeats = 4, antiShuffle = 0, minLaff = 0):
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.__init__(
+ self, air, bldg, numSeats, antiShuffle = antiShuffle, minLaff = minLaff)
+ FSM.__init__( self, "ElevatorFloor_%s_FSM" % ( self.id ) )
+ # Do we need this?
+ # self.zoneId, dummy = bldg.getExteriorAndInteriorZoneId()
+ # Flag that tells if any Toon has jumped out of the elevator yet
+ # (this is used to prevent the griefers who jump off at the last
+ # second)
+ # self.type = ElevatorConstants.ELEVATOR_STAGE
+ self.type = ElevatorConstants.ELEVATOR_COUNTRY_CLUB
+ self.countdownTime = ElevatorConstants.ElevatorData[self.type]['countdown']
+ self.lawOfficeId = lawOfficeId
+ self.anyToonsBailed = 0
+ self.avIds = avIds
+ self.isEntering = 0
+ self.isLocked = 0
+ self.setLocked(0)
+ self.wantState = None
+ self.latchRoom = None
+ self.setLatch(markerId)
+ self.zoneId = bldg.zoneId
+
+ def generate(self):
+ #print("DistributedElevatorFloorAI.generate")
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.generate(self)
+
+ def generateWithRequired(self, zoneId):
+ #print ("DistributedElevatorFloorAI generateWithRequired")
+ self.zoneId = zoneId
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.generateWithRequired(self, self.zoneId)
+
+
+ def delete(self):
+ # TODO: We really need an immediate clear here
+ # At least it does not crash the AI anymore
+ for seatIndex in range(len(self.seats)):
+ avId = self.seats[seatIndex]
+ if avId:
+ self.clearFullNow(seatIndex)
+ self.clearEmptyNow(seatIndex)
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.delete(self)
+
+ def getEntranceId(self):
+ return self.entranceId
+
+ def d_setFloor(self, floorNumber):
+ self.sendUpdate('setFloor', [floorNumber])
+
+ def avIsOKToBoard(self, av):
+ #print("DistributedElevatorFloorAI.avIsOKToBoard %s %s" % (self.accepting, self.isLocked))
+ return (av.hp > 0) and self.accepting and not self.isLocked
+
+ def acceptBoarder(self, avId, seatIndex):
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.acceptBoarder(self, avId, seatIndex)
+ # Add a hook that handles the case where the avatar exits
+ # the district unexpectedly
+ self.acceptOnce(self.air.getAvatarExitEvent(avId),
+ self.__handleUnexpectedExit, extraArgs=[avId])
+ # Put us into waitCountdown state... If we are already there,
+ # this won't do anything.
+
+ #import pdb; pdb.set_trace()
+ if self.state == "WaitEmpty" and (self.countFullSeats() < self.countAvsInZone()):
+ self.request("WaitCountdown")
+ self.bldg.elevatorAlert(avId)
+ elif (self.state in ("WaitCountdown", "WaitEmpty")) and (self.countFullSeats() >= self.countAvsInZone()):
+ taskMgr.doMethodLater(ElevatorConstants.TOON_BOARD_ELEVATOR_TIME, self.goAllAboard, self.quickBoardTask)
+
+ def countAvsInZone(self):
+ matchingZones = 0
+ for avId in self.bldg.avIds:
+ av = self.air.doId2do.get(avId)
+ if av:
+ if av.zoneId == self.bldg.zoneId:
+ matchingZones += 1
+ return matchingZones
+
+
+ def goAllAboard(self, throwAway = 1):
+ self.request("Closing")
+ return Task.done
+
+ def __handleUnexpectedExit(self, avId):
+ #print("DistributedElevatorFloorAI.__handleUnexpectedExit")
+ self.notify.warning("Avatar: " + str(avId) +
+ " has exited unexpectedly")
+ # Find the exiter's seat index
+ seatIndex = self.findAvatar(avId)
+ # Make sure the avatar is really here
+ if seatIndex == None:
+ pass
+ else:
+ # If the avatar is here, his seat is now empty.
+ self.clearFullNow(seatIndex)
+ # Tell the clients that the avatar is leaving that seat
+ self.clearEmptyNow(seatIndex)
+ #self.sendUpdate("emptySlot" + str(seatIndex),
+ # [avId, globalClockDelta.getRealNetworkTime()])
+ # If all the seats are empty, go back into waitEmpty state
+ if self.countFullSeats() == 0:
+ self.request('WaitEmpty')
+
+
+ def acceptExiter(self, avId):
+ #print("DistributedElevatorFloorAI.acceptExiter")
+ # Find the exiter's seat index
+ seatIndex = self.findAvatar(avId)
+ # It is possible that the avatar exited the shard unexpectedly.
+ if seatIndex == None:
+ pass
+ else:
+ # Empty that seat
+ self.clearFullNow(seatIndex)
+ # Make sure there's no griefing by jumping off the elevator
+ # at the last second
+ bailFlag = 0
+ if (self.anyToonsBailed == 0):
+ bailFlag = 1
+ # Reset the clock
+ self.resetCountdown()
+ self.anyToonsBailed = 1
+ # Tell the clients that the avatar is leaving that seat
+ self.sendUpdate("emptySlot" + str(seatIndex),
+ [avId, bailFlag, globalClockDelta.getRealNetworkTime()])
+ # If all the seats are empty, go back into waitEmpty state
+ if self.countFullSeats() == 0:
+ self.request('WaitEmpty')
+ # Wait for the avatar to be done leaving the seat, and then
+ # declare the emptying overwith...
+ taskMgr.doMethodLater(ElevatorConstants.TOON_EXIT_ELEVATOR_TIME,
+ self.clearEmptyNow,
+ self.uniqueName("clearEmpty-%s" % seatIndex),
+ extraArgs = (seatIndex,))
+
+
+ def enterOpening(self):
+ #print("DistributedElevatorFloorAI.enterOpening %s" % (self.doId))
+ self.d_setState('Opening')
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterOpening(self)
+ taskMgr.doMethodLater(ElevatorConstants.ElevatorData[ElevatorConstants.ELEVATOR_NORMAL]['openTime'],
+ self.waitEmptyTask,
+ self.uniqueName('opening-timer'))
+
+ def exitOpening(self):
+ #print("DistributedElevatorFloorAI.exitOpening %s" % (self.doId))
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.exitOpening(self)
+ if self.isLocked:
+ self.wantState = 'closed'
+ if self.wantState == 'closed':
+ self.demand('Closing')
+
+
+
+ ##### WaitEmpty state #####
+
+ def waitEmptyTask(self, task):
+ #print("DistributedElevatorFloorAI.waitEmptyTask %s" % (self.doId))
+ self.request('WaitEmpty')
+ return Task.done
+
+ def enterWaitEmpty(self):
+ self.lastState = self.state
+ #print("DistributedElevatorFloorAI.enterWaitEmpty %s %s from %s" % (self.isLocked, self.doId, self.state))
+ #print("WAIT EMPTY FLOOR VATOR")
+ for i in range(len(self.seats)):
+ self.seats[i] = None
+ print self.seats
+ if self.wantState == 'closed':
+ self.demand('Closing')
+ else:
+ self.d_setState('WaitEmpty')
+ self.accepting = 1
+
+
+
+
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self):
+ self.lastState = self.state
+ #print("DistributedElevatorFloorAI.enterWaitCountdown %s from %s" % (self.doId, self.state))
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterWaitCountdown(self)
+ # Start the countdown...
+ taskMgr.doMethodLater(self.countdownTime, self.timeToGoTask,
+ self.uniqueName('countdown-timer'))
+ if self.lastState == 'WaitCountdown':
+ pass
+ #import pdb; pdb.set_trace()
+
+ def timeToGoTask(self, task):
+ #print("DistributedElevatorFloorAI.timeToGoTask %s" % (self.doId))
+ # It is possible that the players exited the district
+ if self.countFullSeats() > 0:
+ self.request("AllAboard")
+ else:
+ self.request('WaitEmpty')
+ return Task.done
+
+ def resetCountdown(self):
+ taskMgr.remove(self.uniqueName('countdown-timer'))
+ taskMgr.doMethodLater(self.countdownTime, self.timeToGoTask,
+ self.uniqueName('countdown-timer'))
+
+ def enterAllAboard(self):
+ #print("DISELEFLOORAI enter allAboard")
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterAllAboard(self)
+ currentTime = globalClock.getRealTime()
+ elapsedTime = currentTime - self.timeOfBoarding
+ self.notify.debug("elapsed time: " + str(elapsedTime))
+ waitTime = max(ElevatorConstants.TOON_BOARD_ELEVATOR_TIME - elapsedTime, 0)
+ taskMgr.doMethodLater(waitTime, self.closeTask,
+ self.uniqueName('waitForAllAboard'))
+
+ ##### Closing state #####
+
+ def closeTask(self, task):
+ #print("DistributedElevatorFloorAI.closeTask %s" % (self.doId))
+ # It is possible that the players exited the district
+ if self.countFullSeats() >= 1:#len(self.avIds):
+ self.request("Closing")
+ #print("closing")
+ else:
+ self.request('WaitEmpty')
+ #print("wait empty")
+ return Task.done
+
+ def enterClosing(self):
+ #print("DistributedElevatorFloorAI.enterClosing %s" % (self.doId))
+ if self.countFullSeats() > 0:
+ self.sendUpdate("kickToonsOut")
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterClosing(self)
+ taskMgr.doMethodLater(ElevatorConstants.ElevatorData[ElevatorConstants.ELEVATOR_STAGE]['closeTime'],
+ self.elevatorClosedTask,
+ self.uniqueName('closing-timer'))
+ self.d_setState('Closing')
+
+ def elevatorClosedTask(self, task):
+ #print("DistributedElevatorFloorAI.elevatorClosedTask %s" % (self.doId))
+ self.elevatorClosed()
+ return Task.done
+
+ def elevatorClosed(self):
+ if self.isLocked:
+ self.request("Closed")
+ return
+ #print("ELEVATOR CLOSED DOING CALCS")
+ numPlayers = self.countFullSeats()
+ # print(" NUMBER OF PLAYERS IS %s" % (numPlayers))
+ # It is possible the players exited the district
+ if (numPlayers > 0):
+
+ # Create a factory interior just for us
+
+ # Make a nice list for the factory
+ players = []
+ for i in self.seats:
+ if i not in [None, 0]:
+ players.append(i)
+ #lawOfficeZone = self.bldg.createLawOffice(self.lawOfficeId,
+ # self.entranceId, players)
+
+ sittingAvIds = [];
+
+ for seatIndex in range(len(self.seats)):
+ avId = self.seats[seatIndex]
+ if avId:
+ # Tell each player on the elevator that they should enter the factory
+ # And which zone it is in
+ #self.sendUpdateToAvatarId(avId, "setLawOfficeInteriorZone", [lawOfficeZone])
+ # Clear the fill slot
+ #self.clearFullNow(seatIndex)
+ sittingAvIds.append(avId)
+ pass
+ for avId in self.avIds:
+ if not avId in sittingAvIds:
+ #print("THIS AV ID %s IS NOT ON BOARD" % (avId))
+ pass
+
+
+
+ self.bldg.startNextFloor()
+
+ else:
+ self.notify.warning("The elevator left, but was empty.")
+ self.request("Closed")
+
+ def setLocked(self, locked):
+ self.isLocked = locked
+ if locked:
+ if self.state == 'WaitEmpty':
+ self.request('Closing')
+ if self.countFullSeats() == 0:
+ self.wantState = 'closed'
+ else:
+ self.wantState = 'opening'
+ else:
+ self.wantState = 'waitEmpty'
+ if self.state == 'Closed':
+ self.request('Opening')
+
+ def getLocked(self):
+ return self.isLocked
+
+ def unlock(self):
+ #print("DistributedElevatorFloorAI.unlock %s %s" % (self.isLocked, self.doId))
+ if self.isLocked:
+ self.setLocked(0)
+ #self.request('Opening')
+
+ def lock(self):
+ #print("DistributedElevatorFloorAI.lock %s %s" % (self.isLocked, self.doId))
+ if not self.isLocked:
+ self.setLocked(1)
+ #if self.state != 'Closed' or self.state != 'Closing':
+ #self.request('Closing')
+ #self.beClosed()
+
+
+ def start(self):
+ #print("DistributedElevatorFloorAI.start %s" % (self.doId))
+ self.quickBoardTask = self.uniqueName("quickBoard")
+ self.request('Opening')
+ #self.beClosed()
+
+ def beClosed(self):
+ #print("DistributedElevatorFloorAI.beClosed %s" % (self.doId))
+ #self.request('closed')
+ pass
+
+ def setEntering(self, entering):
+ self.isEntering = entering
+
+ def getEntering(self):
+ return self.isEntering
+
+ def enterClosed(self):
+ #print("DistributedElevatorFloorAI.enterClosed %s" % (self.doId))
+ #import pdb; pdb.set_trace()
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterClosed(self)
+ ##### WaitEmpty state #####
+ # Switch back into opening mode since we allow other Toons onboard
+ if self.wantState == 'closed':
+ pass
+ else:
+ self.demand("Opening")
+
+
+ def enterOff(self):
+ self.lastState = self.state
+ #print("DistributedElevatorFloorAI.enterOff")
+ if self.wantState == 'closed':
+ self.demand('Closing')
+ elif self.wantState == 'waitEmpty':
+ self.demand('WaitEmpty')
+
+ def setPos(self, pointPos):
+ self.sendUpdate('setPos', [pointPos[0],pointPos[1],pointPos[2]])
+
+ def setH(self, H):
+ self.sendUpdate('setH', [H])
+
+ def setLatch(self, markerId):
+ self.latch = markerId
+ #self.sendUpdate('setLatch', [markerId])
+
+ def getLatch(self):
+ return self.latch
+
+ def checkBoard(self, av):
+ if (av.hp < self.minLaff):
+ return ElevatorConstants.REJECT_MINLAFF
+ if self.DoBlockedRoomCheck and self.bldg:
+ if hasattr(self.bldg, "blockedRooms"):
+ if self.bldg.blockedRooms:
+ return ElevatorConstants.REJECT_BLOCKED_ROOM
+ return 0
diff --git a/toontown/src/building/DistributedDoor.py b/toontown/src/building/DistributedDoor.py
new file mode 100644
index 0000000..4d1b6cb
--- /dev/null
+++ b/toontown/src/building/DistributedDoor.py
@@ -0,0 +1,1159 @@
+""" DistributedDoor module: contains the DistributedDoor
+ class, the client side representation of a 'landmark door'."""
+
+from toontown.toonbase.ToonBaseGlobal import *
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObject
+from toontown.hood import ZoneUtil
+from toontown.suit import Suit
+from toontown.distributed import DelayDelete
+import FADoorCodes
+from direct.task.Task import Task
+import DoorTypes
+from toontown.toontowngui import TTDialog
+from toontown.toonbase import TTLocalizer
+from toontown.toontowngui import TeaserPanel
+from toontown.distributed.DelayDeletable import DelayDeletable
+
+if( __debug__ ):
+ import pdb
+
+class DistributedDoor(DistributedObject.DistributedObject, DelayDeletable):
+ """
+ DistributedDoor class: The client side representation of a
+ 'landmark door'. Each of these doors can also be 'entered' by bad
+ guys and toons. This object has to worry about updating
+ the display of the door and all of its components on the client's
+ machine. This object also has a server side representation
+ of it, DistributedDoorAI.
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedDoor')
+ #notify.setDebug(True)
+
+ def __init__(self, cr):
+ """constructor for the DistributedDoor"""
+ DistributedObject.DistributedObject.__init__(self, cr)
+
+ self.openSfx = base.loadSfx("phase_3.5/audio/sfx/Door_Open_1.mp3")
+ self.closeSfx = base.loadSfx("phase_3.5/audio/sfx/Door_Close_1.mp3")
+
+ # When we get enough information about the door, we can set up
+ # its nametag. The nametag points out nearby buildings to the
+ # player.
+ self.nametag = None
+
+ assert(self.notify.debug("__init__()"))
+ self.fsm = ClassicFSM.ClassicFSM('DistributedDoor_right',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['closing',
+ 'closed',
+ 'opening',
+ 'open']),
+ State.State('closing',
+ self.enterClosing,
+ self.exitClosing,
+ ['closed', 'opening']),
+ State.State('closed',
+ self.enterClosed,
+ self.exitClosed,
+ ['opening']),
+ State.State('opening',
+ self.enterOpening,
+ self.exitOpening,
+ ['open']),
+ State.State('open',
+ self.enterOpen,
+ self.exitOpen,
+ ['closing', 'open'])],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ self.exitDoorFSM = ClassicFSM.ClassicFSM('DistributedDoor_left',
+ [State.State('off',
+ self.exitDoorEnterOff,
+ self.exitDoorExitOff,
+ ['closing',
+ 'closed',
+ 'opening',
+ 'open']),
+ State.State('closing',
+ self.exitDoorEnterClosing,
+ self.exitDoorExitClosing,
+ ['closed', 'opening']),
+ State.State('closed',
+ self.exitDoorEnterClosed,
+ self.exitDoorExitClosed,
+ ['opening']),
+ State.State('opening',
+ self.exitDoorEnterOpening,
+ self.exitDoorExitOpening,
+ ['open']),
+ State.State('open',
+ self.exitDoorEnterOpen,
+ self.exitDoorExitOpen,
+ ['closing', 'open'])],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.exitDoorFSM.enterInitialState()
+ # self.generate will be called automatically.
+
+ self.specialDoorTypes = { DoorTypes.EXT_HQ:0, DoorTypes.EXT_COGHQ:0, DoorTypes.INT_COGHQ:0, DoorTypes.EXT_KS:0, DoorTypes.INT_KS:0 }
+
+ # bossbot door is narrower and will override this
+ self.doorX = 1.5
+
+ def generate(self):
+ """
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ assert(self.debugPrint("generate()"))
+ DistributedObject.DistributedObject.generate(self)
+ self.avatarTracks=[]
+ self.avatarExitTracks=[]
+ self.avatarIDList=[]
+ self.avatarExitIDList=[]
+ self.doorTrack=None
+ self.doorExitTrack=None
+
+ def disable(self):
+ assert(self.debugPrint("disable()"))
+
+ self.clearNametag()
+
+ # Go to the off state when the object is put in the cache
+ taskMgr.remove(self.checkIsDoorHitTaskName())
+ self.ignore(self.getEnterTriggerEvent())
+ self.ignore(self.getExitTriggerEvent())
+ self.ignore("clearOutToonInterior")
+ self.fsm.request("off")
+ self.exitDoorFSM.request("off")
+ if self.__dict__.has_key('building'):
+ del self.building
+
+ # Clean up a little more gracefully; we might get a disable
+ # message at any time.
+ self.finishAllTracks()
+ self.avatarIDList = []
+ self.avatarExitIDList = []
+
+ if hasattr(self, "tempDoorNodePath"):
+ self.tempDoorNodePath.removeNode()
+ del self.tempDoorNodePath
+ DistributedObject.DistributedObject.disable(self)
+ # self.delete() will automatically be called.
+
+ def delete(self):
+ assert(self.debugPrint("delete()"))
+ del self.fsm
+ del self.exitDoorFSM
+ del self.openSfx
+ del self.closeSfx
+ DistributedObject.DistributedObject.delete(self)
+
+ def wantsNametag(self):
+ """ return true if this door needs an arrow pointing to it. """
+ # We don't create nametags for interior doors.
+ return not ZoneUtil.isInterior(self.zoneId)
+
+ def setupNametag(self):
+ assert(self.debugPrint("setupNametag()"))
+ if not self.wantsNametag():
+ return
+ if self.nametag == None:
+ self.nametag = NametagGroup()
+ self.nametag.setFont(ToontownGlobals.getBuildingNametagFont())
+ if TTLocalizer.BuildingNametagShadow:
+ self.nametag.setShadow(*TTLocalizer.BuildingNametagShadow)
+ self.nametag.setContents(Nametag.CName)
+ self.nametag.setColorCode(NametagGroup.CCToonBuilding)
+ self.nametag.setActive(0)
+ self.nametag.setAvatar(self.getDoorNodePath())
+ # Since some buildings have multiple doors for the same
+ # building, we'll apply a uniquifying code so the
+ # building doesn't appear to have multiple nametags.
+ # Only the nearest door will be tagged.
+ self.nametag.setObjectCode(self.block)
+ name = self.cr.playGame.dnaStore.getTitleFromBlockNumber(self.block)
+ self.nametag.setName(name)
+ self.nametag.manage(base.marginManager)
+
+ def clearNametag(self):
+ assert(self.debugPrint("clearNametag()"))
+ if self.nametag != None:
+ self.nametag.unmanage(base.marginManager)
+ self.nametag.setAvatar(NodePath())
+ self.nametag = None
+
+ def getTriggerName(self):
+ # HQ doors (toon, cog, and kartshop) need to have an index appended, since they are
+ # the only type of door that supports multiple doors on a building.
+ if (self.doorType == DoorTypes.INT_HQ or
+ self.specialDoorTypes.has_key(self.doorType) ):
+ return ("door_trigger_" + str(self.block) + "_" +
+ str(self.doorIndex))
+ else:
+ # Only built ins can have door indices > 0.
+ assert(self.doorIndex == 0)
+ return ("door_trigger_" + str(self.block))
+
+ def getTriggerName_wip(self):
+ ####if ZoneUtil.tutorialDict:
+ #### return "door_trigger_%d" % (self.block, )
+ #name="door_trigger_%d_%d" % (self.block, self.doorIndex)
+ name="door_trigger_%d" % (self.doId, )
+ assert(self.debugPrint("getTriggerName() returning \"%s\""%(name, )))
+ return name
+
+ def getEnterTriggerEvent(self):
+ return "enter" + self.getTriggerName()
+
+ def getExitTriggerEvent(self):
+ return "exit" + self.getTriggerName()
+
+ def hideDoorParts(self):
+ if (self.specialDoorTypes.has_key(self.doorType)):
+ # This work gets done by the DNA in non HQ buildings.
+ self.hideIfHasFlat(self.findDoorNode("rightDoor"))
+ self.hideIfHasFlat(self.findDoorNode("leftDoor"))
+ self.findDoorNode("doorFrameHoleRight").hide()
+ self.findDoorNode("doorFrameHoleLeft").hide()
+ else:
+ return
+
+ def setTriggerName(self):
+ # This may seem strange, but it is an optimization. Buildings with
+ # just one door will already have the right name applied to the
+ # trigger poly (door_trigger_). This was done by
+ # dnaDoor.cxx. So if the doorIndex
+ # is None, we do nothing. Buildings with more than one door will
+ # have the wrong name applied to the trigger poly
+ # (door_trigger_), so we have to find it, and change it
+ # to (door_trigger__).
+ if (self.specialDoorTypes.has_key(self.doorType)):
+ building = self.getBuilding()
+ doorTrigger = building.find("**/door_" + str(self.doorIndex) +
+ "/**/door_trigger*")
+ doorTrigger.node().setName(self.getTriggerName())
+ else:
+ return
+
+ def setTriggerName_wip(self):
+ building = self.getBuilding()
+ doorTrigger = building.find("**/door_%d/**/door_trigger_%d"%(self.doorIndex, self.block))
+ if doorTrigger.isEmpty():
+ doorTrigger = building.find("**/door_trigger_%d"%(self.block, ))
+ if doorTrigger.isEmpty():
+ doorTrigger = building.find("**/door_%d/**/door_trigger_*"%(self.doorIndex,))
+ if doorTrigger.isEmpty():
+ doorTrigger = building.find("**/door_trigger_*")
+ assert(not doorTrigger.isEmpty())
+ doorTrigger.node().setName(self.getTriggerName())
+
+ def setZoneIdAndBlock(self, zoneId, block):
+ assert(self.notify.debug("setZoneIdAndBlock(zoneId="+str(zoneId)
+ +", block="+str(block)+") for doId=" + str(self.doId)))
+ self.zoneId=zoneId
+ self.block=block
+
+ def setDoorType(self, doorType):
+ self.notify.debug("Door type = " + str(doorType) +
+ " on door #" + str(self.doId))
+ self.doorType = doorType
+
+ def setDoorIndex(self, doorIndex):
+ assert(self.notify.debug("Door index = " + str(doorIndex) +
+ " on door #" + str(self.doId)))
+ self.doorIndex = doorIndex
+
+ def setSwing(self, flags):
+ """
+ false: swings inward, true: the door swings outward.
+ bit 1 is the left door, bit 2 is the right door:
+ """
+ self.leftSwing=(flags&1)!=0
+ self.rightSwing=(flags&2)!=0
+
+ def setOtherZoneIdAndDoId(self, zoneId, distributedObjectID):
+ assert(self.debugPrint("setOtherZoneIdAndDoId(zoneId="
+ +str(zoneId)+", distributedObjectID="+str(distributedObjectID)+")"))
+ self.otherZoneId=zoneId
+ self.otherDoId=distributedObjectID
+
+ def setState(self, state, timestamp):
+ assert(self.debugPrint("setState(%s, %d)" % (state, timestamp)))
+ self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+
+ def setExitDoorState(self, state, timestamp):
+ assert(self.debugPrint("setExitDoorState(%s, %d)" % (state, timestamp)))
+ self.exitDoorFSM.request(state,
+ [globalClockDelta.localElapsedTime(timestamp)])
+
+ def announceGenerate(self):
+ # This is called when the door is completely created, so we know
+ # that we have all the info we need to get a proper trigger event.
+ DistributedObject.DistributedObject.announceGenerate(self)
+ self.doPostAnnounceGenerate()
+
+ def doPostAnnounceGenerate(self):
+ """Setup needed stuff after we have an announceGenerate."""
+
+ # determine if this door has a flat or not
+ if self.doorType == DoorTypes.INT_STANDARD:
+ self.bHasFlat = True
+ else:
+ self.bHasFlat = not self.findDoorNode("door*flat", True).isEmpty()
+
+ # Hide doors parts if necessary
+ self.hideDoorParts()
+
+ # Find the door trigger poly, and give it the proper name if necessary
+ self.setTriggerName()
+
+ # Accept a hit on the door trigger
+ self.accept(self.getEnterTriggerEvent(), self.doorTrigger)
+ self.acceptOnce("clearOutToonInterior", self.doorTrigger)
+
+ # Set up the door nametag
+ self.setupNametag()
+
+ def getBuilding(self):
+ # Once we find it, we store it, so we don't have to find it again.
+ if (not self.__dict__.has_key('building')):
+ if self.doorType == DoorTypes.INT_STANDARD:
+ #if ZoneUtil.isInterior(self.zoneId):
+ # building interior.
+ # Hack: assume that the node above the door is the building:
+ door=render.find("**/leftDoor;+s")
+ assert(not door.isEmpty())
+ # Cut the door off of the nodePath to get the building:
+ self.building = door.getParent()
+ elif (self.doorType == DoorTypes.INT_HQ):
+ # I know this is a hackful way to find the building node,
+ # but it works.
+ door=render.find("**/door_0")
+ self.building = door.getParent()
+ elif (self.doorType == DoorTypes.INT_KS):
+ # I know this is a hackful way to find the building node,
+ # but it works.
+ # NOTE - separate to keep exterior kartshop index to match
+ # with interior index. (jjtaylor - 06/24/05)
+ self.building=render.find("**/KartShop_Interior*")
+ elif ((self.doorType == DoorTypes.EXT_STANDARD) or
+ (self.doorType == DoorTypes.EXT_HQ) or
+ (self.doorType == DoorTypes.EXT_KS)):
+ # street or playground.
+ self.building=self.cr.playGame.hood.loader.geom.find(
+ "**/??"+str(self.block)+":*_landmark_*_DNARoot;+s")
+ if self.building.isEmpty(): # [gjeon] for animated buildlngs
+ self.building = self.cr.playGame.hood.loader.geom.find(
+ "**/??"+str(self.block)+":animated_building_*_DNARoot;+s")
+ elif ((self.doorType == DoorTypes.EXT_COGHQ) or
+ (self.doorType == DoorTypes.INT_COGHQ)):
+ self.building=self.cr.playGame.hood.loader.geom
+ else:
+ self.notify.error("No such door type as " + str(self.doorType))
+
+ assert(not self.building.isEmpty())
+ return self.building
+
+ def getBuilding_wip(self):
+ # Once we find it, we store it, so we don't have to find it again.
+ if (not self.__dict__.has_key('building')):
+ if self.__dict__.has_key('block'):
+ self.building=self.cr.playGame.hood.loader.geom.find(
+ "**/??"+str(self.block)+":*_landmark_*_DNARoot;+s")
+ else:
+ self.building=self.cr.playGame.hood.loader.geom
+ print "---------------- door is interior -------"
+
+ #if ZoneUtil.isInterior(self.zoneId):
+ # self.building=self.cr.playGame.hood.loader.geom
+ # #self.building=render
+ # print "---------------- door is interior -------"
+ #else:
+ # self.building=self.cr.playGame.hood.loader.geom.find(
+ # "**/??"+str(self.block)+":*_landmark_*_DNARoot;+s")
+
+ # building interior.
+ # Hack: assume that the node above the door is the building:
+ #door=render.find("**/leftDoor;+s")
+ #assert(not door.isEmpty())
+ # Cut the door off of the nodePath to get the building:
+ #self.building = door.getParent()
+ #if (self.doorType == DoorTypes.INT_STANDARD or
+ # self.doorType == DoorTypes.INT_HQ):
+ # # I know this is a hackful way to find the building node,
+ # # but it works.
+ # door=render.find("**/door_0")
+ # self.building = door.getParent()
+ #elif ((self.doorType == DoorTypes.EXT_STANDARD) or
+ # (self.doorType == DoorTypes.EXT_HQ)):
+ # # street or playground.
+ # self.building=self.cr.playGame.hood.loader.geom.find(
+ # "**/??"+str(self.block)+":*_landmark_*_DNARoot;+s")
+ #else:
+ # self.notify.error("No such door type as " + str(self.doorType))
+ assert(not self.building.isEmpty())
+ return self.building
+
+ def readyToExit(self):
+ assert(self.debugPrint("readyToExit()"))
+ base.transitions.fadeScreen(1.0)
+ # Ask permission to exit:
+ self.sendUpdate("requestExit")
+
+ def avatarEnterDoorTrack(self, avatar, duration):
+ trackName = "avatarEnterDoor-%d-%d" % (self.doId, avatar.doId)
+ track = Parallel(name = trackName)
+ otherNP=self.getDoorNodePath()
+
+ if hasattr(avatar, "stopSmooth"):
+ avatar.stopSmooth()
+
+ # Move the camera:
+ if avatar.doId == base.localAvatar.doId:
+ # Move the camera behind the toon:
+ track.append(
+ LerpPosHprInterval(nodePath=camera,
+ other=avatar,
+ duration=duration,
+ pos=Point3(0, -8, avatar.getHeight()),
+ hpr=VBase3(0, 0, 0),
+ blendType="easeInOut")
+ )
+
+ finalPos = avatar.getParent().getRelativePoint(
+ otherNP, Point3(self.doorX,2,ToontownGlobals.FloorOffset))
+
+ # Move the avatar:
+ moveHere = Sequence(
+ self.getAnimStateInterval(avatar, 'walk'),
+ LerpPosInterval(nodePath=avatar,
+ duration=duration,
+ pos=finalPos,
+ blendType="easeIn")
+ )
+ track.append(moveHere)
+
+ # iris:
+ if (avatar.doId == base.localAvatar.doId):
+ track.append(Sequence(
+ Wait(duration*0.5),
+ Func(base.transitions.irisOut, duration*0.5),
+ Wait(duration*0.5),
+ Func(avatar.b_setParent, ToontownGlobals.SPHidden),
+ ))
+
+ # Prevent the avatar from being deleted:
+ track.delayDelete = DelayDelete.DelayDelete(avatar, 'avatarEnterDoorTrack')
+ return track
+
+ def avatarEnqueueTrack(self, avatar, duration):
+ if hasattr(avatar, "stopSmooth"):
+ avatar.stopSmooth()
+ # Move the avatar:
+ back=-5.-(2.*len(self.avatarIDList))
+ if back<-9.:
+ back=-9.
+ offset=Point3(self.doorX, back, ToontownGlobals.FloorOffset)
+ otherNP=self.getDoorNodePath()
+ walkLike= ActorInterval(
+ avatar, 'walk',
+ startTime=1, duration=duration,
+ endTime=0.0001
+ )
+ standHere = Sequence(
+ LerpPosHprInterval(
+ nodePath=avatar,
+ other=otherNP,
+ duration=duration,
+ pos=offset,
+ hpr=VBase3(0,0,0), # hpr will come from otherNP.
+ blendType="easeInOut"),
+ self.getAnimStateInterval(avatar, 'neutral'),
+ )
+ # Create the track:
+ trackName = "avatarEnqueueDoor-%d-%d" % (self.doId, avatar.doId)
+ track = Parallel(walkLike, standHere, name = trackName)
+ track.delayDelete = DelayDelete.DelayDelete(avatar, 'avatarEnqueueTrack')
+ return track
+
+ def getAnimStateInterval(self, avatar, animName):
+ """
+ returns a FunctionInterval that sets the indicated animation
+ playing on the avatar, however that should be accomplished.
+ Unfortunately, this is slightly different for Suits and Toons.
+ """
+ isSuit = isinstance(avatar, Suit.Suit)
+ if isSuit:
+ return Func(avatar.loop, animName, 0)
+ else:
+ return Func(avatar.setAnimState, animName)
+
+ def isDoorHit(self):
+ """
+ We're checking the angle of attack from the avatar to the
+ door. This is to reduce that, "I got sucked into a door"
+ effect.
+ """
+ #assert(self.spamPrint("isDoorHit()"))
+ vec=base.localAvatar.getRelativeVector(
+ self.currentDoorNp,
+ self.currentDoorVec)
+ netScale = self.currentDoorNp.getNetTransform().getScale()
+ # the door in bossbot has been scaled down, account for that
+ yToTest = vec.getY() / netScale[1]
+ assert(self.debugPrint(" door dot: % .04f" % (vec.getY())))
+ # return true if the avatar is +-60 degrees of looking at the door:
+ return yToTest < -0.5
+
+ def enterDoor(self):
+ assert(self.debugPrint("enterDoor()"))
+ if self.allowedToEnter():
+ messenger.send("DistributedDoor_doorTrigger")
+ self.sendUpdate("requestEnter") # calls back with a avatarEnter.
+ else:
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.fsm.request('stopped')
+ self.dialog = TeaserPanel.TeaserPanel(pageName='otherHoods',
+ doneFunc=self.handleOkTeaser)
+
+ def handleOkTeaser(self):
+ """Handle the user clicking ok on the teaser panel."""
+ self.accept(self.getEnterTriggerEvent(), self.doorTrigger)
+ self.dialog.destroy()
+ del self.dialog
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.fsm.request('walk')
+
+
+ def allowedToEnter(self):
+ """Check if the local toon is allowed to enter."""
+ if base.cr.isPaid():
+ return True
+ place = base.cr.playGame.getPlace()
+ myHoodId = ZoneUtil.getCanonicalHoodId(place.zoneId)
+ # if we're in the estate we should use place.id
+ if hasattr(place, 'id'):
+ myHoodId = place.id
+ if myHoodId in \
+ (ToontownGlobals.ToontownCentral,
+ ToontownGlobals.MyEstate,
+ ToontownGlobals.GoofySpeedway,
+ ToontownGlobals.Tutorial,
+ ):
+ # trialer going to TTC/Estate/Goofy Speedway, let them through
+ return True
+ return False
+
+
+ def checkIsDoorHitTaskName(self):
+ return 'checkIsDoorHit'+self.getTriggerName()
+
+ def checkIsDoorHitTask(self, task):
+ """
+ Check to see if the avatar has turned to face the door that
+ they are already colliding with.
+ """
+ #assert(self.spamPrint("checkIsDoorHitTask()"))
+ if (self.isDoorHit()):
+ self.ignore(self.checkIsDoorHitTaskName())
+ self.ignore(self.getExitTriggerEvent())
+ self.enterDoor()
+ return Task.done
+ return Task.cont
+
+ def cancelCheckIsDoorHitTask(self, args):
+ """
+ Stop waiting to see if the avatar is going to turn to face the
+ door. Instead go back to the default mode of waiting for the
+ avatar to hit the door at all.
+ """
+ taskMgr.remove(self.checkIsDoorHitTaskName())
+ del self.currentDoorNp
+ del self.currentDoorVec
+ self.ignore(self.getExitTriggerEvent())
+ self.accept(self.getEnterTriggerEvent(), self.doorTrigger)
+
+ def doorTrigger(self, args=None):
+ """Call doorTrigger when the avatar first collides with the door."""
+ assert(self.debugPrint("doorTrigger(args="+str(args)+")"))
+ self.ignore(self.getEnterTriggerEvent())
+ if (args==None):
+ # ...we want the trigger to work when the
+ # clearOutToonInterior is sent.
+ self.enterDoor()
+ else:
+ self.currentDoorNp=NodePath(args.getIntoNodePath())
+ self.currentDoorVec=Vec3(args.getSurfaceNormal(self.currentDoorNp))
+ if (self.isDoorHit()):
+ self.enterDoor()
+ else:
+ # ...the door was hit by the side or back of the toon.
+ # Start a task to see if the avatar turns to face the door:
+ self.accept(self.getExitTriggerEvent(), self.cancelCheckIsDoorHitTask)
+ taskMgr.add(self.checkIsDoorHitTask, self.checkIsDoorHitTaskName())
+
+ def avatarEnter(self, avatarID):
+ """Server approves Toon or cog to enter door queue"""
+ assert(self.debugPrint("avatarEnter(avatarID="+str(avatarID)+")"))
+ avatar = self.cr.doId2do.get(avatarID, None)
+ if avatar:
+ avatar.setAnimState('neutral')
+ track=self.avatarEnqueueTrack(avatar, 0.5)
+ track.start()
+ self.avatarTracks.append(track)
+ self.avatarIDList.append(avatarID)
+
+ def rejectEnter(self, reason):
+ assert(self.debugPrint("rejectEnter()"))
+ message = FADoorCodes.reasonDict[reason]
+ if message:
+ self.__faRejectEnter(message)
+ else:
+ self.__basicRejectEnter()
+
+ def __basicRejectEnter(self):
+ """Server doesn't let the avatar in the door queue, but
+ there's no reason we can tell the user."""
+ assert(self.debugPrint("basicRejectEnter()"))
+ # Hang the hook again
+ self.accept(self.getEnterTriggerEvent(), self.doorTrigger)
+ # Go back into walk mode.
+ if self.cr.playGame.getPlace():
+ self.cr.playGame.getPlace().setState('walk')
+
+ def __faRejectEnter(self, message):
+ assert(self.debugPrint("faRejectEnter()"))
+ self.rejectDialog = TTDialog.TTGlobalDialog(
+ message = message,
+ doneEvent = "doorRejectAck",
+ style = TTDialog.Acknowledge)
+ self.rejectDialog.show()
+ self.rejectDialog.delayDelete = DelayDelete.DelayDelete(self, '__faRejectEnter')
+
+ event = 'clientCleanup'
+ self.acceptOnce(event, self.__handleClientCleanup)
+
+ # Make the toon stand still.
+ base.cr.playGame.getPlace().setState('stopped')
+ # Hang a hook for hitting OK
+ self.acceptOnce("doorRejectAck", self.__handleRejectAck)
+ self.acceptOnce("stoppedAsleep", self.__handleFallAsleepDoor)
+
+ def __handleClientCleanup(self):
+ """Handle the user closing the toontown window when the reject dialog is up."""
+ if hasattr(self,'rejectDialog') and self.rejectDialog:
+ self.rejectDialog.doneStatus = 'ok'
+ self.__handleRejectAck()
+
+ def __handleFallAsleepDoor(self):
+ # it's 'ok' to fall asleep =]
+ self.rejectDialog.doneStatus = 'ok'
+ self.__handleRejectAck()
+
+ def __handleRejectAck(self):
+ self.ignore("doorRejectAck")
+ self.ignore("stoppedAsleep")
+ self.ignore('clientCleanup')
+ doneStatus = self.rejectDialog.doneStatus
+ if doneStatus != "ok":
+ self.notify.error("Unrecognized doneStatus: " +
+ str(doneStatus))
+ self.__basicRejectEnter()
+ self.rejectDialog.delayDelete.destroy()
+ self.rejectDialog.cleanup()
+ del self.rejectDialog
+
+ def getDoorNodePath(self):
+ assert(self.debugPrint("getDoorNodePath()"))
+ if self.doorType == DoorTypes.INT_STANDARD:
+ # ...interior door.
+ assert(self.debugPrint("getDoorNodePath() -- isInterior"))
+ otherNP=render.find("**/door_origin")
+ assert(not otherNP.isEmpty())
+ elif self.doorType == DoorTypes.EXT_STANDARD:
+ if hasattr(self, "tempDoorNodePath"):
+ return self.tempDoorNodePath
+ else:
+ # ...exterior door.
+ assert(self.debugPrint("getDoorNodePath() -- exterior"))
+ posHpr=self.cr.playGame.dnaStore.getDoorPosHprFromBlockNumber(self.block)
+ # This will be used as a relative node, even though
+ # it is not attached to render.
+ otherNP=NodePath("doorOrigin")
+ otherNP.setPos(posHpr.getPos())
+ otherNP.setHpr(posHpr.getHpr())
+ # Store this for clean up later
+ self.tempDoorNodePath=otherNP
+ elif (self.specialDoorTypes.has_key(self.doorType)):
+ building = self.getBuilding()
+ otherNP = building.find("**/door_origin_" + str(self.doorIndex))
+ assert(not otherNP.isEmpty())
+ elif (self.doorType == DoorTypes.INT_HQ):
+ otherNP=render.find("**/door_origin_" + str(self.doorIndex))
+ assert(not otherNP.isEmpty())
+ else:
+ self.notify.error("No such door type as " + str(self.doorType))
+
+ return otherNP
+
+ def avatarExitTrack(self, avatar, duration):
+ assert(self.debugPrint("avatarExitTrack(avatar="+str(avatar)
+ +", duration="+str(duration)+")"))
+ if hasattr(avatar, "stopSmooth"):
+ avatar.stopSmooth()
+ # Get the pos and hpr of the door origin:
+ otherNP=self.getDoorNodePath()
+
+ trackName = "avatarExitDoor-%d-%d" % (self.doId, avatar.doId)
+ track = Sequence(name = trackName)
+
+ # Put the avatar in the doorway:
+ track.append(self.getAnimStateInterval(avatar, 'walk'))
+ track.append(PosHprInterval(
+ avatar,
+ Point3(-self.doorX, 0, ToontownGlobals.FloorOffset),
+ VBase3(179, 0, 0),
+ other=otherNP))
+ track.append(Func(avatar.setParent, ToontownGlobals.SPRender))
+
+ if avatar.doId==base.localAvatar.doId:
+ # Position the camera:
+ track.append(PosHprInterval(
+ camera,
+ VBase3(-self.doorX, 5, avatar.getHeight()),
+ VBase3(180, 0, 0),
+ other=otherNP
+ ))
+
+ # Move the avatar through:
+ if (avatar.doId == base.localAvatar.doId):
+ finalPos = render.getRelativePoint(
+ otherNP, Point3(-self.doorX, -6, ToontownGlobals.FloorOffset))
+ else:
+ finalPos = render.getRelativePoint(
+ otherNP, Point3(-self.doorX, -3, ToontownGlobals.FloorOffset))
+
+ track.append(LerpPosInterval(
+ nodePath=avatar,
+ duration=duration,
+ pos=finalPos,
+ blendType="easeInOut"))
+
+ if (avatar.doId == base.localAvatar.doId):
+ # Cleanup localToon:
+ track.append(Func(self.exitCompleted))
+ # iris in:
+ track.append(Func(base.transitions.irisIn))
+
+ if hasattr(avatar, "startSmooth"):
+ track.append(Func(avatar.startSmooth))
+
+ track.delayDelete = DelayDelete.DelayDelete(avatar, 'DistributedDoor.avatarExitTrack')
+ return track
+
+ def exitCompleted(self):
+ assert(self.notify.debug('exitCompleted()'))
+ base.localAvatar.setAnimState('neutral')
+ place = self.cr.playGame.getPlace()
+ if place:
+ place.setState('walk')
+
+ # This is just to ensure the distributed parent gets set properly.
+ base.localAvatar.d_setParent(ToontownGlobals.SPRender)
+
+ def avatarExit(self, avatarID):
+ assert(self.debugPrint("avatarExit(avatarID="+str(avatarID)+")"))
+ # Animate the avatar
+ if (avatarID in self.avatarIDList):
+ # ...this avatar was waiting to go in, and bailed out.
+ assert(self.notify.debug(" bailed out"))
+ self.avatarIDList.remove(avatarID)
+ if (avatarID == base.localAvatar.doId):
+ self.exitCompleted()
+ else:
+ # ...this avatar just went through the out door.
+ assert(self.notify.debug(" regular exit"))
+ self.avatarExitIDList.append(avatarID)
+
+ def finishDoorTrack(self):
+ if self.doorTrack:
+ self.doorTrack.finish()
+ self.doorTrack=None
+
+ def finishDoorExitTrack(self):
+ if self.doorExitTrack:
+ self.doorExitTrack.finish()
+ self.doorExitTrack=None
+
+ def finishAllTracks(self):
+ self.finishDoorTrack()
+ self.finishDoorExitTrack()
+
+ for t in self.avatarTracks:
+ t.finish()
+ DelayDelete.cleanupDelayDeletes(t)
+ self.avatarTracks = []
+
+ for t in self.avatarExitTracks:
+ t.finish()
+ DelayDelete.cleanupDelayDeletes(t)
+ self.avatarExitTracks = []
+
+ ##### off state #####
+
+ def enterOff(self):
+ assert(self.debugPrint("enterOff()"))
+
+ def exitOff(self):
+ assert(self.debugPrint("exitOff()"))
+
+ ##### closing state #####
+
+ def getRequestStatus(self):
+ zoneId=self.otherZoneId
+ # We must set allowRedirect to 0 because we expect to meet
+ # our other door on the other side.
+ request={
+ "loader": ZoneUtil.getBranchLoaderName(zoneId),
+ "where": ZoneUtil.getToonWhereName(zoneId),
+ "how": "doorIn",
+ "hoodId": ZoneUtil.getHoodId(zoneId),
+ "zoneId": zoneId,
+ "shardId": None,
+ "avId": -1,
+ "allowRedirect" : 0,
+ "doorDoId":self.otherDoId
+ }
+ return request
+
+ def enterClosing(self, ts):
+ assert(self.debugPrint("enterClosing()"))
+ # Start animation:
+ # The right hole doorway:
+ doorFrameHoleRight=self.findDoorNode("doorFrameHoleRight")
+ if (doorFrameHoleRight.isEmpty()):
+ self.notify.warning("enterClosing(): did not find doorFrameHoleRight")
+ return
+
+ # Hmmm, you can try setting the door color to something else
+ # other than black. I tried white, but that doesn't look to
+ # good either.
+ #if ZoneUtil.isInterior(self.zoneId):
+ # doorFrameHoleRight.setColor(1., 1., 1., 1.)
+
+ # Right door:
+ rightDoor=self.findDoorNode("rightDoor")
+ if (rightDoor.isEmpty()):
+ self.notify.warning("enterClosing(): did not find rightDoor")
+ return
+
+ # Close the door:
+ otherNP=self.getDoorNodePath()
+ trackName = "doorClose-%d" % (self.doId)
+ if self.rightSwing:
+ h = 100
+ else:
+ h = -100
+ # Stop animation:
+ self.finishDoorTrack()
+ self.doorTrack=Sequence(
+ LerpHprInterval(
+ nodePath=rightDoor,
+ duration=1.0,
+ hpr=VBase3(0, 0, 0),
+ startHpr=VBase3(h, 0, 0),
+ other=otherNP,
+ blendType="easeInOut"),
+ Func(doorFrameHoleRight.hide),
+ Func(self.hideIfHasFlat, rightDoor),
+ SoundInterval(self.closeSfx, node=rightDoor),
+ name = trackName)
+ self.doorTrack.start(ts)
+ if hasattr(self, "done"):
+ request = self.getRequestStatus()
+ messenger.send("doorDoneEvent", [request])
+
+ def exitClosing(self):
+ assert(self.debugPrint("exitClosing()"))
+
+ ##### closed state #####
+
+ def enterClosed(self, ts):
+ assert(self.debugPrint("enterClosed()"))
+
+ def exitClosed(self):
+ assert(self.debugPrint("exitClosed()"))
+
+ ##### opening state #####
+
+ def enterOpening(self, ts):
+ #if( __debug__ ):
+ # import pdb
+ # pdb.set_trace()
+ assert(self.debugPrint("enterOpening()"))
+ # Start animation:
+ # The right doorway:
+ doorFrameHoleRight=self.findDoorNode("doorFrameHoleRight")
+ if (doorFrameHoleRight.isEmpty()):
+ self.notify.warning("enterOpening(): did not find doorFrameHoleRight")
+ return
+
+ # Hmmm, you can try setting the door color to something else
+ # other than black. I tried white, but that doesn't look to
+ # good either.
+ #if ZoneUtil.isInterior(self.zoneId):
+ # doorFrameHoleRight.setColor(1., 1., 1., 1.)
+
+ # Right door:
+ rightDoor=self.findDoorNode("rightDoor")
+ if (rightDoor.isEmpty()):
+ self.notify.warning("enterOpening(): did not find rightDoor")
+ return
+ # Open the door:
+ otherNP=self.getDoorNodePath()
+ trackName = "doorOpen-%d" % (self.doId)
+ if self.rightSwing:
+ h = 100
+ else:
+ h = -100
+ # Stop animation:
+ self.finishDoorTrack()
+ self.doorTrack=Parallel(
+ SoundInterval(self.openSfx, node=rightDoor),
+ Sequence(
+ HprInterval(
+ rightDoor,
+ VBase3(0, 0, 0),
+ other=otherNP,
+ ),
+ Wait(0.4),
+ Func(rightDoor.show),
+ Func(doorFrameHoleRight.show),
+ LerpHprInterval(
+ nodePath=rightDoor,
+ duration=0.6,
+ hpr=VBase3(h, 0, 0),
+ startHpr=VBase3(0, 0, 0),
+ other=otherNP,
+ blendType="easeInOut")),
+ name = trackName)
+ # Start the tracks:
+ self.doorTrack.start(ts)
+
+ def exitOpening(self):
+ assert(self.debugPrint("exitOpening()"))
+
+ ##### open state #####
+
+ def enterOpen(self, ts):
+ assert(self.debugPrint("enterOpen()"))
+ # Run everybody in:
+ for avatarID in self.avatarIDList:
+ assert(self.notify.debug(" avatarID: "+str(avatarID)))
+ avatar = self.cr.doId2do.get(avatarID)
+ if avatar:
+ track=self.avatarEnterDoorTrack(avatar, 1.0)
+ track.start(ts)
+ self.avatarTracks.append(track)
+ if (avatarID == base.localAvatar.doId):
+ self.done=1
+ self.avatarIDList=[]
+
+ def exitOpen(self):
+ assert(self.debugPrint("exitOpen()"))
+ for track in self.avatarTracks:
+ track.finish()
+ DelayDelete.cleanupDelayDeletes(track)
+ self.avatarTracks=[]
+
+ ##### Exit Door off state #####
+
+ def exitDoorEnterOff(self):
+ assert(self.debugPrint("exitDoorEnterOff()"))
+
+ def exitDoorExitOff(self):
+ assert(self.debugPrint("exitDoorExitOff()"))
+
+ ##### Exit Door closing state #####
+
+ def exitDoorEnterClosing(self, ts):
+ assert(self.debugPrint("exitDoorEnterClosing()"))
+ # Start animation:
+ # The left hole doorway:
+ doorFrameHoleLeft=self.findDoorNode("doorFrameHoleLeft")
+ if (doorFrameHoleLeft.isEmpty()):
+ self.notify.warning("enterOpening(): did not find flatDoors")
+ return
+
+ #if ZoneUtil.isInterior(self.zoneId):
+ # doorFrameHoleLeft.setColor(1., 1., 1., 1.)
+ # Left door:
+ if self.leftSwing:
+ h = -100
+ else:
+ h = 100
+ leftDoor=self.findDoorNode("leftDoor")
+ if (not leftDoor.isEmpty()):
+ # Close the door:
+ otherNP=self.getDoorNodePath()
+ trackName = "doorExitTrack-%d" % (self.doId)
+ self.finishDoorExitTrack()
+ self.doorExitTrack = Sequence(
+ LerpHprInterval(
+ nodePath=leftDoor,
+ duration=1.0,
+ hpr=VBase3(0, 0, 0),
+ startHpr=VBase3(h, 0, 0),
+ other=otherNP,
+ blendType="easeInOut"),
+ Func(doorFrameHoleLeft.hide),
+ Func(self.hideIfHasFlat, leftDoor),
+ SoundInterval(self.closeSfx, node=leftDoor),
+ name = trackName)
+ self.doorExitTrack.start(ts)
+ #else:
+ # self.notify.error("enterOpening(): did not find leftDoor")
+
+ def exitDoorExitClosing(self):
+ assert(self.debugPrint("exitDoorExitClosing()"))
+
+ ##### Exit Door closed state #####
+
+ def exitDoorEnterClosed(self, ts):
+ assert(self.debugPrint("exitDoorEnterClosed()"))
+
+ def exitDoorExitClosed(self):
+ assert(self.debugPrint("exitDoorExitClosed()"))
+
+ ##### Exit Door opening state #####
+
+ def exitDoorEnterOpening(self, ts):
+ assert(self.debugPrint("exitDoorEnterOpening()"))
+ # Start animation:
+ # The left hole doorway:
+ doorFrameHoleLeft=self.findDoorNode("doorFrameHoleLeft")
+ if (doorFrameHoleLeft.isEmpty()):
+ self.notify.warning("enterOpening(): did not find flatDoors")
+ return
+
+ #if ZoneUtil.isInterior(self.zoneId):
+ # doorFrameHoleLeft.setColor(1., 1., 1., 1.)
+ # Left door:
+ leftDoor=self.findDoorNode("leftDoor")
+ if self.leftSwing:
+ h = -100
+ else:
+ h = 100
+ if (not leftDoor.isEmpty()):
+ # Open the door:
+ otherNP=self.getDoorNodePath()
+ trackName = "doorDoorExitTrack-%d" % (self.doId)
+ self.finishDoorExitTrack()
+ self.doorExitTrack = Parallel(
+ SoundInterval(self.openSfx, node=leftDoor),
+ Sequence(
+ Func(leftDoor.show),
+ Func(doorFrameHoleLeft.show),
+ LerpHprInterval(nodePath=leftDoor,
+ duration=0.6,
+ hpr=VBase3(h, 0, 0),
+ startHpr=VBase3(0, 0, 0),
+ other=otherNP,
+ blendType="easeInOut")),
+ name = trackName)
+ # Start the tracks:
+ self.doorExitTrack.start(ts)
+ else:
+ self.notify.warning("exitDoorEnterOpening(): did not find leftDoor")
+
+ def exitDoorExitOpening(self):
+ assert(self.debugPrint("exitDoorExitOpening()"))
+
+ ##### Exit Door open state #####
+
+ def exitDoorEnterOpen(self, ts):
+ assert(self.debugPrint("exitDoorEnterOpen()"))
+ # Run everybody out:
+ for avatarID in self.avatarExitIDList:
+ assert(self.notify.debug(" avatarID: "+str(avatarID)))
+ avatar=self.cr.doId2do.get(avatarID)
+ if avatar:
+ track=self.avatarExitTrack(avatar, 0.2)
+ track.start()
+ self.avatarExitTracks.append(track)
+ self.avatarExitIDList=[]
+
+ def exitDoorExitOpen(self):
+ assert(self.debugPrint("exitDoorExitOpen()"))
+ for track in self.avatarExitTracks:
+ track.finish()
+ DelayDelete.cleanupDelayDeletes(track)
+ self.avatarExitTracks=[]
+
+ def findDoorNode(self, string, allowEmpty = False):
+ building = self.getBuilding()
+ if not building:
+ self.notify.warning("getBuilding() returned None, avoiding crash, remark 896029")
+ foundNode = None
+ else:
+ foundNode = building.find("**/door_" + str(self.doorIndex) +
+ "/**/" + string + "*;+s+i")
+ if foundNode.isEmpty():
+ # hack, We should make the trigger finding more general.
+ foundNode = building.find("**/" + string + "*;+s+i")
+ assert(self.debugPrint(" fyi: find door hack"))
+ assert(self.debugPrint("findDoorNode(%s) found %s, %d"%(string, foundNode, self.doorIndex)))
+ if allowEmpty:
+ return foundNode
+ assert(not foundNode.isEmpty())
+ return foundNode
+
+ def hideIfHasFlat(self, node):
+ if self.bHasFlat:
+ node.hide()
+
+ if __debug__:
+ def debugPrint(self, message):
+ """for debugging"""
+ block=self.__dict__.get('block', '?')
+ type=self.__dict__.get('doorType', '?')
+ index=self.__dict__.get('doorIndex', '?')
+ if type == DoorTypes.INT_HQ:
+ type="INT_HQ"
+ elif type == DoorTypes.EXT_HQ:
+ type="EXT_HQ"
+ elif type == DoorTypes.INT_STANDARD:
+ type="INT_ST"
+ elif type == DoorTypes.EXT_STANDARD:
+ type="EXT_ST"
+ elif type == DoorTypes.EXT_COGHQ:
+ type="EXT_COGHQ"
+ elif type == DoorTypes.INT_COGHQ:
+ type="INT_COGHQ"
+ elif( type == DoorTypes.EXT_KS ):
+ type="EXT_KS"
+ elif( type == DoorTypes.INT_KS ):
+ type="INT_KS"
+ return self.notify.debug("%s %s %s %s" %
+ (block, type, index, message))
diff --git a/toontown/src/building/DistributedDoorAI.py b/toontown/src/building/DistributedDoorAI.py
new file mode 100644
index 0000000..2a9b313
--- /dev/null
+++ b/toontown/src/building/DistributedDoorAI.py
@@ -0,0 +1,466 @@
+""" DistributedDoorAI module: contains the DistributedDoorAI
+ class, the server side representation of a 'landmark door'."""
+
+
+from otp.ai.AIBaseGlobal import *
+from direct.task.Task import Task
+from direct.distributed.ClockDelta import *
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObjectAI
+from direct.fsm import State
+from toontown.toonbase import ToontownAccessAI
+
+class DistributedDoorAI(DistributedObjectAI.DistributedObjectAI):
+ """
+ The server side representation of a single door. This is the
+ object that remembers what the door is doing. The client side
+ version updates the client's display based on the state of the door.
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedDoorAI')
+
+ def __init__(self, air, blockNumber, doorType, doorIndex=0,
+ lockValue=0, swing=3):
+ """
+ blockNumber: the landmark building number (from the name)
+ doorIndex: Each door must have a unique index.
+ """
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ assert(self.notify.debug(str(blockNumber)+" DistributedDoorAI("
+ "%s, %s)" % ("the air", str(blockNumber))))
+ self.block = blockNumber
+ self.swing = swing
+ self.otherDoor=None
+ self.doorType = doorType
+ self.doorIndex = doorIndex
+ self.setDoorLock(lockValue)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedDoorAI_right',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['closing',
+ 'closed',
+ 'opening',
+ 'open']),
+ State.State('closing',
+ self.enterClosing,
+ self.exitClosing,
+ ['closed', 'opening']),
+ State.State('closed',
+ self.enterClosed,
+ self.exitClosed,
+ ['opening']),
+ State.State('opening',
+ self.enterOpening,
+ self.exitOpening,
+ ['open']),
+ State.State('open',
+ self.enterOpen,
+ self.exitOpen,
+ ['closing', 'open'])],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ self.exitDoorFSM = ClassicFSM.ClassicFSM('DistributedDoorAI_left',
+ [State.State('off',
+ self.exitDoorEnterOff,
+ self.exitDoorExitOff,
+ ['closing',
+ 'closed',
+ 'opening',
+ 'open']),
+ State.State('closing',
+ self.exitDoorEnterClosing,
+ self.exitDoorExitClosing,
+ ['closed', 'opening']),
+ State.State('closed',
+ self.exitDoorEnterClosed,
+ self.exitDoorExitClosed,
+ ['opening']),
+ State.State('opening',
+ self.exitDoorEnterOpening,
+ self.exitDoorExitOpening,
+ ['open']),
+ State.State('open',
+ self.exitDoorEnterOpen,
+ self.exitDoorExitOpen,
+ ['closing', 'open'])],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.exitDoorFSM.enterInitialState()
+ self.doLaterTask=None
+ self.exitDoorDoLaterTask=None
+ self.avatarsWhoAreEntering={}
+ self.avatarsWhoAreExiting={}
+
+ def delete(self):
+ assert(self.debugPrint("delete()"))
+ taskMgr.remove(self.uniqueName('door_opening-timer'))
+ taskMgr.remove(self.uniqueName('door_open-timer'))
+ taskMgr.remove(self.uniqueName('door_closing-timer'))
+ taskMgr.remove(self.uniqueName('exit_door_open-timer'))
+ taskMgr.remove(self.uniqueName('exit_door_closing-timer'))
+ taskMgr.remove(self.uniqueName('exit_door_opening-timer'))
+ self.ignoreAll()
+ del self.fsm
+ del self.exitDoorFSM
+ del self.otherDoor
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def getDoorIndex(self):
+ return self.doorIndex
+
+ def setSwing(self, swing):
+ self.swing = swing
+
+ def getSwing(self):
+ assert(self.debugPrint("getSwing()"))
+ return self.swing
+
+ def getDoorType(self):
+ return self.doorType
+
+ #def openDoor(self):
+ # """if the door is closed, open it"""
+ # assert(self.debugPrint("openDoor()"))
+ # if (self.isClosed()):
+ # self.fsm.request('opening')
+
+ #def closeDoor(self):
+ # """if the door is open, close it"""
+ # assert(self.debugPrint("closeDoor()"))
+ # if (self.isOpen()):
+ # self.fsm.request('closing')
+
+ def getZoneIdAndBlock(self):
+ r=[self.zoneId, self.block]
+ assert(self.debugPrint("getZoneIdAndBlock() returning: "+str(r)))
+ return r
+
+ def setOtherDoor(self, door):
+ assert(self.debugPrint("setOtherDoor(door="+str(door)+")"))
+ self.otherDoor=door
+
+ def getZoneId(self):
+ assert(self.debugPrint("getZoneId() returning "+str(self.zoneId)))
+ return self.zoneId
+
+ def getState(self):
+ assert(self.debugPrint("getState()"))
+ return [self.fsm.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+
+ def getExitDoorState(self):
+ assert(self.debugPrint("getExitDoorState()"))
+ return [self.exitDoorFSM.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+
+ def isOpen(self):
+ """return true if the door is open or opening"""
+ assert(self.debugPrint("isOpen()"))
+ state=self.fsm.getCurrentState().getName()
+ return state=='open' or state=='opening'
+
+ def isClosed(self):
+ """return true if the door is closed, closing, or off"""
+ assert(self.debugPrint("isClosed()"))
+ return not self.isOpen()
+
+ def setDoorLock(self, locked):
+ """Prevent avatars from entering this door."""
+ self.lockedDoor=locked
+
+ def isLockedDoor(self):
+ """Find out if the door is locked. 0 means no, positive values
+ mean yes. Classes that inherit may attach special meanings to
+ different positive values.
+
+ NOTE: the cog hq doors don't seem to respect the no-locked-doors flag. """
+ if simbase.config.GetBool('no-locked-doors', 0):
+ return 0
+ else:
+ return self.lockedDoor
+
+ def sendReject(self, avatarID, lockedVal):
+ # Zero means the door is unlocked. Positive values mean
+ # the door is locked.
+ # Classes that inherit from Distributed Door may
+ # attach special meanings to different positive values.
+ self.sendUpdateToAvatarId(avatarID, "rejectEnter", [lockedVal])
+
+ def requestEnter(self):
+ assert(self.debugPrint("requestEnter()"))
+ avatarID = self.air.getAvatarIdFromSender()
+ assert(self.notify.debug(" avatarID:%s" % (str(avatarID),)))
+ lockedVal = self.isLockedDoor()
+
+ # Check that player has full access
+ if not ToontownAccessAI.canAccess(avatarID, self.zoneId):
+ lockedVal = True
+
+ if lockedVal:
+ self.sendReject(avatarID, lockedVal)
+ else:
+ self.enqueueAvatarIdEnter(avatarID)
+ self.sendUpdateToAvatarId(avatarID, "setOtherZoneIdAndDoId",
+ [self.otherDoor.getZoneId(), self.otherDoor.getDoId()])
+
+ def enqueueAvatarIdEnter(self, avatarID):
+ assert(self.debugPrint("enqueueAvatarIdEnter(avatarID=%s)"%(avatarID,)))
+ assert(self.debugPrint("enqueueAvatarIdEnter(avatarsWhoAreEntering=%s)"%(self.avatarsWhoAreEntering,)))
+ # By storing the avatarID in the key, we're creating a set of
+ # unique avatarIDs.
+ if not self.avatarsWhoAreEntering.has_key(avatarID):
+ self.avatarsWhoAreEntering[avatarID]=1
+ self.sendUpdate("avatarEnter", [avatarID])
+ self.openDoor(self.fsm)
+
+ def openDoor(self, doorFsm):
+ assert(self.debugPrint("openTheDoor(doorFsm=)"))
+ stateName = doorFsm.getCurrentState().getName()
+ if stateName == 'open':
+ # Reissue the open state:
+ doorFsm.request('open')
+ elif stateName != 'opening':
+ doorFsm.request('opening')
+
+ def requestExit(self):
+ assert(self.debugPrint("requestExit()"))
+ avatarID = self.air.getAvatarIdFromSender()
+ assert(self.notify.debug(" avatarID:%s" % (str(avatarID),)))
+
+ # Make sure you send the avatar exit before telling the door to
+ # open Otherwise the client will get the open door and will not be
+ # on the list of avatars to walk through the door yet.
+ self.sendUpdate("avatarExit", [avatarID])
+ # Ok, now enqueue the avatar (which opens the door)
+ self.enqueueAvatarIdExit(avatarID)
+
+ def enqueueAvatarIdExit(self, avatarID):
+ assert(self.debugPrint("enqueueAvatarIdExit(avatarID=%s)"%(avatarID,)))
+ assert(self.debugPrint("enqueueAvatarIdExit(avatarsWhoAreExiting=%s)"%(self.avatarsWhoAreExiting,)))
+ if self.avatarsWhoAreEntering.has_key(avatarID):
+ del self.avatarsWhoAreEntering[avatarID]
+ elif not self.avatarsWhoAreExiting.has_key(avatarID):
+ self.avatarsWhoAreExiting[avatarID]=1
+ self.openDoor(self.exitDoorFSM)
+ else:
+ assert(self.notify.debug(str(avatarID)
+ +" requested an exit, and they're already exiting"))
+
+ def requestSuitEnter(self, avatarID):
+ """
+ unlike requestEnter(), which is for toons, this is not a
+ distributed call; it's made directly from the AI side as the
+ suit reaches the end of its path.
+ """
+ assert(self.debugPrint("requestSuitEnter(avatarID=%s" % (avatarID,)))
+ self.enqueueAvatarIdEnter(avatarID)
+
+ def requestSuitExit(self, avatarID):
+ """
+ unlike requestExit(), which is for toons, this is not a
+ distributed call; it's made directly from the AI side as the
+ suit reaches the end of its path.
+ """
+ assert(self.debugPrint("requestSuitExit(avatarID=%s" % (avatarID,)))
+ # Send the avatar exit first
+ self.sendUpdate("avatarExit", [avatarID])
+ # Then open the door
+ self.enqueueAvatarIdExit(avatarID)
+
+ def d_setState(self, state):
+ assert(self.debugPrint("d_setState(state="+str(state)+")"))
+ self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()])
+
+ def d_setExitDoorState(self, state):
+ assert(self.debugPrint("d_setExitDoorState(state="+str(state)+")"))
+ self.sendUpdate('setExitDoorState', [state, globalClockDelta.getRealNetworkTime()])
+
+ if __debug__:
+ def debugPrint(self, message):
+ """for debugging"""
+ return self.notify.debug(
+ str(self.__dict__.get('block', '?'))+' '+message)
+
+ ##### off state #####
+
+ def enterOff(self):
+ assert(self.debugPrint("enterOff()"))
+
+ def exitOff(self):
+ assert(self.debugPrint("exitOff()"))
+
+ ##### closing state #####
+
+ def openTask(self, task):
+ assert(self.debugPrint("openTask()"))
+ self.fsm.request("closing")
+ return Task.done
+
+ def enterClosing(self):
+ assert(self.debugPrint("enterClosing()"))
+ self.d_setState('closing')
+ self.doLaterTask=taskMgr.doMethodLater(
+ 1, #CLOSING_DOOR_TIME, #TODO: define this elsewhere
+ self.closingTask,
+ self.uniqueName('door_closing-timer'))
+
+ def exitClosing(self):
+ assert(self.debugPrint("exitClosing()"))
+ if self.doLaterTask:
+ taskMgr.remove(self.doLaterTask)
+ self.doLaterTask=None
+
+ ##### closed state #####
+
+ def closingTask(self, task):
+ assert(self.debugPrint("closingTask()"))
+ self.fsm.request("closed")
+ return Task.done
+
+ def enterClosed(self):
+ assert(self.debugPrint("enterClosed()"))
+ self.d_setState('closed')
+
+ def exitClosed(self):
+ assert(self.debugPrint("exitClosed()"))
+
+ ##### opening state #####
+
+ def enterOpening(self):
+ assert(self.debugPrint("enterOpening()"))
+ self.d_setState('opening')
+ self.doLaterTask=taskMgr.doMethodLater(
+ 1, #OPENING_DOOR_TIME,
+ #todo: define OPENING_DOOR_TIME elsewhere
+ self.openingTask,
+ self.uniqueName('door_opening-timer'))
+
+ def exitOpening(self):
+ assert(self.debugPrint("exitOpening()"))
+ if self.doLaterTask:
+ taskMgr.remove(self.doLaterTask)
+ self.doLaterTask=None
+
+ ##### open state #####
+
+ def openingTask(self, task):
+ assert(self.debugPrint("openingTask()"))
+ self.fsm.request("open")
+ return Task.done
+
+ def enterOpen(self):
+ assert(self.debugPrint("enterOpen()"))
+ self.d_setState('open')
+ self.avatarsWhoAreEntering={}
+ self.doLaterTask=taskMgr.doMethodLater(
+ 1, #STAY_OPEN_DOOR_TIME,
+ #todo: define STAY_OPEN_DOOR_TIME elsewhere
+ self.openTask,
+ self.uniqueName('door_open-timer'))
+
+ def exitOpen(self):
+ assert(self.debugPrint("exitOpen()"))
+ if self.doLaterTask:
+ taskMgr.remove(self.doLaterTask)
+ self.doLaterTask=None
+
+
+ ##### Exit Door off state #####
+
+ def exitDoorEnterOff(self):
+ assert(self.debugPrint("exitDoorEnterOff()"))
+
+ def exitDoorExitOff(self):
+ assert(self.debugPrint("exitDoorExitOff()"))
+
+ ##### Exit Door closing state #####
+
+ def exitDoorOpenTask(self, task):
+ assert(self.debugPrint("exitDoorOpenTask()"))
+ self.exitDoorFSM.request("closing")
+ return Task.done
+
+ def exitDoorEnterClosing(self):
+ assert(self.debugPrint("exitDoorEnterClosing()"))
+ self.d_setExitDoorState('closing')
+ self.exitDoorDoLaterTask=taskMgr.doMethodLater(
+ 1, #CLOSING_DOOR_TIME, #TODO: define this elsewhere
+ self.exitDoorClosingTask,
+ self.uniqueName('exit_door_closing-timer'))
+
+ def exitDoorExitClosing(self):
+ assert(self.debugPrint("exitDoorExitClosing()"))
+ if self.exitDoorDoLaterTask:
+ taskMgr.remove(self.exitDoorDoLaterTask)
+ self.exitDoorDoLaterTask=None
+
+ ##### Exit Door closed state #####
+
+ def exitDoorClosingTask(self, task):
+ assert(self.debugPrint("exitDoorClosingTask()"))
+ self.exitDoorFSM.request("closed")
+ return Task.done
+
+ def exitDoorEnterClosed(self):
+ assert(self.debugPrint("exitDoorEnterClosed()"))
+ self.d_setExitDoorState('closed')
+
+ def exitDoorExitClosed(self):
+ assert(self.debugPrint("exitDoorExitClosed()"))
+ if self.exitDoorDoLaterTask:
+ taskMgr.remove(self.exitDoorDoLaterTask)
+ self.exitDoorDoLaterTask=None
+
+ ##### Exit Door opening state #####
+
+ def exitDoorEnterOpening(self):
+ assert(self.debugPrint("exitDoorEnterOpening()"))
+ self.d_setExitDoorState('opening')
+ self.exitDoorDoLaterTask=taskMgr.doMethodLater(
+ 1, #OPENING_DOOR_TIME,
+ #todo: define OPENING_DOOR_TIME elsewhere
+ self.exitDoorOpeningTask,
+ self.uniqueName('exit_door_opening-timer'))
+
+ def exitDoorExitOpening(self):
+ assert(self.debugPrint("exitDoorExitOpening()"))
+ if self.exitDoorDoLaterTask:
+ taskMgr.remove(self.exitDoorDoLaterTask)
+ self.exitDoorDoLaterTask=None
+
+ ##### Exit Door open state #####
+
+ def exitDoorOpeningTask(self, task):
+ assert(self.debugPrint("exitDoorOpeningTask()"))
+ self.exitDoorFSM.request("open")
+ return Task.done
+
+ def exitDoorEnterOpen(self):
+ assert(self.debugPrint("exitDoorEnterOpen()"))
+ # Tell the clients:
+ self.d_setExitDoorState('open')
+ self.avatarsWhoAreExiting={}
+ # Stay open for a little while:
+ self.exitDoorDoLaterTask=taskMgr.doMethodLater(
+ 1, #STAY_OPEN_DOOR_TIME,
+ #todo: define STAY_OPEN_DOOR_TIME elsewhere
+ self.exitDoorOpenTask,
+ self.uniqueName('exit_door_open-timer'))
+
+ def exitDoorExitOpen(self):
+ assert(self.debugPrint("exitDoorExitOpen()"))
+ if self.exitDoorDoLaterTask:
+ taskMgr.remove(self.exitDoorDoLaterTask)
+ self.exitDoorDoLaterTask=None
+
diff --git a/toontown/src/building/DistributedElevator.py b/toontown/src/building/DistributedElevator.py
new file mode 100644
index 0000000..b9ed398
--- /dev/null
+++ b/toontown/src/building/DistributedElevator.py
@@ -0,0 +1,826 @@
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+from direct.interval.IntervalGlobal import *
+from ElevatorConstants import *
+from ElevatorUtils import *
+from direct.showbase import PythonUtil
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObject
+from direct.fsm import State
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownGlobals
+from direct.task.Task import Task
+from toontown.distributed import DelayDelete
+from toontown.hood import ZoneUtil
+from toontown.toontowngui import TeaserPanel
+from toontown.building import BoardingGroupShow
+
+class DistributedElevator(DistributedObject.DistributedObject):
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedElevator')
+
+ # The jump offsets for a regular elevator are the same for all the slots.
+ # There is a difference in jump offsets in cases like the cog kart elevators.
+ JumpOutOffsets = JumpOutOffsets # Get the values from ElevatorConstants
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+ self.bldgRequest = None
+ self.toonRequests = {}
+ self.deferredSlots = []
+ self.localToonOnBoard = 0
+ self.boardedAvIds = {}
+ self.openSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_open.mp3")
+ self.finalOpenSfx = None
+ self.closeSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_close.mp3")
+ #Points to Elevator.Elevator when localAvatar steps inside
+ self.elevatorFSM=None
+ self.finalCloseSfx = None
+ self.elevatorPoints = ElevatorPoints
+
+ self.fillSlotTrack = None
+
+ self.type = ELEVATOR_NORMAL
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
+ # Tracks on toons, for starting and stopping
+ # stored by avId : track. There is only a need for one at a time,
+ # in fact the point of the dict is to ensure only one is playing at a time
+ self.__toonTracks = {}
+
+ self.fsm = ClassicFSM.ClassicFSM('DistributedElevator',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['opening',
+ 'waitEmpty',
+ 'waitCountdown',
+ 'closing',
+ 'closed',
+ ]),
+ State.State('opening',
+ self.enterOpening,
+ self.exitOpening,
+ ['waitEmpty', 'waitCountdown']),
+ State.State('waitEmpty',
+ self.enterWaitEmpty,
+ self.exitWaitEmpty,
+ ['waitCountdown']),
+ State.State('waitCountdown',
+ self.enterWaitCountdown,
+ self.exitWaitCountdown,
+ ['waitEmpty', 'closing']),
+ State.State('closing',
+ self.enterClosing,
+ self.exitClosing,
+ ['closed', 'waitEmpty']),
+ State.State('closed',
+ self.enterClosed,
+ self.exitClosed,
+ ['opening']),
+ ],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ self.isSetup = 0
+ self.__preSetupState = None
+ self.bigElevator = 0
+
+ # This is bad. Testing?
+ #base.elevator = self
+
+ def generate(self):
+ DistributedObject.DistributedObject.generate(self)
+
+ def setupElevator(self):
+ # Assumes you have a self.leftDoor and self.rightDoor defined
+
+ # Establish a collision sphere. There must be an easier way!
+ collisionRadius = ElevatorData[self.type]['collRadius']
+ self.elevatorSphere = CollisionSphere(0, 5, 0, collisionRadius)
+ self.elevatorSphere.setTangible(0)
+ self.elevatorSphereNode = CollisionNode(self.uniqueName("elevatorSphere"))
+ self.elevatorSphereNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
+ self.elevatorSphereNode.addSolid(self.elevatorSphere)
+ self.elevatorSphereNodePath = self.getElevatorModel().attachNewNode(
+ self.elevatorSphereNode)
+ self.elevatorSphereNodePath.hide()
+ self.elevatorSphereNodePath.reparentTo(self.getElevatorModel())
+ self.elevatorSphereNodePath.stash()
+
+ self.boardedAvIds = {}
+
+ # Establish intervals for opening and closing the doors
+ self.openDoors = getOpenInterval(self,
+ self.leftDoor,
+ self.rightDoor,
+ self.openSfx,
+ self.finalOpenSfx,
+ self.type)
+
+ self.closeDoors = getCloseInterval(self,
+ self.leftDoor,
+ self.rightDoor,
+ self.closeSfx,
+ self.finalCloseSfx,
+ self.type)
+
+ # put a callback at the end of the 'closeDoors' ival so that we can
+ # hide the avatars on board. Otherwise they may still be in the
+ # elevator when the doors open.
+ self.closeDoors = Sequence(self.closeDoors,
+ Func(self.onDoorCloseFinish),
+ )
+
+ self.finishSetup()
+
+ def finishSetup(self):
+
+ # Do all the things that we had to defer until the elevator
+ # was fully set up.
+ self.isSetup = 1
+ self.offsetNP = self.getElevatorModel().attachNewNode('dummyNP')
+
+ if self.__preSetupState:
+ self.fsm.request(self.__preSetupState, [0])
+ self.__preSetupState = None
+
+ for slot in self.deferredSlots:
+ self.fillSlot(*slot)
+
+ self.deferredSlots = []
+
+ def disable(self):
+ if self.bldgRequest:
+ self.cr.relatedObjectMgr.abortRequest(self.bldgRequest)
+ self.bldgRequest = None
+ for request in self.toonRequests.values():
+ self.cr.relatedObjectMgr.abortRequest(request)
+ self.toonRequests = {}
+
+ # stop the intervals in case they're playing
+ if hasattr(self, "openDoors"):
+ self.openDoors.pause()
+ if hasattr(self, "closeDoors"):
+ self.closeDoors.pause()
+
+ # No more toon animating
+ self.clearToonTracks()
+
+ # Go to the off state when the object is put in the cache
+ self.fsm.request("off")
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ if self.isSetup:
+ self.elevatorSphereNodePath.removeNode()
+ del self.elevatorSphereNodePath
+ del self.elevatorSphereNode
+ del self.elevatorSphere
+ del self.bldg
+ if self.leftDoor:
+ del self.leftDoor
+ if self.rightDoor:
+ del self.rightDoor
+ if hasattr(self, "openDoors"):
+ del self.openDoors
+ if hasattr(self, "closeDoors"):
+ del self.closeDoors
+ del self.fsm
+ del self.openSfx
+ del self.closeSfx
+ self.isSetup = 0
+
+ self.offsetNP.removeNode()
+ # Cleanup any leftover elevator messages while leaving the zone.
+ if hasattr(base.localAvatar, "elevatorNotifier"):
+ base.localAvatar.elevatorNotifier.cleanup()
+
+ DistributedObject.DistributedObject.delete(self)
+
+ def setBldgDoId(self, bldgDoId):
+ self.bldgDoId = bldgDoId
+ self.bldgRequest = self.cr.relatedObjectMgr.requestObjects(
+ [bldgDoId], allCallback = self.gotBldg, timeout = 2)
+
+ def gotBldg(self, buildingList):
+ self.bldgRequest = None
+ self.bldg = buildingList[0]
+ if not self.bldg:
+ self.notify.error("setBldgDoId: elevator %d cannot find bldg %d!"
+ % (self.doId, self.bldgDoId))
+ return
+ self.setupElevator()
+
+ def gotToon(self, index, avId, toonList):
+ request = self.toonRequests.get(index)
+ if request:
+ del self.toonRequests[index]
+ self.fillSlot(index, avId)
+ else:
+ self.notify.error("gotToon: already had got toon in slot %s." % (index))
+
+
+ def setState(self, state, timestamp):
+ if self.isSetup:
+ self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+ else:
+ # Save the state until we get setup.
+ self.__preSetupState = state
+
+ def fillSlot0(self, avId, wantBoardingShow):
+ self.fillSlot(0, avId, wantBoardingShow)
+
+ def fillSlot1(self, avId, wantBoardingShow):
+ self.fillSlot(1, avId, wantBoardingShow)
+
+ def fillSlot2(self, avId, wantBoardingShow):
+ self.fillSlot(2, avId, wantBoardingShow)
+
+ def fillSlot3(self, avId, wantBoardingShow):
+ self.fillSlot(3, avId, wantBoardingShow)
+
+ def fillSlot4(self, avId, wantBoardingShow):
+ self.fillSlot(4, avId, wantBoardingShow)
+
+ def fillSlot5(self, avId, wantBoardingShow):
+ self.fillSlot(5, avId, wantBoardingShow)
+
+ def fillSlot6(self, avId, wantBoardingShow):
+ self.fillSlot(6, avId, wantBoardingShow)
+
+ def fillSlot7(self, avId, wantBoardingShow):
+ self.fillSlot(7, avId, wantBoardingShow)
+
+ def fillSlot(self, index, avId, wantBoardingShow = 0):
+ self.notify.debug("%s.fillSlot(%s, %s, ...)" % (self.doId, index, avId))
+ request = self.toonRequests.get(index)
+ if request:
+ self.cr.relatedObjectMgr.abortRequest(request)
+ del self.toonRequests[index]
+
+ if avId == 0:
+ # This means that the slot is now empty, and no action should
+ # be taken.
+ pass
+
+ elif not self.cr.doId2do.has_key(avId):
+ # It's someone who hasn't been generated yet.
+ func = PythonUtil.Functor(
+ self.gotToon, index, avId)
+
+ assert not self.toonRequests.has_key(index)
+ self.toonRequests[index] = self.cr.relatedObjectMgr.requestObjects(
+ [avId], allCallback = func)
+
+ elif not self.isSetup:
+ # We haven't set up the elevator yet.
+ self.deferredSlots.append((index, avId, wantBoardingShow))
+
+ else:
+ # If localToon is boarding, he needs to change state
+ if avId == base.localAvatar.getDoId():
+ place = base.cr.playGame.getPlace()
+ if not place:
+ return
+ place.detectedElevatorCollision(self)
+ elevator = self.getPlaceElevator()
+ if elevator == None:
+ place.fsm.request('elevator')
+ elevator = self.getPlaceElevator()
+ if not elevator:
+ return
+
+ self.localToonOnBoard = 1
+
+ if hasattr(localAvatar, "boardingParty") and localAvatar.boardingParty:
+ localAvatar.boardingParty.forceCleanupInviteePanel()
+ localAvatar.boardingParty.forceCleanupInviterPanels()
+
+ # Cleanup any leftover elevator messages before boarding the elevator.
+ if hasattr(base.localAvatar, "elevatorNotifier"):
+ base.localAvatar.elevatorNotifier.cleanup()
+
+ cameraTrack = Sequence()
+ # Move the camera towards and face the elevator.
+ cameraTrack.append(Func(elevator.fsm.request, "boarding", [self.getElevatorModel()]))
+ # Enable the Hop off button.
+ cameraTrack.append(Func(elevator.fsm.request, "boarded"))
+
+ toon = self.cr.doId2do[avId]
+ # Parent it to the elevator
+ toon.stopSmooth()
+ # avoid wrtReparent so that we don't muck with the toon's scale
+ #toon.wrtReparentTo(self.getElevatorModel())
+
+ if not wantBoardingShow:
+ toon.setZ(self.getElevatorModel(), self.elevatorPoints[index][2])
+ toon.setShadowHeight(0)
+
+ if toon.isDisguised:
+ animInFunc = Sequence(Func(toon.suit.loop, "walk"))
+ #RAU this stops the dog's ears from flapping too much
+ animFunc = Sequence(
+ Func(toon.setAnimState, "neutral", 1.0),
+ Func(toon.suit.loop, "neutral")
+ )
+ else:
+ animInFunc = Sequence(Func(toon.setAnimState, "run", 1.0))
+ animFunc = Func(toon.setAnimState, "neutral", 1.0)
+ toon.headsUp(self.getElevatorModel(), apply(Point3, self.elevatorPoints[index]))
+
+ track = Sequence(
+ # Pos 1: -1.5, 5, 0
+ # Pos 2: 1.5, 5, 0
+ # Pos 3: -2.5, 3, 0
+ # Pos 4: 2.5, 3, 0
+ animInFunc,
+ LerpPosInterval(toon, TOON_BOARD_ELEVATOR_TIME * 0.75,
+ apply(Point3, self.elevatorPoints[index]),
+ other=self.getElevatorModel()),
+ LerpHprInterval(toon, TOON_BOARD_ELEVATOR_TIME * 0.25,
+ Point3(180, 0, 0),
+ other=self.getElevatorModel()),
+ #Func(toon.setAnimState, "neutral", 1.0),
+ Func(self.clearToonTrack, avId),
+ animFunc,
+ name = toon.uniqueName("fillElevator"),
+ autoPause = 1)
+
+ if wantBoardingShow:
+ boardingTrack, boardingTrackType = self.getBoardingTrack(toon, index, False)
+ track = Sequence(boardingTrack, track)
+
+ if avId == base.localAvatar.getDoId():
+ cameraWaitTime = 2.5
+ if (boardingTrackType == BoardingGroupShow.TRACK_TYPE_RUN):
+ cameraWaitTime = 0.5
+ elif (boardingTrackType == BoardingGroupShow.TRACK_TYPE_POOF):
+ cameraWaitTime = 1
+ cameraTrack = Sequence(Wait(cameraWaitTime), cameraTrack)
+
+ if self.canHideBoardingQuitBtn(avId):
+ track = Sequence(Func(localAvatar.boardingParty.groupPanel.disableQuitButton),
+ track)
+
+ # Start the camera track in parallel here
+ if avId == base.localAvatar.getDoId():
+ track = Parallel(cameraTrack, track)
+
+ track.delayDelete = DelayDelete.DelayDelete(toon, 'Elevator.fillSlot')
+ self.storeToonTrack(avId, track)
+ track.start()
+
+ self.fillSlotTrack = track
+
+ assert avId not in self.boardedAvIds
+ self.boardedAvIds[avId] = None
+
+ def emptySlot0(self, avId, bailFlag, timestamp, time):
+ self.emptySlot(0, avId, bailFlag, timestamp, time)
+
+ def emptySlot1(self, avId, bailFlag, timestamp, time):
+ self.emptySlot(1, avId, bailFlag, timestamp, time)
+
+ def emptySlot2(self, avId, bailFlag, timestamp, time):
+ self.emptySlot(2, avId, bailFlag, timestamp, time)
+
+ def emptySlot3(self, avId, bailFlag, timestamp, time):
+ self.emptySlot(3, avId, bailFlag, timestamp, time)
+
+ def emptySlot4(self, avId, bailFlag, timestamp, time):
+ self.emptySlot(4, avId, bailFlag, timestamp, time)
+
+ def emptySlot5(self, avId, bailFlag, timestamp, time):
+ self.emptySlot(5, avId, bailFlag, timestamp)
+
+ def emptySlot6(self, avId, bailFlag, timestamp, time):
+ self.emptySlot(6, avId, bailFlag, timestamp, time)
+
+ def emptySlot7(self, avId, bailFlag, timestamp, time):
+ self.emptySlot(7, avId, bailFlag, timestamp, time)
+
+ def notifyToonOffElevator(self, toon):
+ toon.setAnimState("neutral", 1.0)
+ if toon == base.localAvatar:
+ doneStatus = {
+ 'where' : 'exit',
+ }
+ elevator = self.getPlaceElevator()
+ if elevator:
+ elevator.signalDone(doneStatus)
+ self.localToonOnBoard = 0
+ else:
+ toon.startSmooth()
+ return
+
+ def emptySlot(self, index, avId, bailFlag, timestamp, timeSent = 0):
+ if self.fillSlotTrack:
+ self.fillSlotTrack.finish()
+ self.fillSlotTrack = None
+
+ #print "Emptying slot: %d for %d" % (index, avId)
+ # If localToon is exiting, he needs to change state
+ if avId == 0:
+ # This means that no one is currently exiting, and no action
+ # should be taken
+ pass
+
+ elif not self.isSetup:
+ # We haven't set up the elevator yet. Remove the toon
+ # from the deferredSlots list, if it is there.
+ newSlots = []
+ for slot in self.deferredSlots:
+ if slot[0] != index:
+ newSlots.append(slot)
+
+ self.deferredSlots = newSlots
+
+ else:
+ timeToSet = self.countdownTime
+ if timeSent > 0:
+ timeToSet = timeSent
+ if self.cr.doId2do.has_key(avId):
+ # See if we need to reset the clock
+ # (countdown assumes we've created a clockNode already)
+ if (bailFlag == 1 and hasattr(self, 'clockNode')):
+ if (timestamp < timeToSet and
+ timestamp >= 0):
+ self.countdown(timeToSet - timestamp)
+ else:
+ self.countdown(timeToSet)
+ # If the toon exists, look it up
+ toon = self.cr.doId2do[avId]
+ # avoid wrtReparent so that we don't muck with the toon's scale
+ ## Parent it to render
+ #toon.wrtReparentTo(render)
+ toon.stopSmooth()
+
+ if toon.isDisguised:
+ toon.suit.loop("walk")
+ animFunc = Func(toon.suit.loop, "neutral")
+ else:
+ toon.setAnimState("run", 1.0)
+ animFunc = Func(toon.setAnimState, "neutral", 1.0)
+
+ # Place it on the appropriate spot relative to the
+ # elevator
+ track = Sequence(
+ # TODO: Find the right coords for the elevator
+ LerpPosInterval(toon, TOON_EXIT_ELEVATOR_TIME,
+ Point3(*self.JumpOutOffsets[index]),
+ other=self.getElevatorModel()
+ ),
+ animFunc,
+ # Tell the toon he is free to roam now
+ Func(self.notifyToonOffElevator, toon),
+ Func(self.clearToonTrack, avId),
+ name = toon.uniqueName("emptyElevator"),
+ autoPause = 1)
+
+ if self.canHideBoardingQuitBtn(avId):
+ # Enable the Boarding Group Panel Quit Button here if it is relevant.
+ track.append(Func(localAvatar.boardingParty.groupPanel.enableQuitButton))
+ # Enable the Boarding Group GO Button here if it is relevant.
+ track.append(Func(localAvatar.boardingParty.enableGoButton))
+
+ track.delayDelete = DelayDelete.DelayDelete(toon, 'Elevator.emptySlot')
+ self.storeToonTrack(avId, track)
+ track.start()
+
+ # Tell localToon he is exiting (if localToon is on board)
+ if avId == base.localAvatar.getDoId():
+ messenger.send("exitElevator")
+
+ # if the elevator is generated as a toon is leaving it,
+ # we will not have gotten a corresponding 'fillSlot' message
+ # for that toon, hence the toon will not be found in
+ # boardedAvIds
+ if avId in self.boardedAvIds:
+ del self.boardedAvIds[avId]
+
+ else:
+ self.notify.warning("toon: " + str(avId) +
+ " doesn't exist, and" +
+ " cannot exit the elevator!")
+
+ def allowedToEnter(self):
+ """Check if the local toon is allowed to enter."""
+ if base.cr.isPaid():
+ return True
+ place = base.cr.playGame.getPlace()
+ myHoodId = ZoneUtil.getCanonicalHoodId(place.zoneId)
+ if myHoodId in \
+ (ToontownGlobals.ToontownCentral,
+ ToontownGlobals.MyEstate,
+ ToontownGlobals.GoofySpeedway,
+ ):
+ # trialer going to TTC/Estate/Goofy Speedway, let them through
+ return True
+ return False
+
+ def handleEnterSphere(self, collEntry):
+ self.notify.debug("Entering Elevator Sphere....")
+ #print("handleEnterSphere elevator%s avatar%s" % (self.elevatorTripId, localAvatar.lastElevatorLeft))
+ if self.allowedToEnter():
+ if self.elevatorTripId and (localAvatar.lastElevatorLeft == self.elevatorTripId):
+ #print("NO BACKCIES!")
+ self.rejectBoard(base.localAvatar.doId, REJECT_SHUFFLE)
+
+ # Only toons with hp can board the elevator.
+ elif base.localAvatar.hp > 0:
+ # Put localToon into requestBoard mode.
+ self.cr.playGame.getPlace().detectedElevatorCollision(self)
+ # Tell the server that this avatar wants to board.
+ toon = base.localAvatar
+ self.sendUpdate("requestBoard",[])
+ else:
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.fsm.request('stopped')
+ self.dialog = TeaserPanel.TeaserPanel(pageName='cogHQ',
+ doneFunc=self.handleOkTeaser)
+ def handleOkTeaser(self):
+ """Handle the user clicking ok on the teaser panel."""
+ self.dialog.destroy()
+ del self.dialog
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.fsm.request('walk')
+
+ def rejectBoard(self, avId, reason = 0):
+ # This should only be sent to us if our localToon requested
+ # permission to board the elevator.
+ # reason 0: unknown, 1: shuffle, 2: too low laff, 3: no seat, 4: need promotion
+ print("rejectBoard %s" % (reason))
+ if hasattr(base.localAvatar, "elevatorNotifier"):
+ if reason == REJECT_SHUFFLE:
+ base.localAvatar.elevatorNotifier.showMe(TTLocalizer.ElevatorHoppedOff)
+ elif reason == REJECT_MINLAFF:
+ base.localAvatar.elevatorNotifier.showMe((TTLocalizer.ElevatorMinLaff % (self.minLaff)))
+ elif reason == REJECT_PROMOTION:
+ base.localAvatar.elevatorNotifier.showMe(TTLocalizer.BossElevatorRejectMessage)
+ elif reason == REJECT_NOT_YET_AVAILABLE:
+ base.localAvatar.elevatorNotifier.showMe(TTLocalizer.NotYetAvailable)
+ assert(base.localAvatar.getDoId() == avId)
+
+ doneStatus = {
+ 'where' : 'reject',
+ }
+ elevator = self.getPlaceElevator()
+ if elevator:
+ elevator.signalDone(doneStatus)
+
+ def timerTask(self, task):
+ countdownTime = int(task.duration - task.time)
+ timeStr = str(countdownTime)
+
+ if self.clockNode.getText() != timeStr:
+ self.clockNode.setText(timeStr)
+
+ if task.time >= task.duration:
+ return Task.done
+ else:
+ return Task.cont
+
+ def countdown(self, duration):
+ countdownTask = Task(self.timerTask)
+ countdownTask.duration = duration
+ taskMgr.remove(self.uniqueName("elevatorTimerTask"))
+ return taskMgr.add(countdownTask, self.uniqueName("elevatorTimerTask"))
+
+ def handleExitButton(self):
+ # This gets called when the exit button gets pushed.
+ localAvatar.lastElevatorLeft = self.elevatorTripId
+ self.sendUpdate("requestExit")
+
+ def enterWaitCountdown(self, ts):
+ self.elevatorSphereNodePath.unstash()
+ # Toons may now try to board the elevator
+ self.accept(self.uniqueName('enterelevatorSphere'),
+ self.handleEnterSphere)
+ self.accept("elevatorExitButton", self.handleExitButton)
+ return
+
+ def exitWaitCountdown(self):
+ self.elevatorSphereNodePath.stash()
+ # Toons may not attempt to board the elevator if it isn't waiting
+ self.ignore(self.uniqueName('enterelevatorSphere'))
+ self.ignore("elevatorExitButton")
+ self.ignore("localToonLeft")
+ # Stop the countdown clock...
+ taskMgr.remove(self.uniqueName("elevatorTimerTask"))
+ self.clock.removeNode()
+ del self.clock
+ del self.clockNode
+
+ def enterClosing(self, ts):
+ # Close the elevator doors
+ if self.localToonOnBoard:
+ elevator = self.getPlaceElevator()
+ if elevator:
+ elevator.fsm.request("elevatorClosing")
+ self.closeDoors.start(ts)
+
+ def exitClosing(self):
+ return
+
+ def onDoorCloseFinish(self):
+ """this is called when the elevator doors finish closing on the client
+ """
+ # for any avatars that are still parented to us, remove them from the scene graph
+ # so that they're not there when the doors open again
+ for avId in self.boardedAvIds.keys():
+ av = self.cr.doId2do.get(avId)
+ if av is not None:
+ if av.getParent().compareTo(self.getElevatorModel()) == 0:
+ av.detachNode()
+ self.boardedAvIds = {}
+
+ def enterClosed(self, ts):
+ self.forceDoorsClosed()
+ self.__doorsClosed(self.getZoneId())
+ return
+
+ def exitClosed(self):
+ return
+
+ def forceDoorsOpen(self):
+ openDoors(self.leftDoor, self.rightDoor)
+
+ def forceDoorsClosed(self):
+ self.closeDoors.finish()
+ closeDoors(self.leftDoor, self.rightDoor)
+
+ def enterOff(self):
+ return
+
+ def exitOff(self):
+ return
+
+ def enterWaitEmpty(self, ts):
+ return
+
+ def exitWaitEmpty(self):
+ return
+
+ def enterOpening(self, ts):
+ # Open the elevator doors
+ self.openDoors.start(ts)
+ return
+
+ def exitOpening(self):
+ return
+
+ def startCountdownClock(self, countdownTime, ts):
+ # Start the countdown clock...
+ self.clockNode = TextNode("elevatorClock")
+ self.clockNode.setFont(ToontownGlobals.getSignFont())
+ self.clockNode.setAlign(TextNode.ACenter)
+ self.clockNode.setTextColor(0.5, 0.5, 0.5, 1)
+ self.clockNode.setText(str(int(countdownTime)))
+ self.clock = self.getElevatorModel().attachNewNode(self.clockNode)
+ # TODO: Get the right coordinates for the elevator clock.
+ self.clock.setPosHprScale(0, 2.0, 7.5,
+ 0, 0, 0,
+ 2.0, 2.0, 2.0)
+ if ts < countdownTime:
+ self.countdown(countdownTime - ts)
+
+ def _getDoorsClosedInfo(self):
+ # return loader, where strings
+ return 'suitInterior', 'suitInterior'
+
+ def __doorsClosed(self, zoneId):
+ assert(self.notify.debug('doorsClosed()'))
+ if (self.localToonOnBoard):
+ hoodId = ZoneUtil.getHoodId(zoneId)
+ loader, where = self._getDoorsClosedInfo()
+ doneStatus = {
+ 'loader' : loader,
+ 'where' : where,
+ 'hoodId' : hoodId,
+ 'zoneId' : zoneId,
+ 'shardId' : None,
+ }
+
+ elevator = self.elevatorFSM #self.getPlaceElevator()
+ del self.elevatorFSM
+ elevator.signalDone(doneStatus)
+
+ def getElevatorModel(self):
+ self.notify.error("getElevatorModel: pure virtual -- inheritors must override")
+
+ def getPlaceElevator(self):
+ place = self.cr.playGame.getPlace()
+ if place:
+ if hasattr(place, "elevator"):
+ return place.elevator
+ else:
+ self.notify.warning("Place was in state '%s' instead of Elevator." % (place.fsm.getCurrentState().getName()))
+ place.detectedElevatorCollision(self)
+ else:
+ self.notify.warning("Place didn't exist")
+ return None
+
+ def setElevatorTripId(self, id):
+ self.elevatorTripId = id
+
+ def getElevatorTripId(self):
+ return self.elevatorTripId
+
+ def setAntiShuffle(self, antiShuffle):
+ self.antiShuffle = antiShuffle
+
+ def getAntiShuffle(self):
+ return self.antiShuffle
+
+ def setMinLaff(self, minLaff):
+ self.minLaff = minLaff
+
+ def getMinLaff(self):
+ return self.minLaff
+
+ def storeToonTrack(self, avId, track):
+ # Clear out any currently playing tracks on this toon
+ self.clearToonTrack(avId)
+ # Store this new one
+ self.__toonTracks[avId] = track
+
+ def clearToonTrack(self, avId):
+ # Clear out any currently playing tracks on this toon
+ oldTrack = self.__toonTracks.get(avId)
+ if oldTrack:
+ oldTrack.pause()
+ if self.__toonTracks.get(avId):
+ DelayDelete.cleanupDelayDeletes(self.__toonTracks[avId])
+ del self.__toonTracks[avId]
+
+ def clearToonTracks(self):
+ #We can't use an iter because we are deleting keys
+ keyList = []
+ for key in self.__toonTracks:
+ keyList.append(key)
+
+ for key in keyList:
+ if self.__toonTracks.has_key(key):
+ self.clearToonTrack(key)
+
+ def getDestName(self):
+ return None
+
+ def getOffsetPos(self, seatIndex = 0):
+ """
+ Get the offset position to where the toon might have to
+ teleport to or run to.
+ Note: This is the pos reletive to the elevator.
+ """
+ return self.JumpOutOffsets[seatIndex]
+
+ def getOffsetPosWrtToonParent(self, toon, seatIndex = 0):
+ """
+ Get the offset position to where the toon might have to
+ teleport to or run to.
+ Note: This is the pos reletive to the toon parent.
+ """
+ self.offsetNP.setPos(apply(Point3, self.getOffsetPos(seatIndex)))
+ return self.offsetNP.getPos(toon.getParent())
+
+ def getOffsetPosWrtRender(self, seatIndex = 0):
+ """
+ Get the offset position to where the toon might have to
+ teleport to or run to.
+ Note: This is the pos reletive to the render.
+ """
+ self.offsetNP.setPos(apply(Point3, self.getOffsetPos(seatIndex)))
+ return self.offsetNP.getPos(render)
+
+ def canHideBoardingQuitBtn(self, avId):
+ if (avId == localAvatar.doId) and \
+ hasattr(localAvatar, "boardingParty") and \
+ localAvatar.boardingParty and \
+ localAvatar.boardingParty.groupPanel:
+ return True
+ else:
+ return False
+
+ def getBoardingTrack(self, toon, seatIndex, wantToonRotation):
+ '''
+ Return an interval of the toon teleporting in front of the elevator.
+ Note: The offset (second parameter passed to BoardingGroupShow.getBoardingTrack
+ is to where the toon will teleport/run to. This offset has to be
+ calculated wrt the parent of the toon.
+ Eg: For the CogKart the offset should be computed wrt to the cogKart because the
+ toon is parented to the cogKart.
+ For the other elevators the offset should be computer wrt to render because the
+ toon is parented to render.
+ '''
+ self.boardingGroupShow = BoardingGroupShow.BoardingGroupShow(toon)
+ track, trackType = self.boardingGroupShow.getBoardingTrack(self.getElevatorModel(),
+ self.getOffsetPosWrtToonParent(toon, seatIndex),
+ self.getOffsetPosWrtRender(seatIndex),
+ wantToonRotation)
+ return (track, trackType)
diff --git a/toontown/src/building/DistributedElevatorAI.py b/toontown/src/building/DistributedElevatorAI.py
new file mode 100644
index 0000000..5fff67b
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorAI.py
@@ -0,0 +1,389 @@
+from otp.ai.AIBase import *
+from toontown.toonbase import ToontownGlobals
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+from direct.distributed import DistributedObjectAI
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownAccessAI
+
+class DistributedElevatorAI(DistributedObjectAI.DistributedObjectAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedElevatorAI")
+
+ def __init__(self, air, bldg, numSeats = 4, antiShuffle = 0, minLaff = 0):
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.type = ELEVATOR_NORMAL
+ self.countdownTime = ElevatorData[self.type]['countdown']
+ self.bldg = bldg
+ self.bldgDoId = bldg.getDoId()
+ self.seats = []
+ self.setAntiShuffle(antiShuffle)
+ self.setMinLaff(minLaff)
+ if self.antiShuffle:
+ if not hasattr(simbase.air, "elevatorTripId"):
+ simbase.air.elevatorTripId = 1
+ self.elevatorTripId = simbase.air.elevatorTripId
+ simbase.air.elevatorTripId += 1
+ else:
+ self.elevatorTripId = 0
+
+ for seat in range(numSeats):
+ self.seats.append(None)
+ #self.seats = [None, None, None, None]
+ # Flag that tells whether the elevator is currently accepting boarders
+ self.accepting = 0
+ self.fsm = ClassicFSM.ClassicFSM('DistributedElevatorAI',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['opening',
+ 'closed',
+ ]),
+ State.State('opening',
+ self.enterOpening,
+ self.exitOpening,
+ ['waitEmpty', 'waitCountdown']),
+ State.State('waitEmpty',
+ self.enterWaitEmpty,
+ self.exitWaitEmpty,
+ ['waitCountdown']),
+ State.State('waitCountdown',
+ self.enterWaitCountdown,
+ self.exitWaitCountdown,
+ ['waitEmpty', 'allAboard']),
+ State.State('allAboard',
+ self.enterAllAboard,
+ self.exitAllAboard,
+ ['closing', 'waitEmpty']),
+ State.State('closing',
+ self.enterClosing,
+ self.exitClosing,
+ ['closed', 'waitEmpty']),
+ State.State('closed',
+ self.enterClosed,
+ self.exitClosed,
+ ['opening']),
+ ],
+ # Initial State
+ 'off',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ self.boardingParty = None
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ del self.fsm
+ del self.bldg
+ self.ignoreAll()
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def setBoardingParty(self, party):
+ self.boardingParty = party
+
+ def generate(self):
+ self.start()
+ DistributedObjectAI.DistributedObjectAI.generate(self)
+
+ def getBldgDoId(self):
+ return self.bldgDoId
+
+ def findAvailableSeat(self):
+ for i in range(len(self.seats)):
+ if self.seats[i] == None:
+ return i
+ return None
+
+
+ def findAvatar(self, avId):
+ for i in range(len(self.seats)):
+ if self.seats[i] == avId:
+ return i
+ return None
+
+ def countFullSeats(self):
+ avCounter = 0
+ for i in self.seats:
+ if i:
+ avCounter += 1
+ return avCounter
+
+ def countOpenSeats(self):
+ openSeats = 0
+ for i in range(len(self.seats)):
+ if self.seats[i] == None:
+ openSeats += 1
+ return openSeats
+
+ def rejectingBoardersHandler(self, avId, reason = 0, wantBoardingShow = 0):
+ self.rejectBoarder(avId, reason)
+
+ def rejectBoarder(self, avId, reason = 0):
+ self.sendUpdateToAvatarId(avId, "rejectBoard", [avId, reason])
+
+ def acceptingBoardersHandler(self, avId, reason = 0, wantBoardingShow = 0):
+ self.notify.debug("acceptingBoardersHandler")
+ seatIndex = self.findAvailableSeat()
+ if seatIndex == None:
+ self.rejectBoarder(avId, REJECT_NOSEAT)
+ else:
+ self.acceptBoarder(avId, seatIndex, wantBoardingShow)
+ return None
+
+ def acceptBoarder(self, avId, seatIndex, wantBoardingShow = 0):
+ self.notify.debug("acceptBoarder")
+ # Make sure we have a valid seat number
+ #assert((seatIndex >= 0) and (seatIndex <=3))
+ assert((seatIndex >= 0) and (seatIndex <=7))
+ # Make sure the seat is empty
+ assert(self.seats[seatIndex] == None)
+ # Make sure this avatar isn't already seated
+ if (self.findAvatar(avId) != None):
+ return
+ # Put the avatar in that seat
+ self.seats[seatIndex] = avId
+ # Record the time of boarding
+ self.timeOfBoarding = globalClock.getRealTime()
+
+ if wantBoardingShow:
+ self.timeOfGroupBoarding = globalClock.getRealTime()
+
+ # Tell the clients to put the avatar in that seat
+ self.sendUpdate("fillSlot" + str(seatIndex), [avId, wantBoardingShow])
+
+ def rejectingExitersHandler(self, avId):
+ self.rejectExiter(avId)
+
+ def rejectExiter(self, avId):
+ # This doesn't have to do anything. If your exit is rejected,
+ # you'll know because the elevator leaves.
+ pass
+
+ def acceptingExitersHandler(self, avId):
+ self.acceptExiter(avId)
+
+ def clearEmptyNow(self, seatIndex):
+ self.sendUpdate("emptySlot" + str(seatIndex),
+ [0, 0, globalClockDelta.getRealNetworkTime(), 0])
+
+ def clearFullNow(self, seatIndex):
+ # Get the avatar id
+ avId = self.seats[seatIndex]
+ # If there is no one sitting there, that is kind of strange.
+ if avId == None:
+ self.notify.warning("Clearing an empty seat index: " +
+ str(seatIndex) + " ... Strange...")
+ else:
+ # Empty that seat
+ self.seats[seatIndex] = None
+ # Tell the clients that the avatar is no longer in that seat
+ self.sendUpdate("fillSlot" + str(seatIndex), [0, 0])
+ # If the avatar isn't in a seat, we don't care anymore, so
+ # remove the hook to handle unexpected exits.
+ self.ignore(self.air.getAvatarExitEvent(avId))
+
+ def d_setState(self, state):
+ self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()])
+
+ def getState(self):
+ return self.fsm.getCurrentState().getName()
+
+ def avIsOKToBoard(self, av):
+ return (av.hp > self.minLaff) and self.accepting
+
+ def checkBoard(self, av):
+ if (av.hp < self.minLaff):
+ return REJECT_MINLAFF
+ return 0
+
+ def requestBoard(self, *args):
+ self.notify.debug("requestBoard")
+ avId = self.air.getAvatarIdFromSender()
+ if (self.findAvatar(avId) != None):
+ self.notify.warning("Ignoring multiple requests from %s to board." % (avId))
+ return
+
+ av = self.air.doId2do.get(avId)
+ if av:
+ # Only toons with hp greater than the minLaff may board the elevator.
+ boardResponse = self.checkBoard(av)
+ newArgs = (avId,) + args + (boardResponse,)
+
+ # Check that player has full access
+ if not ToontownAccessAI.canAccess(avId, self.zoneId):
+ self.notify.warning("Toon %s does not have access to theeleavtor. " % (avId))
+ self.rejectingBoardersHandler(*newArgs)
+ return
+
+ # Toons who have an active Boarding Group and
+ # are not the leader will be rejected if they try to board the elevator.
+ if self.boardingParty and self.boardingParty.hasActiveGroup(avId) and \
+ (self.boardingParty.getGroupLeader(avId) != avId):
+ self.notify.warning('Rejecting %s from boarding the elevator because he is already part of a Boarding Group.' %avId)
+ self.rejectingBoardersHandler(*newArgs)
+ return
+
+ if boardResponse == 0:
+ self.acceptingBoardersHandler(*newArgs)
+ else:
+ self.rejectingBoardersHandler(*newArgs)
+ else:
+ self.notify.warning(
+ "avid: %s does not exist, but tried to board an elevator"
+ % avId
+ )
+ return
+
+ def partyAvatarBoard(self, avatar, wantBoardingShow = 0):
+ av = avatar
+ avId = avatar.doId
+
+ if (self.findAvatar(avId) != None):
+ self.notify.warning("Ignoring multiple requests from %s to board." % (avId))
+ return
+
+ if av:
+ # Only toons with hp greater than the minLaff may board the elevator.
+ boardResponse = self.checkBoard(av)
+ newArgs = (avId,) + (boardResponse,) + (wantBoardingShow,)
+ if boardResponse == 0:
+ self.acceptingBoardersHandler(*newArgs)
+ else:
+ self.rejectingBoardersHandler(*newArgs)
+ else:
+ self.notify.warning("avid: %s does not exist, but tried to board an elevator" % avId)
+ return
+
+ def requestExit(self, *args):
+ self.notify.debug("requestExit")
+ avId = self.air.getAvatarIdFromSender()
+ av = self.air.doId2do.get(avId)
+ if av:
+ newArgs = (avId,) + args
+ if self.accepting:
+ self.acceptingExitersHandler(*newArgs)
+ else:
+ self.rejectingExitersHandler(*newArgs)
+ else:
+ self.notify.warning(
+ "avId: %s does not exist, but tried to exit an elevator" % avId
+ )
+ return
+
+ ##### How you start up the elevator #####
+ def start(self):
+ self.open()
+
+ ##### Off state #####
+
+ def enterOff(self):
+ self.accepting = 0
+ self.timeOfBoarding = None
+ self.timeOfGroupBoarding = None
+ # Maybe this task cleanup shouldn't be here, but I didn't know
+ # where else to put it, since emptying the seats isn't associated
+ # with any particular task. Perhaps I should have made a nested
+ # State machine of TrolleyOn, or some such, but it seemed like a lot
+ # of work for a few crummy tasks.
+ if hasattr(self, "doId"):
+ for seatIndex in range(len(self.seats)):
+ taskMgr.remove(self.uniqueName("clearEmpty-" + str(seatIndex)))
+
+ def exitOff(self):
+ self.accepting = 0
+
+ ##### Opening state #####
+
+ def open(self):
+ self.fsm.request('opening')
+
+ def enterOpening(self):
+ self.d_setState('opening')
+ self.accepting = 0
+ for seat in self.seats:
+ seat = None
+
+ def exitOpening(self):
+ self.accepting = 0
+ taskMgr.remove(self.uniqueName('opening-timer'))
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self):
+ self.d_setState('waitCountdown')
+ self.accepting = 1
+
+ def exitWaitCountdown(self):
+ print("exit wait countdown")
+ self.accepting = 0
+ taskMgr.remove(self.uniqueName('countdown-timer'))
+ self.newTrip()
+
+ ##### AllAboard state #####
+
+ def enterAllAboard(self):
+ self.accepting = 0
+
+ def exitAllAboard(self):
+ self.accepting = 0
+ taskMgr.remove(self.uniqueName('waitForAllAboard'))
+
+ ##### Closing state #####
+
+ def enterClosing(self):
+ self.d_setState('closing')
+ self.accepting = 0
+
+ def exitClosing(self):
+ self.accepting = 0
+ taskMgr.remove(self.uniqueName('closing-timer'))
+
+ ##### Closed state #####
+
+ def enterClosed(self):
+ self.d_setState('closed')
+
+ def exitClosed(self):
+ return
+
+ ##### WaitEmpty state #####
+
+ def enterWaitEmpty(self):
+ self.d_setState('waitEmpty')
+ self.accepting = 1
+
+
+ def exitWaitEmpty(self):
+ self.accepting = 0
+
+ def setElevatorTripId(self, id):
+ self.elevatorTripId = id
+
+ def getElevatorTripId(self):
+ return self.elevatorTripId
+
+
+ def newTrip(self):
+ if self.antiShuffle:
+ self.elevatorTripId = simbase.air.elevatorTripId
+ if simbase.air.elevatorTripId > 2100000000:
+ simbase.air.elevatorTripId = 1
+ simbase.air.elevatorTripId += 1
+ self.sendUpdate("setElevatorTripId", [self.elevatorTripId])
+
+ def setAntiShuffle(self, antiShuffle):
+ self.antiShuffle = antiShuffle
+
+ def getAntiShuffle(self):
+ return self.antiShuffle
+
+ def setMinLaff(self, minLaff):
+ self.minLaff = minLaff
+
+ def getMinLaff(self):
+ return self.minLaff
diff --git a/toontown/src/building/DistributedElevatorExt.py b/toontown/src/building/DistributedElevatorExt.py
new file mode 100644
index 0000000..3d71505
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorExt.py
@@ -0,0 +1,186 @@
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+from direct.interval.IntervalGlobal import *
+from ElevatorConstants import *
+from ElevatorUtils import *
+import DistributedElevator
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from toontown.hood import ZoneUtil
+from toontown.toonbase import TTLocalizer
+from toontown.toontowngui import TeaserPanel
+
+class DistributedElevatorExt(DistributedElevator.DistributedElevator):
+ def __init__(self, cr):
+ DistributedElevator.DistributedElevator.__init__(self, cr)
+ # When we get enough information about the elevator, we can
+ # set up its nametag. The nametag points out nearby buildings
+ # to the player.
+ self.nametag = None
+ self.currentFloor = -1
+
+ def setupElevator(self):
+ """
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+ if self.isSetup:
+ # If this particular elevator was previously set up, clear
+ # out the old stuff and start over.
+ self.elevatorSphereNodePath.removeNode()
+
+ self.leftDoor = self.bldg.leftDoor
+ self.rightDoor = self.bldg.rightDoor
+ DistributedElevator.DistributedElevator.setupElevator(self)
+ self.setupNametag()
+
+ def disable(self):
+ self.clearNametag()
+ DistributedElevator.DistributedElevator.disable(self)
+
+ def setupNametag(self):
+ if self.nametag == None:
+ self.nametag = NametagGroup()
+ self.nametag.setFont(ToontownGlobals.getBuildingNametagFont())
+ if TTLocalizer.BuildingNametagShadow:
+ self.nametag.setShadow(*TTLocalizer.BuildingNametagShadow)
+ self.nametag.setContents(Nametag.CName)
+ self.nametag.setColorCode(NametagGroup.CCSuitBuilding)
+ self.nametag.setActive(0)
+ self.nametag.setAvatar(self.getElevatorModel())
+
+ name = self.cr.playGame.dnaStore.getTitleFromBlockNumber(self.bldg.block)
+ if not name:
+ name = TTLocalizer.CogsInc
+ else:
+ name += TTLocalizer.CogsIncExt
+
+ self.nametag.setName(name)
+ self.nametag.manage(base.marginManager)
+
+ def clearNametag(self):
+ if self.nametag != None:
+ self.nametag.unmanage(base.marginManager)
+ self.nametag.setAvatar(NodePath())
+ self.nametag = None
+
+ def getBldgDoorOrigin(self):
+ return self.bldg.getSuitDoorOrigin()
+
+ def gotBldg(self, buildingList):
+ # Do not call the super class here because we have some extra checks we need to do
+ self.bldgRequest = None
+ self.bldg = buildingList[0]
+ if not self.bldg:
+ self.notify.error("setBldgDoId: elevator %d cannot find bldg %d!"
+ % (self.doId, self.bldgDoId))
+ return
+ if self.getBldgDoorOrigin():
+ self.bossLevel = self.bldg.getBossLevel()
+ self.setupElevator()
+ else:
+ self.notify.warning("setBldgDoId: elevator %d cannot find suitDoorOrigin for bldg %d!"
+ % (self.doId, bldgDoId))
+ # This is an elevator stuck to a toon building. This can
+ # happen from time to time if the AI crashes before it can
+ # write out its .building state file, or if someone
+ # removes all the .building files while the AI is down.
+ # We have to handle this gracefully; we'll use the isSetup
+ # flag for this purpose.
+ return
+
+ def setFloor(self, floorNumber):
+ # Darken the old light:
+ if self.currentFloor >= 0:
+ self.bldg.floorIndicator[self.currentFloor].setColor(LIGHT_OFF_COLOR)
+
+ # Brighten the new light:
+ if floorNumber >= 0:
+ self.bldg.floorIndicator[floorNumber].setColor(LIGHT_ON_COLOR)
+
+ # Remember the floor:
+ self.currentFloor = floorNumber
+
+ def handleEnterSphere(self, collEntry):
+ self.notify.debug("Entering Elevator Sphere....")
+
+ # If the localAvatar is part of a boarding group and is not a leader,
+ # he is not allowed to enter the elevator - Show a message.
+ if hasattr(localAvatar, "boardingParty") and \
+ localAvatar.boardingParty and \
+ localAvatar.boardingParty.getGroupLeader(localAvatar.doId) and \
+ localAvatar.boardingParty.getGroupLeader(localAvatar.doId) != localAvatar.doId:
+ base.localAvatar.elevatorNotifier.showMe(TTLocalizer.ElevatorGroupMember)
+ elif self.allowedToEnter():
+ # Tell localToon we are considering entering the elevator
+ self.cr.playGame.getPlace().detectedElevatorCollision(self)
+ else:
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.fsm.request('stopped')
+ self.dialog = TeaserPanel.TeaserPanel(pageName='cogHQ',
+ doneFunc=self.handleOkTeaser)
+
+ def handleEnterElevator(self):
+ #print("EXT handleEnterSphere elevator%s avatar%s" % (self.elevatorTripId, localAvatar.lastElevatorLeft))
+
+ # If the leader of the boarding group wants to enter an elevator do it the Boarding Group style.
+ if hasattr(localAvatar, "boardingParty") and \
+ localAvatar.boardingParty and \
+ localAvatar.boardingParty.getGroupLeader(localAvatar.doId):
+ # If you are the leader of the boarding group, do it the boarding group way.
+ if localAvatar.boardingParty.getGroupLeader(localAvatar.doId) == localAvatar.doId:
+ localAvatar.boardingParty.handleEnterElevator(self)
+
+ elif self.elevatorTripId and (localAvatar.lastElevatorLeft == self.elevatorTripId):
+ self.rejectBoard(base.localAvatar.doId, REJECT_SHUFFLE)
+
+ # Only toons with hp can board the elevator.
+ elif base.localAvatar.hp > 0:
+ # Tell the server that this avatar wants to board.
+ toon = base.localAvatar
+ self.sendUpdate("requestBoard", [])
+ else:
+ self.notify.warning("Tried to board elevator with hp: %d" %
+ base.localAvatar.hp)
+
+ ##### WaitEmpty state #####
+
+ def enterWaitEmpty(self, ts):
+ self.elevatorSphereNodePath.unstash()
+ self.forceDoorsOpen()
+ # Toons may now try to board the elevator
+ self.accept(self.uniqueName('enterelevatorSphere'),
+ self.handleEnterSphere)
+ self.accept(self.uniqueName('enterElevatorOK'),
+ self.handleEnterElevator)
+ DistributedElevator.DistributedElevator.enterWaitEmpty(self, ts)
+
+ def exitWaitEmpty(self):
+ self.elevatorSphereNodePath.stash()
+ # Toons may not attempt to board the elevator if it isn't waiting
+ self.ignore(self.uniqueName('enterelevatorSphere'))
+ self.ignore(self.uniqueName('enterElevatorOK'))
+ DistributedElevator.DistributedElevator.exitWaitEmpty(self)
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self, ts):
+ DistributedElevator.DistributedElevator.enterWaitCountdown(self, ts)
+ self.forceDoorsOpen()
+ self.accept(self.uniqueName('enterElevatorOK'),
+ self.handleEnterElevator)
+ self.startCountdownClock(self.countdownTime, ts)
+
+ def exitWaitCountdown(self):
+ self.ignore(self.uniqueName('enterElevatorOK'))
+ DistributedElevator.DistributedElevator.exitWaitCountdown(self)
+
+ def getZoneId(self):
+ return self.bldg.interiorZoneId
+
+ def getElevatorModel(self):
+ return self.bldg.getSuitElevatorNodePath()
+
diff --git a/toontown/src/building/DistributedElevatorExtAI.py b/toontown/src/building/DistributedElevatorExtAI.py
new file mode 100644
index 0000000..50a905d
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorExtAI.py
@@ -0,0 +1,277 @@
+from otp.ai.AIBase import *
+from toontown.toonbase import ToontownGlobals
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+import DistributedElevatorAI
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+
+class DistributedElevatorExtAI(DistributedElevatorAI.DistributedElevatorAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedElevatorExtAI")
+
+ def __init__(self, air, bldg, numSeats = 4, antiShuffle = 0, minLaff = 0): #antiShufflePOI
+ DistributedElevatorAI.DistributedElevatorAI.__init__(
+ self, air, bldg, numSeats, antiShuffle = antiShuffle, minLaff = minLaff)
+ # Do we need this?
+ # self.zoneId, dummy = bldg.getExteriorAndInteriorZoneId()
+ # Flag that tells if any Toon has jumped out of the elevator yet
+ # (this is used to prevent the griefers who jump off at the last
+ # second)
+ self.anyToonsBailed = 0
+ self.boardingParty = None
+
+ def delete(self):
+ # TODO: We really need an immediate clear here
+ # At least it does not crash the AI anymore
+ for seatIndex in range(len(self.seats)):
+ avId = self.seats[seatIndex]
+ if avId:
+ self.clearFullNow(seatIndex)
+ self.clearEmptyNow(seatIndex)
+ DistributedElevatorAI.DistributedElevatorAI.delete(self)
+
+
+
+ def d_setFloor(self, floorNumber):
+ self.sendUpdate('setFloor', [floorNumber])
+
+ def acceptBoarder(self, avId, seatIndex, wantBoardingShow = 0):
+ DistributedElevatorAI.DistributedElevatorAI.acceptBoarder(self, avId, seatIndex, wantBoardingShow)
+ # Add a hook that handles the case where the avatar exits
+ # the district unexpectedly
+ self.acceptOnce(self.air.getAvatarExitEvent(avId),
+ self.__handleUnexpectedExit, extraArgs=[avId])
+ # Put us into waitCountdown state... If we are already there,
+ # this won't do anything.
+ self.fsm.request("waitCountdown")
+
+ def __handleUnexpectedExit(self, avId):
+ self.notify.warning("Avatar: " + str(avId) +
+ " has exited unexpectedly")
+ # Find the exiter's seat index
+ seatIndex = self.findAvatar(avId)
+ # Make sure the avatar is really here
+ if seatIndex == None:
+ pass
+ else:
+ # If the avatar is here, his seat is now empty.
+ self.clearFullNow(seatIndex)
+ # Tell the clients that the avatar is leaving that seat
+ self.clearEmptyNow(seatIndex)
+ #self.sendUpdate("emptySlot" + str(seatIndex),
+ # [avId, globalClockDelta.getRealNetworkTime()])
+ # If all the seats are empty, go back into waitEmpty state
+ if self.countFullSeats() == 0:
+ self.fsm.request('waitEmpty')
+
+ def acceptExiter(self, avId):
+ #print("DistributedElevatorExtAI.acceptExiter")
+ # Find the exiter's seat index
+ seatIndex = self.findAvatar(avId)
+ # It is possible that the avatar exited the shard unexpectedly.
+ if seatIndex == None:
+ pass
+ else:
+ # Empty that seat
+ self.clearFullNow(seatIndex)
+ # Make sure there's no griefing by jumping off the elevator
+ # at the last second
+ bailFlag = 0
+ timeToSend = self.countdownTime
+ if self.antiShuffle:
+ myTask = taskMgr.getTasksNamed(self.uniqueName('countdown-timer'))[0]
+ #print myTask.name
+ #print myTask.runningTotal
+ #print myTask.dt
+ #print myTask.time
+ #print myTask.wakeTime - globalClock.getFrameTime()
+ #self.uniqueName('countdown-timer')
+ timeLeft = myTask.wakeTime - globalClock.getFrameTime()
+ # This fixes an AI crash with a huge negative timeLeft. AI crash on 04/20/10. timeLeft = -44002.155374000002.
+ # myTask.wakeTime became zero for some reason.
+ timeLeft = max(0, timeLeft)
+ timeToSet = timeLeft + 10.0
+ timeToSet = min(timeLeft + 10.0, self.countdownTime)
+ self.setCountdown(timeToSet)
+ timeToSend = timeToSet
+ self.sendUpdate("emptySlot" + str(seatIndex),
+ [avId, 1, globalClockDelta.getRealNetworkTime(), timeToSend])
+ elif (self.anyToonsBailed == 0):
+ bailFlag = 1
+ # Reset the clock
+ self.resetCountdown()
+ self.anyToonsBailed = 1
+ self.sendUpdate("emptySlot" + str(seatIndex),
+ [avId, bailFlag, globalClockDelta.getRealNetworkTime(), timeToSend])
+ else:
+ self.sendUpdate("emptySlot" + str(seatIndex),
+ [avId, bailFlag, globalClockDelta.getRealNetworkTime(), timeToSend])
+
+ # Tell the clients that the avatar is leaving that seat
+
+ # If all the seats are empty, go back into waitEmpty state
+ if self.countFullSeats() == 0:
+ self.fsm.request('waitEmpty')
+ # Wait for the avatar to be done leaving the seat, and then
+ # declare the emptying overwith...
+ taskMgr.doMethodLater(TOON_EXIT_ELEVATOR_TIME,
+ self.clearEmptyNow,
+ self.uniqueName("clearEmpty-%s" % seatIndex),
+ extraArgs = (seatIndex,))
+
+
+ def enterOpening(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterOpening(self)
+ taskMgr.doMethodLater(ElevatorData[ELEVATOR_NORMAL]['openTime'],
+ self.waitEmptyTask,
+ self.uniqueName('opening-timer'))
+
+
+ ##### WaitEmpty state #####
+
+ def waitEmptyTask(self, task):
+ self.fsm.request('waitEmpty')
+ return Task.done
+
+ def enterWaitEmpty(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterWaitEmpty(self)
+ self.anyToonsBailed = 0
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterWaitCountdown(self)
+ # Start the countdown...
+ taskMgr.doMethodLater(self.countdownTime, self.timeToGoTask,
+ self.uniqueName('countdown-timer'))
+
+ def timeToGoTask(self, task):
+ # It is possible that the players exited the district
+ if self.countFullSeats() > 0:
+ self.fsm.request("allAboard")
+ else:
+ self.fsm.request('waitEmpty')
+ return Task.done
+
+ def resetCountdown(self):
+ taskMgr.remove(self.uniqueName('countdown-timer'))
+ taskMgr.doMethodLater(self.countdownTime, self.timeToGoTask,
+ self.uniqueName('countdown-timer'))
+
+ def setCountdown(self, timeToSet):
+
+ taskMgr.remove(self.uniqueName('countdown-timer'))
+ taskMgr.doMethodLater(timeToSet, self.timeToGoTask,
+ self.uniqueName('countdown-timer'))
+
+ def enterAllAboard(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterAllAboard(self)
+ currentTime = globalClock.getRealTime()
+ elapsedTime = currentTime - self.timeOfBoarding
+ self.notify.debug("elapsed time: " + str(elapsedTime))
+ waitTime = max(TOON_BOARD_ELEVATOR_TIME - elapsedTime, 0)
+
+ waitTime += self.getBoardingShowTimeLeft()
+ taskMgr.doMethodLater(waitTime, self.closeTask,
+ self.uniqueName('waitForAllAboard'))
+
+ def getBoardingShowTimeLeft(self):
+ # This method returns the amount of time left from the last time the
+ # group boarding started. Max time is GROUP_BOARDING_TIME.
+ # If we get a number that is not between 0 and GROUP_BOARDING_TIME return 0.
+ currentTime = globalClock.getRealTime()
+ timeLeft = 0.0
+
+ if hasattr(self, 'timeOfGroupBoarding') and self.timeOfGroupBoarding:
+ elapsedTime = currentTime - self.timeOfGroupBoarding
+ timeLeft = max(MAX_GROUP_BOARDING_TIME - elapsedTime, 0)
+ # In case the timeLeft is more than the theoretical maximum
+ if (timeLeft > MAX_GROUP_BOARDING_TIME):
+ timeLeft = 0.0
+
+ return timeLeft
+
+ ##### Closing state #####
+ def closeTask(self, task):
+ # It is possible that the players exited the district
+ if self.countFullSeats() > 0:
+ self.fsm.request("closing")
+ else:
+ self.fsm.request('waitEmpty')
+ return Task.done
+
+ def enterClosing(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterClosing(self)
+ taskMgr.doMethodLater(ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ self.elevatorClosedTask,
+ self.uniqueName('closing-timer'))
+
+ def elevatorClosedTask(self, task):
+ self.elevatorClosed()
+ return Task.done
+
+ def _createInterior(self):
+ self.bldg.createSuitInterior()
+
+ def elevatorClosed(self):
+ numPlayers = self.countFullSeats()
+
+ # It is possible the players exited the district
+ if (numPlayers > 0):
+
+ # Tell the building to get a suit interior ready for us
+ self._createInterior()
+
+ for seatIndex in range(len(self.seats)):
+ avId = self.seats[seatIndex]
+ # Tell each player on the elevator that they should enter the
+ # building now.
+ if avId:
+ assert(avId > 0)
+ # Clear the fill slot
+ self.clearFullNow(seatIndex)
+ else:
+ self.notify.warning("The elevator left, but was empty.")
+
+ # Switch back into opening mode.
+ self.fsm.request("closed")
+
+
+ def requestExit(self, *args):
+ self.notify.debug("requestExit")
+ avId = self.air.getAvatarIdFromSender()
+ av = self.air.doId2do.get(avId)
+ if self.boardingParty and self.boardingParty.getGroupLeader(avId) and avId:
+ #exit all in boarding party
+ if avId == self.boardingParty.getGroupLeader(avId):
+ memberIds = self.boardingParty.getGroupMemberList(avId)
+ for memberId in (memberIds):
+ member = simbase.air.doId2do.get(memberId)
+ if member:
+ #print("elevator exit member")
+ if self.accepting:
+ self.acceptingExitersHandler(memberId)
+ else:
+ self.rejectingExitersHandler(memberId)
+ else:
+ #print("elevator rejecting group member")
+ self.rejectingExitersHandler(avId)
+ else:
+ if av:
+ newArgs = (avId,) + args
+ if self.accepting:
+ #print("elevator exit leader")
+ self.acceptingExitersHandler(*newArgs)
+ else:
+ self.rejectingExitersHandler(*newArgs)
+ else:
+ self.notify.warning(
+ "avId: %s does not exist, but tried to exit an elevator" % avId
+ )
+ return
+
+
diff --git a/toontown/src/building/DistributedElevatorFSM.py b/toontown/src/building/DistributedElevatorFSM.py
new file mode 100644
index 0000000..6570d4e
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorFSM.py
@@ -0,0 +1,673 @@
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+from direct.interval.IntervalGlobal import *
+from ElevatorConstants import *
+from ElevatorUtils import *
+from direct.showbase import PythonUtil
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.distributed import DistributedObject
+from direct.fsm import State
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownGlobals
+from direct.task.Task import Task
+from toontown.hood import ZoneUtil
+from direct.fsm.FSM import FSM
+
+
+class DistributedElevatorFSM(DistributedObject.DistributedObject, FSM):
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedElevator')
+
+ defaultTransitions = {
+ 'Off' : [ 'Opening', 'Closed', 'Off'],
+ 'Opening' : [ 'WaitEmpty', 'WaitCountdown', 'Opening', 'Closing' ],
+ 'WaitEmpty' : [ 'WaitCountdown', "Closing", "Off" ],
+ 'WaitCountdown' : [ 'WaitEmpty', 'AllAboard', "Closing" ],
+ 'AllAboard' : [ 'WaitEmpty', "Closing" ],
+ 'Closing' : [ 'Closed', 'WaitEmpty', 'Closing', 'Opening' ],
+ 'Closed' : [ 'Opening' ],
+ }
+ id = 0
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+ FSM.__init__( self, "Elevator_%s_FSM" % ( self.id ) )
+ self.bldgRequest = None
+ self.toonRequests = {}
+ self.deferredSlots = []
+ self.localToonOnBoard = 0
+ self.boardedAvIds = {}
+ self.openSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_open.mp3")
+ self.finalOpenSfx = None
+ self.closeSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_close.mp3")
+ #Points to Elevator.Elevator when localAvatar steps inside
+ self.elevatorFSM=None
+ self.finalCloseSfx = None
+ self.elevatorPoints = ElevatorPoints
+
+ self.type = ELEVATOR_NORMAL
+ self.countdownTime = ElevatorData[self.type]['countdown']
+
+ #self.setupStates()
+ #self.enterInitialState()
+ self.isSetup = 0
+ self.__preSetupState = None
+ self.bigElevator = 0
+
+ self.offTrack = [None, None, None, None]
+
+ self.boardingParty = None
+
+ # This is bad. Testing?
+ #base.elevator = self
+
+ def generate(self):
+ DistributedObject.DistributedObject.generate(self)
+
+ def setBoardingParty(self, party):
+ self.boardingParty = party
+
+ def setupElevator(self):
+
+ #print("setting up elevator")
+ # Assumes you have a self.leftDoor and self.rightDoor defined
+
+ # Establish a collision sphere. There must be an easier way!
+ collisionRadius = ElevatorData[self.type]['collRadius']
+ self.elevatorSphere = CollisionSphere(0, 5, 0, collisionRadius)
+ self.elevatorSphere.setTangible(0)
+ self.elevatorSphereNode = CollisionNode(self.uniqueName("elevatorSphere"))
+ self.elevatorSphereNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
+ self.elevatorSphereNode.addSolid(self.elevatorSphere)
+ self.elevatorSphereNodePath = self.getElevatorModel().attachNewNode(
+ self.elevatorSphereNode)
+ self.elevatorSphereNodePath.hide()
+ self.elevatorSphereNodePath.reparentTo(self.getElevatorModel())
+ self.elevatorSphereNodePath.stash()
+
+ self.boardedAvIds = {}
+
+ # Establish intervals for opening and closing the doors
+ self.openDoors = getOpenInterval(self,
+ self.leftDoor,
+ self.rightDoor,
+ self.openSfx,
+ self.finalOpenSfx,
+ self.type)
+
+ self.closeDoors = getCloseInterval(self,
+ self.leftDoor,
+ self.rightDoor,
+ self.closeSfx,
+ self.finalCloseSfx,
+ self.type)
+
+ # put a callback at the end of the 'closeDoors' ival so that we can
+ # hide the avatars on board. Otherwise they may still be in the
+ # elevator when the doors open.
+ self.openDoors = Sequence(self.openDoors,
+ Func(self.onDoorOpenFinish),
+ )
+
+ self.closeDoors = Sequence(self.closeDoors,
+ Func(self.onDoorCloseFinish),
+ )
+
+ self.finishSetup()
+
+ def finishSetup(self):
+
+ # Do all the things that we had to defer until the elevator
+ # was fully set up.
+ self.isSetup = 1
+
+ if self.__preSetupState:
+ self.request(self.__preSetupState, 0)
+ self.__preSetupState = None
+
+ for slot in self.deferredSlots:
+ self.fillSlot(*slot)
+
+ self.deferredSlots = []
+
+ def disable(self):
+ #print("Elevator Disable")
+ for track in self.offTrack:
+ if track:
+ if track.isPlaying():
+ track.pause()
+ track = None
+ if self.bldgRequest:
+ self.cr.relatedObjectMgr.abortRequest(self.bldgRequest)
+ self.bldgRequest = None
+ for request in self.toonRequests.values():
+ self.cr.relatedObjectMgr.abortRequest(request)
+ self.toonRequests = {}
+
+ # stop the intervals in case they're playing
+ if hasattr(self, "openDoors"):
+ self.openDoors.pause()
+ if hasattr(self, "closeDoors"):
+ self.closeDoors.pause()
+
+ # Go to the off state when the object is put in the cache
+ self.request("off")
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ #print("Elevator Delete")
+ for track in self.offTrack:
+ if track:
+ if track.isPlaying():
+ track.pause()
+ track = None
+ self.ignoreAll()
+ if self.isSetup:
+ self.elevatorSphereNodePath.removeNode()
+ del self.elevatorSphereNodePath
+ del self.elevatorSphereNode
+ del self.elevatorSphere
+ del self.bldg
+ del self.leftDoor
+ del self.rightDoor
+ del self.openDoors
+ del self.closeDoors
+ #del self.fsm
+ del self.openSfx
+ del self.closeSfx
+ self.isSetup = 0
+ #FSM.delete(self)
+ DistributedObject.DistributedObject.delete(self)
+
+ def setBldgDoId(self, bldgDoId):
+ self.bldgDoId = bldgDoId
+ self.bldgRequest = self.cr.relatedObjectMgr.requestObjects(
+ [bldgDoId], allCallback = self.gotBldg, timeout = 2)
+
+ def gotBldg(self, buildingList):
+
+ self.bldgRequest = None
+ self.bldg = buildingList[0]
+ if not self.bldg:
+ self.notify.error("setBldgDoId: elevator %d cannot find bldg %d!"
+ % (self.doId, self.bldgDoId))
+ return
+ self.setupElevator()
+
+ def gotToon(self, index, avId, toonList):
+ request = self.toonRequests.get(index)
+ if request:
+ del self.toonRequests[index]
+ self.fillSlot(index, avId)
+ else:
+ self.notify.error("gotToon: already had got toon in slot %s." % (index))
+
+
+ def setState(self, state, timestamp):
+ #print("REQUEST FOR STATE HAS BEEN RECEIVED %s" % (state))
+
+ if self.isSetup:
+ self.request(state, globalClockDelta.localElapsedTime(timestamp))
+ else:
+ #print("NOT SETUP FOOL!")
+ # Save the state until we get setup.
+ self.__preSetupState = state
+
+ def fillSlot0(self, avId):
+ self.fillSlot(0, avId)
+
+ def fillSlot1(self, avId):
+ self.fillSlot(1, avId)
+
+ def fillSlot2(self, avId):
+ self.fillSlot(2, avId)
+
+ def fillSlot3(self, avId):
+ self.fillSlot(3, avId)
+
+ def fillSlot4(self, avId):
+ self.fillSlot(4, avId)
+
+ def fillSlot5(self, avId):
+ self.fillSlot(5, avId)
+
+ def fillSlot6(self, avId):
+ self.fillSlot(6, avId)
+
+ def fillSlot7(self, avId):
+ self.fillSlot(7, avId)
+
+ def fillSlot(self, index, avId):
+ self.notify.debug("%s.fillSlot(%s, %s, ...)" % (self.doId, index, avId))
+ request = self.toonRequests.get(index)
+ if request:
+ self.cr.relatedObjectMgr.abortRequest(request)
+ del self.toonRequests[index]
+
+ if avId == 0:
+ # This means that the slot is now empty, and no action should
+ # be taken.
+ pass
+
+ elif not self.cr.doId2do.has_key(avId):
+ # It's someone who hasn't been generated yet.
+ func = PythonUtil.Functor(
+ self.gotToon, index, avId)
+
+ assert not self.toonRequests.has_key(index)
+ self.toonRequests[index] = self.cr.relatedObjectMgr.requestObjects(
+ [avId], allCallback = func)
+
+ elif not self.isSetup:
+ # We haven't set up the elevator yet.
+ self.deferredSlots.append((index, avId))
+
+ else:
+ # If localToon is boarding, he needs to change state
+ if avId == base.localAvatar.getDoId():
+ self.localToonOnBoard = 1
+ elevator = self.getPlaceElevator()
+ elevator.fsm.request("boarding", [self.getElevatorModel()])
+ elevator.fsm.request("boarded")
+
+ toon = self.cr.doId2do[avId]
+ # Parent it to the elevator
+ toon.stopSmooth()
+ # avoid wrtReparent so that we don't muck with the toon's scale
+ #toon.wrtReparentTo(self.getElevatorModel())
+ toon.setZ(self.getElevatorModel(), self.getScaledPoint(index)[2])
+ toon.setShadowHeight(0)
+
+ if toon.isDisguised:
+ toon.suit.loop("walk")
+ animFunc = Func(toon.suit.loop, "neutral")
+ else:
+ toon.setAnimState("run", 1.0)
+ animFunc = Func(toon.setAnimState, "neutral", 1.0)
+ toon.headsUp(self.getElevatorModel(), apply(Point3, self.getScaledPoint(index)))
+
+ track = Sequence(
+ # Pos 1: -1.5, 5, 0
+ # Pos 2: 1.5, 5, 0
+ # Pos 3: -2.5, 3, 0
+ # Pos 4: 2.5, 3, 0
+ LerpPosInterval(toon, TOON_BOARD_ELEVATOR_TIME * 0.75,
+ apply(Point3, self.getScaledPoint(index)),
+ other=self.getElevatorModel()),
+ LerpHprInterval(toon, TOON_BOARD_ELEVATOR_TIME * 0.25,
+ Point3(180, 0, 0),
+ other=self.getElevatorModel()),
+ #Func(toon.setAnimState, "neutral", 1.0),
+ animFunc,
+ name = toon.uniqueName("fillElevator"),
+ autoPause = 1)
+ track.start()
+
+ assert avId not in self.boardedAvIds
+ self.boardedAvIds[avId] = index
+ #print("Boarded Av Ids %s" % (self.boardedAvIds))
+
+ def emptySlot0(self, avId, bailFlag, timestamp):
+ self.emptySlot(0, avId, bailFlag, timestamp)
+
+ def emptySlot1(self, avId, bailFlag, timestamp):
+ self.emptySlot(1, avId, bailFlag, timestamp)
+
+ def emptySlot2(self, avId, bailFlag, timestamp):
+ self.emptySlot(2, avId, bailFlag, timestamp)
+
+ def emptySlot3(self, avId, bailFlag, timestamp):
+ self.emptySlot(3, avId, bailFlag, timestamp)
+
+ def emptySlot4(self, avId, bailFlag, timestamp):
+ self.emptySlot(4, avId, bailFlag, timestamp)
+
+ def emptySlot5(self, avId, bailFlag, timestamp):
+ self.emptySlot(5, avId, bailFlag, timestamp)
+
+ def emptySlot6(self, avId, bailFlag, timestamp):
+ self.emptySlot(6, avId, bailFlag, timestamp)
+
+ def emptySlot7(self, avId, bailFlag, timestamp):
+ self.emptySlot(7, avId, bailFlag, timestamp)
+
+ def notifyToonOffElevator(self, toon):
+ if self.cr: #hack test
+ toon.setAnimState("neutral", 1.0)
+ if toon == base.localAvatar:
+ print("moving the local toon off the elevator")
+ doneStatus = {
+ 'where' : 'exit',
+ }
+ elevator = self.getPlaceElevator()
+ elevator.signalDone(doneStatus)
+ self.localToonOnBoard = 0
+ else:
+ toon.startSmooth()
+ return
+ else:
+ #print("notifyToonOffElevator NOCR")
+ pass
+
+ def emptySlot(self, index, avId, bailFlag, timestamp):
+ print "Emptying slot: %d for %d" % (index, avId)
+ # If localToon is exiting, he needs to change state
+ if avId == 0:
+ # This means that no one is currently exiting, and no action
+ # should be taken
+ pass
+
+ elif not self.isSetup:
+ # We haven't set up the elevator yet. Remove the toon
+ # from the deferredSlots list, if it is there.
+ newSlots = []
+ for slot in self.deferredSlots:
+ if slot[0] != index:
+ newSlots.append(slot)
+
+ self.deferredSlots = newSlots
+
+ else:
+ if self.cr.doId2do.has_key(avId):
+ # See if we need to reset the clock
+ # (countdown assumes we've created a clockNode already)
+ if (bailFlag == 1 and hasattr(self, 'clockNode')):
+ if (timestamp < self.countdownTime and
+ timestamp >= 0):
+ self.countdown(self.countdownTime - timestamp)
+ else:
+ self.countdown(self.countdownTime)
+ # If the toon exists, look it up
+ toon = self.cr.doId2do[avId]
+ # avoid wrtReparent so that we don't muck with the toon's scale
+ ## Parent it to render
+ #toon.wrtReparentTo(render)
+ toon.stopSmooth()
+
+ if toon.isDisguised:
+ toon.suit.loop("walk")
+ animFunc = Func(toon.suit.loop, "neutral")
+ else:
+ toon.setAnimState("run", 1.0)
+ animFunc = Func(toon.setAnimState, "neutral", 1.0)
+
+ # Place it on the appropriate spot relative to the
+ # elevator
+
+ if self.offTrack[index]:
+ if self.offTrack[index].isPlaying():
+ self.offTrack[index].finish()
+ self.offTrack[index] = None
+
+
+
+
+ self.offTrack[index] = Sequence(
+ # TODO: Find the right coords for the elevator
+
+ LerpPosInterval(toon, TOON_EXIT_ELEVATOR_TIME,
+ Point3(0,-ElevatorData[self.type]['collRadius'],0),
+ startPos = apply(Point3, self.getScaledPoint(index)),
+ other=self.getElevatorModel()
+ ),
+ animFunc,
+ # Tell the toon he is free to roam now
+ Func(self.notifyToonOffElevator, toon),
+ name = toon.uniqueName("emptyElevator"),
+ autoPause = 1)
+
+
+ # Tell localToon he is exiting (if localToon is on board)
+ if avId == base.localAvatar.getDoId():
+ messenger.send("exitElevator")
+ scale = base.localAvatar.getScale()
+ self.offTrack[index].append(Func(base.camera.setScale, scale))
+
+ self.offTrack[index].start()
+
+ # if the elevator is generated as a toon is leaving it,
+ # we will not have gotten a corresponding 'fillSlot' message
+ # for that toon, hence the toon will not be found in
+ # boardedAvIds
+ if avId in self.boardedAvIds:
+ del self.boardedAvIds[avId]
+
+ else:
+ self.notify.warning("toon: " + str(avId) +
+ " doesn't exist, and" +
+ " cannot exit the elevator!")
+
+ def handleEnterSphere(self, collEntry):
+ self.notify.debug("Entering Elevator Sphere....")
+ print("FSMhandleEnterSphere elevator%s avatar%s" % (self.elevatorTripId, localAvatar.lastElevatorLeft))
+ if self.elevatorTripId and (localAvatar.lastElevatorLeft == self.elevatorTripId):
+ #print("NO BACKCIES!")
+ self.rejectBoard(base.localAvatar.doId, REJECT_SHUFFLE)
+ # Only toons with hp can board the elevator.
+ elif base.localAvatar.hp > 0:
+ # Put localToon into requestBoard mode.
+ self.cr.playGame.getPlace().detectedElevatorCollision(self)
+ # Tell the server that this avatar wants to board.
+ toon = base.localAvatar
+ self.sendUpdate("requestBoard",[])
+
+ def rejectBoard(self, avId, reason = 0):
+ # reason 0: unknown, 1: shuffle, 2: too low laff, 3: no seat, 4: need promotion
+ # This should only be sent to us if our localToon requested
+ # permission to board the elevator.
+ print("rejectBoard %s" % (reason))
+ if hasattr(base.localAvatar, "elevatorNotifier"):
+ if reason == REJECT_SHUFFLE:
+ base.localAvatar.elevatorNotifier.showMe(TTLocalizer.ElevatorHoppedOff)
+ elif reason == REJECT_MINLAFF:
+ base.localAvatar.elevatorNotifier.showMe((TTLocalizer.ElevatorMinLaff % (self.minLaff)))
+ elif reason == REJECT_PROMOTION:
+ base.localAvatar.elevatorNotifier.showMe(TTLocalizer.BossElevatorRejectMessage)
+ elif reason == REJECT_BLOCKED_ROOM:
+ base.localAvatar.elevatorNotifier.showMe(TTLocalizer.ElevatorBlockedRoom)
+ assert(base.localAvatar.getDoId() == avId)
+ doneStatus = {
+ 'where' : 'reject',
+ }
+ elevator = self.getPlaceElevator()
+ if elevator:
+ elevator.signalDone(doneStatus)
+
+ def timerTask(self, task):
+ countdownTime = int(task.duration - task.time)
+ timeStr = str(countdownTime)
+
+ if self.clockNode.getText() != timeStr:
+ self.clockNode.setText(timeStr)
+
+ if task.time >= task.duration:
+ return Task.done
+ else:
+ return Task.cont
+
+ def countdown(self, duration):
+ countdownTask = Task(self.timerTask)
+ countdownTask.duration = duration
+ taskMgr.remove(self.uniqueName("elevatorTimerTask"))
+ return taskMgr.add(countdownTask, self.uniqueName("elevatorTimerTask"))
+
+ def handleExitButton(self):
+ # This gets called when the exit button gets pushed.
+ self.sendUpdate("requestExit")
+
+ def enterWaitCountdown(self, ts):
+ self.elevatorSphereNodePath.unstash()
+ # Toons may now try to board the elevator
+ self.accept(self.uniqueName('enterelevatorSphere'),
+ self.handleEnterSphere)
+ self.accept("elevatorExitButton", self.handleExitButton)
+ self.lastState = self.state
+ return
+
+ def exitWaitCountdown(self):
+ self.elevatorSphereNodePath.stash()
+ # Toons may not attempt to board the elevator if it isn't waiting
+ self.ignore(self.uniqueName('enterelevatorSphere'))
+ self.ignore("elevatorExitButton")
+ self.ignore("localToonLeft")
+ # Stop the countdown clock...
+ taskMgr.remove(self.uniqueName("elevatorTimerTask"))
+ self.clock.removeNode()
+ del self.clock
+ del self.clockNode
+
+ def enterClosing(self, ts):
+ # Close the elevator doors
+ #print("distributedElevator.enterClosing")
+ #print(self.lastState)
+ if self.localToonOnBoard:
+ elevator = self.getPlaceElevator()
+ #elevator.fsm.request("elevatorClosing")
+ #if not self.closeDoors.isPlaying() and self.fsm.getCurrentState().getName() != 'closing': #badLine here REMOVE IT!!!
+ if self.closeDoors.isPlaying() or self.lastState == 'closed' or self.openDoors.isPlaying():
+ self.doorsNeedToClose = 1
+ else:
+ self.doorsNeedToClose = 0
+ self.closeDoors.start(ts)
+ #print("closeDoors.start-eC")
+ #else:
+ # self.doorsNeedToClose = 1
+
+ def exitClosing(self):
+ return
+
+ def onDoorOpenFinish(self):
+ #print("door open finish")
+ pass
+
+ def onDoorCloseFinish(self):
+ """this is called when the elevator doors finish closing on the client
+ """
+ #import pdb; pdb.set_trace()
+ # for any avatars that are still parented to us, remove them from the scene graph
+ # so that they're not there when the doors open again
+ for avId in self.boardedAvIds.keys():
+ av = self.cr.doId2do.get(avId)
+ if av is not None:
+ if av.getParent().compareTo(self.getElevatorModel()) == 0:
+ av.detachNode()
+ #self.boardedAvIds = {}
+
+ def enterClosed(self, ts):
+ #print("DistributedElevator.enterClosed %s" % (self.doId))
+ #self.forceDoorsClosed()
+ self.__doorsClosed(self.getZoneId())
+ return
+
+ def exitClosed(self):
+ #import pdb; pdb.set_trace()
+ return
+
+ def forceDoorsOpen(self):
+ #print("forcing doors open FSM")
+ openDoors(self.leftDoor, self.rightDoor)
+
+
+ def forceDoorsClosed(self):
+ #print("DistributedElevator.forceDoorsClosed %s" % (self.doId))
+ #import pdb; pdb.set_trace()
+ self.closeDoors.finish()
+ closeDoors(self.leftDoor, self.rightDoor)
+
+ def enterOff(self):
+ self.lastState = self.state
+ return
+
+ def exitOff(self):
+ return
+
+ def enterWaitEmpty(self, ts):
+ self.lastState = self.state
+ return
+
+ def exitWaitEmpty(self):
+ return
+
+ def enterOpening(self, ts):
+ # Open the elevator doors
+ self.openDoors.start(ts)
+ self.lastState = self.state
+ return
+
+ def exitOpening(self):
+ return
+
+ def startCountdownClock(self, countdownTime, ts):
+ # Start the countdown clock...
+ self.clockNode = TextNode("elevatorClock")
+ self.clockNode.setFont(ToontownGlobals.getSignFont())
+ self.clockNode.setAlign(TextNode.ACenter)
+ self.clockNode.setTextColor(0.5, 0.5, 0.5, 1)
+ self.clockNode.setText(str(int(countdownTime)))
+ self.clock = self.getElevatorModel().attachNewNode(self.clockNode)
+ # TODO: Get the right coordinates for the elevator clock.
+ self.clock.setPosHprScale(0, 4.4, 6.0,
+ 0, 0, 0,
+ 2.0, 2.0, 2.0)
+ if ts < countdownTime:
+ self.countdown(countdownTime - ts)
+
+ def __doorsClosed(self, zoneId):
+ assert(self.notify.debug('doorsClosed()'))
+ if (self.localToonOnBoard):
+ self.localAvatar.stopGlitchKiller()
+ hoodId = ZoneUtil.getHoodId(zoneId)
+ loader = 'suitInterior'
+ where = 'suitInterior'
+ if base.cr.wantCogdominiums:
+ loader = 'cogdoInterior'
+ where = 'cogdoInterior'
+ doneStatus = {
+ 'loader' : loader,
+ 'where' : where,
+ 'hoodId' : hoodId,
+ 'zoneId' : zoneId,
+ 'shardId' : None,
+ }
+
+ elevator = self.elevatorFSM #self.getPlaceElevator()
+ del self.elevatorFSM
+ elevator.signalDone(doneStatus)
+
+ def getElevatorModel(self):
+ self.notify.error("getElevatorModel: pure virtual -- inheritors must override")
+
+ def getPlaceElevator(self):
+ place = self.cr.playGame.getPlace()
+ if not hasattr(place, "elevator"):
+ self.notify.warning("Place was in state '%s' instead of Elevator." % (place.state))
+ place.detectedElevatorCollision(self)
+ return None
+ return place.elevator
+
+ def getScaledPoint(self, index):
+ point = self.elevatorPoints[index]
+ #import pdb; pdb.set_trace()
+ return point
+
+ def setElevatorTripId(self, id):
+ self.elevatorTripId = id
+
+ def getElevatorTripId(self):
+ return self.elevatorTripId
+
+ def setAntiShuffle(self, antiShuffle):
+ self.antiShuffle = antiShuffle
+
+ def getAntiShuffle(self):
+ return self.antiShuffle
+
+ def setMinLaff(self, minLaff):
+ self.minLaff = minLaff
+
+ def getMinLaff(self):
+ return self.minLaff
+
+ def getDestName(self):
+ return None
+
diff --git a/toontown/src/building/DistributedElevatorFSMAI.py b/toontown/src/building/DistributedElevatorFSMAI.py
new file mode 100644
index 0000000..f2d32a5
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorFSMAI.py
@@ -0,0 +1,355 @@
+from otp.ai.AIBase import *
+from toontown.toonbase import ToontownGlobals
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+from direct.distributed import DistributedObjectAI
+#from direct.fsm import ClassicFSM
+#from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm.FSM import FSM
+
+
+class DistributedElevatorFSMAI(DistributedObjectAI.DistributedObjectAI, FSM):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedElevatorFSMAI")
+ #"""
+ defaultTransitions = {
+ 'Off' : [ 'Opening', 'Closed'],
+ 'Opening' : [ 'WaitEmpty', 'WaitCountdown', 'Opening', 'Closing' ],
+ 'WaitEmpty' : [ 'WaitCountdown', "Closing" ],
+ 'WaitCountdown' : [ 'WaitEmpty', 'AllAboard', "Closing" ],
+ 'AllAboard' : [ 'WaitEmpty', "Closing" ],
+ 'Closing' : [ 'Closed', 'WaitEmpty', 'Closing', 'Opening' ],
+ 'Closed' : [ 'Opening' ],
+ }
+ #"""
+ id = 0
+
+ def __init__(self, air, bldg, numSeats = 4, antiShuffle = 0, minLaff = 0):
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ FSM.__init__( self, "Elevator_%s_FSM" % ( self.id ) )
+ self.type = ELEVATOR_NORMAL
+ self.countdownTime = ElevatorData[self.type]['countdown']
+ self.bldg = bldg
+ self.bldgDoId = bldg.getDoId()
+ self.seats = []
+ for seat in range(numSeats):
+ self.seats.append(None)
+ #self.seats = [None, None, None, None]
+ # Flag that tells whether the elevator is currently accepting boarders
+ self.accepting = 0
+ #self.setupStates()
+ #self.fsm.enterInitialState()
+ self.setAntiShuffle(antiShuffle)
+ self.setMinLaff(minLaff)
+ if self.antiShuffle:
+ if not hasattr(simbase.air, "elevatorTripId"):
+ simbase.air.elevatorTripId = 1
+ self.elevatorTripId = simbase.air.elevatorTripId
+ simbase.air.elevatorTripId += 1
+ else:
+ self.elevatorTripId = 0
+
+
+
+ def delete(self):
+ #self.requestFinalState()
+ #del self.fsm
+ del self.bldg
+ self.ignoreAll()
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def generate(self):
+ #print("distributedElevator.generate")
+ DistributedObjectAI.DistributedObjectAI.generate(self)
+ self.start()
+
+ def getBldgDoId(self):
+ return self.bldgDoId
+
+ def findAvailableSeat(self):
+ for i in range(len(self.seats)):
+ if self.seats[i] == None:
+ return i
+ return None
+
+ def findAvatar(self, avId):
+ #print("find Avatar")
+ #print self.seats
+ for i in range(len(self.seats)):
+ if self.seats[i] == avId:
+ return i
+ return None
+
+ def countFullSeats(self):
+ avCounter = 0
+ for i in self.seats:
+ if i:
+ avCounter += 1
+ return avCounter
+
+ def countOpenSeats(self):
+ openSeats = 0
+ for i in range(len(self.seats)):
+ if self.seats[i] == None:
+ openSeats += 1
+ return openSeats
+
+ def rejectingBoardersHandler(self, avId, reason = 0):
+ self.rejectBoarder(avId, reason)
+
+ def rejectBoarder(self, avId, reason = 0):
+ self.sendUpdateToAvatarId(avId, "rejectBoard", [avId, reason])
+
+ def acceptingBoardersHandler(self, avId, reason = 0):
+ self.notify.debug("acceptingBoardersHandler")
+ seatIndex = self.findAvailableSeat()
+ if seatIndex == None:
+ #print("rejectBoarder")
+ self.rejectBoarder(avId, REJECT_NOSEAT)
+ else:
+ #print("acceptBoarder")
+ self.acceptBoarder(avId, seatIndex)
+ return None
+
+ def acceptBoarder(self, avId, seatIndex):
+ self.notify.debug("acceptBoarder")
+ # Make sure we have a valid seat number
+ #assert((seatIndex >= 0) and (seatIndex <=3))
+ assert((seatIndex >= 0) and (seatIndex <=7))
+ # Make sure the seat is empty
+ assert(self.seats[seatIndex] == None)
+ # Make sure this avatar isn't already seated
+ if (self.findAvatar(avId) != None):
+ return
+ # Put the avatar in that seat
+ self.seats[seatIndex] = avId
+ # Record the time of boarding
+ self.timeOfBoarding = globalClock.getRealTime()
+ # Tell the clients to put the avatar in that seat
+ self.sendUpdate("fillSlot" + str(seatIndex),
+ [avId])
+
+ def rejectingExitersHandler(self, avId):
+ self.rejectExiter(avId)
+
+ def rejectExiter(self, avId):
+ # This doesn't have to do anything. If your exit is rejected,
+ # you'll know because the elevator leaves.
+ pass
+
+ def acceptingExitersHandler(self, avId):
+ self.acceptExiter(avId)
+
+ def clearEmptyNow(self, seatIndex):
+ self.sendUpdate("emptySlot" + str(seatIndex),
+ [0, 0, globalClockDelta.getRealNetworkTime()])
+
+ def clearFullNow(self, seatIndex):
+ # Get the avatar id
+ avId = self.seats[seatIndex]
+ # If there is no one sitting there, that is kind of strange.
+ if avId == None:
+ self.notify.warning("Clearing an empty seat index: " +
+ str(seatIndex) + " ... Strange...")
+ else:
+ # Empty that seat
+ self.seats[seatIndex] = None
+ # Tell the clients that the avatar is no longer in that seat
+ self.sendUpdate("fillSlot" + str(seatIndex),
+ [0])
+ # If the avatar isn't in a seat, we don't care anymore, so
+ # remove the hook to handle unexpected exits.
+ self.ignore(self.air.getAvatarExitEvent(avId))
+
+ def d_setState(self, state):
+ self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()])
+
+ def getState(self):
+ return self.state
+
+ def avIsOKToBoard(self, av):
+ return (av.hp > self.minLaff) and self.accepting
+
+ def checkBoard(self, av):
+ if (av.hp < self.minLaff):
+ return REJECT_MINLAFF
+ return 0
+
+ def requestBoard(self, *args):
+ self.notify.debug("requestBoard")
+ avId = self.air.getAvatarIdFromSender()
+ if (self.findAvatar(avId) != None):
+ self.notify.warning("Ignoring multiple requests from %s to board." % (avId))
+ return
+
+ av = self.air.doId2do.get(avId)
+ if av:
+ newArgs = (avId,) + args
+ # Only toons with hp greater than the minLaff may board the elevator.
+ boardResponse = self.checkBoard(av)
+ newArgs = (avId,) + args + (boardResponse,)
+ if boardResponse == 0:
+ self.acceptingBoardersHandler(*newArgs)
+ else:
+ self.rejectingBoardersHandler(*newArgs)
+ else:
+ self.notify.warning(
+ "avid: %s does not exist, but tried to board an elevator"
+ % avId
+ )
+ return
+
+ def requestExit(self, *args):
+ if hasattr(self, 'air'):
+ #print("REQUEST DGG.EXIT")
+ self.notify.debug("requestExit")
+ avId = self.air.getAvatarIdFromSender()
+ av = self.air.doId2do.get(avId)
+ if av:
+ newArgs = (avId,) + args
+ if self.accepting:
+ self.acceptingExitersHandler(*newArgs)
+ else:
+ self.rejectingExitersHandler(*newArgs)
+ else:
+ self.notify.warning(
+ "avId: %s does not exist, but tried to exit an elevator" % avId
+ )
+ return
+
+ ##### How you start up the elevator #####
+ def start(self):
+ self.open()
+
+ ##### Off state #####
+
+ def enterOff(self):
+ #print("DistributedElevatorAI.enterOff")
+ self.accepting = 0
+ self.timeOfBoarding = None
+ # Maybe this task cleanup shouldn't be here, but I didn't know
+ # where else to put it, since emptying the seats isn't associated
+ # with any particular task. Perhaps I should have made a nested
+ # State machine of TrolleyOn, or some such, but it seemed like a lot
+ # of work for a few crummy tasks.
+ if hasattr(self, "doId"):
+ for seatIndex in range(len(self.seats)):
+ taskMgr.remove(self.uniqueName("clearEmpty-" + str(seatIndex)))
+
+ def exitOff(self):
+ #print("DistributedElevatorAI.exitOff")
+ self.accepting = 0
+
+ ##### Opening state #####
+
+ def open(self):
+ self.request('Opening')
+
+ def enterOpening(self):
+ #print("DistributedElevatorAI.enterOpening")
+ self.d_setState('Opening')
+ self.accepting = 0
+ for seat in self.seats:
+ seat = None
+
+ def exitOpening(self):
+ #print("DistributedElevatorAI.exitOpening")
+ self.accepting = 0
+ taskMgr.remove(self.uniqueName('opening-timer'))
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self):
+ #print("DistributedElevatorAI.enterWaitCountdown")
+ self.d_setState('WaitCountdown')
+ self.accepting = 1
+
+ def exitWaitCountdown(self):
+ #print("DistributedElevatorAI.exitWaitCountdown")
+ print("exit wait countdown")
+ self.accepting = 0
+ taskMgr.remove(self.uniqueName('countdown-timer'))
+ self.newTrip()
+
+ ##### AllAboard state #####
+
+ def enterAllAboard(self):
+ #print("DistributedElevatorAI.enterAllAboard")
+ self.accepting = 0
+
+ def exitAllAboard(self):
+ #print("DistributedElevatorAI.exitAllAboard")
+ self.accepting = 0
+ taskMgr.remove(self.uniqueName('waitForAllAboard'))
+
+ ##### Closing state #####
+
+ def enterClosing(self):
+ #print("DistributedElevatorAI.enterClosing")
+ self.d_setState('Closing')
+ self.accepting = 0
+
+ def exitClosing(self):
+ #print("DistributedElevatorAI.exitClosing")
+ self.accepting = 0
+ taskMgr.remove(self.uniqueName('closing-timer'))
+
+ ##### Closed state #####
+
+ def enterClosed(self):
+ #print("DistributedElevatorAI.enterClosed")
+ if hasattr(self, "doId"):
+ print self.doId
+ self.d_setState('Closed')
+
+ def exitClosed(self):
+ #print("DistributedElevatorAI.exitClosed")
+ return
+
+ ##### WaitEmpty state #####
+
+ def enterWaitEmpty(self):
+ #print("DistributedElevatorAI.enterWaitEmpty")
+ #print("WAIT EMPTY")
+ for i in range(len(self.seats)):
+ self.seats[i] = None
+ print self.seats
+ self.d_setState('WaitEmpty')
+ self.accepting = 1
+
+
+
+
+
+
+ def exitWaitEmpty(self):
+ self.accepting = 0
+
+ def setElevatorTripId(self, id):
+ self.elevatorTripId = id
+
+ def getElevatorTripId(self):
+ return self.elevatorTripId
+
+ def newTrip(self):
+ if self.antiShuffle:
+ self.elevatorTripId = simbase.air.elevatorTripId
+ if simbase.air.elevatorTripId > 2100000000:
+ simbase.air.elevatorTripId = 1
+ simbase.air.elevatorTripId += 1
+ self.sendUpdate("setElevatorTripId", [self.elevatorTripId])
+
+ def setAntiShuffle(self, antiShuffle):
+ self.antiShuffle = antiShuffle
+
+ def getAntiShuffle(self):
+ return self.antiShuffle
+
+ def setMinLaff(self, minLaff):
+ self.minLaff = minLaff
+
+ def getMinLaff(self):
+ return self.minLaff
diff --git a/toontown/src/building/DistributedElevatorFloor.py b/toontown/src/building/DistributedElevatorFloor.py
new file mode 100644
index 0000000..448c910
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorFloor.py
@@ -0,0 +1,436 @@
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+from direct.interval.IntervalGlobal import *
+from ElevatorConstants import *
+from ElevatorUtils import *
+import DistributedElevatorFSM
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from toontown.hood import ZoneUtil
+from toontown.toonbase import TTLocalizer
+from direct.fsm.FSM import FSM
+from direct.task import Task
+
+class DistributedElevatorFloor(DistributedElevatorFSM.DistributedElevatorFSM):
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedElevatorFloor')
+
+ defaultTransitions = {
+ 'Off' : [ 'Opening', 'Closed'],
+ 'Opening' : [ 'WaitEmpty', 'WaitCountdown', 'Opening', 'Closing' ],
+ 'WaitEmpty' : [ 'WaitCountdown', "Closing" ],
+ 'WaitCountdown' : [ 'WaitEmpty', 'AllAboard', "Closing", 'WaitCountdown' ],
+ 'AllAboard' : [ 'WaitEmpty', "Closing" ],
+ 'Closing' : [ 'Closed', 'WaitEmpty', 'Closing', 'Opening' ],
+ 'Closed' : [ 'Opening' ],
+ }
+ id = 0
+
+ def __init__(self, cr):
+ DistributedElevatorFSM.DistributedElevatorFSM.__init__(self, cr)
+ FSM.__init__( self, "ElevatorFloor_%s_FSM" % ( self.id ) )
+ # When we get enough information about the elevator, we can
+ # set up its nametag. The nametag points out nearby buildings
+ # to the player.
+ self.type = ELEVATOR_STAGE
+ self.countdownTime = ElevatorData[self.type]['countdown']
+ self.nametag = None
+ self.currentFloor = -1
+ self.isLocked = 0
+ self.isEntering = 0
+ self.doorOpeningFlag = 0 #for when the door is forced closed while opening
+ self.doorsNeedToClose = 0 #for when the door is forced closed while opening
+ self.wantState = 0
+ #self.latchRoom = None
+ self.latch = None
+
+ self.lastState = self.state
+
+
+
+ def setupElevator2(self):
+ self.elevatorModel = loader.loadModel("phase_4/models/modules/elevator")
+ #self.elevatorModel = loader.loadModel("phase_11/models/lawbotHQ/LB_Elevator")
+ #self.elevatorModel.reparentTo(render)
+ self.elevatorModel.reparentTo(hidden)
+ self.elevatorModel.setScale(1.05)
+ self.leftDoor = self.elevatorModel.find("**/left-door")
+ self.rightDoor = self.elevatorModel.find("**/right-door")
+ # No lights on this elevator
+ self.elevatorModel.find("**/light_panel").removeNode()
+ self.elevatorModel.find("**/light_panel_frame").removeNode()
+ """
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+ if self.isSetup:
+ # If this particular elevator was previously set up, clear
+ # out the old stuff and start over.
+ self.elevatorSphereNodePath.removeNode()
+
+ #self.leftDoor = self.bldg.leftDoor
+ #self.rightDoor = self.bldg.rightDoor
+ DistributedElevatorFSM.DistributedElevatorFSM.setupElevator(self)
+
+ def setupElevator(self):
+ """setupElevator(self)
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+
+ # TODO: place this on a node indexed by the entraceId
+ self.elevatorModel = loader.loadModel(
+ "phase_11/models/lawbotHQ/LB_ElevatorScaled")
+ if not self.elevatorModel:
+ self.notify.error("No Elevator Model in DistributedElevatorFloor.setupElevator. Please inform JML. Fool!")
+
+ # The big cog icon on the top is only visible at the BossRoom.
+ #icon = self.elevatorModel.find('**/big_frame/')
+ #if not icon.isEmpty():
+ # icon.hide()
+
+ self.leftDoor = self.elevatorModel.find("**/left-door")
+ if self.leftDoor.isEmpty():
+ self.leftDoor = self.elevatorModel.find("**/left_door")
+
+ self.rightDoor = self.elevatorModel.find("**/right-door")
+ if self.rightDoor.isEmpty():
+ self.rightDoor = self.elevatorModel.find("**/right_door")
+
+ #self.elevatorModel.setH(180)
+
+ DistributedElevatorFSM.DistributedElevatorFSM.setupElevator(self)
+
+ def generate(self):
+ DistributedElevatorFSM.DistributedElevatorFSM.generate(self)
+ #self.accept("LawOffice_Spec_Loaded", self.__placeElevator)
+
+ def announceGenerate(self):
+ DistributedElevatorFSM.DistributedElevatorFSM.announceGenerate(self)
+ if self.latch:
+ self.notify.info("Setting latch in announce generate")
+ self.setLatch(self.latch)
+
+ def __placeElevator(self):
+ self.notify.debug("PLACING ELEVATOR FOOL!!")
+ if self.isEntering:
+ elevatorNode = render.find("**/elevator_origin")
+ if not elevatorNode.isEmpty():
+ self.elevatorModel.setPos(0,0, 0)
+ self.elevatorModel.reparentTo(elevatorNode)
+ else:
+ #explode
+ self.notify.debug("NO NODE elevator_origin!!")
+ else:
+ elevatorNode = render.find("**/SlidingDoor")
+ if not elevatorNode.isEmpty():
+ self.elevatorModel.setPos(0,10,-15)
+ self.elevatorModel.setH(180)
+ self.elevatorModel.reparentTo(elevatorNode)
+ else:
+ #explode
+ self.notify.debug("NO NODE SlidingDoor!!")
+
+ def setLatch(self, markerId):
+ self.notify.info("Setting latch")
+ #room = self.cr.doId2do.get(roomId)
+ marker = self.cr.doId2do.get(markerId)
+ self.latchRequest = self.cr.relatedObjectMgr.requestObjects(
+ [markerId], allCallback = self.set2Latch, timeout = 5)
+ self.latch = markerId
+
+
+ def set2Latch(self, taskMgrFooler = None):
+ if hasattr(self, "cr"): #might callback to dead object
+ marker = self.cr.doId2do.get(self.latch)
+ if marker:
+ self.elevatorModel.reparentTo(marker)
+ return
+ taskMgr.doMethodLater(10.0, self._repart2Marker, "elevatorfloor-markerReparent")
+ self.notify.warning("Using backup, do method later version of latch")
+
+ def _repart2Marker(self, taskFoolio = 0):
+ if hasattr(self, "cr"): #might call to dead object
+ marker = self.cr.doId2do.get(self.latch)
+ if marker:
+ self.elevatorModel.reparentTo(marker)
+ else:
+ self.notify.error("could not find latch even in defered try")
+
+ def setPos(self, x, y, z):
+ self.elevatorModel.setPos(x, y, z)
+
+ def setH(self, H):
+ self.elevatorModel.setH(H)
+
+ def delete(self):
+ DistributedElevatorFSM.DistributedElevatorFSM.delete(self)
+ self.elevatorModel.removeNode()
+ del self.elevatorModel
+ self.ignore("LawOffice_Spec_Loaded")
+ self.ignoreAll()
+
+ def disable(self):
+ #self.clearNametag()
+ DistributedElevatorFSM.DistributedElevatorFSM.disable(self)
+
+ def setEntranceId(self, entranceId):
+ self.entranceId = entranceId
+
+ # These hard coded poshprs should be replaced with nodes in the model
+ if self.entranceId == 0:
+ # Front of the factory (south entrance)
+ self.elevatorModel.setPosHpr(62.74, -85.31, 0.00, 2.00, 0.00, 0.00)
+ elif self.entranceId == 1:
+ # Side of the factory (west entrance)
+ self.elevatorModel.setPosHpr(-162.25, 26.43, 0.00, 269.00, 0.00, 0.00)
+ else:
+ self.notify.error("Invalid entranceId: %s" % entranceId)
+
+
+
+ def gotBldg(self, buildingList):
+ return
+
+ def setFloor(self, floorNumber):
+ # Darken the old light:
+ if self.currentFloor >= 0:
+ self.bldg.floorIndicator[self.currentFloor].setColor(LIGHT_OFF_COLOR)
+
+ # Brighten the new light:
+ if floorNumber >= 0:
+ self.bldg.floorIndicator[floorNumber].setColor(LIGHT_ON_COLOR)
+
+ # Remember the floor:
+ self.currentFloor = floorNumber
+
+ def handleEnterSphere(self, collEntry):
+ #print("Entering Elevator Sphere....")
+ # Tell localToon we are considering entering the elevator
+ self.cr.playGame.getPlace().detectedElevatorCollision(self)
+
+ def handleEnterElevator(self):
+ #print("Entering Elevator....")
+ # Only toons with hp can board the elevator.
+ if base.localAvatar.hp > 0:
+ # Tell the server that this avatar wants to board.
+ toon = base.localAvatar
+ self.sendUpdate("requestBoard",[])
+ else:
+ self.notify.warning("Tried to board elevator with hp: %d" %
+ base.localAvatar.hp)
+
+ ##### WaitEmpty state #####
+
+ def enterWaitEmpty(self, ts):
+ self.lastState = self.state
+ #print("Entering WaitEmpty %s" % (self.doId))
+ self.elevatorSphereNodePath.unstash()
+ self.forceDoorsOpen()
+ # Toons may now try to board the elevator
+ self.accept(self.uniqueName('enterelevatorSphere'),
+ self.handleEnterSphere)
+ self.accept(self.uniqueName('enterElevatorOK'),
+ self.handleEnterElevator)
+ DistributedElevatorFSM.DistributedElevatorFSM.enterWaitEmpty(self, ts)
+
+ def exitWaitEmpty(self):
+ self.lastState = self.state
+ #print("Exiting WaitEmpty")
+ self.elevatorSphereNodePath.stash()
+ # Toons may not attempt to board the elevator if it isn't waiting
+ self.ignore(self.uniqueName('enterelevatorSphere'))
+ self.ignore(self.uniqueName('enterElevatorOK'))
+ DistributedElevatorFSM.DistributedElevatorFSM.exitWaitEmpty(self)
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self, ts):
+ self.lastState = self.state
+ #print("Entering WaitCountdown")
+ DistributedElevatorFSM.DistributedElevatorFSM.enterWaitCountdown(self, ts)
+ self.forceDoorsOpen()
+ self.accept(self.uniqueName('enterElevatorOK'),
+ self.handleEnterElevator)
+ self.startCountdownClock(self.countdownTime, ts)
+
+ def exitWaitCountdown(self):
+ self.lastState = self.state
+ #print("Exiting WaitCountdown")
+ self.ignore(self.uniqueName('enterElevatorOK'))
+ DistributedElevatorFSM.DistributedElevatorFSM.exitWaitCountdown(self)
+
+ def enterClosing(self, ts):
+ self.lastState = self.state
+ #print("Entering Closing")
+ #base.transitions.irisOut(2.0)
+ taskMgr.doMethodLater(1.00, self._delayIris, "delayedIris")
+ DistributedElevatorFSM.DistributedElevatorFSM.enterClosing(self, ts)
+
+ def _delayIris(self, tskfooler = 0):
+ base.transitions.irisOut(1.0)
+ base.localAvatar.pauseGlitchKiller()
+ return Task.done
+
+ def kickToonsOut(self):
+ #print"TOONS BEING KICKED OUT"
+ if not self.localToonOnBoard:
+ zoneId = self.cr.playGame.hood.hoodId
+ self.cr.playGame.getPlace().fsm.request('teleportOut', [{
+ "loader": ZoneUtil.getLoaderName(zoneId),
+ "where": ZoneUtil.getToonWhereName(zoneId),
+ "how": "teleportIn",
+ "hoodId": zoneId,
+ "zoneId": zoneId,
+ "shardId": None,
+ "avId": -1,
+ }])
+
+
+ def exitClosing(self):
+ self.lastState = self.state
+ #print("Exiting Closing")
+ DistributedElevatorFSM.DistributedElevatorFSM.exitClosing(self)
+
+ def enterClosed(self, ts):
+ self.lastState = self.state
+ #print("Entering Closed")
+ self.forceDoorsClosed()
+ self.__doorsClosed(self.getZoneId())
+ return
+
+ def exitClosed(self):
+ self.lastState = self.state
+ #print("Exiting Closed")
+ DistributedElevatorFSM.DistributedElevatorFSM.exitClosed(self)
+
+ def enterOff(self):
+ self.lastState = self.state
+ #print("Entering Off")
+ if self.wantState == 'closed':
+ self.demand('Closing')
+ elif self.wantState == 'waitEmpty':
+ self.demand('WaitEmpty')
+
+ DistributedElevatorFSM.DistributedElevatorFSM.enterOff(self)
+
+ def exitOff(self):
+ self.lastState = self.state
+ #print("Exiting Off")
+ DistributedElevatorFSM.DistributedElevatorFSM.exitOff(self)
+
+ def enterOpening(self, ts):
+ self.lastState = self.state
+ #print("Entering Opening")
+ DistributedElevatorFSM.DistributedElevatorFSM.enterOpening(self,ts)
+
+ def exitOpening(self):
+ #print("Exiting Opening")
+ #print("WE ARE ACTAULLY CALLING exitOpening!!!!")
+ #import pdb; pdb.set_trace()
+ DistributedElevatorFSM.DistributedElevatorFSM.exitOpening(self)
+ self.kickEveryoneOut()
+
+ return
+
+ def getZoneId(self):
+ return 0
+
+ def setBldgDoId(self, bldgDoId):
+ # The doId is junk, there is no building object for the factory
+ # exterior elevators. Do the appropriate things that
+ # DistributedElevator.gotBldg does.
+ self.bldg = None
+ self.setupElevator()
+
+ def getElevatorModel(self):
+ return self.elevatorModel
+
+ def kickEveryoneOut(self):
+ #makes the toons leave the elevator
+ bailFlag = 0
+ #print self.boardedAvIds
+ for avId, slot in self.boardedAvIds.items():
+ #print("Kicking toon out! avId %s Slot %s" % (avId, slot))
+ self.emptySlot(slot, avId, bailFlag, globalClockDelta.getRealNetworkTime())
+ if avId == base.localAvatar.doId:
+ pass
+
+
+
+ def __doorsClosed(self, zoneId):
+ return
+
+ def onDoorCloseFinish(self):
+ """this is called when the elevator doors finish closing on the client
+ """
+
+ def setLocked(self, locked):
+ self.isLocked = locked
+ if locked:
+ if self.state == 'WaitEmpty':
+ self.request('Closing')
+ if self.countFullSeats() == 0:
+ self.wantState = 'closed'
+ else:
+ self.wantState = 'opening'
+ else:
+ self.wantState = 'waitEmpty'
+ if self.state == 'Closed':
+ self.request('Opening')
+
+ def getLocked(self):
+ return self.isLocked
+
+ def setEntering(self, entering):
+ self.isEntering = entering
+
+ def getEntering(self):
+ return self.isEntering
+
+
+ def forceDoorsOpen(self):
+ #print("forcing doors open Floor")
+ openDoors(self.leftDoor, self.rightDoor)
+
+
+ def forceDoorsClosed(self):
+ #print("DistributedElevator.forceDoorsClosed %s" % (self.doId))
+ #import pdb; pdb.set_trace()
+ if self.openDoors.isPlaying():
+ #print("door opening playing")
+ self.doorsNeedToClose = 1
+ else:
+ self.closeDoors.finish()
+ closeDoors(self.leftDoor, self.rightDoor)
+
+ def enterOff(self):
+ self.lastState = self.state
+ return
+
+ def exitOff(self):
+ return
+
+
+ def setLawOfficeInteriorZone(self, zoneId):
+ if (self.localToonOnBoard):
+ hoodId = self.cr.playGame.hood.hoodId
+ doneStatus = {
+ 'loader' : "cogHQLoader",
+ 'where' : 'factoryInterior', #should be lawOffice
+ 'how' : "teleportIn",
+ 'zoneId' : zoneId,
+ 'hoodId' : hoodId,
+ }
+ self.cr.playGame.getPlace().elevator.signalDone(doneStatus)
+
+# def emptySlot(self, index, avId, bailFlag, timestamp):
+# pass
+
+
+
+
+
+
+
diff --git a/toontown/src/building/DistributedElevatorFloorAI.py b/toontown/src/building/DistributedElevatorFloorAI.py
new file mode 100644
index 0000000..a50d6d9
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorFloorAI.py
@@ -0,0 +1,397 @@
+from otp.ai.AIBase import *
+from toontown.toonbase import ToontownGlobals
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+import DistributedElevatorFSMAI
+#from direct.fsm import ClassicFSM
+#from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm.FSM import FSM
+
+class DistributedElevatorFloorAI(DistributedElevatorFSMAI.DistributedElevatorFSMAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedElevatorFloorAI")
+ #"""
+ defaultTransitions = {
+ 'Off' : [ 'Opening', 'Closed'],
+ 'Opening' : [ 'WaitEmpty', 'WaitCountdown', 'Opening', 'Closing' ],
+ 'WaitEmpty' : [ 'WaitCountdown', "Closing", 'WaitEmpty'],
+ 'WaitCountdown' : [ 'WaitEmpty', 'AllAboard', "Closing", 'WaitCountdown' ],
+ 'AllAboard' : [ 'WaitEmpty', "Closing" ],
+ 'Closing' : [ 'Closed', 'WaitEmpty', 'Closing', 'Opening' ],
+ 'Closed' : [ 'Opening' ],
+ }
+ #"""
+ id = 0
+
+
+ def __init__(self, air, lawOfficeId,bldg, avIds, markerId = None, numSeats = 4, antiShuffle = 0, minLaff = 0):
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.__init__(
+ self, air, bldg, numSeats, antiShuffle = antiShuffle, minLaff = minLaff)
+ FSM.__init__( self, "ElevatorFloor_%s_FSM" % ( self.id ) )
+ # Do we need this?
+ # self.zoneId, dummy = bldg.getExteriorAndInteriorZoneId()
+ # Flag that tells if any Toon has jumped out of the elevator yet
+ # (this is used to prevent the griefers who jump off at the last
+ # second)
+ self.type = ELEVATOR_STAGE
+ self.countdownTime = ElevatorData[self.type]['countdown']
+ self.lawOfficeId = lawOfficeId
+ self.anyToonsBailed = 0
+ self.avIds = avIds
+ self.isEntering = 0
+ self.isLocked = 0
+ self.setLocked(0)
+ self.wantState = None
+ self.latchRoom = None
+ self.setLatch(markerId)
+ self.zoneId = bldg.zoneId
+
+ def generate(self):
+ #print("DistributedElevatorFloorAI.generate")
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.generate(self)
+
+ def generateWithRequired(self, zoneId):
+ #print ("DistributedElevatorFloorAI generateWithRequired")
+ self.zoneId = zoneId
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.generateWithRequired(self, self.zoneId)
+
+
+ def delete(self):
+ # TODO: We really need an immediate clear here
+ # At least it does not crash the AI anymore
+ for seatIndex in range(len(self.seats)):
+ avId = self.seats[seatIndex]
+ if avId:
+ self.clearFullNow(seatIndex)
+ self.clearEmptyNow(seatIndex)
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.delete(self)
+
+ def getEntranceId(self):
+ return self.entranceId
+
+ def d_setFloor(self, floorNumber):
+ self.sendUpdate('setFloor', [floorNumber])
+
+ def avIsOKToBoard(self, av):
+ #print("DistributedElevatorFloorAI.avIsOKToBoard %s %s" % (self.accepting, self.isLocked))
+ return (av.hp > 0) and self.accepting and not self.isLocked
+
+ def acceptBoarder(self, avId, seatIndex):
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.acceptBoarder(self, avId, seatIndex)
+ # Add a hook that handles the case where the avatar exits
+ # the district unexpectedly
+ self.acceptOnce(self.air.getAvatarExitEvent(avId),
+ self.__handleUnexpectedExit, extraArgs=[avId])
+ # Put us into waitCountdown state... If we are already there,
+ # this won't do anything.
+
+ #import pdb; pdb.set_trace()
+ if self.state == "WaitEmpty" and (self.countFullSeats() < self.countAvsInZone()):
+ self.request("WaitCountdown")
+ self.bldg.elevatorAlert(avId)
+ elif (self.state in ("WaitCountdown", "WaitEmpty")) and (self.countFullSeats() >= self.countAvsInZone()):
+ taskMgr.doMethodLater(TOON_BOARD_ELEVATOR_TIME, self.goAllAboard, self.quickBoardTask)
+
+ def countAvsInZone(self):
+ matchingZones = 0
+ for avId in self.bldg.avIds:
+ av = self.air.doId2do.get(avId)
+ if av:
+ if av.zoneId == self.bldg.zoneId:
+ matchingZones += 1
+ return matchingZones
+
+
+ def goAllAboard(self, throwAway = 1):
+ self.request("Closing")
+ return Task.done
+
+ def __handleUnexpectedExit(self, avId):
+ #print("DistributedElevatorFloorAI.__handleUnexpectedExit")
+ self.notify.warning("Avatar: " + str(avId) +
+ " has exited unexpectedly")
+ # Find the exiter's seat index
+ seatIndex = self.findAvatar(avId)
+ # Make sure the avatar is really here
+ if seatIndex == None:
+ pass
+ else:
+ # If the avatar is here, his seat is now empty.
+ self.clearFullNow(seatIndex)
+ # Tell the clients that the avatar is leaving that seat
+ self.clearEmptyNow(seatIndex)
+ #self.sendUpdate("emptySlot" + str(seatIndex),
+ # [avId, globalClockDelta.getRealNetworkTime()])
+ # If all the seats are empty, go back into waitEmpty state
+ if self.countFullSeats() == 0:
+ self.request('WaitEmpty')
+
+
+ def acceptExiter(self, avId):
+ #print("DistributedElevatorFloorAI.acceptExiter")
+ # Find the exiter's seat index
+ seatIndex = self.findAvatar(avId)
+ # It is possible that the avatar exited the shard unexpectedly.
+ if seatIndex == None:
+ pass
+ else:
+ # Empty that seat
+ self.clearFullNow(seatIndex)
+ # Make sure there's no griefing by jumping off the elevator
+ # at the last second
+ bailFlag = 0
+ if (self.anyToonsBailed == 0):
+ bailFlag = 1
+ # Reset the clock
+ self.resetCountdown()
+ self.anyToonsBailed = 1
+ # Tell the clients that the avatar is leaving that seat
+ self.sendUpdate("emptySlot" + str(seatIndex),
+ [avId, bailFlag, globalClockDelta.getRealNetworkTime()])
+ # If all the seats are empty, go back into waitEmpty state
+ if self.countFullSeats() == 0:
+ self.request('WaitEmpty')
+ # Wait for the avatar to be done leaving the seat, and then
+ # declare the emptying overwith...
+ taskMgr.doMethodLater(TOON_EXIT_ELEVATOR_TIME,
+ self.clearEmptyNow,
+ self.uniqueName("clearEmpty-%s" % seatIndex),
+ extraArgs = (seatIndex,))
+
+
+ def enterOpening(self):
+ #print("DistributedElevatorFloorAI.enterOpening %s" % (self.doId))
+ self.d_setState('Opening')
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterOpening(self)
+ taskMgr.doMethodLater(ElevatorData[ELEVATOR_NORMAL]['openTime'],
+ self.waitEmptyTask,
+ self.uniqueName('opening-timer'))
+
+ def exitOpening(self):
+ #print("DistributedElevatorFloorAI.exitOpening %s" % (self.doId))
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.exitOpening(self)
+ if self.isLocked:
+ self.wantState = 'closed'
+ if self.wantState == 'closed':
+ self.demand('Closing')
+
+
+
+ ##### WaitEmpty state #####
+
+ def waitEmptyTask(self, task):
+ #print("DistributedElevatorFloorAI.waitEmptyTask %s" % (self.doId))
+ self.request('WaitEmpty')
+ return Task.done
+
+ def enterWaitEmpty(self):
+ self.lastState = self.state
+ #print("DistributedElevatorFloorAI.enterWaitEmpty %s %s from %s" % (self.isLocked, self.doId, self.state))
+ #print("WAIT EMPTY FLOOR VATOR")
+ for i in range(len(self.seats)):
+ self.seats[i] = None
+ print self.seats
+ if self.wantState == 'closed':
+ self.demand('Closing')
+ else:
+ self.d_setState('WaitEmpty')
+ self.accepting = 1
+
+
+
+
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self):
+ self.lastState = self.state
+ #print("DistributedElevatorFloorAI.enterWaitCountdown %s from %s" % (self.doId, self.state))
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterWaitCountdown(self)
+ # Start the countdown...
+ taskMgr.doMethodLater(self.countdownTime, self.timeToGoTask,
+ self.uniqueName('countdown-timer'))
+ if self.lastState == 'WaitCountdown':
+ pass
+ #import pdb; pdb.set_trace()
+
+ def timeToGoTask(self, task):
+ #print("DistributedElevatorFloorAI.timeToGoTask %s" % (self.doId))
+ # It is possible that the players exited the district
+ if self.countFullSeats() > 0:
+ self.request("AllAboard")
+ else:
+ self.request('WaitEmpty')
+ return Task.done
+
+ def resetCountdown(self):
+ taskMgr.remove(self.uniqueName('countdown-timer'))
+ taskMgr.doMethodLater(self.countdownTime, self.timeToGoTask,
+ self.uniqueName('countdown-timer'))
+
+ def enterAllAboard(self):
+ #print("DISELEFLOORAI enter allAboard")
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterAllAboard(self)
+ currentTime = globalClock.getRealTime()
+ elapsedTime = currentTime - self.timeOfBoarding
+ self.notify.debug("elapsed time: " + str(elapsedTime))
+ waitTime = max(TOON_BOARD_ELEVATOR_TIME - elapsedTime, 0)
+ taskMgr.doMethodLater(waitTime, self.closeTask,
+ self.uniqueName('waitForAllAboard'))
+
+ ##### Closing state #####
+
+ def closeTask(self, task):
+ #print("DistributedElevatorFloorAI.closeTask %s" % (self.doId))
+ # It is possible that the players exited the district
+ if self.countFullSeats() >= 1:#len(self.avIds):
+ self.request("Closing")
+ #print("closing")
+ else:
+ self.request('WaitEmpty')
+ #print("wait empty")
+ return Task.done
+
+ def enterClosing(self):
+ #print("DistributedElevatorFloorAI.enterClosing %s" % (self.doId))
+ if self.countFullSeats() > 0:
+ self.sendUpdate("kickToonsOut")
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterClosing(self)
+ taskMgr.doMethodLater(ElevatorData[ELEVATOR_STAGE]['closeTime'],
+ self.elevatorClosedTask,
+ self.uniqueName('closing-timer'))
+ self.d_setState('Closing')
+
+ def elevatorClosedTask(self, task):
+ #print("DistributedElevatorFloorAI.elevatorClosedTask %s" % (self.doId))
+ self.elevatorClosed()
+ return Task.done
+
+ def elevatorClosed(self):
+ if self.isLocked:
+ self.request("Closed")
+ return
+ #print("ELEVATOR CLOSED DOING CALCS")
+ numPlayers = self.countFullSeats()
+ # print(" NUMBER OF PLAYERS IS %s" % (numPlayers))
+ # It is possible the players exited the district
+ if (numPlayers > 0):
+
+ # Create a factory interior just for us
+
+ # Make a nice list for the factory
+ players = []
+ for i in self.seats:
+ if i not in [None, 0]:
+ players.append(i)
+ #lawOfficeZone = self.bldg.createLawOffice(self.lawOfficeId,
+ # self.entranceId, players)
+
+ sittingAvIds = [];
+
+ for seatIndex in range(len(self.seats)):
+ avId = self.seats[seatIndex]
+ if avId:
+ # Tell each player on the elevator that they should enter the factory
+ # And which zone it is in
+ #self.sendUpdateToAvatarId(avId, "setLawOfficeInteriorZone", [lawOfficeZone])
+ # Clear the fill slot
+ #self.clearFullNow(seatIndex)
+ sittingAvIds.append(avId)
+ pass
+ for avId in self.avIds:
+ if not avId in sittingAvIds:
+ #print("THIS AV ID %s IS NOT ON BOARD" % (avId))
+ pass
+
+
+
+ self.bldg.startNextFloor()
+
+ else:
+ self.notify.warning("The elevator left, but was empty.")
+ self.request("Closed")
+
+ def setLocked(self, locked):
+ self.isLocked = locked
+ if locked:
+ if self.state == 'WaitEmpty':
+ self.request('Closing')
+ if self.countFullSeats() == 0:
+ self.wantState = 'closed'
+ else:
+ self.wantState = 'opening'
+ else:
+ self.wantState = 'waitEmpty'
+ if self.state == 'Closed':
+ self.request('Opening')
+
+ def getLocked(self):
+ return self.isLocked
+
+ def unlock(self):
+ #print("DistributedElevatorFloorAI.unlock %s %s" % (self.isLocked, self.doId))
+ if self.isLocked:
+ self.setLocked(0)
+ #self.request('Opening')
+
+ def lock(self):
+ #print("DistributedElevatorFloorAI.lock %s %s" % (self.isLocked, self.doId))
+ if not self.isLocked:
+ self.setLocked(1)
+ #if self.state != 'Closed' or self.state != 'Closing':
+ #self.request('Closing')
+ #self.beClosed()
+
+
+ def start(self):
+ #print("DistributedElevatorFloorAI.start %s" % (self.doId))
+ self.quickBoardTask = self.uniqueName("quickBoard")
+ self.request('Opening')
+ #self.beClosed()
+
+ def beClosed(self):
+ #print("DistributedElevatorFloorAI.beClosed %s" % (self.doId))
+ #self.request('closed')
+ pass
+
+ def setEntering(self, entering):
+ self.isEntering = entering
+
+ def getEntering(self):
+ return self.isEntering
+
+ def enterClosed(self):
+ #print("DistributedElevatorFloorAI.enterClosed %s" % (self.doId))
+ #import pdb; pdb.set_trace()
+ DistributedElevatorFSMAI.DistributedElevatorFSMAI.enterClosed(self)
+ ##### WaitEmpty state #####
+ # Switch back into opening mode since we allow other Toons onboard
+ if self.wantState == 'closed':
+ pass
+ else:
+ self.demand("Opening")
+
+
+ def enterOff(self):
+ self.lastState = self.state
+ #print("DistributedElevatorFloorAI.enterOff")
+ if self.wantState == 'closed':
+ self.demand('Closing')
+ elif self.wantState == 'waitEmpty':
+ self.demand('WaitEmpty')
+
+ def setPos(self, pointPos):
+ self.sendUpdate('setPos', [pointPos[0],pointPos[1],pointPos[2]])
+
+ def setH(self, H):
+ self.sendUpdate('setH', [H])
+
+ def setLatch(self, markerId):
+ self.latch = markerId
+ #self.sendUpdate('setLatch', [markerId])
+
+ def getLatch(self):
+ return self.latch
\ No newline at end of file
diff --git a/toontown/src/building/DistributedElevatorInt.py b/toontown/src/building/DistributedElevatorInt.py
new file mode 100644
index 0000000..2fc7de3
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorInt.py
@@ -0,0 +1,66 @@
+from pandac.PandaModules import *
+from direct.distributed.ClockDelta import *
+from direct.interval.IntervalGlobal import *
+from ElevatorConstants import *
+from ElevatorUtils import *
+import DistributedElevator
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from toontown.hood import ZoneUtil
+from toontown.toonbase import TTLocalizer
+
+class DistributedElevatorInt(DistributedElevator.DistributedElevator):
+
+ def __init__(self, cr):
+ DistributedElevator.DistributedElevator.__init__(self, cr)
+ self.countdownTime = base.config.GetFloat('int-elevator-timeout', INTERIOR_ELEVATOR_COUNTDOWN_TIME)
+
+
+ def setupElevator(self):
+ """setupElevator(self)
+ Called when the building doId is set at construction time,
+ this method sets up the elevator for business.
+ """
+ self.leftDoor = self.bldg.leftDoorOut
+ self.rightDoor = self.bldg.rightDoorOut
+ DistributedElevator.DistributedElevator.setupElevator(self)
+
+ def forcedExit(self, avId):
+ # This should only be sent to us if the timer expired on the
+ # elevator before we boarded.
+ assert(base.localAvatar.getDoId() == avId)
+ # Teleport to the last safe zone visited.
+ target_sz = base.localAvatar.defaultZone
+ base.cr.playGame.getPlace().fsm.request('teleportOut', [{
+ "loader" : ZoneUtil.getLoaderName(target_sz),
+ "where" : ZoneUtil.getWhereName(target_sz, 1),
+ 'how' : 'teleportIn',
+ 'hoodId' : target_sz,
+ 'zoneId' : target_sz,
+ 'shardId' : None,
+ 'avId' : -1,
+ }], force = 1)
+
+ ##### WaitCountdown state #####
+
+ def enterWaitCountdown(self, ts):
+ DistributedElevator.DistributedElevator.enterWaitCountdown(self, ts)
+ # Toons may also teleport away
+ self.acceptOnce("localToonLeft", self.__handleTeleportOut)
+ self.startCountdownClock(self.countdownTime, ts)
+
+ def __handleTeleportOut(self):
+ # Tell the server we are leaving.
+ self.sendUpdate('requestBuildingExit', [])
+
+ def exitWaitCountdown(self):
+ self.ignore("localToonLeft")
+ DistributedElevator.DistributedElevator.exitWaitCountdown(self)
+
+ def getZoneId(self):
+ return self.bldg.getZoneId()
+
+ def getElevatorModel(self):
+ return self.bldg.elevatorModelOut
diff --git a/toontown/src/building/DistributedElevatorIntAI.py b/toontown/src/building/DistributedElevatorIntAI.py
new file mode 100644
index 0000000..7a945a3
--- /dev/null
+++ b/toontown/src/building/DistributedElevatorIntAI.py
@@ -0,0 +1,196 @@
+from otp.ai.AIBase import *
+from toontown.toonbase import ToontownGlobals
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+import copy
+import DistributedElevatorAI
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from direct.task import Task
+from direct.directnotify import DirectNotifyGlobal
+from toontown.battle import BattleBase
+
+class DistributedElevatorIntAI(DistributedElevatorAI.DistributedElevatorAI):
+
+ def __init__(self, air, bldg, avIds):
+ """__init__(air)
+ avIds is a list of the avatars we are waiting for to board.
+ """
+ DistributedElevatorAI.DistributedElevatorAI.__init__(self, air, bldg)
+ self.countdownTime = simbase.config.GetFloat('int-elevator-timeout', INTERIOR_ELEVATOR_COUNTDOWN_TIME)
+
+ self.avIds = copy.copy(avIds)
+ # Hang hooks for unexpected exit cases
+ for avId in avIds:
+ self.acceptOnce(self.air.getAvatarExitEvent(avId),
+ self.__handleAllAvsUnexpectedExit,
+ extraArgs=[avId])
+
+ def checkBoard(self, av):
+ result = 0
+ if not av.doId in self.avIds:
+ result = REJECT_NOSEAT
+ else:
+ result = DistributedElevatorAI.DistributedElevatorAI.checkBoard(self,av)
+ return result
+
+ def acceptBoarder(self, avId, seatIndex, wantBoardingShow = 0):
+ DistributedElevatorAI.DistributedElevatorAI.acceptBoarder(self, avId, seatIndex, wantBoardingShow)
+ # If all the avatars we are waiting for are now aboard, then
+ # close the doors
+ self.__closeIfNecessary()
+
+ def __closeIfNecessary(self):
+ numFullSeats = self.countFullSeats()
+ if not (numFullSeats <= len(self.avIds)):
+ self.notify.warning("we are about to crash. self.seats=%s self.avIds=%s" % (self.seats, self.avIds))
+ assert (numFullSeats <= len(self.avIds))
+ if numFullSeats == len(self.avIds):
+ self.fsm.request("allAboard")
+
+ def __handleAllAvsUnexpectedExit(self, avId):
+ self.notify.warning("Avatar: " + str(avId) +
+ " has exited unexpectedly")
+
+ # Find the exiter's seat index (if it has one)
+ seatIndex = self.findAvatar(avId)
+
+ avIdCount = self.avIds.count(avId)
+ if avIdCount == 1:
+ self.avIds.remove(avId)
+ elif avIdCount == 0:
+ self.notify.warning("Strange... %d exited unexpectedly, but I don't have them on my list." % avId)
+ else:
+ self.notify.error("This list is screwed up! %s" % self.avIds)
+
+ # Make sure the avatar is really here
+ if seatIndex == None:
+ self.notify.debug("%d is not boarded, but exited" % avId)
+ else:
+ # If the avatar is here, his seat is now empty.
+ self.clearFullNow(seatIndex)
+ # Tell the clients that the avatar is leaving that seat
+ self.clearEmptyNow(seatIndex)
+
+ self.__closeIfNecessary()
+
+ def acceptExiter(self, avId):
+ # Find the exiter's seat index
+ seatIndex = self.findAvatar(avId)
+ # It is possible that the avatar exited the shard unexpectedly.
+ if seatIndex == None:
+ pass
+ else:
+ # Empty that seat
+ self.clearFullNow(seatIndex)
+ # Tell the clients that the avatar is leaving that seat
+ self.sendUpdate("emptySlot" + str(seatIndex),
+ [avId, 0, globalClockDelta.getRealNetworkTime(), self.countdownTime])
+
+ # Wait for the avatar to be done leaving the seat, and then
+ # declare the emptying overwith...
+ taskMgr.doMethodLater(TOON_EXIT_ELEVATOR_TIME,
+ self.clearEmptyNow,
+ self.uniqueName("clearEmpty-%s" % seatIndex),
+ extraArgs = (seatIndex,))
+
+ def d_forcedExit(self, avId):
+ self.sendUpdateToAvatarId(avId, "forcedExit", [avId])
+
+ def requestBuildingExit(self):
+ avId = self.air.getAvatarIdFromSender()
+ self.notify.debug("requestBuildingExit from %d" % avId)
+ if self.accepting:
+ if avId in self.avIds:
+ self.avIds.remove(avId)
+ self.__closeIfNecessary()
+ else:
+ self.notify.warning(
+ "avId: %s not known, but tried to exit the building"
+ % avId
+ )
+
+ def enterOpening(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterOpening(self)
+ taskMgr.doMethodLater(ElevatorData[ELEVATOR_NORMAL]['openTime'],
+ self.waitCountdownTask,
+ self.uniqueName('opening-timer'))
+
+ ##### WaitCountdown state #####
+
+ def waitCountdownTask(self, task):
+ self.fsm.request("waitCountdown")
+ return Task.done
+
+ def enterWaitCountdown(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterWaitCountdown(self)
+ # Start the countdown...
+ taskMgr.doMethodLater(self.countdownTime + \
+ BattleBase.SERVER_BUFFER_TIME,
+ self.timeToGoTask,
+ self.uniqueName('countdown-timer'))
+
+ def timeToGoTask(self, task):
+ self.allAboard()
+ return Task.done
+
+
+ ##### AllAboard state #####
+
+ def allAboard(self):
+ # Override the base class here
+ # If there is no one here, just get out of here.
+ if (len(self.avIds) == 0):
+ assert(self.notify.debug('last toon gone'))
+ self.bldg.handleAllAboard([None, None, None, None])
+ else:
+ self.fsm.request("allAboard")
+
+ def enterAllAboard(self):
+ # Kick any toons still here who are not in the elevator
+ # back to the safe zone.
+ for avId in self.avIds:
+ if (self.findAvatar(avId) == None):
+ self.d_forcedExit(avId)
+ DistributedElevatorAI.DistributedElevatorAI.enterAllAboard(self)
+ if self.timeOfBoarding != None:
+ currentTime = globalClock.getRealTime()
+ elapsedTime = currentTime - self.timeOfBoarding
+ self.notify.debug("elapsed time: " + str(elapsedTime))
+ waitTime = max(TOON_BOARD_ELEVATOR_TIME - elapsedTime, 0)
+ taskMgr.doMethodLater(waitTime, self.closeTask,
+ self.uniqueName('waitForAllAboard'))
+ else:
+ self.fsm.request('closing')
+
+ ##### Closing state #####
+
+ def closeTask(self, task):
+ self.fsm.request("closing")
+ return Task.done
+
+ def enterClosing(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterClosing(self)
+ taskMgr.doMethodLater(ElevatorData[ELEVATOR_NORMAL]['closeTime'] + \
+ BattleBase.SERVER_BUFFER_TIME,
+ self.elevatorClosedTask,
+ self.uniqueName('closing-timer'))
+
+ def elevatorClosedTask(self, task):
+ self.fsm.request("closed")
+ return Task.done
+
+ ##### Closed state #####
+
+ def __doorsClosed(self):
+ # Pass back control to the suit interior, and report who
+ # is on the elevator.
+ self.bldg.handleAllAboard(self.seats)
+ # Now we are officially closed.
+ self.fsm.request("closed")
+
+ def enterClosed(self):
+ DistributedElevatorAI.DistributedElevatorAI.enterClosed(self)
+ self.__doorsClosed()
+
diff --git a/toontown/src/building/DistributedGagshopInterior.py b/toontown/src/building/DistributedGagshopInterior.py
new file mode 100644
index 0000000..7c4c4ee
--- /dev/null
+++ b/toontown/src/building/DistributedGagshopInterior.py
@@ -0,0 +1,146 @@
+from toontown.toonbase.ToonBaseGlobal import *
+from pandac.PandaModules import *
+from toontown.toonbase.ToontownGlobals import *
+
+import random
+from direct.distributed import DistributedObject
+from direct.directnotify import DirectNotifyGlobal
+import ToonInteriorColors
+from toontown.hood import ZoneUtil
+
+
+class DistributedGagshopInterior(DistributedObject.DistributedObject):
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedGagshopInterior')
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+ self.dnaStore=cr.playGame.dnaStore
+
+ def generate(self):
+ DistributedObject.DistributedObject.generate(self)
+
+ def announceGenerate(self):
+ DistributedObject.DistributedObject.announceGenerate(self)
+ self.setup()
+
+ def randomDNAItem(self, category, findFunc):
+ codeCount = self.dnaStore.getNumCatalogCodes(category)
+ index = self.randomGenerator.randint(0, codeCount-1)
+ code = self.dnaStore.getCatalogCode(category, index)
+ # findFunc will probably be findNode or findTexture
+ return findFunc(code)
+
+ def replaceRandomInModel(self, model):
+ """Replace named nodes with random items.
+ Here are the name Here is
+ prefixes that are what they
+ affected: do:
+
+ random_mox_ change the Model Only.
+ random_mcx_ change the Model and the Color.
+ random_mrx_ change the Model and Recurse.
+ random_tox_ change the Texture Only.
+ random_tcx_ change the Texture and the Color.
+
+ x is simply a uniquifying integer because Multigen will not
+ let you have multiple nodes with the same name
+
+ """
+ baseTag="random_"
+ npc=model.findAllMatches("**/"+baseTag+"???_*")
+ for i in range(npc.getNumPaths()):
+ np=npc.getPath(i)
+ name=np.getName()
+
+ b=len(baseTag)
+ category=name[b+4:]
+ key1=name[b]
+ key2=name[b+1]
+
+ assert(key1 in ["m", "t"])
+ assert(key2 in ["c", "o", "r"])
+ if key1 == "m":
+ # ...model.
+ model = self.randomDNAItem(category, self.dnaStore.findNode)
+ assert(not model.isEmpty())
+ newNP = model.copyTo(np)
+ if key2 == "r":
+ self.replaceRandomInModel(newNP)
+ elif key1 == "t":
+ # ...texture.
+ texture=self.randomDNAItem(category, self.dnaStore.findTexture)
+ assert(texture)
+ np.setTexture(texture,100)
+ newNP=np
+ if key2 == "c":
+ if (category == "TI_wallpaper") or (category == "TI_wallpaper_border"):
+ self.randomGenerator.seed(self.zoneId)
+ newNP.setColorScale(
+ self.randomGenerator.choice(self.colors[category]))
+ else:
+ newNP.setColorScale(
+ self.randomGenerator.choice(self.colors[category]))
+
+ def setZoneIdAndBlock(self, zoneId, block):
+ self.zoneId = zoneId
+ self.block = block
+
+ def chooseDoor(self):
+ # I copy/pasted this door string choosing code from
+ # DistributedToonInterior.
+ # Door:
+ doorModelName="door_double_round_ul" # hack zzzzzzz
+ # Switch leaning of the door:
+ if doorModelName[-1:] == "r":
+ doorModelName=doorModelName[:-1]+"l"
+ else:
+ doorModelName=doorModelName[:-1]+"r"
+ door=self.dnaStore.findNode(doorModelName)
+ return door
+
+ def setup(self):
+ self.dnaStore=base.cr.playGame.dnaStore
+ # Set up random generator
+ self.randomGenerator = random.Random()
+ self.randomGenerator.seed(self.zoneId)
+
+ self.interior = loader.loadModel('phase_4/models/modules/gagShop_interior')
+ self.interior.reparentTo(render)
+
+
+ # Load a color dictionary for this hood:
+ hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
+ self.colors = ToonInteriorColors.colors[hoodId]
+ # Replace all the "random_xxx_" nodes:
+ self.replaceRandomInModel(self.interior)
+
+ # Pick a door model
+ door = self.chooseDoor()
+ # Find the door origins
+ doorOrigin = render.find("**/door_origin;+s")
+ doorNP = door.copyTo(doorOrigin)
+ assert(not doorNP.isEmpty())
+ assert(not doorOrigin.isEmpty())
+ doorOrigin.setScale(0.8, 0.8, 0.8)
+ doorOrigin.setPos(doorOrigin, 0, -0.025, 0)
+ doorColor = self.randomGenerator.choice(self.colors["TI_door"])
+ DNADoor.setupDoor(doorNP,
+ self.interior, doorOrigin,
+ self.dnaStore, str(self.block),
+ doorColor)
+ doorFrame = doorNP.find("door_*_flat")
+ doorFrame.wrtReparentTo(self.interior)
+ doorFrame.setColor(doorColor)
+
+ del self.colors
+ del self.dnaStore
+ del self.randomGenerator
+ self.interior.flattenMedium()
+
+ def disable(self):
+ self.interior.removeNode()
+ del self.interior
+ DistributedObject.DistributedObject.disable(self)
+
diff --git a/toontown/src/building/DistributedGagshopInteriorAI.py b/toontown/src/building/DistributedGagshopInteriorAI.py
new file mode 100644
index 0000000..664c6b2
--- /dev/null
+++ b/toontown/src/building/DistributedGagshopInteriorAI.py
@@ -0,0 +1,20 @@
+from direct.distributed import DistributedObjectAI
+from direct.directnotify import DirectNotifyGlobal
+
+class DistributedGagshopInteriorAI(DistributedObjectAI.DistributedObjectAI):
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedGagshopInteriorAI')
+
+ def __init__(self, block, air, zoneId):
+ # Right now, this doesn't do much.
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.block = block
+ self.zoneId = zoneId
+
+ def getZoneIdAndBlock(self):
+ r=[self.zoneId, self.block]
+ return r
+
+
+
diff --git a/toontown/src/building/DistributedHQInterior.py b/toontown/src/building/DistributedHQInterior.py
new file mode 100644
index 0000000..1fa4d3d
--- /dev/null
+++ b/toontown/src/building/DistributedHQInterior.py
@@ -0,0 +1,293 @@
+from toontown.toonbase.ToonBaseGlobal import *
+from pandac.PandaModules import *
+from toontown.toonbase.ToontownGlobals import *
+
+import random
+from direct.task.Task import Task
+from direct.distributed import DistributedObject
+from direct.directnotify import DirectNotifyGlobal
+import ToonInteriorColors
+import cPickle
+from toontown.toonbase import TTLocalizer
+
+class DistributedHQInterior(DistributedObject.DistributedObject):
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedHQInterior')
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+ self.dnaStore=cr.playGame.dnaStore
+ # These are stored in sorted order, highest score first
+ self.leaderAvIds = []
+ self.leaderNames = []
+ self.leaderScores = []
+ self.numLeaders = 10
+ self.tutorial = 0
+
+ def generate(self):
+ DistributedObject.DistributedObject.generate(self)
+ self.interior = loader.loadModel('phase_3.5/models/modules/HQ_interior')
+ self.interior.reparentTo(render)
+ # hide these props until we intergrate them fully
+ self.interior.find("**/cream").hide()
+ self.interior.find("**/crashed_piano").hide()
+ self.buildLeaderBoard()
+
+ def announceGenerate(self):
+ DistributedObject.DistributedObject.announceGenerate(self)
+ self.setupDoors()
+ # Flatten everything
+ self.interior.flattenMedium()
+ # Then put the leaderboard under
+ # We do not want the leaderboard flattened because we need to update it
+ # Also, throw a decal transition on
+ emptyBoard = self.interior.find("**/empty_board")
+ # emptyBoard.getChild(0).node().setEffect(DecalEffect.make())
+ self.leaderBoard.reparentTo(emptyBoard.getChild(0))
+
+ def setTutorial(self, flag):
+ if self.tutorial == flag:
+ return
+ else:
+ self.tutorial = flag
+ if self.tutorial:
+ # don't show the scope in the tutorial building for viz
+ self.interior.find("**/periscope").hide()
+ self.interior.find("**/speakers").hide()
+ else:
+ # show the scope in the tutorial building for viz
+ self.interior.find("**/periscope").show()
+ self.interior.find("**/speakers").show()
+
+ def setZoneIdAndBlock(self, zoneId, block):
+ self.zoneId = zoneId
+ self.block = block
+
+ def buildLeaderBoard(self):
+ # Create the models and layout the elements to create
+ # the leaderboard. This gets called once on load
+ # TODO: do we want avatar dna / faces here?
+ self.leaderBoard = hidden.attachNewNode('leaderBoard')
+ # Away from the wall by 0.1 since the decal does not seem to be working
+ self.leaderBoard.setPosHprScale(0.1,0,4.5,90,0,0,0.9,0.9,0.9)
+ z = 0
+ row = self.buildTitleRow()
+ row.reparentTo(self.leaderBoard)
+ row.setPos(0,0,z)
+ z -= 1
+ # Store the text nodes so we can setText() them later with
+ # score updates
+ self.nameTextNodes = []
+ self.scoreTextNodes = []
+ # Store the trophy stars next to each name because they need
+ # to change with score updates too
+ self.trophyStars = []
+
+ # Create blank entries for each row in the leaderboard
+ for i in range(self.numLeaders):
+ row, nameText, scoreText, trophyStar = self.buildLeaderRow()
+ self.nameTextNodes.append(nameText)
+ self.scoreTextNodes.append(scoreText)
+ self.trophyStars.append(trophyStar)
+ row.reparentTo(self.leaderBoard)
+ row.setPos(0,0,z)
+ z -= 1
+
+ def updateLeaderBoard(self):
+ # Refresh the data and graphics on the leaderboard with our new information
+ # Kill any existing star tasks
+ taskMgr.remove(self.uniqueName("starSpinHQ"))
+ # Fill in the names, scores, and stars we have
+ # We may not have the max number of leaderNames
+ for i in range(len(self.leaderNames)):
+ name = self.leaderNames[i]
+ score = self.leaderScores[i]
+ self.nameTextNodes[i].setText(name)
+ self.scoreTextNodes[i].setText(str(score))
+ self.updateTrophyStar(self.trophyStars[i], score)
+ # Fill in the rest with empty looking strings and hide the stars
+ for i in range(len(self.leaderNames), self.numLeaders):
+ self.nameTextNodes[i].setText("-")
+ self.scoreTextNodes[i].setText("-")
+ self.trophyStars[i].hide()
+
+ def buildTitleRow(self):
+ # Build the title row on the leaderboard
+ row = hidden.attachNewNode("leaderRow")
+ nameText = TextNode("titleRow")
+ nameText.setFont(ToontownGlobals.getSignFont())
+ nameText.setAlign(TextNode.ACenter)
+ nameText.setTextColor(0.5, 0.75, 0.7, 1)
+ nameText.setText(TTLocalizer.LeaderboardTitle)
+ namePath = row.attachNewNode(nameText)
+ # Centered
+ namePath.setPos(0,0,0)
+ return row
+
+ def buildLeaderRow(self):
+ # Build a single row on the leaderboard
+ row = hidden.attachNewNode("leaderRow")
+
+ # Text node for the toon name
+ nameText = TextNode("nameText")
+ nameText.setFont(ToontownGlobals.getToonFont())
+ nameText.setAlign(TextNode.ALeft)
+ nameText.setTextColor(1, 1, 1, 0.7)
+ nameText.setText("-")
+ namePath = row.attachNewNode(nameText)
+ namePath.setPos(*TTLocalizer.DHtoonNamePos)
+ namePath.setScale(TTLocalizer.DHtoonName)
+
+ # Text node for the score
+ scoreText = TextNode("scoreText")
+ scoreText.setFont(ToontownGlobals.getToonFont())
+ scoreText.setAlign(TextNode.ARight)
+ scoreText.setTextColor(1, 1, 0.1, 0.7)
+ scoreText.setText("-")
+ scorePath = row.attachNewNode(scoreText)
+ scorePath.setPos(*TTLocalizer.DHscorePos)
+
+ # Put a star on the row, just like over the Toon heads
+ trophyStar = self.buildTrophyStar()
+ trophyStar.reparentTo(row)
+
+ return row, nameText, scoreText, trophyStar
+
+ def setLeaderBoard(self, leaderData):
+ # This message is sent from the AI when the leaderboard is updated
+ # We assume that because we got this message, something must have changed,
+ # or we are in our generate
+ avIds, names, scores = cPickle.loads(leaderData)
+ # Note, these lists are in order, highest score first
+ self.notify.debug("setLeaderBoard: avIds: %s, names: %s, scores: %s" % (avIds, names, scores))
+ self.leaderAvIds = avIds
+ self.leaderNames = names
+ self.leaderScores = scores
+ # Refresh the display
+ self.updateLeaderBoard()
+
+ def chooseDoor(self):
+ # I copy/pasted this door string choosing code from
+ # DistributedToonInterior.
+ # Door:
+ doorModelName="door_double_round_ul" # hack zzzzzzz
+ # Switch leaning of the door:
+ if doorModelName[-1:] == "r":
+ doorModelName=doorModelName[:-1]+"l"
+ else:
+ doorModelName=doorModelName[:-1]+"r"
+ door=self.dnaStore.findNode(doorModelName)
+ return door
+
+ def setupDoors(self):
+ # Set up random generator
+ self.randomGenerator = random.Random()
+ self.randomGenerator.seed(self.zoneId)
+
+ # Pick a color list. For now, I've picked ToontownCentral.
+ # Maybe there will be a special color scheme for HQ interiors
+ self.colors = ToonInteriorColors.colors[ToontownCentral]
+
+ # Pick a door model
+ door = self.chooseDoor()
+ # Find the door origins
+ doorOrigins = render.findAllMatches("**/door_origin*")
+ numDoorOrigins = doorOrigins.getNumPaths()
+ for npIndex in range(numDoorOrigins):
+ doorOrigin = doorOrigins[npIndex]
+ doorOriginNPName = doorOrigin.getName()
+ doorOriginIndexStr = doorOriginNPName[len("door_origin_"):]
+ newNode = ModelNode("door_" + doorOriginIndexStr)
+ newNodePath = NodePath(newNode)
+ newNodePath.reparentTo(self.interior)
+ doorNP = door.copyTo(newNodePath)
+ assert(not doorNP.isEmpty())
+ assert(not doorOrigin.isEmpty())
+ doorOrigin.setScale(0.8, 0.8, 0.8)
+ doorOrigin.setPos(doorOrigin, 0, -0.025, 0)
+ doorColor = self.randomGenerator.choice(self.colors["TI_door"])
+ triggerId = str(self.block) + "_" + doorOriginIndexStr
+ DNADoor.setupDoor(doorNP,
+ newNodePath, doorOrigin,
+ self.dnaStore, triggerId,
+ doorColor)
+ doorFrame = doorNP.find("door_*_flat")
+ #doorFrame.wrtReparentTo(self.interior)
+ doorFrame.setColor(doorColor)
+
+ del self.dnaStore
+ del self.randomGenerator
+
+ def disable(self):
+ self.leaderBoard.removeNode()
+ del self.leaderBoard
+ self.interior.removeNode()
+ del self.interior
+ del self.nameTextNodes
+ del self.scoreTextNodes
+ del self.trophyStars
+ taskMgr.remove(self.uniqueName("starSpinHQ"))
+ DistributedObject.DistributedObject.disable(self)
+
+ # TODO: perhaps the star code should be abstracted out and shared
+ # between the leaderboard and the Toons.
+
+ def buildTrophyStar(self):
+ trophyStar = loader.loadModel('phase_3.5/models/gui/name_star')
+ trophyStar.hide()
+ trophyStar.setPos(*TTLocalizer.DHtrophyPos)
+ return trophyStar
+
+ def updateTrophyStar(self, trophyStar, score):
+ # Customize the star just like the ones over Toon's heads
+ scale = 0.8
+ if score >= ToontownGlobals.TrophyStarLevels[4]:
+ # A gold star!
+ trophyStar.show()
+ trophyStar.setScale(scale)
+ trophyStar.setColor(ToontownGlobals.TrophyStarColors[4])
+ if score >= ToontownGlobals.TrophyStarLevels[5]:
+ # Spinning!
+ task = taskMgr.add(self.__starSpin, self.uniqueName("starSpinHQ"))
+ task.trophyStarSpeed = 15
+ task.trophyStar = trophyStar
+
+ elif score >= ToontownGlobals.TrophyStarLevels[2]:
+ # A silver star!
+ trophyStar.show()
+ trophyStar.setScale(0.75 * scale)
+ trophyStar.setColor(ToontownGlobals.TrophyStarColors[2])
+ # Spinning!
+ if score >= ToontownGlobals.TrophyStarLevels[3]:
+ task = taskMgr.add(self.__starSpin, self.uniqueName("starSpinHQ"))
+ task.trophyStarSpeed = 10
+ task.trophyStar = trophyStar
+
+ elif score >= ToontownGlobals.TrophyStarLevels[0]:
+ # A bronze star.
+ trophyStar.show()
+ trophyStar.setScale(0.75 * scale)
+ trophyStar.setColor(ToontownGlobals.TrophyStarColors[0])
+ # Spinning!
+ if score >= ToontownGlobals.TrophyStarLevels[1]:
+ task = taskMgr.add(self.__starSpin, self.uniqueName("starSpinHQ"))
+ task.trophyStarSpeed = 8
+ task.trophyStar = trophyStar
+
+ else:
+ trophyStar.hide()
+
+ def __starSpin(self, task):
+ # The little star spin task
+ now = globalClock.getFrameTime()
+ r = now * task.trophyStarSpeed % 360.0
+ task.trophyStar.setR(r)
+ return Task.cont
+
+"""
+from toontown.makeatoon import NameGenerator
+ng = NameGenerator.NameGenerator()
+data = [[0,1,2,3,4,5,6,7,8,9],[ng.randomName(), ng.randomName(), ng.randomName(), ng.randomName(), ng.randomName(), ng.randomName(), ng.randomName(), ng.randomName(), ng.randomName(), ng.randomName()], [35,26,18,15,14,10,6,4,3,2]]
+hq.setLeaderBoard(cPickle.dumps(data))
+"""
diff --git a/toontown/src/building/DistributedHQInteriorAI.py b/toontown/src/building/DistributedHQInteriorAI.py
new file mode 100644
index 0000000..35db845
--- /dev/null
+++ b/toontown/src/building/DistributedHQInteriorAI.py
@@ -0,0 +1,60 @@
+from direct.distributed import DistributedObjectAI
+from direct.directnotify import DirectNotifyGlobal
+import cPickle
+
+class DistributedHQInteriorAI(DistributedObjectAI.DistributedObjectAI):
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedHQInteriorAI')
+
+ def __init__(self, block, air, zoneId):
+ # Right now, this doesn't do much.
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.block = block
+ self.zoneId = zoneId
+ self.tutorial = 0
+
+ # When the leaders change, the trophy mgr will notify us
+ self.isDirty = False
+ self.accept("leaderboardChanged", self.leaderboardChanged)
+ self.accept("leaderboardFlush", self.leaderboardFlush)
+
+ def delete(self):
+ # This is important because the tutorial interiors get created and deleted
+ self.ignore("leaderboardChanged")
+ self.ignore('leaderboardFlush')
+ self.ignore("setLeaderBoard")
+ self.ignore('AIStarted')
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def getZoneIdAndBlock(self):
+ r=[self.zoneId, self.block]
+ return r
+
+ def leaderboardChanged(self):
+ # This message is sent when the leaders change.
+ self.isDirty = True
+
+ def leaderboardFlush(self):
+ # This message is sent every 30 seconds after AI startup, to
+ # update the leaderboard if necessary.
+ if self.isDirty:
+ self.sendNewLeaderBoard()
+
+ def sendNewLeaderBoard(self):
+ if self.air:
+ self.isDirty = False
+ self.sendUpdate("setLeaderBoard", [cPickle.dumps(self.air.trophyMgr.getLeaderInfo(), 1)])
+
+ def getLeaderBoard(self):
+ # Since this is a required field, we need a getter
+ # This needs to be returned as parallel lists of avIds, name, and scores
+ return cPickle.dumps(self.air.trophyMgr.getLeaderInfo(), 1)
+
+ def getTutorial(self):
+ return self.tutorial
+
+ def setTutorial(self, flag):
+ if self.tutorial != flag:
+ self.tutorial = flag
+ self.sendUpdate("setTutorial", [self.tutorial])
diff --git a/toontown/src/building/DistributedKartShopInterior.py b/toontown/src/building/DistributedKartShopInterior.py
new file mode 100644
index 0000000..5e3fb64
--- /dev/null
+++ b/toontown/src/building/DistributedKartShopInterior.py
@@ -0,0 +1,117 @@
+##########################################################################
+# Module: DistributedKartShopInterior.py
+# Purpose: This module oversees the construction of the KartShop Interior
+# and KartShop NPCs on the client-side.
+# Date: 6/8/05
+# Author: jjtaylor (jjtaylor@schellgames.com)
+##########################################################################
+
+##########################################################################
+# Panda/Direct Import Modules
+##########################################################################
+from direct.directnotify import DirectNotifyGlobal
+from direct.distributed.DistributedObject import DistributedObject
+from pandac.PandaModules import *
+
+##########################################################################
+# Toontwon Import Modules
+##########################################################################
+from toontown.building import ToonInteriorColors
+from toontown.hood import ZoneUtil
+from toontown.toonbase.ToonBaseGlobal import *
+from toontown.toonbase.ToontownGlobals import *
+
+if( __debug__ ):
+ import pdb
+
+class DistributedKartShopInterior( DistributedObject ):
+ """
+ Class: DistributedKartShopInterior
+ Purpose: The class provides the interior of the KartShop on the
+ client side. It also properly sets up the random NPCs
+ which are generated as KartShopClerks.
+ """
+
+ ######################################################################
+ # Class Variable Definitions
+ ######################################################################
+ notify = DirectNotifyGlobal.directNotify.newCategory( "DistributedKartShopInterior" )
+
+
+ def __init__( self, cr ):
+ """
+ Purpose: The __init__ Method sets up the KartShop Interior object
+ by initlaizing the super class as well as setting the appropriate
+ dnaStore for the object.
+
+ Params: cr - The client repository which maintains all client-side
+ distributed objects.
+ Return: None
+ """
+
+ # Initialize the Super Class, then set the dna store.
+ DistributedObject.__init__( self, cr )
+ self.dnaStore = cr.playGame.dnaStore
+
+ def generate( self ):
+ """
+ Purpose: The generate Method handles the basic generation of the
+ KartShopInterior object by generating the super class.
+
+ Params: None
+ Return: None
+ """
+ DistributedObject.generate( self )
+
+ def announceGenerate( self ):
+ """
+ Purpose: The announceGenerate Method tells the super class
+ to send a message to the world that the object has been
+ generated and all of its required fields have been filled in. It
+ also sets up the Interior.
+
+ Params: None
+ Return: None
+ """
+
+ DistributedObject.announceGenerate( self )
+ self.__handleInteriorSetup()
+
+ def disable( self ):
+ """
+ Purpose: The disable Method performs the necessary cleanup
+ of the KartShopInterior object on the client side.
+
+ Params: None
+ Return None
+ """
+ self.interior.removeNode()
+ del self.interior
+ DistributedObject.disable( self )
+
+
+ def setZoneIdAndBlock( self, zoneId, block ):
+ """
+ Purpose: The setZoneIdAndBlock Method properly sets the zoneId
+ and block of the interior.
+
+ Params: zoneId - the zone id of the interior
+ block - the block of the interior
+ Return: None
+ """
+ self.zoneId = zoneId
+ self.block = block
+
+ def __handleInteriorSetup( self ):
+ """
+ Purpose: The __handleInteriorSetup Method properly sets up the
+ interior of the Kart Shop on the client side.
+
+ Params: None
+ Return: None
+ """
+
+ # Load the appropriate Interior Model for the Kart Shop
+ self.interior = loader.loadModel( 'phase_6/models/karting/KartShop_Interior' )
+ self.interior.reparentTo( render )
+ self.interior.flattenMedium()
diff --git a/toontown/src/building/DistributedKartShopInteriorAI.py b/toontown/src/building/DistributedKartShopInteriorAI.py
new file mode 100644
index 0000000..ab7d78b
--- /dev/null
+++ b/toontown/src/building/DistributedKartShopInteriorAI.py
@@ -0,0 +1,64 @@
+##########################################################################
+# Module: DistributedKartShopInteriorAI.py
+# Purpose: This module oversees the construction of the KartShop Interior
+# on the AI server side.
+# Date: 6/8/05
+# Author: jjtaylor (jjtaylor@schellgames.com)
+##########################################################################
+
+##########################################################################
+# Panda/Direct Import Modules
+##########################################################################
+from direct.directnotify import DirectNotifyGlobal
+from direct.distributed.DistributedObjectAI import DistributedObjectAI
+
+class DistributedKartShopInteriorAI( DistributedObjectAI ):
+ """
+ Purpose: The DistributedKartShopInteriorAI class represents the
+ interior of the KartShop on the AI server side.
+ """
+
+ ######################################################################
+ # Class Variable Definitions
+ ######################################################################
+ notify = DirectNotifyGlobal.directNotify.newCategory( "DistributedKartShopInteriorAI" )
+
+ def __init__( self, block, air, zoneId ):
+ """
+ Purpose: The __init__ Method handles the initialization of the
+ KartShopInteriorAI object by initializing the super class, and
+ setting the instance variables.
+
+ Params: block - the block of the KartShop
+ air - The AI Repository which stores all of the
+ distributed objects on the AI Server side.
+ zoneId - the kart shops zone id
+ Return: None
+ """
+
+ # Initialize the Super Class
+ DistributedObjectAI.__init__( self, air )
+
+ # Initialize instance variables
+ self.block = block
+ self.zoneId = zoneId
+
+ def generate( self ):
+ """
+ Purpose: The generate Method performs the necessary object
+ setup.
+
+ Params: None
+ Return: None
+ """
+ DistributedObjectAI.generate( self )
+
+ def getZoneIdAndBlock( self ):
+ """
+ Purpose: The getZoneIdAndBlock Method returns the zoneId and
+ the block of the KartShopInterior.
+
+ Params: None
+ Return: [] - containing the zoneId and block.
+ """
+ return [ self.zoneId, self.block ]
diff --git a/toontown/src/building/DistributedKnockKnockDoor.py b/toontown/src/building/DistributedKnockKnockDoor.py
new file mode 100644
index 0000000..b77a2d5
--- /dev/null
+++ b/toontown/src/building/DistributedKnockKnockDoor.py
@@ -0,0 +1,222 @@
+"""
+DistributedKnockKnockDoor module: contains the DistributedKnockKnockDoor
+class, the client side representation of a DistributedKnockKnockDoorAI.
+"""
+
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+
+from KnockKnockJokes import *
+
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+import DistributedAnimatedProp
+from toontown.distributed import DelayDelete
+from toontown.toonbase import TTLocalizer
+from toontown.hood import ZoneUtil
+
+
+class DistributedKnockKnockDoor(DistributedAnimatedProp.DistributedAnimatedProp):
+ """
+ The client side representation of a knock, knock door.
+ """
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedKnockKnockDoor')
+
+ def __init__(self, cr):
+ """
+ cr is a ClientRepository.
+ constructor for the DistributedKnockKnockDoor
+ """
+ assert(self.debugPrint("__init()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.__init__(self, cr)
+ self.fsm.setName('DistributedKnockKnockDoor')
+ # self.generate will be called automatically.
+ self.rimshot = None
+ self.knockSfx = None
+
+ def generate(self):
+ """
+ This method is called when the DistributedAnimatedProp is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ assert(self.debugPrint("generate()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.generate(self)
+ self.avatarTracks=[]
+ self.avatarId=0
+
+ def announceGenerate(self):
+ assert(self.debugPrint("announceGenerate()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.announceGenerate(self)
+ self.accept("exitKnockKnockDoorSphere_"+str(self.propId),
+ self.exitTrigger)
+ self.acceptAvatar()
+
+ def disable(self):
+ assert(self.debugPrint("disable()"))
+ self.ignore("exitKnockKnockDoorSphere_"+str(self.propId))
+ self.ignore("enterKnockKnockDoorSphere_"+str(self.propId))
+ DistributedAnimatedProp.DistributedAnimatedProp.disable(self)
+ assert(len(self.avatarTracks)==0)
+ # self.delete() will automatically be called.
+
+ def delete(self):
+ assert(self.debugPrint("delete()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.delete(self)
+ if self.rimshot:
+ self.rimshot = None
+ if self.knockSfx:
+ self.knockSfx = None
+
+ def acceptAvatar(self):
+ self.acceptOnce(
+ "enterKnockKnockDoorSphere_"+str(self.propId),
+ self.enterTrigger)
+
+ def setAvatarInteract(self, avatarId):
+ assert(self.debugPrint("setAvatarInteract(avatarId=%s)" %(avatarId,)))
+ DistributedAnimatedProp.DistributedAnimatedProp.setAvatarInteract(self, avatarId)
+
+ def avatarExit(self, avatarId):
+ assert(self.debugPrint("avatarExit(avatarId=%s)"%(avatarId,)))
+ if avatarId == self.avatarId:
+ for track in self.avatarTracks:
+ track.finish()
+ DelayDelete.cleanupDelayDeletes(track)
+ self.avatarTracks=[]
+
+ def knockKnockTrack(self, avatar, duration):
+ if avatar == None:
+ return None
+
+ # NOTE: the use of this rimshot sfx (which is in phase_5)
+ # means we better not have any knock knock doors in phase_4,
+ # which is true now.
+ self.rimshot = base.loadSfx("phase_5/audio/sfx/AA_heal_telljoke.mp3")
+ self.knockSfx = base.loadSfx("phase_5/audio/sfx/GUI_knock_3.mp3")
+
+ joke = KnockKnockJokes[self.propId%len(KnockKnockJokes)]
+
+ # For a marketing contest we are putting user-submitted knock knock jokes on
+ # the first side doors (on the left) of the three TTC streets.
+ place = base.cr.playGame.getPlace()
+ if place:
+ zone = place.getZoneId()
+ branch = ZoneUtil.getBranchZone(zone)
+
+ if branch == ToontownGlobals.SillyStreet:
+ if self.propId == 44:
+ joke = KnockKnockContestJokes[ToontownGlobals.SillyStreet]
+ elif branch == ToontownGlobals.LoopyLane:
+ if self.propId in KnockKnockContestJokes[ToontownGlobals.LoopyLane].keys():
+ joke = KnockKnockContestJokes[ToontownGlobals.LoopyLane][self.propId]
+ elif branch == ToontownGlobals.PunchlinePlace:
+ if self.propId == 1:
+ joke = KnockKnockContestJokes[ToontownGlobals.PunchlinePlace]
+ elif branch == ToontownGlobals.PolarPlace:
+ if self.propId in KnockKnockContestJokes[ToontownGlobals.PolarPlace].keys():
+ joke = KnockKnockContestJokes[ToontownGlobals.PolarPlace][self.propId]
+
+ self.nametag = None
+ self.nametagNP = None
+
+ doorNP=render.find("**/KnockKnockDoorSphere_"+str(self.propId)+";+s")
+ if doorNP.isEmpty():
+ self.notify.warning("Could not find KnockKnockDoorSphere_%s" % (self.propId))
+ return None
+
+ self.nametag = NametagGroup()
+ self.nametag.setAvatar(doorNP)
+ self.nametag.setFont(ToontownGlobals.getToonFont())
+ # nametag.setName must come after setFont().
+ self.nametag.setName(TTLocalizer.DoorNametag)
+ # Do not allow user to click on door nametag
+ self.nametag.setActive(0)
+ self.nametag.manage(base.marginManager)
+ self.nametag.getNametag3d().setBillboardOffset(4)
+ nametagNode = self.nametag.getNametag3d().upcastToPandaNode()
+ self.nametagNP=render.attachNewNode(nametagNode)
+ self.nametagNP.setName("knockKnockDoor_nt_"+str(self.propId))
+ pos=doorNP.node().getSolid(0).getCenter()
+ self.nametagNP.setPos(pos+Vec3(0, 0, avatar.getHeight()+2))
+ d=duration*0.125
+ track=Sequence(
+ Parallel(
+ Sequence(Wait(d * 0.5), SoundInterval(self.knockSfx)),
+ Func(self.nametag.setChat, TTLocalizer.DoorKnockKnock, CFSpeech),
+ Wait(d)
+ ),
+ Func(avatar.setChatAbsolute, TTLocalizer.DoorWhosThere, CFSpeech | CFTimeout,
+ openEnded = 0),
+ Wait(d),
+ Func(self.nametag.setChat, joke[0], CFSpeech),
+ Wait(d),
+ Func(avatar.setChatAbsolute, joke[0]+TTLocalizer.DoorWhoAppendix,
+ CFSpeech | CFTimeout,
+ openEnded = 0),
+ Wait(d),
+ Func(self.nametag.setChat, joke[1], CFSpeech),
+ Parallel(
+ SoundInterval(self.rimshot, startTime = 2.0),
+ Wait(d*4),
+ ),
+ Func(self.cleanupTrack)
+ )
+ track.delayDelete = DelayDelete.DelayDelete(avatar, 'knockKnockTrack')
+ return track
+
+ def cleanupTrack(self):
+ avatar = self.cr.doId2do.get(self.avatarId, None)
+ if avatar:
+ avatar.clearChat()
+ if self.nametag:
+ self.nametag.unmanage(base.marginManager)
+ self.nametagNP.removeNode()
+ self.nametag = None
+ self.nametagNP = None
+
+ ##### off state #####
+
+ def enterOff(self):
+ assert(self.debugPrint("enterOff()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.enterOff(self)
+
+ def exitOff(self):
+ assert(self.debugPrint("exitOff()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.exitOff(self)
+
+ ##### attract state #####
+
+ def enterAttract(self, ts):
+ assert(self.debugPrint("enterAttract()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.enterAttract(self, ts)
+ self.acceptAvatar()
+
+ def exitAttract(self):
+ assert(self.debugPrint("exitAttract()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.exitAttract(self)
+
+ ##### playing state #####
+
+ def enterPlaying(self, ts):
+ assert(self.debugPrint("enterPlaying()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.enterPlaying(self, ts)
+ if self.avatarId:
+ # Start animation at time stamp:
+ avatar = self.cr.doId2do.get(self.avatarId, None)
+ track=self.knockKnockTrack(avatar, 8)
+ if track != None:
+ track.start(ts)
+ self.avatarTracks.append(track)
+
+ def exitPlaying(self):
+ assert(self.debugPrint("exitPlaying()"))
+ DistributedAnimatedProp.DistributedAnimatedProp.exitPlaying(self)
+ for track in self.avatarTracks:
+ track.finish()
+ DelayDelete.cleanupDelayDeletes(track)
+ self.avatarTracks=[]
+ self.avatarId=0
diff --git a/toontown/src/building/DistributedKnockKnockDoorAI.py b/toontown/src/building/DistributedKnockKnockDoorAI.py
new file mode 100644
index 0000000..5241c1d
--- /dev/null
+++ b/toontown/src/building/DistributedKnockKnockDoorAI.py
@@ -0,0 +1,83 @@
+"""
+DistributedKnockKnockDoorAI module: contains the DistributedKnockKnockDoorAI
+class, the server side representation of a DistributedKnockKnockDoor.
+"""
+
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+import DistributedAnimatedPropAI
+from direct.task.Task import Task
+from direct.fsm import State
+
+
+class DistributedKnockKnockDoorAI(DistributedAnimatedPropAI.DistributedAnimatedPropAI):
+ """
+ The server side representation of a
+ single landmark door. This is the object that remembers what the
+ door is doing. The child of this object, the DistributedDoor
+ object, is the client side version and updates the display that
+ client's display based on the state of the door.
+ """
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedKnockKnockDoorAI')
+
+ def __init__(self, air, propId):
+ """
+ blockNumber: the landmark building number (from the name)
+ """
+ DistributedAnimatedPropAI.DistributedAnimatedPropAI.__init__(
+ self, air, propId)
+ assert(self.debugPrint(
+ "DistributedKnockKnockDoorAI(%s, %s)"
+ %("the air", propId)))
+ self.fsm.setName('DistributedKnockKnockDoor')
+ self.propId=propId
+ self.doLaterTask=None
+
+ ##### off state #####
+
+ def enterOff(self):
+ assert(self.debugPrint("enterOff()"))
+ DistributedAnimatedPropAI.DistributedAnimatedPropAI.enterOff(self)
+
+ def exitOff(self):
+ assert(self.debugPrint("exitOff()"))
+ DistributedAnimatedPropAI.DistributedAnimatedPropAI.exitOff(self)
+
+ ##### attract state #####
+
+ def attractTask(self, task):
+ assert(self.debugPrint("attractTask()"))
+ self.fsm.request("attract")
+ return Task.done
+
+ def enterAttract(self):
+ assert(self.debugPrint("enterAttract()"))
+ DistributedAnimatedPropAI.DistributedAnimatedPropAI.enterAttract(self)
+
+ def exitAttract(self):
+ assert(self.debugPrint("exitAttract()"))
+ DistributedAnimatedPropAI.DistributedAnimatedPropAI.exitAttract(self)
+
+ ##### playing state #####
+
+ def enterPlaying(self):
+ assert(self.debugPrint("enterPlaying()"))
+ DistributedAnimatedPropAI.DistributedAnimatedPropAI.enterPlaying(self)
+ assert(self.doLaterTask==None)
+ self.doLaterTask=taskMgr.doMethodLater(
+ 9, #KNOCK_KNOCK_DOOR_TIME, #TODO: define this elsewhere
+ self.attractTask,
+ self.uniqueName('knockKnock-timer'))
+
+ def exitPlaying(self):
+ assert(self.debugPrint("exitPlaying()"))
+ DistributedAnimatedPropAI.DistributedAnimatedPropAI.exitPlaying(self)
+ taskMgr.remove(self.doLaterTask)
+ self.doLaterTask=None
+
+
diff --git a/toontown/src/building/DistributedPetshopInterior.py b/toontown/src/building/DistributedPetshopInterior.py
new file mode 100644
index 0000000..65583e0
--- /dev/null
+++ b/toontown/src/building/DistributedPetshopInterior.py
@@ -0,0 +1,167 @@
+from toontown.toonbase.ToonBaseGlobal import *
+from pandac.PandaModules import *
+from toontown.toonbase.ToontownGlobals import *
+
+import random
+from direct.distributed import DistributedObject
+from direct.directnotify import DirectNotifyGlobal
+from direct.actor import Actor
+import ToonInteriorColors
+from toontown.hood import ZoneUtil
+
+
+class DistributedPetshopInterior(DistributedObject.DistributedObject):
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedPetshopInterior')
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+ self.dnaStore=cr.playGame.dnaStore
+
+ def generate(self):
+ DistributedObject.DistributedObject.generate(self)
+
+ def announceGenerate(self):
+ DistributedObject.DistributedObject.announceGenerate(self)
+ self.setup()
+
+ def randomDNAItem(self, category, findFunc):
+ codeCount = self.dnaStore.getNumCatalogCodes(category)
+ index = self.randomGenerator.randint(0, codeCount-1)
+ code = self.dnaStore.getCatalogCode(category, index)
+ # findFunc will probably be findNode or findTexture
+ return findFunc(code)
+
+ def replaceRandomInModel(self, model):
+ """Replace named nodes with random items.
+ Here are the name Here is
+ prefixes that are what they
+ affected: do:
+
+ random_mox_ change the Model Only.
+ random_mcx_ change the Model and the Color.
+ random_mrx_ change the Model and Recurse.
+ random_tox_ change the Texture Only.
+ random_tcx_ change the Texture and the Color.
+
+ x is simply a uniquifying integer because Multigen will not
+ let you have multiple nodes with the same name
+
+ """
+ baseTag="random_"
+ npc=model.findAllMatches("**/"+baseTag+"???_*")
+ for i in range(npc.getNumPaths()):
+ np=npc.getPath(i)
+ name=np.getName()
+
+ b=len(baseTag)
+ category=name[b+4:]
+ key1=name[b]
+ key2=name[b+1]
+
+ assert(key1 in ["m", "t"])
+ assert(key2 in ["c", "o", "r"])
+ if key1 == "m":
+ # ...model.
+ model = self.randomDNAItem(category, self.dnaStore.findNode)
+ assert(not model.isEmpty())
+ newNP = model.copyTo(np)
+ if key2 == "r":
+ self.replaceRandomInModel(newNP)
+ elif key1 == "t":
+ # ...texture.
+ texture=self.randomDNAItem(category, self.dnaStore.findTexture)
+ assert(texture)
+ np.setTexture(texture,100)
+ newNP=np
+ if key2 == "c":
+ if (category == "TI_wallpaper") or (category == "TI_wallpaper_border"):
+ self.randomGenerator.seed(self.zoneId)
+ newNP.setColorScale(
+ self.randomGenerator.choice(self.colors[category]))
+ else:
+ newNP.setColorScale(
+ self.randomGenerator.choice(self.colors[category]))
+
+ def setZoneIdAndBlock(self, zoneId, block):
+ self.zoneId = zoneId
+ self.block = block
+
+ def chooseDoor(self):
+ # I copy/pasted this door string choosing code from
+ # DistributedToonInterior.
+ # Door:
+ doorModelName="door_double_round_ul" # hack zzzzzzz
+ # Switch leaning of the door:
+ if doorModelName[-1:] == "r":
+ doorModelName=doorModelName[:-1]+"l"
+ else:
+ doorModelName=doorModelName[:-1]+"r"
+ door=self.dnaStore.findNode(doorModelName)
+ return door
+
+ def setup(self):
+ self.dnaStore=base.cr.playGame.dnaStore
+ # Set up random generator
+ self.randomGenerator = random.Random()
+ self.randomGenerator.seed(self.zoneId)
+
+ self.interior = loader.loadModel('phase_4/models/modules/PetShopInterior')
+ self.interior.reparentTo(render)
+
+ # Fish swimming around behind the glass
+ self.fish = Actor.Actor(
+ 'phase_4/models/props/interiorfish-zero',
+ { 'swim' : 'phase_4/models/props/interiorfish-swim',})
+ self.fish.reparentTo(self.interior)
+
+ # Give them a little bluish green color to make it seem like they
+ # are behind the glass. The 0.8 alpha makes their fins a little
+ # translucent which is kinda cool.
+ self.fish.setColorScale(0.8,0.9,1,0.8)
+ # Reposition them just a bit to bring them down and behind the glass
+ self.fish.setScale(0.8)
+ self.fish.setPos(0,6,-4)
+ # Slow it down a bit! Geez!
+ self.fish.setPlayRate(0.7, 'swim')
+ self.fish.loop('swim')
+
+ # Load a color dictionary for this hood:
+ hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
+ self.colors = ToonInteriorColors.colors[hoodId]
+ # Replace all the "random_xxx_" nodes:
+ self.replaceRandomInModel(self.interior)
+
+ # Pick a door model
+ door = self.chooseDoor()
+ # Find the door origins
+ doorOrigin = render.find("**/door_origin;+s")
+ doorNP = door.copyTo(doorOrigin)
+ assert(not doorNP.isEmpty())
+ assert(not doorOrigin.isEmpty())
+ doorOrigin.setScale(0.8, 0.8, 0.8)
+ doorOrigin.setPos(doorOrigin, 0, -0.25, 0)
+ doorColor = self.randomGenerator.choice(self.colors["TI_door"])
+ DNADoor.setupDoor(doorNP,
+ self.interior, doorOrigin,
+ self.dnaStore, str(self.block),
+ doorColor)
+ doorFrame = doorNP.find("door_*_flat")
+ doorFrame.wrtReparentTo(self.interior)
+ doorFrame.setColor(doorColor)
+
+ del self.colors
+ del self.dnaStore
+ del self.randomGenerator
+ self.interior.flattenMedium()
+
+ def disable(self):
+ self.fish.stop()
+ self.fish.cleanup()
+ del self.fish
+ self.interior.removeNode()
+ del self.interior
+ DistributedObject.DistributedObject.disable(self)
+
+
diff --git a/toontown/src/building/DistributedPetshopInteriorAI.py b/toontown/src/building/DistributedPetshopInteriorAI.py
new file mode 100644
index 0000000..787e488
--- /dev/null
+++ b/toontown/src/building/DistributedPetshopInteriorAI.py
@@ -0,0 +1,28 @@
+from direct.distributed import DistributedObjectAI
+from direct.directnotify import DirectNotifyGlobal
+
+class DistributedPetshopInteriorAI(DistributedObjectAI.DistributedObjectAI):
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedPetshopInteriorAI')
+
+ def __init__(self, block, air, zoneId):
+ # Right now, this doesn't do much.
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.block = block
+ self.zoneId = zoneId
+
+
+ def generate(self):
+ DistributedObjectAI.DistributedObjectAI.generate(self)
+
+ #self.interior = loader.loadModel('phase_4/models/modules/PetShopInterior')
+ #render = self.getRender()
+ #self.interior.instanceTo(render)
+
+ def getZoneIdAndBlock(self):
+ r=[self.zoneId, self.block]
+ return r
+
+
+
diff --git a/toontown/src/building/DistributedSuitInterior.py b/toontown/src/building/DistributedSuitInterior.py
new file mode 100644
index 0000000..86c1b71
--- /dev/null
+++ b/toontown/src/building/DistributedSuitInterior.py
@@ -0,0 +1,627 @@
+""" DistributedSuitInterior module"""
+
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+import ElevatorUtils
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import ToontownBattleGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObject
+from direct.fsm import State
+from toontown.battle import BattleBase
+from toontown.hood import ZoneUtil
+
+class DistributedSuitInterior(DistributedObject.DistributedObject):
+ """
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedSuitInterior')
+
+ id = 0
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+
+ self.toons = []
+ self.activeIntervals = {}
+
+ self.openSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_open.mp3")
+ self.closeSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_close.mp3")
+
+ self.suits = []
+ self.reserveSuits = []
+ self.joiningReserves = []
+
+ self.distBldgDoId = None
+
+ # we increment this each time we come out of an elevator:
+ self.currentFloor = -1
+ self.numFloors = None
+
+ self.elevatorName = self.__uniqueName('elevator')
+ self.floorModel = None
+
+ self.elevatorOutOpen = 0
+
+ # initial cog positions vary based on the cog office model
+ self.BottomFloor_SuitPositions = [
+ Point3(0, 15, 0),
+ Point3(10, 20, 0),
+ Point3(-7, 24, 0),
+ Point3(-10, 0, 0)]
+ self.BottomFloor_SuitHs = [75, 170, -91, -44] # Heading angles
+
+ self.Cubicle_SuitPositions = [
+ Point3(0, 18, 0),
+ Point3(10, 12, 0),
+ Point3(-9, 11, 0),
+ Point3(-3, 13, 0)]
+ self.Cubicle_SuitHs = [170, 56, -52, 10]
+
+ self.BossOffice_SuitPositions = [
+ Point3(0, 15, 0),
+ Point3(10, 20, 0),
+ Point3(-10, 6, 0),
+ Point3(-17, 34, 11),
+ ]
+ self.BossOffice_SuitHs = [170, 120, 12, 38]
+
+ self.waitMusic = base.loadMusic(
+ 'phase_7/audio/bgm/encntr_toon_winning_indoor.mid')
+ self.elevatorMusic = base.loadMusic(
+ 'phase_7/audio/bgm/tt_elevator.mid')
+
+ self.fsm = ClassicFSM.ClassicFSM('DistributedSuitInterior',
+ [State.State('WaitForAllToonsInside',
+ self.enterWaitForAllToonsInside,
+ self.exitWaitForAllToonsInside,
+ ['Elevator']),
+ State.State('Elevator',
+ self.enterElevator,
+ self.exitElevator,
+ ['Battle']),
+ State.State('Battle',
+ self.enterBattle,
+ self.exitBattle,
+ ['Resting',
+ 'Reward',
+ 'ReservesJoining']),
+ State.State('ReservesJoining',
+ self.enterReservesJoining,
+ self.exitReservesJoining,
+ ['Battle']),
+ State.State('Resting',
+ self.enterResting,
+ self.exitResting,
+ ['Elevator']),
+ State.State('Reward',
+ self.enterReward,
+ self.exitReward,
+ ['Off']),
+ State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Elevator',
+ 'WaitForAllToonsInside',
+ 'Battle']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ # make sure we're in the initial state
+ self.fsm.enterInitialState()
+
+ def __uniqueName(self, name):
+ DistributedSuitInterior.id += 1
+ return (name + '%d' % DistributedSuitInterior.id)
+
+ def generate(self):
+ """generate(self)
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ assert(self.notify.debug("generate()"))
+ DistributedObject.DistributedObject.generate(self)
+
+ # listen for the generate event, which will be thrown after the
+ # required fields are filled in
+ self.announceGenerateName = self.uniqueName('generate')
+ self.accept(self.announceGenerateName, self.handleAnnounceGenerate)
+
+ # Load the elevator model
+ self.elevatorModelIn = loader.loadModel(
+ 'phase_4/models/modules/elevator')
+ self.leftDoorIn = self.elevatorModelIn.find('**/left-door')
+ self.rightDoorIn = self.elevatorModelIn.find('**/right-door')
+
+ self.elevatorModelOut = loader.loadModel(
+ 'phase_4/models/modules/elevator')
+ self.leftDoorOut = self.elevatorModelOut.find('**/left-door')
+ self.rightDoorOut = self.elevatorModelOut.find('**/right-door')
+
+ def setElevatorLights(self, elevatorModel):
+ """
+ Sets up the lights on the interior elevators to represent the
+ number of floors in the building, and to light up the current
+ floor number.
+ """
+ npc=elevatorModel.findAllMatches("**/floor_light_?;+s")
+ for i in range(npc.getNumPaths()):
+ np=npc.getPath(i)
+ # Get the last character, and make it zero based:
+ floor=int(np.getName()[-1:])-1
+
+ if floor == self.currentFloor:
+ np.setColor(LIGHT_ON_COLOR)
+ elif floor < self.numFloors:
+ np.setColor(LIGHT_OFF_COLOR)
+ else:
+ np.hide()
+
+ def handleAnnounceGenerate(self, obj):
+ """
+ handleAnnounceGenerate is called after all of the required fields are
+ filled in
+ 'obj' is another copy of self
+ """
+ self.ignore(self.announceGenerateName)
+
+ assert(self.notify.debug('joining DistributedSuitInterior'))
+ # Update the minigame AI to join our local toon doId
+ self.sendUpdate('setAvatarJoined', [])
+
+ def disable(self):
+ assert(self.notify.debug('disable()'))
+ self.fsm.requestFinalState()
+ self.__cleanupIntervals()
+ self.ignoreAll()
+ self.__cleanup()
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ assert(self.notify.debug('delete()'))
+ del self.waitMusic
+ del self.elevatorMusic
+ del self.openSfx
+ del self.closeSfx
+ del self.fsm
+ # No more battle multiplier
+ base.localAvatar.inventory.setBattleCreditMultiplier(1)
+ DistributedObject.DistributedObject.delete(self)
+
+ def __cleanup(self):
+ self.toons = []
+ self.suits = []
+ self.reserveSuits = []
+ self.joiningReserves = []
+ # Clean up elevator models
+ if (self.elevatorModelIn != None):
+ self.elevatorModelIn.removeNode()
+ if (self.elevatorModelOut != None):
+ self.elevatorModelOut.removeNode()
+ # Clean up current floor
+ if (self.floorModel != None):
+ self.floorModel.removeNode()
+ self.leftDoorIn = None
+ self.rightDoorIn = None
+ self.leftDoorOut = None
+ self.rightDoorOut = None
+
+ def __addToon(self, toon):
+ assert(self.notify.debug('addToon(%d)' % toon.doId))
+ self.accept(toon.uniqueName('disable'),
+ self.__handleUnexpectedExit, extraArgs=[toon])
+
+ def __handleUnexpectedExit(self, toon):
+ self.notify.warning('handleUnexpectedExit() - toon: %d' % toon.doId)
+ self.__removeToon(toon, unexpected=1)
+
+ def __removeToon(self, toon, unexpected=0):
+ assert(self.notify.debug('removeToon() - toon: %d' % toon.doId))
+ if (self.toons.count(toon) == 1):
+ self.toons.remove(toon)
+ self.ignore(toon.uniqueName('disable'))
+
+ def __finishInterval(self, name):
+ """ Force the specified interval to jump to the end
+ """
+ if (self.activeIntervals.has_key(name)):
+ interval = self.activeIntervals[name]
+ if (interval.isPlaying()):
+ assert(self.notify.debug('finishInterval(): %s' % \
+ interval.getName()))
+ interval.finish()
+
+ def __cleanupIntervals(self):
+ for interval in self.activeIntervals.values():
+ interval.finish()
+ self.activeIntervals = {}
+
+ def __closeInElevator(self):
+ self.leftDoorIn.setPos(3.5, 0, 0)
+ self.rightDoorIn.setPos(-3.5, 0, 0)
+
+ ##### Messages from the server #####
+
+ def getZoneId(self):
+ return self.zoneId
+
+ def setZoneId(self, zoneId):
+ self.zoneId = zoneId
+
+ def getExtZoneId(self):
+ return self.extZoneId
+
+ def setExtZoneId(self, extZoneId):
+ self.extZoneId = extZoneId
+
+ def getDistBldgDoId(self):
+ return self.distBldgDoId
+
+ def setDistBldgDoId(self, distBldgDoId):
+ self.distBldgDoId = distBldgDoId
+
+ def setNumFloors(self, numFloors):
+ self.numFloors = numFloors
+
+ def setToons(self, toonIds, hack):
+ assert(self.notify.debug('setToons(): %s' % toonIds))
+ self.toonIds = toonIds
+ oldtoons = self.toons
+ self.toons = []
+ for toonId in toonIds:
+ if (toonId != 0):
+ if (self.cr.doId2do.has_key(toonId)):
+ toon = self.cr.doId2do[toonId]
+ toon.stopSmooth()
+ self.toons.append(toon)
+ if (oldtoons.count(toon) == 0):
+ assert(self.notify.debug('setToons() - new toon: %d' % \
+ toon.doId))
+ self.__addToon(toon)
+ else:
+ self.notify.warning('setToons() - no toon: %d' % toonId)
+ for toon in oldtoons:
+ if (self.toons.count(toon) == 0):
+ self.__removeToon(toon)
+
+ def setSuits(self, suitIds, reserveIds, values):
+ assert(self.notify.debug('setSuits(): active %s reserve %s values %s' \
+ % (suitIds, reserveIds, values)))
+ oldsuits = self.suits
+ self.suits = []
+ self.joiningReserves = []
+ for suitId in suitIds:
+ if (self.cr.doId2do.has_key(suitId)):
+ suit = self.cr.doId2do[suitId]
+ self.suits.append(suit)
+ # Set this on the client
+ suit.fsm.request('Battle')
+ # This will allow client to respond to setState() from the
+ # server from here on out
+ suit.buildingSuit = 1
+ suit.reparentTo(render)
+ if (oldsuits.count(suit) == 0):
+ assert(self.notify.debug('setSuits() suit: %d joining' % \
+ suit.doId))
+ self.joiningReserves.append(suit)
+ else:
+ self.notify.warning('setSuits() - no suit: %d' % suitId)
+ self.reserveSuits = []
+ assert(len(reserveIds) == len(values))
+ for index in range(len(reserveIds)):
+ suitId = reserveIds[index]
+ if (self.cr.doId2do.has_key(suitId)):
+ suit = self.cr.doId2do[suitId]
+ self.reserveSuits.append((suit, values[index]))
+ else:
+ self.notify.warning('setSuits() - no suit: %d' % suitId)
+
+ if (len(self.joiningReserves) > 0):
+ assert(self.notify.debug('setSuits() reserves joining'))
+ self.fsm.request('ReservesJoining')
+
+ def setState(self, state, timestamp):
+ assert(self.notify.debug("setState(%s, %d)" % \
+ (state, timestamp)))
+ self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+
+ ##### Messages to the server #####
+
+ def d_elevatorDone(self):
+ assert(self.notify.debug('network:elevatorDone(%d)' % base.localAvatar.doId))
+ self.sendUpdate('elevatorDone', [])
+
+ def d_reserveJoinDone(self):
+ assert(self.notify.debug('network:reserveJoinDone(%d)' % base.localAvatar.doId))
+ self.sendUpdate('reserveJoinDone', [])
+
+ # Specific State Functions
+
+ ##### Off state #####
+
+ def enterOff(self, ts=0):
+ assert(self.notify.debug('enterOff()'))
+ return None
+
+ def exitOff(self):
+ return None
+
+ ##### WaitForAllToonsInside state #####
+
+ def enterWaitForAllToonsInside(self, ts=0):
+ assert(self.notify.debug('enterWaitForAllToonsInside()'))
+ return None
+
+ def exitWaitForAllToonsInside(self):
+ return None
+
+ ##### Elevator state #####
+
+ def __playElevator(self, ts, name, callback):
+ # Load the floor model
+
+ SuitHs = [] # Heading angles
+ SuitPositions = []
+
+ if self.floorModel:
+ self.floorModel.removeNode()
+
+ if (self.currentFloor == 0):
+ # bottom floor
+ self.floorModel = loader.loadModel('phase_7/models/modules/suit_interior')
+ SuitHs = self.BottomFloor_SuitHs
+ SuitPositions = self.BottomFloor_SuitPositions
+ elif (self.currentFloor == (self.numFloors - 1)):
+ # Top floor
+ self.floorModel = loader.loadModel('phase_7/models/modules/boss_suit_office')
+ SuitHs = self.BossOffice_SuitHs
+ SuitPositions = self.BossOffice_SuitPositions
+ else:
+ # middle floor
+ self.floorModel = loader.loadModel('phase_7/models/modules/cubicle_room')
+ SuitHs = self.Cubicle_SuitHs
+ SuitPositions = self.Cubicle_SuitPositions
+
+ self.floorModel.reparentTo(render)
+
+ # We need to name this something more useful (and we'll need the
+ # location of the opposite elevator as well)
+ elevIn = self.floorModel.find('**/elevator-in')
+ elevOut = self.floorModel.find('**/elevator-out')
+
+ # Position the suits
+
+ assert(len(self.suits) <= 4)
+ for index in range(len(self.suits)):
+ assert(self.notify.debug('setting suit: %d to pos: %s' % \
+ (self.suits[index].doId, SuitPositions[index])))
+ self.suits[index].setPos(SuitPositions[index])
+ if (len(self.suits) > 2):
+ self.suits[index].setH(SuitHs[index])
+ else:
+ self.suits[index].setH(170) # if there's 2 or 1 suits, make them face fwd since there's no other suits they would be to be talking to
+ self.suits[index].loop('neutral')
+
+ # Position the toons
+ for toon in self.toons:
+ toon.reparentTo(self.elevatorModelIn)
+ assert(self.toonIds.count(toon.doId) == 1)
+ index = self.toonIds.index(toon.doId)
+ assert(index >= 0 and index <= 3)
+ toon.setPos(ElevatorPoints[index][0],
+ ElevatorPoints[index][1],
+ ElevatorPoints[index][2])
+ toon.setHpr(180, 0, 0)
+ toon.loop('neutral')
+
+ # Show the elevator and position it in the correct place for the floor
+ self.elevatorModelIn.reparentTo(elevIn)
+ # Start with the doors in closed position
+ self.leftDoorIn.setPos(3.5, 0, 0)
+ self.rightDoorIn.setPos(-3.5, 0, 0)
+
+ # Show the elevator and position it in the correct place for the floor
+ self.elevatorModelOut.reparentTo(elevOut)
+ # Start with the doors in closed position
+ self.leftDoorOut.setPos(3.5, 0, 0)
+ self.rightDoorOut.setPos(-3.5, 0, 0)
+
+ # Position the camera behind the toons
+ camera.reparentTo(self.elevatorModelIn)
+ camera.setH(180)
+ camera.setPos(0, 14, 4)
+
+ # Play elevator music
+ base.playMusic(self.elevatorMusic, looping=1, volume=0.8)
+
+ # Ride the elevator, then open the doors.
+ track = Sequence(
+ ElevatorUtils.getRideElevatorInterval(ELEVATOR_NORMAL),
+ ElevatorUtils.getOpenInterval(self, self.leftDoorIn, self.rightDoorIn,
+ self.openSfx, None, type = ELEVATOR_NORMAL),
+ Func(camera.wrtReparentTo, render),
+ )
+
+ for toon in self.toons:
+ track.append(Func(toon.wrtReparentTo, render))
+ track.append(Func(callback))
+ track.start(ts)
+ self.activeIntervals[name] = track
+
+ def enterElevator(self, ts=0):
+ # Load model for the current floor and the suit models for the floor
+ assert(self.notify.debug('enterElevator()'))
+
+ self.currentFloor += 1
+ self.cr.playGame.getPlace().currentFloor = self.currentFloor
+ self.setElevatorLights(self.elevatorModelIn)
+ self.setElevatorLights(self.elevatorModelOut)
+
+ self.__playElevator(ts, self.elevatorName, self.__handleElevatorDone)
+
+ # Get the floor multiplier
+ mult = ToontownBattleGlobals.getCreditMultiplier(self.currentFloor)
+ # Now set the inventory battleCreditMult
+ base.localAvatar.inventory.setBattleCreditMultiplier(mult)
+
+ def __handleElevatorDone(self):
+ assert(self.notify.debug('handleElevatorDone()'))
+ self.d_elevatorDone()
+
+ def exitElevator(self):
+ self.elevatorMusic.stop()
+ self.__finishInterval(self.elevatorName)
+ return None
+
+ ##### Battle state #####
+
+ def __playCloseElevatorOut(self, name):
+ # Close the elevator doors
+ track = Sequence(
+ Wait(SUIT_LEAVE_ELEVATOR_TIME),
+ Parallel(SoundInterval(self.closeSfx),
+ LerpPosInterval(self.leftDoorOut,
+ ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ ElevatorUtils.getLeftClosePoint(ELEVATOR_NORMAL),
+ startPos=Point3(0, 0, 0),
+ blendType='easeOut'),
+ LerpPosInterval(self.rightDoorOut,
+ ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ ElevatorUtils.getRightClosePoint(ELEVATOR_NORMAL),
+ startPos=Point3(0, 0, 0),
+ blendType='easeOut')
+ ),
+ )
+ track.start()
+ self.activeIntervals[name] = track
+
+ def enterBattle(self, ts=0):
+ assert(self.notify.debug('enterBattle()'))
+ if (self.elevatorOutOpen == 1):
+ self.__playCloseElevatorOut(self.uniqueName('close-out-elevator'))
+ # Watch reserve suits as they walk from the elevator
+ camera.setPos(0, -15, 6)
+ camera.headsUp(self.elevatorModelOut)
+ return None
+
+ def exitBattle(self):
+ if (self.elevatorOutOpen == 1):
+ self.__finishInterval(self.uniqueName('close-out-elevator'))
+ self.elevatorOutOpen = 0
+ return None
+
+ ##### ReservesJoining state #####
+
+ def __playReservesJoining(self, ts, name, callback):
+ # Position the joining suits
+ index = 0
+ assert(len(self.joiningReserves) <= 4)
+ for suit in self.joiningReserves:
+ suit.reparentTo(render)
+ suit.setPos(self.elevatorModelOut, Point3(ElevatorPoints[index][0],
+ ElevatorPoints[index][1],
+ ElevatorPoints[index][2]))
+ index += 1
+ suit.setH(180)
+ suit.loop('neutral')
+
+ # Aim the camera at the far elevator
+ track = Sequence(
+ Func(camera.wrtReparentTo, self.elevatorModelOut),
+ Func(camera.setPos, Point3(0, -8, 2)),
+ Func(camera.setHpr, Vec3(0, 10, 0)),
+
+ # Open the elevator doors
+ Parallel(SoundInterval(self.openSfx),
+ LerpPosInterval(self.leftDoorOut,
+ ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ Point3(0, 0, 0),
+ startPos=ElevatorUtils.getLeftClosePoint(ELEVATOR_NORMAL),
+ blendType='easeOut'),
+ LerpPosInterval(self.rightDoorOut,
+ ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ Point3(0, 0, 0),
+ startPos=ElevatorUtils.getRightClosePoint(ELEVATOR_NORMAL),
+ blendType='easeOut'),
+ ),
+
+ # Hold the camera angle for a couple of beats
+ Wait(SUIT_HOLD_ELEVATOR_TIME),
+
+ # Reparent the camera to render (enterWaitForInput will
+ # position it properly again by the battle)
+ Func(camera.wrtReparentTo, render),
+ Func(callback),
+ )
+ track.start(ts)
+ self.activeIntervals[name] = track
+
+ def enterReservesJoining(self, ts=0):
+ assert(self.notify.debug('enterReservesJoining()'))
+ self.__playReservesJoining(ts, self.uniqueName('reserves-joining'),
+ self.__handleReserveJoinDone)
+ return None
+
+ def __handleReserveJoinDone(self):
+ assert(self.notify.debug('handleReserveJoinDone()'))
+ self.joiningReserves = []
+ self.elevatorOutOpen = 1
+ self.d_reserveJoinDone()
+
+ def exitReservesJoining(self):
+ self.__finishInterval(self.uniqueName('reserves-joining'))
+ return None
+
+ ##### Resting state #####
+
+ def enterResting(self, ts=0):
+ assert(self.notify.debug('enterResting()'))
+ base.playMusic(self.waitMusic, looping=1, volume=0.7)
+ self.__closeInElevator()
+ return
+
+ def exitResting(self):
+ self.waitMusic.stop()
+ return
+
+ ##### Reward state #####
+
+ def enterReward(self, ts=0):
+ assert(self.notify.debug('enterReward()'))
+ base.localAvatar.b_setParent(ToontownGlobals.SPHidden)
+ request = {
+ "loader": ZoneUtil.getBranchLoaderName(self.extZoneId),
+ "where": ZoneUtil.getToonWhereName(self.extZoneId),
+ "how": "elevatorIn",
+ "hoodId": ZoneUtil.getHoodId(self.extZoneId),
+ "zoneId": self.extZoneId,
+ "shardId": None,
+ "avId": -1,
+ "bldgDoId": self.distBldgDoId
+ }
+ # Presumably, suitInterior.py has hung a hook waiting for
+ # this request. I mimicked what DistributedDoor was doing.
+ messenger.send("DSIDoneEvent", [request])
+ return None
+
+ def exitReward(self):
+ return None
+
+ ##### Reset state #####
+
+ #def enterReset(self, ts=0):
+ # assert(self.notify.debug('enterReset()'))
+ # self.__cleanup()
+ # return None
+
+ #def exitReset(self):
+ # return None
diff --git a/toontown/src/building/DistributedSuitInteriorAI.py b/toontown/src/building/DistributedSuitInteriorAI.py
new file mode 100644
index 0000000..f167734
--- /dev/null
+++ b/toontown/src/building/DistributedSuitInteriorAI.py
@@ -0,0 +1,706 @@
+from toontown.toonbase.ToontownBattleGlobals import *
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+from ElevatorConstants import *
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObjectAI
+from direct.fsm import State
+from toontown.battle import DistributedBattleBldgAI
+from toontown.battle import BattleBase
+from direct.task import Timer
+import DistributedElevatorIntAI
+import copy
+
+class DistributedSuitInteriorAI(DistributedObjectAI.DistributedObjectAI):
+ """
+ DistributedSuitInteriorAI class:
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedSuitInteriorAI')
+
+ def __init__(self, air, elevator):
+ self.air = air
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.extZoneId, self.zoneId = elevator.bldg.getExteriorAndInteriorZoneId()
+ self.numFloors = elevator.bldg.planner.numFloors
+ assert(len(elevator.seats) == 4)
+
+ self.avatarExitEvents = []
+ self.toons = []
+ self.toonSkillPtsGained = {}
+ self.toonExp = {}
+ self.toonOrigQuests = {}
+ self.toonItems = {}
+ self.toonOrigMerits = {}
+ self.toonMerits = {}
+ self.toonParts = {}
+ self.helpfulToons = []
+
+ self.currentFloor = 0
+ self.topFloor = self.numFloors - 1
+ self.bldg = elevator.bldg
+ self.elevator = elevator
+
+ self.suits = []
+ self.activeSuits = []
+ self.reserveSuits = []
+ self.joinedReserves = []
+ self.suitsKilled = []
+ self.suitsKilledPerFloor = []
+ self.battle = None
+
+ self.timer = Timer.Timer()
+
+ self.responses = {}
+ self.ignoreResponses = 0
+ self.ignoreElevatorDone = 0
+ self.ignoreReserveJoinDone = 0
+
+ # Register all the toons
+ self.toonIds = copy.copy(elevator.seats)
+ for toonId in self.toonIds:
+ if (toonId != None):
+ self.__addToon(toonId)
+ assert(len(self.toons) > 0)
+
+ # Build a map of id:(name, style) pairs to have around for the
+ # end in case the toons are successful. These elements are
+ # filled in as each toon registers with the building.
+ self.savedByMap = {}
+
+ self.fsm = ClassicFSM.ClassicFSM(
+ 'DistributedSuitInteriorAI',
+ [State.State('WaitForAllToonsInside',
+ self.enterWaitForAllToonsInside,
+ self.exitWaitForAllToonsInside,
+ ['Elevator']),
+ State.State('Elevator',
+ self.enterElevator,
+ self.exitElevator,
+ ['Battle']),
+ State.State('Battle',
+ self.enterBattle,
+ self.exitBattle,
+ ['ReservesJoining',
+ 'BattleDone']),
+ State.State('ReservesJoining',
+ self.enterReservesJoining,
+ self.exitReservesJoining,
+ ['Battle']),
+ State.State('BattleDone',
+ self.enterBattleDone,
+ self.exitBattleDone,
+ ['Resting',
+ 'Reward']),
+ State.State('Resting',
+ self.enterResting,
+ self.exitResting,
+ ['Elevator']),
+ State.State('Reward',
+ self.enterReward,
+ self.exitReward,
+ ['Off']),
+ State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['WaitForAllToonsInside'])],
+ # Initial state
+ 'Off',
+ # Final state
+ 'Off',
+ onUndefTransition = ClassicFSM.ClassicFSM.ALLOW)
+ self.fsm.enterInitialState()
+
+ def delete(self):
+ assert(self.notify.debug('delete()'))
+ self.ignoreAll()
+ self.toons = []
+ self.toonIds = []
+ self.fsm.requestFinalState()
+ del self.fsm
+ del self.bldg
+ del self.elevator
+ self.timer.stop()
+ del self.timer
+ self.__cleanupFloorBattle()
+
+ taskName = self.taskName('deleteInterior')
+ taskMgr.remove(taskName)
+
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def __handleUnexpectedExit(self, toonId):
+ self.notify.warning('toon: %d exited unexpectedly' % toonId)
+ self.__removeToon(toonId)
+ if (len(self.toons) == 0):
+ assert(self.notify.debug('last toon is gone!'))
+ self.timer.stop()
+ # The last toon exited unexpectedly - if we're in a battle, let
+ # the battle clean up first, if we're in Resting state, let
+ # the interior elevator clean up first, otherwise, just
+ # reset the suit interior.
+ if (self.fsm.getCurrentState().getName() == 'Resting'):
+ pass
+ elif (self.battle == None):
+ self.bldg.deleteSuitInterior()
+
+ def __addToon(self, toonId):
+ assert(self.notify.debug('addToon(%d)' % toonId))
+ if (not self.air.doId2do.has_key(toonId)):
+ self.notify.warning('addToon() - no toon for doId: %d' % toonId)
+ return
+
+ # Handle unexpected exits for the toon
+ event = self.air.getAvatarExitEvent(toonId)
+ self.avatarExitEvents.append(event)
+ self.accept(event, self.__handleUnexpectedExit, extraArgs=[toonId])
+
+ self.toons.append(toonId)
+ assert(not self.responses.has_key(toonId))
+ self.responses[toonId] = 0
+
+ def __removeToon(self, toonId):
+ assert(self.notify.debug('removeToon(%d)' % toonId))
+ if self.toons.count(toonId):
+ self.toons.remove(toonId)
+
+ if self.toonIds.count(toonId):
+ self.toonIds[self.toonIds.index(toonId)] = None
+
+ if self.responses.has_key(toonId):
+ del self.responses[toonId]
+
+ # Ignore future exit events for the toon
+ event = self.air.getAvatarExitEvent(toonId)
+ if self.avatarExitEvents.count(event):
+ self.avatarExitEvents.remove(event)
+ self.ignore(event)
+
+ def __resetResponses(self):
+ self.responses = {}
+ for toon in self.toons:
+ self.responses[toon] = 0
+ self.ignoreResponses = 0
+
+ def __allToonsResponded(self):
+ for toon in self.toons:
+ assert(self.responses.has_key(toon))
+ if (self.responses[toon] == 0):
+ return 0
+ self.ignoreResponses = 1
+ return 1
+
+ # Distributed Messages
+
+ # setZoneIdAndBlock
+
+ def getZoneId(self):
+ assert(self.notify.debug('network:getZoneId()'))
+ return self.zoneId
+
+ def getExtZoneId(self):
+ return self.extZoneId
+
+ def getDistBldgDoId(self):
+ return self.bldg.getDoId()
+
+ def getNumFloors(self):
+ return self.numFloors
+
+ # setToons()
+
+ def d_setToons(self):
+ assert(self.notify.debug('network:setToons()'))
+ self.sendUpdate('setToons', self.getToons())
+
+ def getToons(self):
+ sendIds = []
+ for toonId in self.toonIds:
+ if (toonId == None):
+ sendIds.append(0)
+ else:
+ sendIds.append(toonId)
+ assert(self.notify.debug('getToons(): %s' % sendIds))
+ return [sendIds, 0]
+
+ # setSuits()
+
+ def d_setSuits(self):
+ assert(self.notify.debug('network:setSuits()'))
+ self.sendUpdate('setSuits', self.getSuits())
+
+ def getSuits(self):
+ suitIds = []
+ for suit in self.activeSuits:
+ suitIds.append(suit.doId)
+ reserveIds = []
+ values = []
+ for info in self.reserveSuits:
+ reserveIds.append(info[0].doId)
+ values.append(info[1])
+ return [suitIds, reserveIds, values]
+
+ # setState()
+
+ def b_setState(self, state):
+ self.d_setState(state)
+ self.setState(state)
+
+ def d_setState(self, state):
+ assert(self.notify.debug('network:setState(%s)' % state))
+ stime = globalClock.getRealTime() + BattleBase.SERVER_BUFFER_TIME
+ self.sendUpdate('setState', [state, globalClockDelta.localToNetworkTime(stime)])
+
+ def setState(self, state):
+ self.fsm.request(state)
+
+ def getState(self):
+ return [self.fsm.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+
+ ##### Messages from the clients #####
+
+ def setAvatarJoined(self):
+ """setAvatarJoined(self)
+ This message is sent from a client toon to indicate that it
+ has finished loading the interior.
+ """
+ avId = self.air.getAvatarIdFromSender()
+ if (self.toons.count(avId) == 0):
+ self.air.writeServerEvent('suspicious', avId, 'DistributedSuitInteriorAI.setAvatarJoined from toon not in %s.' % (self.toons))
+ self.notify.warning('setAvatarJoined() - av: %d not in list' % \
+ avId)
+ return
+
+ avatar = self.air.doId2do.get(avId)
+ if avatar != None:
+ self.savedByMap[avId] = (avatar.getName(), avatar.dna.asTuple())
+
+ assert(self.responses.has_key(avId))
+ self.responses[avId] += 1
+ assert(self.notify.debug('toon: %d in suit interior' % avId))
+ if (self.__allToonsResponded()):
+ self.fsm.request('Elevator')
+
+ def elevatorDone(self):
+ """elevatorDone(self)
+ This message is sent from a client toon to indicate that it has
+ finished viewing the elevator movie.
+ """
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ assert(self.notify.debug('elevatorDone() ignoring toon: %d' % \
+ toonId))
+ return
+ elif (self.fsm.getCurrentState().getName() != 'Elevator'):
+ self.notify.warning('elevatorDone() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('elevatorDone() - toon not in toon list: %d' \
+ % toonId)
+ assert(self.notify.debug('toons: %s toonIds: %s' % \
+ (self.toons, self.toonIds)))
+ return
+ assert(self.responses.has_key(toonId))
+ self.responses[toonId] += 1
+ assert(self.notify.debug('toon: %d done with elevator' % toonId))
+ if (self.__allToonsResponded() and
+ self.ignoreElevatorDone == 0):
+ self.b_setState('Battle')
+
+ def reserveJoinDone(self):
+ """reserveJoinDone(self)
+ This message is sent from a client toon to indicate that it has
+ finished viewing the ReservesJoining movie.
+ """
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ assert(self.notify.debug('reserveJoinDone() ignoring toon: %d' % \
+ toonId))
+ return
+ elif (self.fsm.getCurrentState().getName() != 'ReservesJoining'):
+ self.notify.warning('reserveJoinDone() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('reserveJoinDone() - toon not in list: %d' \
+ % toonId)
+ assert(self.notify.debug('toons: %s toonIds: %s' % \
+ (self.toons, self.toonIds)))
+ return
+ assert(self.responses.has_key(toonId))
+ self.responses[toonId] += 1
+ assert(self.notify.debug('toon: %d done with joining reserves' % \
+ toonId))
+ if (self.__allToonsResponded() and
+ self.ignoreReserveJoinDone == 0):
+ self.b_setState('Battle')
+
+ # Specific State Functions
+
+ ##### Off state #####
+
+ def enterOff(self):
+ assert(self.notify.debug('enterOff()'))
+ return None
+
+ def exitOff(self):
+ return None
+
+ ##### WaitForAllToonsInside state #####
+
+ def enterWaitForAllToonsInside(self):
+ assert(self.notify.debug('enterWaitForAllToonsInside()'))
+ self.__resetResponses()
+ return None
+
+ def exitWaitForAllToonsInside(self):
+ self.__resetResponses()
+ return None
+
+ ##### Elevator state #####
+
+ def enterElevator(self):
+ assert(self.notify.debug('enterElevator()'))
+
+ # Create the suits and place them in their initial positions on
+ # the floor
+ assert(self.currentFloor < self.numFloors)
+ suitHandles = self.bldg.planner.genFloorSuits(self.currentFloor)
+ self.suits = suitHandles['activeSuits']
+ assert(len(self.suits) > 0)
+ self.activeSuits = []
+ for suit in self.suits:
+ self.activeSuits.append(suit)
+ self.reserveSuits = suitHandles['reserveSuits']
+
+ self.d_setToons()
+ self.d_setSuits()
+ self.__resetResponses()
+
+ self.d_setState('Elevator')
+
+ self.timer.startCallback(BattleBase.ELEVATOR_T + \
+ ElevatorData[ELEVATOR_NORMAL]['openTime'] + \
+ BattleBase.SERVER_BUFFER_TIME, self.__serverElevatorDone)
+ return None
+
+ def __serverElevatorDone(self):
+ assert(self.notify.debug('serverElevatorDone()'))
+ self.ignoreElevatorDone = 1
+ self.b_setState('Battle')
+
+ def exitElevator(self):
+ self.timer.stop()
+ self.__resetResponses()
+ return None
+
+ ##### Battle state #####
+
+ def __createFloorBattle(self):
+ assert(len(self.toons) > 0)
+ if (self.currentFloor == self.topFloor):
+ assert(self.notify.debug('createFloorBattle() - boss battle'))
+ bossBattle = 1
+ else:
+ bossBattle = 0
+ # Create the battle for the floor
+ self.battle = DistributedBattleBldgAI.DistributedBattleBldgAI(
+ self.air, self.zoneId,
+ self.__handleRoundDone, self.__handleBattleDone,
+ bossBattle=bossBattle)
+
+ # We store the lists of experience gained and suits killed in
+ # the DistributedSuitInterior object, and share these pointers
+ # in all battles created for this building. This way, each
+ # battle will actually be modifying the same objects, these,
+ # and will thus accumulate the experience from previous
+ # battles.
+ self.battle.suitsKilled = self.suitsKilled
+ self.battle.suitsKilledPerFloor = self.suitsKilledPerFloor
+ self.battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained
+ self.battle.toonExp = self.toonExp
+ self.battle.toonOrigQuests = self.toonOrigQuests
+ self.battle.toonItems = self.toonItems
+ self.battle.toonOrigMerits = self.toonOrigMerits
+ self.battle.toonMerits = self.toonMerits
+ self.battle.toonParts = self.toonParts
+ self.battle.helpfulToons = self.helpfulToons
+
+ # We must set the members of a building battle before we
+ # generate it.
+ self.battle.setInitialMembers(self.toons, self.suits)
+ self.battle.generateWithRequired(self.zoneId)
+
+ # We get a bonus factor applied toward each attack's experience credit.
+ mult = getCreditMultiplier(self.currentFloor)
+
+ # If there is an invasion, multiply the exp for the duration of this battle
+ # Now, if the invasion ends midway through this battle, the players will
+ # continue getting credit. This is ok I guess.
+ if self.air.suitInvasionManager.getInvading():
+ mult *= getInvasionMultiplier()
+
+ self.battle.battleCalc.setSkillCreditMultiplier(mult)
+
+ def __cleanupFloorBattle(self):
+ for suit in self.suits:
+ self.notify.debug('cleaning up floor suit: %d' % suit.doId)
+ if suit.isDeleted():
+ self.notify.debug('whoops, suit %d is deleted.' % suit.doId)
+ else:
+ suit.requestDelete()
+ self.suits = []
+ self.reserveSuits = []
+ self.activeSuits = []
+ if (self.battle != None):
+ self.battle.requestDelete()
+ self.battle = None
+
+ def __handleRoundDone(self, toonIds, totalHp, deadSuits):
+ # Determine if any reserves need to join
+ assert(self.notify.debug('handleRoundDone() - hp: %d' % totalHp))
+ # Calculate the total max HP for all the suits currently on the floor
+ totalMaxHp = 0
+ for suit in self.suits:
+ totalMaxHp += suit.maxHP
+
+ for suit in deadSuits:
+ self.activeSuits.remove(suit)
+
+ # Determine if any reserve suits need to join
+ if (len(self.reserveSuits) > 0 and len(self.activeSuits) < 4):
+ assert(self.notify.debug('potential reserve suits: %d' % \
+ len(self.reserveSuits)))
+ self.joinedReserves = []
+ assert(totalHp <= totalMaxHp)
+ hpPercent = 100 - (totalHp / totalMaxHp * 100.0)
+ assert(self.notify.debug('totalHp: %d totalMaxHp: %d percent: %f' \
+ % (totalHp, totalMaxHp, hpPercent)))
+ for info in self.reserveSuits:
+ if (info[1] <= hpPercent and
+ len(self.activeSuits) < 4):
+ assert(self.notify.debug('reserve: %d joining percent: %f' \
+ % (info[0].doId, info[1])))
+ self.suits.append(info[0])
+ self.activeSuits.append(info[0])
+ self.joinedReserves.append(info)
+ for info in self.joinedReserves:
+ self.reserveSuits.remove(info)
+ if (len(self.joinedReserves) > 0):
+ # setSuits() triggers the state change on the client
+ self.fsm.request('ReservesJoining')
+ self.d_setSuits()
+ return
+
+ # See if the battle is done
+ if (len(self.activeSuits) == 0):
+ self.fsm.request('BattleDone', [toonIds])
+ else:
+ # No reserve suits to join - tell the battle to continue
+ self.battle.resume()
+
+ def __handleBattleDone(self, zoneId, toonIds):
+ assert(self.notify.debug('%s.handleBattleDone(%s, %s)' % (self.doId, zoneId, toonIds)))
+ #self.fsm.request('BattleDone', [toonIds])
+ if (len(toonIds) == 0):
+ assert(self.notify.debug('handleBattleDone() - last toon gone'))
+
+ # Rather than shutting down immediately, we give the last
+ # toon a few seconds to finish playing his teleport-out
+ # animation before the world goes away.
+
+ taskName = self.taskName('deleteInterior')
+ taskMgr.doMethodLater(10, self.__doDeleteInterior, taskName)
+
+ elif (self.currentFloor == self.topFloor):
+ # This is not b_setState, because enterReward has to do
+ # some things before the broadcast takes place. enterReward
+ # will call d_setState when it is ready.
+ self.setState('Reward')
+ else:
+ self.b_setState('Resting')
+
+ def __doDeleteInterior(self, task):
+ self.bldg.deleteSuitInterior()
+
+ def enterBattle(self):
+ assert(self.notify.debug('enterBattle()'))
+ if (self.battle == None):
+ self.__createFloorBattle()
+ self.elevator.d_setFloor(self.currentFloor)
+ return None
+
+ def exitBattle(self):
+ return None
+
+ ##### ReservesJoining state #####
+
+ def enterReservesJoining(self):
+ assert(self.notify.debug('enterReservesJoining()'))
+ self.__resetResponses()
+ self.timer.startCallback(
+ ElevatorData[ELEVATOR_NORMAL]['openTime'] + \
+ SUIT_HOLD_ELEVATOR_TIME + \
+ BattleBase.SERVER_BUFFER_TIME,
+ self.__serverReserveJoinDone)
+ return None
+
+ def __serverReserveJoinDone(self):
+ """__serverReserveJoinDone()
+ This callback is made only if some of the toons don't send
+ their reserveJoinDone() message in a reasonable time--rather
+ than waiting for everyone, we simply carry on without them.
+ """
+ assert(self.notify.debug('serverReserveJoinDone()'))
+ self.ignoreReserveJoinDone = 1
+ self.b_setState('Battle')
+
+ def exitReservesJoining(self):
+ self.timer.stop()
+ self.__resetResponses()
+ # Join the suits to the battle and tell it to resume
+ for info in self.joinedReserves:
+ self.battle.suitRequestJoin(info[0])
+ self.battle.resume()
+ self.joinedReserves = []
+ return None
+
+ ##### BattleDone state #####
+
+ def enterBattleDone(self, toonIds):
+ assert(self.notify.debug('enterBattleDone()'))
+ # Find out if any toons are gone
+ if (len(toonIds) != len(self.toons)):
+ deadToons = []
+ for toon in self.toons:
+ if (toonIds.count(toon) == 0):
+ deadToons.append(toon)
+ for toon in deadToons:
+ self.__removeToon(toon)
+ self.d_setToons()
+
+ if (len(self.toons) == 0):
+ self.bldg.deleteSuitInterior()
+ else:
+ if (self.currentFloor == self.topFloor):
+ # Toons beat the building
+ self.battle.resume(self.currentFloor, topFloor=1)
+ else:
+ # The building isn't finished yet - gather up experience and
+ # activate the elevator
+ self.battle.resume(self.currentFloor, topFloor=0)
+ return None
+
+ def exitBattleDone(self):
+ self.__cleanupFloorBattle()
+ return None
+
+ ##### Resting state #####
+
+ def __handleEnterElevator(self):
+ self.fsm.request('Elevator')
+
+ def enterResting(self):
+ assert(self.notify.debug('enterResting()'))
+ # Tell the elevator to start accepting entrants
+ self.intElevator = DistributedElevatorIntAI.DistributedElevatorIntAI(
+ self.air, self, self.toons)
+ self.intElevator.generateWithRequired(self.zoneId)
+ return None
+
+ def handleAllAboard(self, seats):
+ if not hasattr(self, "fsm"):
+ # If we've already been cleaned up, never mind.
+ return
+
+ assert(self.fsm.getCurrentState().getName() == "Resting")
+ assert(self.notify.debug('handleAllAboard() - toons: %s' % self.toons))
+
+ # Make sure the number of empty seats is correct. If it is empty,
+ # reset and get us out of here.
+ numOfEmptySeats = seats.count(None)
+ if (numOfEmptySeats == 4):
+ self.bldg.deleteSuitInterior()
+ return
+ elif (numOfEmptySeats >= 0) and (numOfEmptySeats <=3):
+ pass
+ else:
+ self.error("Bad number of empty seats: %s" % numOfEmptySeats)
+
+ for toon in self.toons:
+ if seats.count(toon) == 0:
+ self.__removeToon(toon)
+ self.toonIds = copy.copy(seats)
+ self.toons = []
+ for toonId in self.toonIds:
+ if (toonId != None):
+ self.toons.append(toonId)
+
+ self.d_setToons()
+
+ # Increment the floor number
+ self.currentFloor += 1
+ self.fsm.request('Elevator')
+ return
+
+ def exitResting(self):
+ self.intElevator.requestDelete()
+ del self.intElevator
+ return None
+
+ ##### Reward state #####
+
+ def enterReward(self):
+ assert(self.notify.debug('enterReward()'))
+ # Tell the building to get ready for the victors to come outside.
+
+ # We pass in a *copy* of the toonIds list, not the list
+ # itself, so that when we pull toons out of our list (for
+ # instance, if they disconnect unexpectedly), it won't affect
+ # the building's victor list.
+ victors = self.toonIds[:]
+
+ # Build a new savedBy list that includes only the Toons that
+ # made it to the end.
+ savedBy = []
+ for v in victors:
+ tuple = self.savedByMap.get(v)
+ if tuple:
+ savedBy.append([v, tuple[0], tuple[1]])
+
+ # Going to waitForVictors deletes the elevator
+ self.bldg.fsm.request("waitForVictors", [victors, savedBy])
+ # Tell the players to go back outside.
+ self.d_setState('Reward')
+ return None
+
+ def exitReward(self):
+ return None
+
+ ##### Reset state #####
+
+ #def enterReset(self):
+ # assert(self.notify.debug('enterReset()'))
+ # # Unload all floor and suit models, but keep the suit database -
+ # # the contents of the building should remain the same until the
+ # # toons retake the building
+ # self.__cleanupFloorBattle()
+ # self.currentFloor = 0
+ # # -1 means the lobby.
+ # self.elevator.d_setFloor(-1)
+ # # Get rid of the toons
+ # for toon in copy.copy(self.toons):
+ # self.__removeToon(toon)
+ # self.savedByMap = {}
+ # self.b_setState('Off')
+ # self.elevator.open()
+ # self.requestDelete()
+
+ #def exitReset(self):
+ # return None
diff --git a/toontown/src/building/DistributedToonHallInterior.py b/toontown/src/building/DistributedToonHallInterior.py
new file mode 100644
index 0000000..c027332
--- /dev/null
+++ b/toontown/src/building/DistributedToonHallInterior.py
@@ -0,0 +1,1140 @@
+""" DistributedToonInterior module"""
+
+from toontown.toonbase.ToonBaseGlobal import *
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+from direct.showbase import Audio3DManager
+
+from toontown.toonbase import ToontownGlobals
+import cPickle
+from DistributedToonInterior import DistributedToonInterior
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObject
+from direct.fsm import State
+from direct.actor import Actor
+import random
+import time
+import ToonInteriorColors
+from toontown.hood import ZoneUtil
+from toontown.toon import ToonDNA
+from toontown.toon import ToonHead
+
+class DistributedToonHallInterior(DistributedToonInterior):
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedToonHallInterior')
+
+ def __init__(self, cr):
+ DistributedToonInterior.__init__(self, cr)
+
+ self.sillyFSM = ClassicFSM.ClassicFSM("SillyOMeter",
+ [State.State('Setup',
+ self.enterSetup,
+ self.exitSetup,
+ ['Phase0', 'Phase1', 'Phase2', 'Phase3', \
+ 'Phase4', 'Phase5', 'Phase6', 'Phase7', \
+ 'Phase8', 'Phase9', 'Phase10', 'Phase11', \
+ 'Phase12', 'Phase13', 'Phase14', 'Phase15', \
+ 'Flat', \
+ 'Off']),
+ State.State('Phase0',
+ self.enterPhase0,
+ self.exitPhase0,
+ ['Phase1', 'Off']),
+ State.State('Phase1',
+ self.enterPhase1,
+ self.exitPhase1,
+ ['Phase2', 'Off']),
+ State.State('Phase2',
+ self.enterPhase2,
+ self.exitPhase2,
+ ['Phase3', 'Off']),
+ State.State('Phase3',
+ self.enterPhase3,
+ self.exitPhase3,
+ ['Phase4', 'Off']),
+ State.State('Phase4',
+ self.enterPhase4,
+ self.exitPhase4,
+ ['Phase5', 'Off']),
+ State.State('Phase5',
+ self.enterPhase5,
+ self.exitPhase5,
+ ['Phase6', 'Off']),
+ State.State('Phase6',
+ self.enterPhase6,
+ self.exitPhase6,
+ ['Phase7', 'Off']),
+ State.State('Phase7',
+ self.enterPhase7,
+ self.exitPhase7,
+ ['Phase8', 'Off']),
+ State.State('Phase8',
+ self.enterPhase8,
+ self.exitPhase8,
+ ['Phase9', 'Off']),
+ State.State('Phase9',
+ self.enterPhase9,
+ self.exitPhase9,
+ ['Phase10', 'Off']),
+ State.State('Phase10',
+ self.enterPhase10,
+ self.exitPhase10,
+ ['Phase11', 'Off']),
+ State.State('Phase11',
+ self.enterPhase11,
+ self.exitPhase11,
+ ['Phase12', 'Off']),
+ State.State('Phase12',
+ self.enterPhase12,
+ self.exitPhase12,
+ ['Phase13', 'Off']),
+ State.State('Phase13',
+ self.enterPhase13,
+ self.exitPhase13,
+ ['Phase14','Off']),
+ State.State('Phase14',
+ self.enterPhase14,
+ self.exitPhase14,
+ ['Phase15', 'Off']),
+ State.State('Phase15',
+ self.enterPhase15,
+ self.exitPhase15,
+ ['Off']),
+ State.State('Flat',
+ self.enterFlat,
+ self.exitFlat,
+ ['Off']),
+ State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ []),],
+ 'Setup',
+ 'Off',
+ )
+ def setup(self):
+ assert self.notify.debugStateCall(self)
+ self.dnaStore=base.cr.playGame.dnaStore
+ self.randomGenerator=random.Random()
+
+ # The math here is a little arbitrary. I'm trying to get a
+ # substantially different seed for each zondId, even on the
+ # same street. But we don't want to weigh to much on the
+ # block number, because we want the same block number on
+ # different streets to be different.
+ # Here we use the block number and a little of the branchId:
+ # seedX=self.zoneId&0x00ff
+ # Here we're using only the branchId:
+ # seedY=self.zoneId/100
+ # Here we're using only the block number:
+ # seedZ=256-int(self.block)
+ self.randomGenerator.seed(self.zoneId)
+
+ interior = self.randomDNAItem("TI_hall", self.dnaStore.findNode)
+ assert(not interior.isEmpty())
+ self.interior = interior.copyTo(render)
+
+ # Load a color dictionary for this hood:
+ hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
+ self.colors = ToonInteriorColors.colors[hoodId]
+ # Replace all the "random_xxx_" nodes:
+ self.replaceRandomInModel(self.interior)
+
+ # Door:
+ doorModelName="door_double_round_ul" # hack zzzzzzz
+ # Switch leaning of the door:
+ if doorModelName[-1:] == "r":
+ doorModelName=doorModelName[:-1]+"l"
+ else:
+ doorModelName=doorModelName[:-1]+"r"
+ #import pdb; pdb.set_trace()
+ door=self.dnaStore.findNode(doorModelName)
+ # Determine where should we put the door:
+ door_origin=render.find("**/door_origin;+s")
+ doorNP=door.copyTo(door_origin)
+ assert(not doorNP.isEmpty())
+ assert(not door_origin.isEmpty())
+ # The rooms are too small for doors:
+ door_origin.setScale(0.8, 0.8, 0.8)
+ # Move the origin away from the wall so it does not shimmer
+ # We do this instead of decals
+ door_origin.setPos(door_origin, 0, -0.025, 0)
+ color=self.randomGenerator.choice(self.colors["TI_door"])
+ DNADoor.setupDoor(doorNP,
+ self.interior, door_origin,
+ self.dnaStore,
+ str(self.block), color)
+ # Setting the wallpaper texture with a priority overrides
+ # the door texture, if it's decalled. So, we're going to
+ # move it out from the decal, and float it in front of
+ # the wall:
+ doorFrame = doorNP.find("door_*_flat")
+ doorFrame.wrtReparentTo(self.interior)
+ doorFrame.setColor(color)
+
+ del self.colors
+ del self.dnaStore
+ del self.randomGenerator
+
+ # Get rid of any transitions and extra nodes
+ self.interior.flattenMedium()
+
+ self.sillyFSM.enterInitialState()
+
+ def selectPhase(self, newPhase):
+ try:
+ gotoPhase = 'Phase'+str(newPhase)
+ if self.sillyFSM.hasStateNamed(gotoPhase) and (not self.sillyFSM.getCurrentState() == self.sillyFSM.getStateNamed(gotoPhase)):
+ self.sillyFSM.request(gotoPhase)
+ except:
+ self.notify.warning('Illegal phase transition requested %s' %newPhase)
+
+ def startIfNeeded(self):
+ """Check our current phase, if valid go to the right state."""
+ assert self.notify.debugStateCall(self)
+ # we need a try to stop the level editor from crashing
+ try:
+ self.curPhase = self.getPhaseToRun()
+ if self.curPhase >= 0:
+ self.sillyFSM.request('Phase'+str(self.curPhase))
+ else:
+ # if the holiday is not running, display our flat "under repair"
+ self.sillyFSM.request('Flat')
+ except:
+ pass
+
+ def getPhaseToRun(self):
+ """This will return -1 if we should not be running, otherwise it returns
+ the phase we should go to."""
+ result = -1
+ enoughInfoToRun = False
+ # first see if the holiday is running, and we can get the cur phase
+ if base.cr.newsManager.isHolidayRunning(ToontownGlobals.SILLYMETER_HOLIDAY):
+ if hasattr(base.cr, "SillyMeterMgr") and not base.cr.SillyMeterMgr.isDisabled():
+ enoughInfoToRun = True
+ else:
+ if hasattr(base.cr, "SillyMeterMgr"):
+ self.notify.debug("isDisabled = %s" % base.cr.SillyMeterMgr.isDisabled())
+ else:
+ self.notify.debug("base.cr does not have SillyMeterMgr")
+ else:
+ self.notify.debug("holiday is not running")
+ self.notify.debug("enoughInfoToRun = %s" % enoughInfoToRun)
+ if enoughInfoToRun and \
+ base.cr.SillyMeterMgr.getIsRunning():
+ result = base.cr.SillyMeterMgr.getCurPhase()
+
+ return result
+
+ def calculatePhaseDuration(self):
+ """
+ Figure out the duration of this particular phase
+ """
+ result = -1
+ valid = False
+ # first see if the holiday is running, and we can get the cur phase
+ if base.cr.newsManager.isHolidayRunning(ToontownGlobals.SILLYMETER_HOLIDAY):
+ if hasattr(base.cr, "SillyMeterMgr") and not base.cr.SillyMeterMgr.isDisabled():
+ valid = True
+ else:
+ if hasattr(base.cr, "SillyMeterMgr"):
+ self.notify.debug("isDisabled = %s" % base.cr.SillyMeterMgr.isDisabled())
+ else:
+ self.notify.debug("base.cr does not have SillyMeterMgr")
+ else:
+ self.notify.debug("holiday is not running")
+ self.notify.debug("valid = %s" % valid)
+ if valid and \
+ base.cr.SillyMeterMgr.getIsRunning():
+ result = base.cr.SillyMeterMgr.getCurPhaseDuration()
+
+ return result
+
+ def calculateFrameOffset(self, phaseDuration, numFrames):
+ """
+ Figure out the duration of this particular phase
+ """
+ result = -1
+ valid = False
+ # first see if the holiday is running, and we can get the cur phase
+ if base.cr.newsManager.isHolidayRunning(ToontownGlobals.SILLYMETER_HOLIDAY):
+ if hasattr(base.cr, "SillyMeterMgr") and not base.cr.SillyMeterMgr.isDisabled():
+ valid = True
+ else:
+ if hasattr(base.cr, "SillyMeterMgr"):
+ self.notify.debug("isDisabled = %s" % base.cr.SillyMeterMgr.isDisabled())
+ else:
+ self.notify.debug("base.cr does not have SillyMeterMgr")
+ else:
+ self.notify.debug("holiday is not running")
+ self.notify.debug("valid = %s" % valid)
+ if valid and \
+ base.cr.SillyMeterMgr.getIsRunning():
+ startTime = time.mktime(base.cr.SillyMeterMgr.getCurPhaseStartDate().timetuple())
+ serverTime = time.mktime(base.cr.toontownTimeManager.getCurServerDateTime().timetuple())
+ offset = (serverTime-startTime)/phaseDuration
+ if offset < 0:
+ result = -1
+ else:
+ result = offset*numFrames
+
+ return result
+
+ def calculateFrameRange(self, frameNo):
+ """
+ Return a range through which the thermometer should loop
+ """
+ pass
+
+ def enterSetup(self):
+ """
+ The silly meter has three different phases, each with thier own animation.
+ In addition one animation plays continuously
+ """
+
+ ropes = loader.loadModel("phase_4/models/modules/tt_m_ara_int_ropes")
+ ropes.reparentTo(self.interior)
+
+ self.sillyMeter = Actor.Actor("phase_4/models/props/tt_a_ara_ttc_sillyMeter_default", \
+ { "arrowTube" : "phase_4/models/props/tt_a_ara_ttc_sillyMeter_arrowFluid",
+ "phaseOne" : "phase_4/models/props/tt_a_ara_ttc_sillyMeter_phaseOne",
+ "phaseTwo" : "phase_4/models/props/tt_a_ara_ttc_sillyMeter_phaseTwo",
+ "phaseThree" : "phase_4/models/props/tt_a_ara_ttc_sillyMeter_phaseThree",
+ "phaseFour" : "phase_4/models/props/tt_a_ara_ttc_sillyMeter_phaseFour",
+ "phaseFourToFive" : "phase_4/models/props/tt_a_ara_ttc_sillyMeter_phaseFourToFive",
+ "phaseFive" : "phase_4/models/props/tt_a_ara_ttc_sillyMeter_phaseFive",
+ })
+ self.sillyMeter.reparentTo(self.interior)
+
+ self.flatSillyMeter = loader.loadModel("phase_3.5/models/modules/tt_m_ara_int_sillyMeterFlat")
+ self.flatSillyMeter.reparentTo(self.interior)
+ self.flatSillyMeter.hide()
+
+ self.flatDuck = loader.loadModel("phase_3.5/models/modules/tt_m_ara_int_scientistDuckFlat")
+ loc1 = self.interior.find("**/npc_origin_1")
+ if loc1:
+ self.flatDuck.reparentTo(loc1)
+ self.flatDuck.hide()
+
+ self.flatMonkey = loader.loadModel("phase_3.5/models/modules/tt_m_ara_int_scientistMonkeyFlat")
+ loc1 = self.interior.find("**/npc_origin_2")
+ if loc1:
+ self.flatMonkey.reparentTo(loc1)
+ self.flatMonkey.hide()
+
+ self.flatHorse = loader.loadModel("phase_3.5/models/modules/tt_m_ara_int_scientistHorseFlat")
+ loc1 = self.interior.find("**/npc_origin_3")
+ if loc1:
+ self.flatHorse.reparentTo(loc1)
+ self.flatHorse.hide()
+
+
+ self.smPhase1 = self.sillyMeter.find("**/stage1")
+ self.smPhase2 = self.sillyMeter.find("**/stage2")
+ self.smPhase3 = self.sillyMeter.find("**/stage3")
+ self.smPhase4 = self.sillyMeter.find("**/stage4")
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.smPhase4.hide()
+
+ thermometerLocator = self.sillyMeter.findAllMatches("**/uvj_progressBar")[1]
+ thermometerMesh = self.sillyMeter.find("**/tube")
+ thermometerMesh.setTexProjector(thermometerMesh.findTextureStage("default"), thermometerLocator, self.sillyMeter)
+ self.sillyMeter.flattenMedium()
+
+ self.sillyMeter.makeSubpart("arrow", ["uvj_progressBar*", "def_springA"])
+ self.sillyMeter.makeSubpart("meter", ["def_pivot"], ["uvj_progressBar*", "def_springA"])
+
+ # Load in the sounds
+
+ self.audio3d = Audio3DManager.Audio3DManager(base.sfxManagerList[0], camera)
+
+ self.phase1Sfx = self.audio3d.loadSfx("phase_4/audio/sfx/tt_s_prp_sillyMeterPhaseOne.mp3")
+ self.phase1Sfx.setLoop(True)
+
+ self.phase2Sfx = self.audio3d.loadSfx("phase_4/audio/sfx/tt_s_prp_sillyMeterPhaseTwo.mp3")
+ self.phase2Sfx.setLoop(True)
+
+ self.phase3Sfx = self.audio3d.loadSfx("phase_4/audio/sfx/tt_s_prp_sillyMeterPhaseThree.mp3")
+ self.phase3Sfx.setLoop(True)
+
+ self.phase4Sfx = self.audio3d.loadSfx("phase_4/audio/sfx/tt_s_prp_sillyMeterPhaseFour.mp3")
+ self.phase4Sfx.setLoop(True)
+
+ self.phase4To5Sfx = self.audio3d.loadSfx("phase_4/audio/sfx/tt_s_prp_sillyMeterPhaseFourToFive.mp3")
+ self.phase4To5Sfx.setLoop(False)
+
+ self.phase5Sfx = self.audio3d.loadSfx("phase_4/audio/sfx/tt_s_prp_sillyMeterPhaseFive.mp3")
+ self.phase5Sfx.setLoop(True)
+
+ self.arrowSfx = self.audio3d.loadSfx("phase_4/audio/sfx/tt_s_prp_sillyMeterArrow.mp3") # The arrow reaches its destination
+ self.arrowSfx.setLoop(False)
+
+ self.audio3d.setDropOffFactor(0.1)
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+ self.startIfNeeded()
+
+ def exitSetup(self):
+ """
+ Clean up
+ """
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase0(self):
+ """
+ Enter the first phase of silly
+ """
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow", duration = phaseDuration, constrainedLoop = 1, startFrame = 1, endFrame = 30),
+ Sequence(Func(self.phase1Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase1Sfx, self.sillyMeter)))
+
+ self.animSeq.start()
+ # Start the stage animations
+ self.sillyMeter.loop("phaseOne", partName="meter")
+
+ #self.sillyMeter.loop("phaseOne", fromFrame = 1, toFrame = 96)
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase0(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.audio3d.detachSound(self.phase1Sfx)
+ self.phase1Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase1(self):
+ """
+ Enter the first phase of silly
+ """
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+ # if phaseDuration == -1:
+ # playRate = 1
+ # else:
+ # playRate = 1.0/phaseDuration
+
+ # frameNo = self.calculateFrameOffset(phaseDuration, self.sillyMeter.getNumFrames("arrowTube"))
+
+ # if frameNo == -1:
+ # frameNo = 1
+
+ self.audio3d.attachSoundToObject(self.phase1Sfx, self.sillyMeter)
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 31, endFrame = 42),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 42, endFrame = 71),
+ Sequence(Func(self.phase1Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase1Sfx, self.sillyMeter))),
+ )
+ self.animSeq.start()
+ # Start the stage animations
+ self.sillyMeter.loop("phaseOne", partName="meter")
+
+ #self.sillyMeter.loop("phaseOne", fromFrame = 1, toFrame = 96)
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase1(self):
+ """
+ Clean up
+ """
+ self.audio3d.detachSound(self.phase1Sfx)
+ self.phase1Sfx.stop()
+
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase2(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 42, endFrame = 71),
+ Sequence(Func(self.phase1Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase1Sfx, self.sillyMeter)))
+ self.animSeq.start()
+
+ # Start the stage animations
+ self.smPhase2.show()
+ self.sillyMeter.loop("phaseOne", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase2(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.audio3d.detachSound(self.phase1Sfx)
+ self.phase1Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase3(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 72, endFrame = 83),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 83, endFrame = 112),
+ Sequence(Func(self.phase1Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase1Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.sillyMeter.loop("phaseOne", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase3(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.audio3d.detachSound(self.phase1Sfx)
+ self.phase1Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase4(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 113, endFrame = 124),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 124, endFrame = 153),
+ Sequence(Func(self.phase1Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase1Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.sillyMeter.loop("phaseOne", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase4(self):
+ """
+ Clean Up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.audio3d.detachSound(self.phase1Sfx)
+ self.phase1Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase5(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 154, endFrame = 165),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 165, endFrame = 194),
+ Sequence(Func(self.phase2Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase2Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.sillyMeter.loop("phaseTwo", partName="meter")
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase5(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.audio3d.detachSound(self.phase2Sfx)
+ self.phase2Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase6(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 195, endFrame = 206),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 206, endFrame = 235),
+ Sequence(Func(self.phase2Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase2Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.sillyMeter.loop("phaseTwo", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase6(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.audio3d.detachSound(self.phase2Sfx)
+ self.phase2Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase7(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 236, endFrame = 247),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 247, endFrame = 276),
+ Sequence(Func(self.phase3Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase3Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.sillyMeter.loop("phaseThree", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase7(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.audio3d.detachSound(self.phase3Sfx)
+ self.phase3Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase8(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 277, endFrame = 288),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 288, endFrame = 317),
+ Sequence(Func(self.phase3Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase3Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.sillyMeter.loop("phaseThree", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase8(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.audio3d.detachSound(self.phase3Sfx)
+ self.phase3Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase9(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 318, endFrame = 329),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 329, endFrame = 358),
+ Sequence(Func(self.phase3Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase3Sfx, self.sillyMeter))),
+ )
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.sillyMeter.loop("phaseThree", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase9(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.audio3d.detachSound(self.phase3Sfx)
+ self.phase3Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase10(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 359, endFrame = 370),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 370, endFrame = 399),
+ Sequence(Func(self.phase4Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase4Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.smPhase4.show()
+ self.sillyMeter.loop("phaseFour", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase10(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.smPhase4.hide()
+ self.audio3d.detachSound(self.phase4Sfx)
+ self.phase4Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase11(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 400, endFrame = 411),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 411, endFrame = 440),
+ Sequence(Func(self.phase4Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase4Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.smPhase4.show()
+ self.sillyMeter.loop("phaseFour", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase11(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.smPhase4.hide()
+ self.audio3d.detachSound(self.phase4Sfx)
+ self.phase4Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase12(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Sequence(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",constrainedLoop = 0, startFrame = 441, endFrame = 452),
+ Func(self.arrowSfx.play)),
+ Parallel(ActorInterval(self.sillyMeter, "arrowTube", partName = "arrow",duration = phaseDuration, constrainedLoop = 1, startFrame = 452, endFrame = 481),
+ Sequence(Func(self.phase4Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase4Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.smPhase4.show()
+ self.sillyMeter.loop("phaseFour", partName="meter")
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase12(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.smPhase4.hide()
+ self.audio3d.detachSound(self.phase4Sfx)
+ self.phase4Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase13(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Sequence(Parallel(Func(self.phase4To5Sfx.play),
+ ActorInterval(self.sillyMeter, "phaseFourToFive", constrainedLoop = 0, startFrame = 1, endFrame = 120),),
+ Parallel(ActorInterval(self.sillyMeter, "phaseFive", duration = phaseDuration, constrainedLoop = 1, startFrame = 1, endFrame = 48),
+ Sequence(Func(self.phase5Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase5Sfx, self.sillyMeter))))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.smPhase4.show()
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase13(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.smPhase4.hide()
+ self.audio3d.detachSound(self.phase5Sfx)
+ self.phase5Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase14(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Parallel(ActorInterval(self.sillyMeter, "phaseFive", duration = phaseDuration, constrainedLoop = 1, startFrame = 1, endFrame = 48),
+ Sequence(Func(self.phase5Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase5Sfx, self.sillyMeter)))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.smPhase4.show()
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase14(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.smPhase4.hide()
+ self.audio3d.detachSound(self.phase5Sfx)
+ self.phase5Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterPhase15(self):
+
+ phaseDuration = self.calculatePhaseDuration()
+
+ if phaseDuration < 0:
+ # Assume a week phase duration
+ phaseDuration = 604800
+
+ self.animSeq = Parallel(ActorInterval(self.sillyMeter, "phaseFive", duration = phaseDuration, constrainedLoop = 1, startFrame = 1, endFrame = 48),
+ Sequence(Func(self.phase5Sfx.play),
+ Func(self.audio3d.attachSoundToObject, self.phase5Sfx, self.sillyMeter)))
+ self.animSeq.start()
+
+ self.smPhase2.show()
+ self.smPhase3.show()
+ self.smPhase4.show()
+
+ self.accept("SillyMeterPhase", self.selectPhase)
+
+ def exitPhase15(self):
+ """
+ Clean up
+ """
+ self.animSeq.finish()
+ del self.animSeq
+
+ self.smPhase2.hide()
+ self.smPhase3.hide()
+ self.smPhase4.hide()
+ self.audio3d.detachSound(self.phase5Sfx)
+ self.phase5Sfx.stop()
+ self.sillyMeter.stop()
+
+ self.ignore("SillyMeterPhase")
+
+ def enterFlat(self):
+ """Show the flat silly meter and scientists."""
+ self.sillyMeter.hide()
+ self.flatSillyMeter.show()
+ self.flatDuck.show()
+ self.flatMonkey.show()
+ self.flatHorse.show()
+ pass
+
+ def exitFlat(self):
+ """Cleanup Flat phase."""
+ self.sillyMeter.show()
+ self.flatSillyMeter.hide()
+ self.flatDuck.hide()
+ self.flatMonkey.hide()
+ self.flatHorse.hide()
+ pass
+
+ def enterOff(self):
+ """
+ Turn it off
+ """
+ if hasattr(self, 'animSeq') and self.animSeq:
+ self.animSeq.finish()
+ self.ignore("SillyMeterPhase")
+ if hasattr(self, 'sillyMeter'):
+ del self.sillyMeter
+ if hasattr(self, 'smPhase1'):
+ del self.smPhase1
+ if hasattr(self, 'smPhase2'):
+ del self.smPhase2
+ if hasattr(self, 'smPhase3'):
+ del self.smPhase3
+ if hasattr(self, 'smPhase4'):
+ del self.smPhase4
+ self.cleanUpSounds()
+
+ def exitOff(self):
+ """
+ Clean up
+ """
+ pass
+
+ def enterToon(self):
+ assert self.notify.debugStateCall(self)
+ self.toonhallView = (Point3(0,-5,3), # pos
+ Point3(0,12.0,7.0), # fwd
+ Point3(0.0,10.0,5.0), # up
+ Point3(0.0,10.0,5.0), # down
+ 1)
+ self.setupCollisions(2.5)
+ self.firstEnter = 1
+
+ self.accept('CamChangeColl'+'-into', self.handleCloseToWall)
+
+ def exitToon(self):
+ assert self.notify.debugStateCall(self)
+
+ def handleCloseToWall(self, collEntry):
+ # We don't want to change camera's near the ropes
+ if self.firstEnter == 0:
+ return
+ interiorRopes = self.interior.find("**/*interior_ropes")
+ if interiorRopes==collEntry.getIntoNodePath().getParent():
+ return
+ self.restoreCam()
+ self.accept('CamChangeColl'+'-exit', self.handleAwayFromWall)
+
+ def handleAwayFromWall(self, collEntry):
+ if self.firstEnter == 1:
+ # Remove existing collisions which handled first entrance
+ self.cleanUpCollisions()
+ # Add new collisions
+ self.setupCollisions(0.75)
+ self.oldView = base.localAvatar.cameraIndex
+ base.localAvatar.addCameraPosition(self.toonhallView)
+ self.firstEnter = 0
+ self.setUpToonHallCam()
+ return
+
+ flippy = self.interior.find("**/*Flippy*/*NPCToon*")
+ if flippy==collEntry.getIntoNodePath():
+ self.setUpToonHallCam()
+
+ def setupCollisions(self, radius):
+ # Add a collision solid to handle camera changes
+ r = base.localAvatar.getClampedAvatarHeight()*radius
+
+ cs = CollisionSphere(0,0,0, r)
+ cn = CollisionNode('CamChangeColl')
+ cn.addSolid(cs)
+ cn.setFromCollideMask(ToontownGlobals.WallBitmask)
+ cn.setIntoCollideMask(BitMask32.allOff())
+
+ self.camChangeNP = base.localAvatar.getPart('torso', '1000').attachNewNode(cn)
+
+ self.cHandlerEvent = CollisionHandlerEvent()
+ self.cHandlerEvent.addInPattern('%fn-into')
+ self.cHandlerEvent.addOutPattern('%fn-exit')
+
+ base.cTrav.addCollider(self.camChangeNP, self.cHandlerEvent)
+
+ def cleanUpCollisions(self):
+ base.cTrav.removeCollider(self.camChangeNP)
+ self.camChangeNP.detachNode()
+ if hasattr(self, 'camChangeNP'):
+ del self.camChangeNP
+ if hasattr(self, 'cHandlerEvent'):
+ del self.cHandlerEvent
+
+ def cleanUpSounds(self):
+
+ def __cleanUpSound__(soundFile):
+ if soundFile.status() == soundFile.PLAYING:
+ soundFile.setLoop(False)
+ soundFile.stop()
+
+ if hasattr(self, "audio3d"):
+ self.audio3d.disable()
+ del self.audio3d
+
+ if hasattr(self, "phase1Sfx"):
+ __cleanUpSound__(self.phase1Sfx)
+ del self.phase1Sfx
+
+ if hasattr(self, "phase2Sfx"):
+ __cleanUpSound__(self.phase2Sfx)
+ del self.phase2Sfx
+
+ if hasattr(self, "phase3Sfx"):
+ __cleanUpSound__(self.phase3Sfx)
+ del self.phase3Sfx
+
+ if hasattr(self, "phase4Sfx"):
+ __cleanUpSound__(self.phase4Sfx)
+ del self.phase4Sfx
+
+ if hasattr(self, "phase4To5Sfx"):
+ __cleanUpSound__(self.phase4To5Sfx)
+ del self.phase4To5Sfx
+
+ if hasattr(self, "phase5Sfx"):
+ __cleanUpSound__(self.phase5Sfx)
+ del self.phase5Sfx
+
+ if hasattr(self, "arrowSfx"):
+ __cleanUpSound__(self.arrowSfx)
+ del self.arrowSfx
+
+ def setUpToonHallCam(self):
+ base.localAvatar.setCameraFov(75)
+ base.localAvatar.setCameraSettings(self.toonhallView)
+
+ def restoreCam(self):
+ base.localAvatar.setCameraFov(ToontownGlobals.DefaultCameraFov)
+ if hasattr(self, 'oldView'):
+ base.localAvatar.setCameraPositionByIndex(self.oldView)
+
+ def disable(self):
+ assert self.notify.debugStateCall(self)
+ self.setUpToonHallCam()
+ base.localAvatar.removeCameraPosition()
+ base.localAvatar.resetCameraPosition()
+ self.restoreCam()
+ self.ignoreAll()
+ self.cleanUpCollisions()
+ if hasattr(self, 'sillyFSM'):
+ self.sillyFSM.requestFinalState()
+ del self.sillyFSM
+ DistributedToonInterior.disable(self)
+
+ def delete(self):
+ assert self.notify.debugStateCall(self)
+ #self.sillyFSM.requestFinalState()
+ DistributedToonInterior.delete(self)
+
diff --git a/toontown/src/building/DistributedToonHallInteriorAI.py b/toontown/src/building/DistributedToonHallInteriorAI.py
new file mode 100644
index 0000000..ac7c662
--- /dev/null
+++ b/toontown/src/building/DistributedToonHallInteriorAI.py
@@ -0,0 +1,57 @@
+from DistributedToonInteriorAI import *
+from toontown.toonbase import ToontownGlobals
+
+class DistributedToonHallInteriorAI(DistributedToonInteriorAI):
+ """
+ DistributedToonHallInteriorAI class:
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedToonHallInteriorAI')
+
+ def __init__(self, block, air, zoneId, building):
+ DistributedToonInteriorAI.__init__(self, block, air, zoneId, building)
+
+ self.accept("ToonEnteredZone", self.logToonEntered)
+ self.accept("ToonLeftZone", self.logToonLeft)
+
+ def logToonEntered(self, avId, zoneId):
+ result = self.getCurPhase()
+ if result == -1:
+ phase = "not available"
+ else:
+ phase = str(result)
+ self.air.writeServerEvent('sillyMeter', avId, 'enter|%s' %phase)
+
+ def logToonLeft(self, avId, zoneId):
+ result = self.getCurPhase()
+ if result == -1:
+ phase = "not available"
+ else:
+ phase = str(result)
+ self.air.writeServerEvent('sillyMeter', avId, 'exit|%s'%phase)
+
+ def getCurPhase(self):
+ result = -1
+ enoughInfoToRun = False
+ # first see if the holiday is running, and we can get the cur phase
+ if ToontownGlobals.SILLYMETER_HOLIDAY in simbase.air.holidayManager.currentHolidays \
+ and simbase.air.holidayManager.currentHolidays[ToontownGlobals.SILLYMETER_HOLIDAY] != None \
+ and simbase.air.holidayManager.currentHolidays[ToontownGlobals.SILLYMETER_HOLIDAY].getRunningState():
+ if hasattr(simbase.air, "SillyMeterMgr"):
+ enoughInfoToRun = True
+ else:
+ self.notify.debug("simbase.air does not have SillyMeterMgr")
+ else:
+ self.notify.debug("holiday is not running")
+ self.notify.debug("enoughInfoToRun = %s" % enoughInfoToRun)
+ if enoughInfoToRun and \
+ simbase.air.SillyMeterMgr.getIsRunning():
+ result = simbase.air.SillyMeterMgr.getCurPhase()
+ return result
+
+ def delete(self):
+ assert(self.debugPrint("delete()"))
+ self.ignoreAll()
+ DistributedToonInteriorAI.delete(self)
+
\ No newline at end of file
diff --git a/toontown/src/building/DistributedToonInterior.py b/toontown/src/building/DistributedToonInterior.py
new file mode 100644
index 0000000..5662304
--- /dev/null
+++ b/toontown/src/building/DistributedToonInterior.py
@@ -0,0 +1,365 @@
+""" DistributedToonInterior module"""
+
+from toontown.toonbase.ToonBaseGlobal import *
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+
+from toontown.toonbase import ToontownGlobals
+import cPickle
+import ToonInterior
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObject
+from direct.fsm import State
+import random
+import ToonInteriorColors
+from toontown.hood import ZoneUtil
+from toontown.toon import ToonDNA
+from toontown.toon import ToonHead
+
+# These four coordinates define the region we have available to fit
+# the sign from the front of the building. Signs will be scaled down
+# to fit the smallest dimension, and centered within the box.
+SIGN_LEFT = -4
+SIGN_RIGHT = 4
+SIGN_BOTTOM = -3.5
+SIGN_TOP = 1.5
+
+# This is the factor by which the trophy frames are scaled up to make
+# room for long names like "FarrtKnocker".
+FrameScale = 1.4
+
+class DistributedToonInterior(DistributedObject.DistributedObject):
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedToonInterior')
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+ assert self.notify.debugStateCall(self)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedToonInterior',
+ [State.State('toon',
+ self.enterToon,
+ self.exitToon,
+ ['beingTakenOver']),
+ State.State('beingTakenOver',
+ self.enterBeingTakenOver,
+ self.exitBeingTakenOver,
+ []),
+ State.State('off',
+ self.enterOff,
+ self.exitOff,
+ []),
+ ],
+ # Initial State
+ 'toon',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+ # self.generate will be called automatically.
+
+ def generate(self):
+ """
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ assert self.notify.debugStateCall(self)
+ DistributedObject.DistributedObject.generate(self)
+
+ def announceGenerate(self):
+ assert self.notify.debugStateCall(self)
+ DistributedObject.DistributedObject.announceGenerate(self)
+ self.setup()
+
+ def disable(self):
+ assert self.notify.debugStateCall(self)
+ self.interior.removeNode()
+ del self.interior
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ assert self.notify.debugStateCall(self)
+ del self.fsm
+ DistributedObject.DistributedObject.delete(self)
+
+ def randomDNAItem(self, category, findFunc):
+ codeCount = self.dnaStore.getNumCatalogCodes(category)
+ index = self.randomGenerator.randint(0, codeCount-1)
+ code = self.dnaStore.getCatalogCode(category, index)
+ # findFunc will probably be findNode or findTexture
+ return findFunc(code)
+
+ def replaceRandomInModel(self, model):
+ """Replace named nodes with random items.
+ Here are the name Here is
+ prefixes that are what they
+ affected: do:
+
+ random_mox_ change the Model Only.
+ random_mcx_ change the Model and the Color.
+ random_mrx_ change the Model and Recurse.
+ random_tox_ change the Texture Only.
+ random_tcx_ change the Texture and the Color.
+
+ x is simply a uniquifying integer because Multigen will not
+ let you have multiple nodes with the same name
+ """
+ assert self.notify.debugStateCall(self)
+ baseTag="random_"
+ npc=model.findAllMatches("**/"+baseTag+"???_*")
+ for i in range(npc.getNumPaths()):
+ np=npc.getPath(i)
+ name=np.getName()
+
+ b=len(baseTag)
+ category=name[b+4:]
+ key1=name[b]
+ key2=name[b+1]
+
+ assert(key1 in ["m", "t"])
+ assert(key2 in ["c", "o", "r"])
+ if key1 == "m":
+ # ...model.
+ model = self.randomDNAItem(category, self.dnaStore.findNode)
+ assert(not model.isEmpty())
+ newNP = model.copyTo(np)
+ # room has collisions already: remove collisions from models
+ c = render.findAllMatches('**/collision')
+ c.stash()
+ if key2 == "r":
+ self.replaceRandomInModel(newNP)
+ elif key1 == "t":
+ # ...texture.
+ texture=self.randomDNAItem(category, self.dnaStore.findTexture)
+ assert(texture)
+ np.setTexture(texture,100)
+ newNP=np
+ if key2 == "c":
+ if (category == "TI_wallpaper") or (category == "TI_wallpaper_border"):
+ self.randomGenerator.seed(self.zoneId)
+ newNP.setColorScale(
+ self.randomGenerator.choice(self.colors[category]))
+ else:
+ newNP.setColorScale(
+ self.randomGenerator.choice(self.colors[category]))
+
+
+ def setup(self):
+ assert self.notify.debugStateCall(self)
+ self.dnaStore=base.cr.playGame.dnaStore
+ self.randomGenerator=random.Random()
+
+ # The math here is a little arbitrary. I'm trying to get a
+ # substantially different seed for each zondId, even on the
+ # same street. But we don't want to weigh to much on the
+ # block number, because we want the same block number on
+ # different streets to be different.
+ # Here we use the block number and a little of the branchId:
+ # seedX=self.zoneId&0x00ff
+ # Here we're using only the branchId:
+ # seedY=self.zoneId/100
+ # Here we're using only the block number:
+ # seedZ=256-int(self.block)
+
+ self.randomGenerator.seed(self.zoneId)
+
+ interior = self.randomDNAItem("TI_room", self.dnaStore.findNode)
+ assert(not interior.isEmpty())
+ self.interior = interior.copyTo(render)
+
+ # Load a color dictionary for this hood:
+ hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
+ self.colors = ToonInteriorColors.colors[hoodId]
+ # Replace all the "random_xxx_" nodes:
+ self.replaceRandomInModel(self.interior)
+
+ # Door:
+ doorModelName="door_double_round_ul" # hack zzzzzzz
+ # Switch leaning of the door:
+ if doorModelName[-1:] == "r":
+ doorModelName=doorModelName[:-1]+"l"
+ else:
+ doorModelName=doorModelName[:-1]+"r"
+ door=self.dnaStore.findNode(doorModelName)
+ # Determine where should we put the door:
+ door_origin=render.find("**/door_origin;+s")
+ doorNP=door.copyTo(door_origin)
+ assert(not doorNP.isEmpty())
+ assert(not door_origin.isEmpty())
+ # The rooms are too small for doors:
+ door_origin.setScale(0.8, 0.8, 0.8)
+ # Move the origin away from the wall so it does not shimmer
+ # We do this instead of decals
+ door_origin.setPos(door_origin, 0, -0.025, 0)
+ color=self.randomGenerator.choice(self.colors["TI_door"])
+ DNADoor.setupDoor(doorNP,
+ self.interior, door_origin,
+ self.dnaStore,
+ str(self.block), color)
+ # Setting the wallpaper texture with a priority overrides
+ # the door texture, if it's decalled. So, we're going to
+ # move it out from the decal, and float it in front of
+ # the wall:
+ doorFrame = doorNP.find("door_*_flat")
+ doorFrame.wrtReparentTo(self.interior)
+ doorFrame.setColor(color)
+
+ # Grab the sign off the front of the building and copy it into
+ # the interior, here.
+ sign=hidden.find(
+ "**/tb%s:*_landmark_*_DNARoot/**/sign;+s"%(self.block,))
+ if not sign.isEmpty():
+ signOrigin=self.interior.find("**/sign_origin;+s")
+ assert(not signOrigin.isEmpty())
+ # Copy, rather than instance, to avoid bug in flatten:
+ newSignNP=sign.copyTo(signOrigin)
+
+ # Restore the depth write flag, since the DNA turned it off.
+ newSignNP.setDepthWrite(1, 1)
+
+ mat=self.dnaStore.getSignTransformFromBlockNumber(int(self.block))
+
+ # Invert the sign transform matrix to undo the
+ # transformation that has already placed it on the
+ # front of the building.
+ inv = Mat4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
+ inv.invertFrom(mat)
+ newSignNP.setMat(inv)
+ # And flatten out that matrix so we can manipulate it further.
+ newSignNP.flattenLight()
+
+ # Now scale the sign to fit the room, and pull it out from
+ # the wall a tiny bit. We actually want to scale it to
+ # fit within the box defined by (SIGN_LEFT, SIGN_RIGHT,
+ # SIGN_BOTTOM, SIGN_TOP).
+
+ # First, we need to determine the actual bounds of the sign.
+ ll = Point3()
+ ur = Point3()
+ newSignNP.calcTightBounds(ll, ur)
+ width = ur[0] - ll[0]
+ height = ur[2] - ll[2]
+
+ if width != 0 and height != 0:
+ # And how much would we need to scale it in each dimension
+ # to make it fit?
+ xScale = (SIGN_RIGHT - SIGN_LEFT) / width
+ zScale = (SIGN_TOP - SIGN_BOTTOM) / height
+
+ # Now choose the smaller scale of the two, so it will fit
+ # within its bounds without being squashed or stretched.
+ scale = min(xScale, zScale)
+
+ xCenter = (ur[0] + ll[0]) / 2.0
+ zCenter = (ur[2] + ll[2]) / 2.0
+ newSignNP.setPosHprScale(
+ (SIGN_RIGHT + SIGN_LEFT) / 2.0 - xCenter * scale, -0.1,
+ (SIGN_TOP + SIGN_BOTTOM) / 2.0 - zCenter * scale,
+ 0.0, 0.0, 0.0,
+ scale, scale, scale)
+
+ # Who Saved It:
+ trophyOrigin=self.interior.find("**/trophy_origin")
+ assert(not trophyOrigin.isEmpty())
+ trophy=self.buildTrophy()
+ if trophy:
+ trophy.reparentTo(trophyOrigin)
+
+ del self.colors
+ del self.dnaStore
+ del self.randomGenerator
+
+ # Get rid of any transitions and extra nodes
+ self.interior.flattenMedium()
+
+ def setZoneIdAndBlock(self, zoneId, block):
+ assert self.notify.debugStateCall(self)
+ self.zoneId=zoneId
+ self.block=block
+
+ def setToonData(self, toonData):
+ assert self.notify.debugStateCall(self)
+ savedBy = cPickle.loads(toonData)
+ self.savedBy = savedBy
+
+ def buildTrophy(self):
+ assert self.notify.debugStateCall(self)
+ if self.savedBy == None:
+ return None
+
+ numToons = len(self.savedBy)
+ pos = 1.25 - 1.25 * numToons
+
+ trophy = hidden.attachNewNode('trophy')
+ for avId, name, dnaTuple in self.savedBy:
+ frame = self.buildFrame(name, dnaTuple)
+ frame.reparentTo(trophy)
+ frame.setPos(pos, 0, 0)
+ pos += 2.5
+ return trophy
+
+ def buildFrame(self, name, dnaTuple):
+ assert self.notify.debugStateCall(self)
+ frame = loader.loadModel('phase_3.5/models/modules/trophy_frame')
+
+ dna = ToonDNA.ToonDNA()
+ apply(dna.newToonFromProperties, dnaTuple)
+
+ head = ToonHead.ToonHead()
+ head.setupHead(dna)
+
+ head.setPosHprScale(
+ 0, -0.05, -0.05,
+ 180, 0, 0,
+ 0.55, 0.02, 0.55)
+ if dna.head[0] == 'r':
+ # Give rabbits a bit more space above the head.
+ head.setZ(-0.15)
+ elif dna.head[0] == 'h':
+ # Give horses a bit more space below the head.
+ head.setZ(0.05)
+ elif dna.head[0] == 'm':
+ # Mice should be a bit smaller to fit the ears.
+ head.setScale(0.45, 0.02, 0.45)
+
+ head.reparentTo(frame)
+
+ nameText = TextNode("trophy")
+ nameText.setFont(ToontownGlobals.getToonFont())
+ nameText.setAlign(TextNode.ACenter)
+ nameText.setTextColor(0, 0, 0, 1)
+ nameText.setWordwrap(5.36 * FrameScale)
+ nameText.setText(name)
+
+ namePath = frame.attachNewNode(nameText.generate())
+ namePath.setPos(0, -0.03, -.6)
+ namePath.setScale(0.186 / FrameScale)
+
+ frame.setScale(FrameScale, 1.0, FrameScale)
+ return frame
+
+ def setState(self, state, timestamp):
+ assert self.notify.debugStateCall(self)
+ self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+
+ def enterOff(self):
+ assert self.notify.debugStateCall(self)
+
+ def exitOff(self):
+ assert self.notify.debugStateCall(self)
+
+ def enterToon(self):
+ assert self.notify.debugStateCall(self)
+
+ def exitToon(self):
+ assert self.notify.debugStateCall(self)
+
+ def enterBeingTakenOver(self, ts):
+ """Kick everybody out of the building"""
+ assert self.notify.debugStateCall(self)
+ messenger.send("clearOutToonInterior")
+
+ def exitBeingTakenOver(self):
+ assert self.notify.debugStateCall(self)
diff --git a/toontown/src/building/DistributedToonInteriorAI.py b/toontown/src/building/DistributedToonInteriorAI.py
new file mode 100644
index 0000000..47412af
--- /dev/null
+++ b/toontown/src/building/DistributedToonInteriorAI.py
@@ -0,0 +1,119 @@
+from toontown.toonbase.ToontownGlobals import *
+""" DistributedToonInteriorAI module: contains the DistributedToonInteriorAI
+ class, the server side representation of a 'landmark door'."""
+
+
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+
+import cPickle
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObjectAI
+from direct.fsm import State
+from toontown.toon import NPCToons
+
+class DistributedToonInteriorAI(DistributedObjectAI.DistributedObjectAI):
+ """
+ DistributedToonInteriorAI class:
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedToonInteriorAI')
+
+ def __init__(self, block, air, zoneId, building):
+ """blockNumber: the landmark building number (from the name)"""
+ #self.air=air
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.block=block
+ self.zoneId=zoneId
+ self.building=building
+ assert(self.debugPrint(
+ "DistributedToonInteriorAI(air=%s, zoneId=%s, building=%s)"
+ %(air, zoneId, building)))
+
+ # Make any npcs that may be in this interior zone
+ # If there are none specified, this will just be an empty list
+ self.npcs = NPCToons.createNpcsInZone(air, zoneId)
+
+ # TODO
+ #for i in range(len(self.npcs)):
+ # npc = self.npcs[i]
+ # npc.d_setIndex(i)
+
+ self.fsm = ClassicFSM.ClassicFSM('DistributedToonInteriorAI',
+ [State.State('toon',
+ self.enterToon,
+ self.exitToon,
+ ['beingTakenOver']),
+ State.State('beingTakenOver',
+ self.enterBeingTakenOver,
+ self.exitBeingTakenOver,
+ []),
+ State.State('off',
+ self.enterOff,
+ self.exitOff,
+ []),
+ ],
+ # Initial State
+ 'toon',
+ # Final State
+ 'off',
+ )
+ self.fsm.enterInitialState()
+
+ def delete(self):
+ assert(self.debugPrint("delete()"))
+ self.ignoreAll()
+ for npc in self.npcs:
+ npc.requestDelete()
+ del self.npcs
+ del self.fsm
+ del self.building
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def getZoneIdAndBlock(self):
+ r=[self.zoneId, self.block]
+ assert(self.debugPrint("getZoneIdAndBlock() returning: "+str(r)))
+ return r
+
+ def getToonData(self):
+ assert(self.notify.debug("getToonData()"))
+ return cPickle.dumps(self.building.savedBy, 1)
+
+ def getState(self):
+ r=[self.fsm.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+ assert(self.debugPrint("getState() returning: "+str(r)))
+ return r
+
+ def setState(self, state):
+ assert(self.debugPrint("setState("+str(state)+")"))
+ self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()])
+ self.fsm.request(state)
+
+ def enterOff(self):
+ assert(self.debugPrint("enterOff()"))
+
+ def exitOff(self):
+ assert(self.debugPrint("exitOff()"))
+
+ def enterToon(self):
+ assert(self.debugPrint("enterToon()"))
+
+ def exitToon(self):
+ assert(self.debugPrint("exitToon()"))
+
+ def enterBeingTakenOver(self):
+ """Kick everybody out of the building"""
+ assert(self.debugPrint("enterBeingTakenOver()"))
+
+ def exitBeingTakenOver(self):
+ assert(self.debugPrint("exitBeingTakenOver()"))
+
+ if __debug__:
+ def debugPrint(self, message):
+ """for debugging"""
+ return self.notify.debug(
+ str(self.__dict__.get('zoneId', '?'))+' '+message)
+
diff --git a/toontown/src/building/DistributedTrophyMgr.py b/toontown/src/building/DistributedTrophyMgr.py
new file mode 100644
index 0000000..b405eb9
--- /dev/null
+++ b/toontown/src/building/DistributedTrophyMgr.py
@@ -0,0 +1,44 @@
+
+from direct.distributed import DistributedObject
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import TTLocalizer
+
+class DistributedTrophyMgr(DistributedObject.DistributedObject):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTrophyMgr')
+
+ neverDisable = 1
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+
+ def generate(self):
+ if base.cr.trophyManager != None:
+ base.cr.trophyManager.delete()
+ base.cr.trophyManager = self
+ DistributedObject.DistributedObject.generate(self)
+
+ def disable(self):
+ """disable(self)
+ This method is called when the DistributedObject is removed from
+ active duty and stored in a cache.
+ """
+ # Warning! disable() is NOT called for TrophyManager! Duh!
+ base.cr.trophyManager = None
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ """delete(self)
+ This method is called when the DistributedObject is permanently
+ removed from the world and deleted from the cache.
+ """
+ base.cr.trophyManager = None
+ DistributedObject.DistributedObject.delete(self)
+
+ def d_requestTrophyScore(self):
+ """
+ Call this message to request the avatar's current trophy
+ score. This will eventually cause the trophyScoreUpdate
+ message (above) to be sent.
+ """
+ self.sendUpdate('requestTrophyScore', [])
diff --git a/toontown/src/building/DistributedTrophyMgrAI.py b/toontown/src/building/DistributedTrophyMgrAI.py
new file mode 100644
index 0000000..953f005
--- /dev/null
+++ b/toontown/src/building/DistributedTrophyMgrAI.py
@@ -0,0 +1,131 @@
+
+from direct.distributed import DistributedObjectAI
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import TTLocalizer
+
+class DistributedTrophyMgrAI(DistributedObjectAI.DistributedObjectAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTrophyMgrAI')
+
+ def __init__(self, air):
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ # Dict of avId : score
+ self.trophyDict = {}
+ # Dict of avId : name since it is hard to query av names
+ self.nameDict = {}
+ # How many leaders do we want to keep track of?
+ self.maxLeaders = 10
+ self.__leaders = []
+ self.__leaderScores = []
+ self.__leaderAvIds = []
+ self.__leaderNames = []
+ self.__minLeaderScore = 0
+
+ def getTrophyScore(self, avId):
+ # Returns the trophy score for a particular avatar.
+ return self.trophyDict.get(avId, 0)
+
+ def requestTrophyScore(self):
+ # This message is sent from a client wanting to be told his
+ # own trophy score.
+ avId = self.air.getAvatarIdFromSender()
+ av = self.air.doId2do.get(avId)
+ if av:
+ av.d_setTrophyScore(self.getTrophyScore(avId))
+
+ def recomputeLeaders(self, avId, score):
+ """
+ Computes a list of score,avId pairs in sorted order, highest first
+ """
+ # Make a copy so we can manipulate it
+ i = map(lambda t: list(t), self.trophyDict.items())
+
+ # Recompute only if the score is greater than the lowest score
+ # or if there are less than 10 players in the list
+ if ((score > self.__minLeaderScore) or (len(i) < 10)):
+ # Reverse the items so we have score first
+ map(lambda r: r.reverse(),i)
+ # Sort by score
+ i.sort()
+ # Reverse that score so highest are first
+ i.reverse()
+ # Truncate the leaders to the max
+ # TODO: what about a tie?
+ self.__leaders = i[:self.maxLeaders]
+
+ # Keep some side tracking variables as an optimization so we
+ # do not need to compute them every time.
+ self.__leaderScores = map(lambda t: t[0], self.__leaders)
+ self.__minLeaderScore = min(self.__leaderScores)
+ self.__leaderAvIds = map(lambda t: t[1], self.__leaders)
+ self.__leaderNames = map(lambda avId: self.nameDict[avId], self.__leaderAvIds)
+
+ self.notify.debug("recomputed leaders:\n leaderScores: %s\n leaderAvIds: %s\n leaderNames: %s" %
+ (self.__leaderScores, self.__leaderAvIds, self.__leaderNames))
+
+ # Yep, it changed (well, most likely changed)
+ return True
+
+ else:
+ return False
+
+ def getLeaderInfo(self):
+ return self.__leaderAvIds, self.__leaderNames, self.__leaderScores
+
+ def getScoreFromNumFloors(self, numFloors):
+ # Based on the number of floors from this building, compute
+ # the trophy score received
+ return numFloors
+
+ def addTrophy(self, avId, name, numFloors):
+ addedScore = self.getScoreFromNumFloors(numFloors)
+ score = self.getTrophyScore(avId) + addedScore
+ self.trophyDict[avId] = score
+ self.nameDict[avId] = name
+ if self.recomputeLeaders(avId, score):
+ # Send a message to the DistributedHQInteriorAIs to update
+ # their leaderboards
+ messenger.send("leaderboardChanged")
+ self.notify.debug("addTrophy: %s avId: %s" % (addedScore, avId))
+ av = self.air.doId2do.get(avId)
+ if av:
+ av.d_setTrophyScore(score)
+ self.air.writeServerEvent(
+ 'trophy', avId, "%s|%s" % (score, addedScore))
+
+ def removeTrophy(self, avId, numFloors):
+ if self.trophyDict.has_key(avId):
+ removedScore = self.getScoreFromNumFloors(numFloors)
+ score = self.getTrophyScore(avId) - removedScore
+ self.trophyDict[avId] = score
+ if self.recomputeLeaders(avId, score):
+ # Send a message to the DistributedHQInteriorAIs to update
+ # their leaderboards
+ messenger.send("leaderboardChanged")
+ self.notify.debug("removeTrophy: %s avId: %s" % (removedScore, avId))
+
+ # Whisper to the avatar to let them know their precious trophy is gone
+ av = self.air.doId2do.get(avId)
+ if av:
+ av.d_setSystemMessage(0, TTLocalizer.RemoveTrophy)
+ av.d_setTrophyScore(self.trophyDict[avId])
+
+ if self.trophyDict[avId] <= 0:
+ del self.trophyDict[avId]
+ del self.nameDict[avId]
+ self.notify.debug("removeTrophy avId: %s removed from dict" % avId)
+ self.air.writeServerEvent(
+ 'trophy', avId, "%s|%s" % (score, -removedScore))
+ else:
+ # This should not happen
+ self.notify.warning("Tried to remove a trophy from avId: %s that has no trophies" % avId)
+
+ def getSortedScores(self):
+ """
+ Returns a list of score,avId pairs in sorted order, highest first
+ """
+ i = map(lambda t: list(t), self.trophyDict.items())
+ map(lambda r: r.reverse(),i)
+ i.sort()
+ i.reverse()
+ return i
diff --git a/toontown/src/building/DistributedTutorialInterior.py b/toontown/src/building/DistributedTutorialInterior.py
new file mode 100644
index 0000000..81c32f6
--- /dev/null
+++ b/toontown/src/building/DistributedTutorialInterior.py
@@ -0,0 +1,255 @@
+from toontown.toonbase.ToonBaseGlobal import *
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+
+from toontown.toonbase import ToontownGlobals
+import ToonInterior
+from direct.directnotify import DirectNotifyGlobal
+from direct.distributed import DistributedObject
+import random
+import ToonInteriorColors
+from toontown.hood import ZoneUtil
+from toontown.char import Char
+from toontown.suit import SuitDNA
+from toontown.suit import Suit
+from toontown.quest import QuestParser
+
+class DistributedTutorialInterior(DistributedObject.DistributedObject):
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedTutorialInterior')
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+
+ def generate(self):
+ DistributedObject.DistributedObject.generate(self)
+
+ def announceGenerate(self):
+ DistributedObject.DistributedObject.announceGenerate(self)
+ self.setup()
+
+ def disable(self):
+ self.interior.removeNode()
+ del self.interior
+ self.street.removeNode()
+ del self.street
+ self.sky.removeNode()
+ del self.sky
+ self.mickeyMovie.cleanup()
+ del self.mickeyMovie
+ self.suitWalkTrack.finish()
+ del self.suitWalkTrack
+ self.suit.delete()
+ del self.suit
+ self.ignore("enterTutotialInterior")
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ DistributedObject.DistributedObject.delete(self)
+
+ def randomDNAItem(self, category, findFunc):
+ codeCount = self.dnaStore.getNumCatalogCodes(category)
+ index = self.randomGenerator.randint(0, codeCount-1)
+ code = self.dnaStore.getCatalogCode(category, index)
+ # findFunc will probably be findNode or findTexture
+ return findFunc(code)
+
+ def replaceRandomInModel(self, model):
+ """Replace named nodes with random items.
+ Here are the name Here is
+ prefixes that are what they
+ affected: do:
+
+ random_mox_ change the Model Only.
+ random_mcx_ change the Model and the Color.
+ random_mrx_ change the Model and Recurse.
+ random_tox_ change the Texture Only.
+ random_tcx_ change the Texture and the Color.
+
+ x is simply a uniquifying integer because Multigen will not
+ let you have multiple nodes with the same name
+
+ """
+ baseTag="random_"
+ npc=model.findAllMatches("**/"+baseTag+"???_*")
+ for i in range(npc.getNumPaths()):
+ np=npc.getPath(i)
+ name=np.getName()
+
+ b=len(baseTag)
+ category=name[b+4:]
+ key1=name[b]
+ key2=name[b+1]
+
+ assert(key1 in ["m", "t"])
+ assert(key2 in ["c", "o", "r"])
+ if key1 == "m":
+ # ...model.
+ model = self.randomDNAItem(category, self.dnaStore.findNode)
+ assert(not model.isEmpty())
+ newNP = model.copyTo(np)
+ # room has collisions already: remove collisions from models
+ c = render.findAllMatches('**/collision')
+ c.stash()
+ if key2 == "r":
+ self.replaceRandomInModel(newNP)
+ elif key1 == "t":
+ # ...texture.
+ texture=self.randomDNAItem(category, self.dnaStore.findTexture)
+ assert(texture)
+ np.setTexture(texture,100)
+ newNP=np
+ if key2 == "c":
+ if (category == "TI_wallpaper") or (category == "TI_wallpaper_border"):
+ self.randomGenerator.seed(self.zoneId)
+ newNP.setColorScale(
+ self.randomGenerator.choice(self.colors[category]))
+ else:
+ newNP.setColorScale(
+ self.randomGenerator.choice(self.colors[category]))
+
+ def setup(self):
+ self.dnaStore=base.cr.playGame.dnaStore
+ self.randomGenerator=random.Random()
+
+ # The math here is a little arbitrary. I'm trying to get a
+ # substantially different seed for each zondId, even on the
+ # same street. But we don't want to weigh to much on the
+ # block number, because we want the same block number on
+ # different streets to be different.
+ # Here we use the block number and a little of the branchId:
+ # seedX=self.zoneId&0x00ff
+ # Here we're using only the branchId:
+ # seedY=self.zoneId/100
+ # Here we're using only the block number:
+ # seedZ=256-int(self.block)
+
+ self.randomGenerator.seed(self.zoneId)
+
+ self.interior = loader.loadModel("phase_3.5/models/modules/toon_interior_tutorial")
+ self.interior.reparentTo(render)
+ dnaStore = DNAStorage()
+ node = loader.loadDNAFile(self.cr.playGame.hood.dnaStore, "phase_3.5/dna/tutorial_street.dna")
+ self.street = render.attachNewNode(node)
+ self.street.flattenMedium()
+ self.street.setPosHpr(-17,42,-0.5,180,0,0)
+ # Get rid of the building we are in
+ self.street.find("**/tb2:toon_landmark_TT_A1_DNARoot").stash()
+ # Get rid of the flashing doors on the HQ building
+ self.street.find("**/tb1:toon_landmark_hqTT_DNARoot/**/door_flat_0").stash()
+ # Get rid of collisions because we do not need them and they get in the way
+ self.street.findAllMatches("**/+CollisionNode").stash()
+ self.skyFile = "phase_3.5/models/props/TT_sky"
+ self.sky = loader.loadModel(self.skyFile)
+ self.sky.setScale(0.8)
+ # Parent the sky to our camera, the task will counter rotate it
+ self.sky.reparentTo(render)
+ # Turn off depth tests on the sky because as the cloud layers interpenetrate
+ # we do not want to see the polys cutoff. Since there is nothing behing them
+ # we can get away with this.
+ self.sky.setDepthTest(0)
+ self.sky.setDepthWrite(0)
+ self.sky.setBin("background", 100)
+ # Make sure they are drawn in the correct order in the hierarchy
+ # The sky should be first, then the clouds
+ self.sky.find("**/Sky").reparentTo(self.sky, -1)
+
+ # Load a color dictionary for this hood:
+ hoodId = ZoneUtil.getCanonicalHoodId(self.zoneId)
+ self.colors = ToonInteriorColors.colors[hoodId]
+ # Replace all the "random_xxx_" nodes:
+ self.replaceRandomInModel(self.interior)
+
+ # Door:
+ doorModelName="door_double_round_ul" # hack zzzzzzz
+ # Switch leaning of the door:
+ if doorModelName[-1:] == "r":
+ doorModelName=doorModelName[:-1]+"l"
+ else:
+ doorModelName=doorModelName[:-1]+"r"
+ door=self.dnaStore.findNode(doorModelName)
+ # Determine where should we put the door:
+ door_origin=render.find("**/door_origin;+s")
+ doorNP=door.copyTo(door_origin)
+ assert(not doorNP.isEmpty())
+ assert(not door_origin.isEmpty())
+ # The rooms are too small for doors:
+ door_origin.setScale(0.8, 0.8, 0.8)
+ # Move the origin away from the wall so it does not shimmer
+ # We do this instead of decals
+ door_origin.setPos(door_origin, 0, -0.025, 0)
+ color=self.randomGenerator.choice(self.colors["TI_door"])
+ DNADoor.setupDoor(doorNP,
+ self.interior, door_origin,
+ self.dnaStore,
+ str(self.block), color)
+ # Setting the wallpaper texture with a priority overrides
+ # the door texture, if it's decalled. So, we're going to
+ # move it out from the decal, and float it in front of
+ # the wall:
+ doorFrame = doorNP.find("door_*_flat")
+ doorFrame.wrtReparentTo(self.interior)
+ doorFrame.setColor(color)
+
+ del self.colors
+ del self.dnaStore
+ del self.randomGenerator
+
+ # Get rid of any transitions and extra nodes
+ self.interior.flattenMedium()
+
+ # Ok, this is a hack, but I'm tired of this freakin tutorial.
+ # The problem is the interior must be created first so the npc can find the origin
+ # of where to stand, but in this case the npc must be created first so the tutorial
+ # can get a handle on him. Instead, I'll let the npc be created first which means
+ # he will not find his origin. We'll just do that work here again.
+ npcOrigin = self.interior.find("**/npc_origin_" + `self.npc.posIndex`)
+ # Now he's no longer parented to render, but no one minds.
+ if not npcOrigin.isEmpty():
+ self.npc.reparentTo(npcOrigin)
+ self.npc.clearMat()
+
+ # TODO: no suit if you have already beat him
+ self.createSuit()
+
+ self.mickeyMovie = QuestParser.NPCMoviePlayer("tutorial_mickey", base.localAvatar, self.npc)
+ self.acceptOnce("enterTutorialInterior", self.mickeyMovie.play)
+
+ def createSuit(self):
+ # Create a suit
+ self.suit = Suit.Suit()
+ suitDNA = SuitDNA.SuitDNA()
+ suitDNA.newSuit('f')
+ self.suit.setDNA(suitDNA)
+ self.suit.loop('neutral')
+ self.suit.setPosHpr(-20,8,0,0,0,0)
+ self.suit.reparentTo(self.interior)
+
+ self.suitWalkTrack = Sequence(
+ self.suit.hprInterval(0.1, Vec3(0,0,0)),
+ Func(self.suit.loop, 'walk'),
+ self.suit.posInterval(2, Point3(-20,20,0)),
+ Func(self.suit.loop, 'neutral'),
+ Wait(1.0),
+ self.suit.hprInterval(0.1, Vec3(180,0,0)),
+ Func(self.suit.loop, 'walk'),
+ self.suit.posInterval(2, Point3(-20,10,0)),
+ Func(self.suit.loop, 'neutral'),
+ Wait(1.0),
+ )
+ self.suitWalkTrack.loop()
+
+ def setZoneIdAndBlock(self, zoneId, block):
+ self.zoneId=zoneId
+ self.block=block
+
+ def setTutorialNpcId(self, npcId):
+ self.npcId = npcId
+ self.npc = self.cr.doId2do[npcId]
+
+
+
+
diff --git a/toontown/src/building/DistributedTutorialInteriorAI.py b/toontown/src/building/DistributedTutorialInteriorAI.py
new file mode 100644
index 0000000..77bbddf
--- /dev/null
+++ b/toontown/src/building/DistributedTutorialInteriorAI.py
@@ -0,0 +1,40 @@
+from toontown.toonbase.ToontownGlobals import *
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.distributed import DistributedObjectAI
+from toontown.toon import NPCToons
+
+class DistributedTutorialInteriorAI(DistributedObjectAI.DistributedObjectAI):
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedTutorialInteriorAI')
+
+ def __init__(self, block, air, zoneId, building, npcId):
+ """blockNumber: the landmark building number (from the name)"""
+ #self.air=air
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.block=block
+ self.zoneId=zoneId
+ self.building=building
+ self.tutorialNpcId = npcId
+
+
+ # Make any npcs that may be in this interior zone
+ # If there are none specified, this will just be an empty list
+ self.npcs = NPCToons.createNpcsInZone(air, zoneId)
+
+ def delete(self):
+ self.ignoreAll()
+ for npc in self.npcs:
+ npc.requestDelete()
+ del self.npcs
+ del self.building
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def getZoneIdAndBlock(self):
+ return [self.zoneId, self.block]
+
+ def getTutorialNpcId(self):
+ return self.tutorialNpcId
diff --git a/toontown/src/building/DoorTypes.py b/toontown/src/building/DoorTypes.py
new file mode 100644
index 0000000..e5d1023
--- /dev/null
+++ b/toontown/src/building/DoorTypes.py
@@ -0,0 +1,33 @@
+# These are the types of door models that a DistributedDoor can be.
+
+# An exterior standard door. This is most of the types of doors on landmark
+# building exteriors.
+EXT_STANDARD = 1
+
+# An interior standard door. This is most of the doors on building interiors.
+INT_STANDARD = 2
+
+# A door on the outside of an HQ building. These doors have models built
+# into the building, and there is possibly more than one of them on
+# each building.
+EXT_HQ = 3
+
+# A door on the inside of an HQ building. These are like interior standard
+# doors, but there can be more than one of them leading out of the building.
+INT_HQ = 4
+
+# The exterior/interior doors of an estate building must be handled
+# differently since the houses on an estate could possibly change
+EXT_HOUSE = 5
+INT_HOUSE = 6
+
+# CogHQ main building -> lobby doors
+EXT_COGHQ = 7
+INT_COGHQ = 8
+
+# KartShop exterior -> interior doors
+EXT_KS = 9
+INT_KS = 10
+
+# for animated landmark buildings
+EXT_ANIM_STANDARD = 11
diff --git a/toontown/src/building/Elevator.py b/toontown/src/building/Elevator.py
new file mode 100644
index 0000000..4bda27b
--- /dev/null
+++ b/toontown/src/building/Elevator.py
@@ -0,0 +1,263 @@
+from pandac.PandaModules import *
+from toontown.toonbase.ToonBaseGlobal import *
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.fsm import StateData
+from toontown.launcher import DownloadForceAcknowledge
+from toontown.toonbase import TTLocalizer
+from direct.showbase import PythonUtil
+
+class Elevator(StateData.StateData):
+ def __init__(self, elevatorState, doneEvent, distElevator):
+
+ StateData.StateData.__init__(self, doneEvent)
+
+ self.fsm = ClassicFSM.ClassicFSM('Elevator',
+ [State.State('start',
+ self.enterStart,
+ self.exitStart,
+ ['elevatorDFA',
+ ]),
+ State.State('elevatorDFA',
+ self.enterElevatorDFA,
+ self.exitElevatorDFA,
+ ['requestBoard',
+ 'final'
+ ]),
+ State.State('requestBoard',
+ self.enterRequestBoard,
+ self.exitRequestBoard,
+ ['boarding']),
+ State.State('boarding',
+ self.enterBoarding,
+ self.exitBoarding,
+ ['boarded']),
+ State.State('boarded',
+ self.enterBoarded,
+ self.exitBoarded,
+ ['requestExit',
+ 'elevatorClosing',
+ 'final']),
+ State.State('requestExit',
+ self.enterRequestExit,
+ self.exitRequestExit,
+ ['exiting', 'elevatorClosing']),
+ State.State('elevatorClosing',
+ self.enterElevatorClosing,
+ self.exitElevatorClosing,
+ ['final']),
+ State.State('exiting',
+ self.enterExiting,
+ self.exitExiting,
+ ['final']),
+ State.State('final',
+ self.enterFinal,
+ self.exitFinal,
+ ['start'])],
+ # Initial State
+ 'start',
+ # Final State
+ 'final',
+ )
+
+ self.dfaDoneEvent = "elevatorDfaDoneEvent"
+ self.elevatorState = elevatorState
+ self.distElevator = distElevator
+ distElevator.elevatorFSM=self
+ self.reverseBoardingCamera = False
+ self.skipDFABoard = 0
+
+ def load(self):
+ self.elevatorState.addChild(self.fsm)
+ self.buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui")
+ self.upButton = self.buttonModels.find("**//InventoryButtonUp")
+ self.downButton = self.buttonModels.find("**/InventoryButtonDown")
+ self.rolloverButton = self.buttonModels.find(
+ "**/InventoryButtonRollover")
+
+ def unload(self):
+ self.elevatorState.removeChild(self.fsm)
+ self.distElevator.elevatorFSM = None # kill the loop
+ del self.distElevator
+ del self.fsm
+ del self.elevatorState
+ self.buttonModels.removeNode()
+ del self.buttonModels
+ del self.upButton
+ del self.downButton
+ del self.rolloverButton
+
+ def enter(self):
+ self.fsm.enterInitialState()
+ self.fsm.request("elevatorDFA")
+
+ def exit(self):
+ self.ignoreAll()
+
+ def signalDone(self, doneStatus):
+ messenger.send(self.doneEvent, [doneStatus])
+
+ def enterStart(self):
+ pass
+
+ def exitStart(self):
+ pass
+
+ def enterElevatorDFA(self):
+ self.acceptOnce(self.dfaDoneEvent, self.enterDFACallback)
+ self.dfa = DownloadForceAcknowledge.DownloadForceAcknowledge(
+ self.dfaDoneEvent)
+ # The suit interiors are download phase 7
+ self.dfa.enter(7)
+ return
+
+ def enterDFACallback(self, DFAdoneStatus):
+ self.dfa.exit()
+ del self.dfa
+
+ if (DFAdoneStatus["mode"] == "complete"):
+ if self.skipDFABoard:
+ self.skipDFABoard = 0
+ else:
+ self.fsm.request("requestBoard")
+ elif (DFAdoneStatus["mode"] == "incomplete"):
+ elevatorDoneStatus = {}
+ elevatorDoneStatus["where"] = "reject"
+ # Let our parent fsm know that we are done.
+ messenger.send(self.doneEvent, [elevatorDoneStatus])
+ else:
+ self.notify.error("Unrecognized doneStatus: " + str(DFAdoneStatus))
+
+ def exitElevatorDFA(self):
+ self.ignore(self.dfaDoneEvent)
+
+ def enterRequestBoard(self):
+ # Let the distributed elevator know that it can safely make
+ # the request.
+ messenger.send(self.distElevator.uniqueName("enterElevatorOK"))
+
+ def exitRequestBoard(self):
+ pass
+
+ def enterBoarding(self, nodePath):
+ camera.wrtReparentTo(nodePath)
+ if self.reverseBoardingCamera:
+ heading = PythonUtil.fitDestAngle2Src( camera.getH( nodePath), 180 )
+ self.cameraBoardTrack = LerpPosHprInterval(camera, 1.5,
+ Point3(0, 18, 8),
+ Point3(heading, -10, 0))
+ else:
+ self.cameraBoardTrack = LerpPosHprInterval(
+ camera, 1.5, Point3(0, -16, 5.5), Point3(0, 0, 0))
+
+ self.cameraBoardTrack.start()
+
+ def exitBoarding(self):
+ self.ignore("boardedElevator")
+
+ def enterBoarded(self):
+ self.enableExitButton()
+
+ def exitBoarded(self):
+ # Remove the boarding task... You might think this should be
+ # removed in exitBoarding, but we want the camera move to continue
+ # into the boarded state. Since boarding only goes directly into
+ # boarded state, it's okay. Probably boarding and boarded should
+ # be the same state.
+ self.cameraBoardTrack.finish()
+ self.disableExitButton()
+
+ def enableExitButton(self):
+
+ self.exitButton = DirectButton(
+ relief = None,
+ text = TTLocalizer.ElevatorHopOff,
+ text_fg = (0.9, 0.9, 0.9, 1),
+ text_pos = (0, -0.23),
+ text_scale = TTLocalizer.EelevatorHopOff,
+ image = (self.upButton, self.downButton, self.rolloverButton),
+ image_color = (0.5, 0.5, 0.5, 1),
+ image_scale = (20, 1, 11),
+ pos = (0, 0, 0.8),
+ scale = 0.15,
+ command = lambda self=self: self.fsm.request("requestExit"),
+ )
+
+ if hasattr(localAvatar, "boardingParty") and \
+ localAvatar.boardingParty and \
+ localAvatar.boardingParty.getGroupLeader(localAvatar.doId) and \
+ localAvatar.boardingParty.getGroupLeader(localAvatar.doId) != localAvatar.doId:
+
+ self.exitButton['command'] = None
+ self.exitButton.hide()
+
+## self.hopWarning = DirectLabel(
+## parent = self.exitButton,
+## relief = None,
+## pos = Vec3(0, 0, 0.0),
+## text = TTLocalizer.ElevatorLeaderOff,
+## text_fg = (0.9, 0.9, 0.9, 1),
+## text_pos = (0, -1.1),
+## text_scale = 0.6,
+## )
+## self.hopWarning.reparentTo(self.exitButton.stateNodePath[2])
+
+ if self.distElevator.antiShuffle:
+ self.hopWarning = DirectLabel(
+ parent = self.exitButton,
+ relief = None,
+ pos = Vec3(0, 0, 0.0),
+ text = TTLocalizer.ElevatorStayOff,
+ text_fg = (0.9, 0.9, 0.9, 1),
+ text_pos = (0, -1.1),
+ text_scale = 0.6,
+ )
+ self.hopWarning.reparentTo(self.exitButton.stateNodePath[2])
+
+ else:
+ self.hopWarning = None
+
+ def disableExitButton(self):
+ if self.hopWarning:
+ self.hopWarning.destroy()
+ self.exitButton.destroy()
+
+ def enterRequestExit(self):
+ messenger.send("elevatorExitButton")
+
+ def exitRequestExit(self):
+ pass
+
+ def enterElevatorClosing(self):
+ # A camera move
+ #camera.lerpPosHprXYZHPR(0, 18.55, 3.75, -180, 0, 0, 3,
+ # blendType = "easeInOut", task="closingCamera")
+
+ # TODO: Actually go inside the building... Set doneStatus to
+ # whatever is necessary to enter the building, and call the
+ # done event.
+ assert(self.notify.debug('enterElevatorClosing()'))
+
+ def exitElevatorClosing(self):
+ #taskMgr.remove("closingCamera")
+ pass
+
+ def enterExiting(self):
+ pass
+
+ def exitExiting(self):
+ pass
+
+ def enterFinal(self):
+ assert(self.notify.debug('enterFinal()'))
+
+ def exitFinal(self):
+ pass
+
+ def setReverseBoardingCamera(self, newVal):
+ """Set the flag if True, we reverse the boarding camera."""
+ self.reverseBoardingCamera = newVal
diff --git a/toontown/src/building/ElevatorConstants.py b/toontown/src/building/ElevatorConstants.py
new file mode 100644
index 0000000..787527e
--- /dev/null
+++ b/toontown/src/building/ElevatorConstants.py
@@ -0,0 +1,162 @@
+from pandac.PandaModules import *
+
+
+# The various types of elevators
+ELEVATOR_NORMAL = 0
+ELEVATOR_VP = 1
+ELEVATOR_MINT = 2
+ELEVATOR_CFO = 3
+ELEVATOR_CJ = 4
+ELEVATOR_OFFICE = 5
+ELEVATOR_STAGE = 6
+ELEVATOR_BB = 7
+ELEVATOR_COUNTRY_CLUB = 8 # country club cog golf kart / elevator
+
+# Reasons for rejecting a toons
+REJECT_NOREASON = 0
+REJECT_SHUFFLE = 1
+REJECT_MINLAFF = 2
+REJECT_NOSEAT = 3
+REJECT_PROMOTION = 4
+REJECT_BLOCKED_ROOM = 5 # a room needs to be defeated first
+REJECT_NOT_YET_AVAILABLE = 6 # deliberately blocked by devs for now
+REJECT_BOARDINGPARTY = 7 #the reject came from the boarding party.
+REJECT_NOTPAID = 8
+
+MAX_GROUP_BOARDING_TIME = 6.0
+
+if __dev__:
+ try:
+ config = simbase.config
+ except:
+ config = base.config
+ elevatorCountdown = config.GetFloat('elevator-countdown', -1)
+ if elevatorCountdown != -1:
+ bboard.post('elevatorCountdown', elevatorCountdown)
+
+# Constants used for elevator coordination
+ElevatorData = {
+ ELEVATOR_NORMAL : { "openTime" : 2.0,
+ "closeTime" : 2.0,
+ "width" : 3.5,
+ "countdown" : bboard.get('elevatorCountdown',15.0),
+ "sfxVolume" : 1.0,
+ "collRadius": 5,
+ },
+ ELEVATOR_VP : { "openTime" : 4.0,
+ "closeTime" : 4.0,
+ "width" : 11.5,
+ "countdown" : bboard.get('elevatorCountdown',30.0),
+ "sfxVolume" : 0.7,
+ "collRadius": 7.5,
+ },
+ ELEVATOR_MINT : { "openTime" : 2.0,
+ "closeTime" : 2.0,
+ "width" : 5.875,
+ "countdown" : bboard.get('elevatorCountdown',15.0),
+ "sfxVolume" : 1.0,
+ "collRadius": 5,
+ },
+ ELEVATOR_OFFICE : { "openTime" : 2.0,
+ "closeTime" : 2.0,
+ "width" : 5.875,
+ "countdown" : bboard.get('elevatorCountdown',15.0),
+ "sfxVolume" : 1.0,
+ "collRadius": 5,
+ },
+ ELEVATOR_CFO : { "openTime" : 3.0,
+ "closeTime" : 3.0,
+ "width" : 8.166,
+ "countdown" : bboard.get('elevatorCountdown',30.0),
+ "sfxVolume" : 0.7,
+ "collRadius": 7.5,
+ },
+ ELEVATOR_CJ : { "openTime" : 4.0,
+ "closeTime" : 4.0,
+ "width" : 15.8,
+ "countdown" : bboard.get('elevatorCountdown',30.0),
+ "sfxVolume" : 0.7,
+ "collRadius": 7.5,
+ },
+ ELEVATOR_STAGE : { "openTime" : 4.0,
+ "closeTime" : 4.0,
+ "width" : 6.5,
+ "countdown" : bboard.get('elevatorCountdown',42.0),
+ "sfxVolume" : 1.0,
+ "collRadius": 9.5,
+ },
+ ELEVATOR_BB : { "openTime" : 4.0,
+ "closeTime" : 4.0,
+ "width" : 6.3,
+ "countdown" : bboard.get('elevatorCountdown',30.0),
+ "sfxVolume" : 0.7,
+ "collRadius": 7.5,
+ },
+ ELEVATOR_COUNTRY_CLUB : { "openTime" : 2.0,
+ "closeTime" : 2.0,
+ "width" : 5.875,
+ "countdown" : bboard.get('elevatorCountdown',15.0),
+ "sfxVolume" : 1.0,
+ "collRadius": 4,
+ },
+ }
+
+TOON_BOARD_ELEVATOR_TIME = 1.0
+TOON_EXIT_ELEVATOR_TIME = 1.0
+TOON_VICTORY_EXIT_TIME = 1.0
+SUIT_HOLD_ELEVATOR_TIME = 1.0
+SUIT_LEAVE_ELEVATOR_TIME = 2.0
+# 120 seemed like to long when somebody was just joking
+# or griefing by not getting in the elevator. How bout 60?
+# ...60 not long enough to account for network lags, apparently
+# (is xp being skipped if this times out?)
+INTERIOR_ELEVATOR_COUNTDOWN_TIME = 90
+
+# The color of the "off" elevator lights.
+LIGHT_OFF_COLOR = Vec4(0.5, 0.5, 0.5, 1.0)
+
+# The color of the "on" elevator light.
+LIGHT_ON_COLOR = Vec4(1.0, 1.0, 1.0, 1.0)
+
+ElevatorPoints = [[-1.5, 5, 0.1], # Back left
+ [1.5, 5, 0.1], # Back right
+ [-2.5, 3, 0.1], # Front left
+ [2.5, 3, 0.1], # Front right
+ [-3.5, 5, 0.1], # Left of back left
+ [3.5, 5, 0.1], # Right of back right
+ [-4, 3, 0.1], # Left of front left
+ [4, 3, 0.1]] # Right of front right
+
+JumpOutOffsets = [[-1.5, -5, -0], # Slot 1 - Back left
+ [1.5, -5, -0], # Slot 2 - Back right
+ [-2.5, -7, -0], # Slot 3 - Front left
+ [2.5, -7, -0], # Slot 4 - Front right
+ [-3.5, -5, -0], # Slot 5 - Left of back left
+ [3.5, -5, -0], # Slot 6 - Right of back right
+ [-4, -7, -0], # Slot 7 - Left of front left
+ [4, -7, -0]] # Slot 8 - Right of front right
+
+BigElevatorPoints = [[-2.5, 9, 0.1], # Back left center
+ [2.5, 9, 0.1], # Back right center
+ [-8.0, 9, 0.1], # Back left left
+ [8.0, 9, 0.1], # Back right right
+ [-2.5, 4, 0.1], # Front left center
+ [2.5, 4, 0.1], # Front right center
+ [-8.0, 4, 0.1], # Front left left
+ [8.0, 4, 0.1]] # Front right right
+
+BossbotElevatorPoints = [[-2.5, 7.5, 0.1], # Back left center
+ [2.5, 7.5, 0.1], # Back right center
+ [-5.5, 7.5, 0.1], # Back left left
+ [5.5, 7.5, 0.1], # Back right right
+ [-2.5, 3.5, 0.1], # Front left center
+ [2.5, 3.5, 0.1], # Front right center
+ [-5.5, 3.5, 0.1], # Front left left
+ [5.5, 3.5, 0.1]] # Front right right
+
+# The victors will fan out into a semicircle after they leave the
+# elevator to observe the transformation.
+ElevatorOutPoints = [[-4.6, -5.2, 0.1], # Back left
+ [4.6, -5.2, 0.1], # Back right
+ [-1.6, -6.2, 0.1], # Front left
+ [1.6, -6.2, 0.1]] # Front right
diff --git a/toontown/src/building/ElevatorUtils.py b/toontown/src/building/ElevatorUtils.py
new file mode 100644
index 0000000..8aad597
--- /dev/null
+++ b/toontown/src/building/ElevatorUtils.py
@@ -0,0 +1,190 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from ElevatorConstants import *
+
+def getLeftClosePoint(type):
+ width = ElevatorData[type]["width"]
+ return Point3(width, 0, 0)
+
+def getRightClosePoint(type):
+ width = ElevatorData[type]["width"]
+ return Point3(-width, 0, 0)
+
+def getLeftOpenPoint(type):
+ return Point3(0, 0, 0)
+
+def getRightOpenPoint(type):
+ return Point3(0, 0, 0)
+
+def closeDoors(leftDoor, rightDoor, type = ELEVATOR_NORMAL):
+ closedPosLeft = getLeftClosePoint(type)
+ closedPosRight = getRightClosePoint(type)
+
+ leftDoor.setPos(closedPosLeft)
+ rightDoor.setPos(closedPosRight)
+
+def openDoors(leftDoor, rightDoor, type = ELEVATOR_NORMAL):
+ openPosLeft = getLeftOpenPoint(type)
+ openPosRight = getRightOpenPoint(type)
+
+ leftDoor.setPos(openPosLeft)
+ rightDoor.setPos(openPosRight)
+
+
+def getLeftOpenInterval(distObj, leftDoor, type):
+ openTime = ElevatorData[type]["openTime"]
+ closedPos = getLeftClosePoint(type)
+ openPos = getLeftOpenPoint(type)
+ leftOpenInterval = LerpPosInterval(
+ leftDoor,
+ openTime,
+ openPos,
+ startPos = closedPos,
+ blendType = "easeOut",
+ name = distObj.uniqueName("leftDoorOpen"),
+ )
+ return leftOpenInterval
+
+def getRightOpenInterval(distObj, rightDoor, type):
+ openTime = ElevatorData[type]["openTime"]
+ closedPos = getRightClosePoint(type)
+ openPos = getRightOpenPoint(type)
+ rightOpenInterval = LerpPosInterval(
+ rightDoor,
+ openTime,
+ openPos,
+ startPos = closedPos,
+ blendType = "easeOut",
+ name = distObj.uniqueName("rightDoorOpen"),
+ )
+ return rightOpenInterval
+
+def getOpenInterval(distObj, leftDoor, rightDoor, openSfx, finalOpenSfx, type = ELEVATOR_NORMAL):
+ left = getLeftOpenInterval(distObj, leftDoor, type)
+ right = getRightOpenInterval(distObj, rightDoor, type)
+ openDuration = left.getDuration()
+ sfxVolume = ElevatorData[type]['sfxVolume']
+ if finalOpenSfx:
+ sound = Sequence(
+ SoundInterval(openSfx, duration = openDuration, volume = sfxVolume, node = leftDoor),
+ SoundInterval(finalOpenSfx, volume = sfxVolume, node = leftDoor),
+ )
+ else:
+ sound = SoundInterval(openSfx, volume = sfxVolume, node = leftDoor)
+ return Parallel(
+ # Localize to one of the doors
+ sound,
+ left,
+ right,
+ )
+
+def getLeftCloseInterval(distObj, leftDoor, type):
+ closeTime = ElevatorData[type]["closeTime"]
+ closedPos = getLeftClosePoint(type)
+ openPos = getLeftOpenPoint(type)
+ leftCloseInterval = LerpPosInterval(
+ leftDoor,
+ closeTime,
+ closedPos,
+ startPos = openPos,
+ blendType = "easeOut",
+ name = distObj.uniqueName("leftDoorClose"),
+ )
+ return leftCloseInterval
+
+def getRightCloseInterval(distObj, rightDoor, type):
+ closeTime = ElevatorData[type]["closeTime"]
+ closedPos = getRightClosePoint(type)
+ openPos = getRightOpenPoint(type)
+ rightCloseInterval = LerpPosInterval(
+ rightDoor,
+ closeTime,
+ closedPos,
+ startPos = openPos,
+ blendType = "easeOut",
+ name = distObj.uniqueName("rightDoorClose"),
+ )
+ return rightCloseInterval
+
+def getCloseInterval(distObj, leftDoor, rightDoor, closeSfx, finalCloseSfx, type = ELEVATOR_NORMAL):
+ left = getLeftCloseInterval(distObj, leftDoor, type)
+ right = getRightCloseInterval(distObj, rightDoor, type)
+ closeDuration = left.getDuration()
+ sfxVolume = ElevatorData[type]['sfxVolume']
+ if finalCloseSfx:
+ sound = Sequence(
+ SoundInterval(closeSfx, duration = closeDuration, volume = sfxVolume, node = leftDoor),
+ SoundInterval(finalCloseSfx, volume = sfxVolume, node = leftDoor),
+ )
+ else:
+ sound = SoundInterval(closeSfx, volume = sfxVolume, node = leftDoor)
+ return Parallel(
+ # Localize to one of the doors
+ sound,
+ left,
+ right,
+ )
+
+def getRideElevatorInterval(type = ELEVATOR_NORMAL):
+ # Returns an interval showing the camera tracking up and down as
+ # the elevator rides to the top.
+ if (type == ELEVATOR_VP) or (type == ELEVATOR_CFO) or (type==ELEVATOR_CJ):
+ yValue = 30
+ zMin = 7.8
+ zMid = 8
+ zMax = 8.2
+ elif (type == ELEVATOR_BB):
+ # update DistributedBossbotBoss.enterElevator if these values changs
+ yValue = 21
+ zMin = 7
+ zMid = 7.2
+ zMax = 7.4
+
+ if type in (ELEVATOR_VP, ELEVATOR_CFO, ELEVATOR_CJ, ELEVATOR_BB):
+ ival = Sequence(
+ Wait(0.5),
+ LerpPosInterval(camera,
+ 0.5,
+ Point3(0, yValue, zMin),
+ startPos=Point3(0, yValue, zMid),
+ blendType='easeOut'),
+ LerpPosInterval(camera,
+ 0.5,
+ Point3(0, yValue, zMid),
+ startPos=Point3(0, yValue, zMin)),
+ Wait(1.0),
+ LerpPosInterval(camera,
+ 0.5,
+ Point3(0, yValue, zMax),
+ startPos=Point3(0, yValue, zMid),
+ blendType='easeOut'),
+ LerpPosInterval(camera,
+ 1.0,
+ Point3(0, yValue, zMid),
+ startPos=Point3(0, yValue, zMax)),
+ )
+
+ else:
+ ival = Sequence(
+ Wait(0.5),
+ LerpPosInterval(camera,
+ 0.5,
+ Point3(0, 14, 3.8),
+ startPos=Point3(0, 14, 4),
+ blendType='easeOut'),
+ LerpPosInterval(camera,
+ 0.5,
+ Point3(0, 14, 4),
+ startPos=Point3(0, 14, 3.8)),
+ Wait(1.0),
+ LerpPosInterval(camera,
+ 0.5,
+ Point3(0, 14, 4.2),
+ startPos=Point3(0, 14, 4),
+ blendType='easeOut'),
+ LerpPosInterval(camera,
+ 1.0,
+ Point3(0, 14, 4),
+ startPos=Point3(0, 14, 4.2)),
+ )
+ return ival
diff --git a/toontown/src/building/FADoorCodes.py b/toontown/src/building/FADoorCodes.py
new file mode 100644
index 0000000..51763c8
--- /dev/null
+++ b/toontown/src/building/FADoorCodes.py
@@ -0,0 +1,60 @@
+# The following are codes that indicate different reasons that
+# a door might be locked.
+
+from toontown.toonbase import TTLocalizer
+
+# Unlocked
+UNLOCKED = 0
+
+# You must talk to "Tutorial Tom" before entering the tutorial street
+TALK_TO_TOM = 1
+
+# You must defeat the Flunky before you can enter toon hq.
+DEFEAT_FLUNKY_HQ = 2
+
+# You must talk to "HQ Harry" to get your reward
+TALK_TO_HQ = 3
+
+# You must talk to "HQ Harry" to get your reward
+WRONG_DOOR_HQ = 4
+
+# You must go to the playground
+GO_TO_PLAYGROUND = 5
+
+# You must defeat the Flunky before you can enter toon hq.
+DEFEAT_FLUNKY_TOM = 6
+
+# You must talk to "HQ Harry" to get your reward
+TALK_TO_HQ_TOM = 7
+
+# A suit is heading towards the building.
+SUIT_APPROACHING = 8
+
+# A suit is taking over the building. Stay away.
+BUILDING_TAKEOVER = 9
+
+# The toon does not have a complete cog diguise to enter the lobby
+SB_DISGUISE_INCOMPLETE = 10
+CB_DISGUISE_INCOMPLETE = 11
+LB_DISGUISE_INCOMPLETE = 12
+BB_DISGUISE_INCOMPLETE = 13
+
+# Strings associated with codes
+reasonDict = {
+ UNLOCKED: TTLocalizer.FADoorCodes_UNLOCKED,
+ TALK_TO_TOM: TTLocalizer.FADoorCodes_TALK_TO_TOM,
+ DEFEAT_FLUNKY_HQ: TTLocalizer.FADoorCodes_DEFEAT_FLUNKY_HQ,
+ TALK_TO_HQ: TTLocalizer.FADoorCodes_TALK_TO_HQ,
+ WRONG_DOOR_HQ: TTLocalizer.FADoorCodes_WRONG_DOOR_HQ,
+ GO_TO_PLAYGROUND: TTLocalizer.FADoorCodes_GO_TO_PLAYGROUND,
+ DEFEAT_FLUNKY_TOM: TTLocalizer.FADoorCodes_DEFEAT_FLUNKY_TOM,
+ TALK_TO_HQ_TOM: TTLocalizer.FADoorCodes_TALK_TO_HQ_TOM,
+ SUIT_APPROACHING: TTLocalizer.FADoorCodes_SUIT_APPROACHING,
+ BUILDING_TAKEOVER: TTLocalizer.FADoorCodes_BUILDING_TAKEOVER,
+ SB_DISGUISE_INCOMPLETE: TTLocalizer.FADoorCodes_SB_DISGUISE_INCOMPLETE,
+ CB_DISGUISE_INCOMPLETE: TTLocalizer.FADoorCodes_CB_DISGUISE_INCOMPLETE,
+ LB_DISGUISE_INCOMPLETE: TTLocalizer.FADoorCodes_LB_DISGUISE_INCOMPLETE,
+ BB_DISGUISE_INCOMPLETE: TTLocalizer.FADoorCodes_BB_DISGUISE_INCOMPLETE,
+ }
+
+
diff --git a/toontown/src/building/GagshopBuildingAI.py b/toontown/src/building/GagshopBuildingAI.py
new file mode 100644
index 0000000..5a8f31b
--- /dev/null
+++ b/toontown/src/building/GagshopBuildingAI.py
@@ -0,0 +1,66 @@
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+import DistributedDoorAI
+import DistributedGagshopInteriorAI
+import FADoorCodes
+import DoorTypes
+from toontown.toon import NPCToons
+from toontown.quest import Quests
+
+# This is not a distributed class... It just owns and manages some distributed
+# classes.
+
+class GagshopBuildingAI:
+ def __init__(self, air, exteriorZone, interiorZone, blockNumber):
+ # While this is not a distributed object, it needs to know about
+ # the repository.
+ self.air = air
+ self.exteriorZone = exteriorZone
+ self.interiorZone = interiorZone
+
+ self.setup(blockNumber)
+
+ def cleanup(self):
+ for npc in self.npcs:
+ npc.requestDelete()
+ del self.npcs
+ self.door.requestDelete()
+ del self.door
+ self.insideDoor.requestDelete()
+ del self.insideDoor
+ self.interior.requestDelete()
+ del self.interior
+ return
+
+ def setup(self, blockNumber):
+ # The interior
+ self.interior=DistributedGagshopInteriorAI.DistributedGagshopInteriorAI(
+ blockNumber, self.air, self.interiorZone)
+
+ #desc = (self.interiorZone, "HQ Officer", ('dls', 'ms', 'm', 'm', 6,0,6,6,40,8), "m", 1, 0)
+ #self.npc = NPCToons.createNPC(self.air, Quests.ToonHQ, desc, self.interiorZone)
+
+ self.npcs = NPCToons.createNpcsInZone(self.air, self.interiorZone)
+
+ self.interior.generateWithRequired(self.interiorZone)
+ # Outside door
+ door=DistributedDoorAI.DistributedDoorAI(
+ self.air, blockNumber, DoorTypes.EXT_STANDARD)
+ # Inside door
+ insideDoor=DistributedDoorAI.DistributedDoorAI(
+ self.air,
+ blockNumber,
+ DoorTypes.INT_STANDARD)
+ # Tell them about each other:
+ door.setOtherDoor(insideDoor)
+ insideDoor.setOtherDoor(door)
+ # Put them in the right zones
+ door.zoneId=self.exteriorZone
+ insideDoor.zoneId=self.interiorZone
+ # Now that they both now about each other, generate them:
+ door.generateWithRequired(self.exteriorZone)
+ insideDoor.generateWithRequired(self.interiorZone)
+ # keep track of them:
+ self.door=door
+ self.insideDoor=insideDoor
+ return
diff --git a/toontown/src/building/HQBuildingAI.py b/toontown/src/building/HQBuildingAI.py
new file mode 100644
index 0000000..a9f8245
--- /dev/null
+++ b/toontown/src/building/HQBuildingAI.py
@@ -0,0 +1,110 @@
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+import DistributedDoorAI
+import DistributedHQInteriorAI
+import FADoorCodes
+import DoorTypes
+from toontown.toon import NPCToons
+from toontown.quest import Quests
+
+# This is not a distributed class... It just owns and manages some distributed
+# classes.
+
+class HQBuildingAI:
+ def __init__(self, air, exteriorZone, interiorZone, blockNumber):
+ # While this is not a distributed object, it needs to know about
+ # the repository.
+ self.air = air
+ self.exteriorZone = exteriorZone
+ self.interiorZone = interiorZone
+
+ self.setup(blockNumber)
+
+ def cleanup(self):
+ for npc in self.npcs:
+ npc.requestDelete()
+ del self.npcs
+ self.door0.requestDelete()
+ del self.door0
+ self.door1.requestDelete()
+ del self.door1
+ self.insideDoor0.requestDelete()
+ del self.insideDoor0
+ self.insideDoor1.requestDelete()
+ del self.insideDoor1
+ self.interior.requestDelete()
+ del self.interior
+ return
+
+ def setup(self, blockNumber):
+ # The interior
+ self.interior=DistributedHQInteriorAI.DistributedHQInteriorAI(
+ blockNumber, self.air, self.interiorZone)
+
+ #desc = (self.interiorZone, "HQ Officer", ('dls', 'ms', 'm', 'm', 6,0,6,6,40,8), "m", 1, 0)
+ #self.npc = NPCToons.createNPC(self.air, Quests.ToonHQ, desc, self.interiorZone)
+
+ self.npcs = NPCToons.createNpcsInZone(self.air, self.interiorZone)
+
+ self.interior.generateWithRequired(self.interiorZone)
+ # Outside door 0.
+ door0=DistributedDoorAI.DistributedDoorAI(
+ self.air, blockNumber, DoorTypes.EXT_HQ,
+ doorIndex=0)
+ # Outside door 1.
+ door1=DistributedDoorAI.DistributedDoorAI(
+ self.air, blockNumber, DoorTypes.EXT_HQ,
+ doorIndex=1)
+ # Inside door 0.
+ insideDoor0=DistributedDoorAI.DistributedDoorAI(
+ self.air,
+ blockNumber,
+ DoorTypes.INT_HQ,
+ doorIndex=0)
+ # Inside door 1.
+ insideDoor1=DistributedDoorAI.DistributedDoorAI(
+ self.air,
+ blockNumber,
+ DoorTypes.INT_HQ,
+ doorIndex=1)
+ # Tell them about each other:
+ door0.setOtherDoor(insideDoor0)
+ insideDoor0.setOtherDoor(door0)
+ door1.setOtherDoor(insideDoor1)
+ insideDoor1.setOtherDoor(door1)
+ # Put them in the right zones
+ door0.zoneId=self.exteriorZone
+ door1.zoneId=self.exteriorZone
+ insideDoor0.zoneId=self.interiorZone
+ insideDoor1.zoneId=self.interiorZone
+ # Now that they both now about each other, generate them:
+ door0.generateWithRequired(self.exteriorZone)
+ door1.generateWithRequired(self.exteriorZone)
+ door0.sendUpdate("setDoorIndex", [door0.getDoorIndex()])
+ door1.sendUpdate("setDoorIndex", [door1.getDoorIndex()])
+ insideDoor0.generateWithRequired(self.interiorZone)
+ insideDoor1.generateWithRequired(self.interiorZone)
+ insideDoor0.sendUpdate("setDoorIndex", [insideDoor0.getDoorIndex()])
+ insideDoor1.sendUpdate("setDoorIndex", [insideDoor1.getDoorIndex()])
+ # keep track of them:
+ self.door0=door0
+ self.door1=door1
+ self.insideDoor0=insideDoor0
+ self.insideDoor1=insideDoor1
+ return
+
+ def isSuitBlock(self):
+ # For compatibility with DistributedBuildingAI
+ return 0
+
+ def isSuitBuilding(self):
+ # For compatibility with DistributedBuildingAI
+ return 0
+
+ def isCogdo(self):
+ # For compatibility with DistributedBuildingAI
+ return 0
+
+ def isEstablishedSuitBlock(self):
+ # For compatibility with DistributedBuildingAI
+ return 0
diff --git a/toontown/src/building/KartShopBuildingAI.py b/toontown/src/building/KartShopBuildingAI.py
new file mode 100644
index 0000000..46adad7
--- /dev/null
+++ b/toontown/src/building/KartShopBuildingAI.py
@@ -0,0 +1,116 @@
+##########################################################################
+# Module: KartShopBuildingAI.py
+# Purpose: This module oversees the construction of the KartShop Interior
+# and KartShop NPC objects on the AI Server-side.
+# Date: 6/9/05
+# Author: jjtaylor (jjtaylor@schellgames.com)
+##########################################################################
+
+##########################################################################
+# Panda/Direct Import Modules
+##########################################################################
+from direct.directnotify import DirectNotifyGlobal
+from pandac.PandaModules import *
+
+##########################################################################
+# Toontwon Import Modules
+##########################################################################
+from toontown.building import FADoorCodes, DoorTypes
+from toontown.building.DistributedDoorAI import DistributedDoorAI
+from toontown.building.DistributedKartShopInteriorAI import DistributedKartShopInteriorAI
+from toontown.hood import ZoneUtil
+from toontown.toon import NPCToons
+from toontown.toonbase import ToontownGlobals
+
+if( __debug__ ):
+ import pdb
+
+class KartShopBuildingAI:
+ """
+ Purpose: The KartShopBuildingAI Class oversees the creation of the
+ KartShop Interior Object as well as the NPC clerks that are found
+ within the KartShop.
+ """
+
+ # Initialize Class Variables
+ notify = DirectNotifyGlobal.directNotify.newCategory( "KartShopBuildingAI" )
+
+ def __init__( self, air, exteriorZone, interiorZone, blockNumber ):
+ """
+ Purpose: The __init__ Method provides the appropriate initialization
+ of the KartShopBuildingAI object, including instance variables.
+
+ Params: air - Reference to the AI Repository.
+ exteriorZone - b
+ interiorZone - b
+ blockNumber - b
+ Return: None
+ """
+ self.air = air
+ self.exteriorZone = exteriorZone
+ self.interiorZone = interiorZone
+
+ self.setup( blockNumber )
+
+ def cleanup( self ):
+ """
+ Purpose: The cleanup Method properly handles the cleanup of the
+ references and objects that are associated with the
+ """
+
+ # Request Delete for NPC Objects
+ for npc in self.npcs:
+ npc.requestDelete()
+ del self.npcs
+
+ # Request Delete for Door Objects
+ self.outsideDoor0.requestDelete()
+ self.outsideDoor1.requestDelete()
+ self.insideDoor0.requestDelete()
+ self.insideDoor1.requestDelete()
+
+ # Remove door object references
+ del self.outsideDoor0, self.insideDoor0
+ del self.outsideDoor1, self.insideDoor1
+
+ self.kartShopInterior.requestDelete()
+ del self.kartShopInterior
+
+ def setup( self, blockNumber ):
+ # Create the Interior Object on the AI Side
+ self.kartShopInterior = DistributedKartShopInteriorAI( blockNumber, self.air, self.interiorZone )
+ # Initialize the npc clerks.
+ self.npcs = NPCToons.createNpcsInZone( self.air, self.interiorZone )
+
+ # Tell the DistributedKartShopInteriorAI object to generate.
+ self.kartShopInterior.generateWithRequired( self.interiorZone )
+
+ # Handle the Door Generation.
+ # TODO - NEED TO CHANGE THE DOOR DGG.TYPE AND HANDLE IT IN DistributedDoor.py
+ self.outsideDoor0 = DistributedDoorAI( self.air, blockNumber, DoorTypes.EXT_KS, doorIndex = 1 )
+ self.outsideDoor1 = DistributedDoorAI( self.air, blockNumber, DoorTypes.EXT_KS, doorIndex = 2 )
+ self.insideDoor0 = DistributedDoorAI( self.air, blockNumber, DoorTypes.INT_KS, doorIndex = 1 )
+ self.insideDoor1 = DistributedDoorAI( self.air, blockNumber, DoorTypes.INT_KS, doorIndex = 2 )
+
+ # Assign inside and outside doors to one another, respectively.
+ self.outsideDoor0.setOtherDoor( self.insideDoor0 )
+ self.outsideDoor1.setOtherDoor( self.insideDoor1 )
+ self.insideDoor0.setOtherDoor( self.outsideDoor0 )
+ self.insideDoor1.setOtherDoor( self.outsideDoor1 )
+
+ # Place the doors in the proper zones.
+ self.outsideDoor0.zoneId = self.exteriorZone
+ self.outsideDoor1.zoneId = self.exteriorZone
+ self.insideDoor0.zoneId = self.interiorZone
+ self.insideDoor1.zoneId = self.interiorZone
+
+ # Generate the Doors
+ self.outsideDoor0.generateWithRequired( self.exteriorZone )
+ self.outsideDoor1.generateWithRequired( self.exteriorZone )
+ self.insideDoor0.generateWithRequired( self.interiorZone )
+ self.insideDoor1.generateWithRequired( self.interiorZone )
+
+ self.outsideDoor0.sendUpdate( "setDoorIndex", [ self.outsideDoor0.getDoorIndex() ] )
+ self.outsideDoor1.sendUpdate( "setDoorIndex", [ self.outsideDoor1.getDoorIndex() ] )
+ self.insideDoor0.sendUpdate( "setDoorIndex", [ self.insideDoor0.getDoorIndex() ] )
+ self.insideDoor1.sendUpdate( "setDoorIndex", [ self.insideDoor1.getDoorIndex() ] )
diff --git a/toontown/src/building/KnockKnockJokes.py b/toontown/src/building/KnockKnockJokes.py
new file mode 100644
index 0000000..2d932cf
--- /dev/null
+++ b/toontown/src/building/KnockKnockJokes.py
@@ -0,0 +1,32 @@
+
+
+# This file contains the data for the knock, knock jokes.
+# "Knock, knock.", "Who's there?", and " who?" are assumed.
+#
+# For example:
+# KnockKnockJokes = {
+# ["Dave",
+# "Dave Schuyler"],
+# ["Jan",
+# "Jan Wallace"],
+# }
+#
+# ...would be output by the program as:
+# Knock, knock.
+# Who's There?
+# Dave
+# Dave Who?
+# Dave Schuyler
+#
+# Knock, knock.
+# Who's There?
+# Jan
+# Jan Who?
+# Jan Wallace
+
+from toontown.toonbase import TTLocalizer
+KnockKnockJokes = TTLocalizer.KnockKnockJokes
+KnockKnockContestJokes = TTLocalizer.KnockKnockContestJokes
+
+
+
diff --git a/toontown/src/building/PetshopBuildingAI.py b/toontown/src/building/PetshopBuildingAI.py
new file mode 100644
index 0000000..a3c092f
--- /dev/null
+++ b/toontown/src/building/PetshopBuildingAI.py
@@ -0,0 +1,83 @@
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+import DistributedDoorAI
+import DistributedPetshopInteriorAI
+import FADoorCodes
+import DoorTypes
+from toontown.toon import NPCToons
+from toontown.toonbase import ToontownGlobals
+from toontown.quest import Quests
+from toontown.pets import DistributedPetAI, PetTraits, PetUtil
+from toontown.hood import ZoneUtil
+
+# This is not a distributed class... It just owns and manages some distributed
+# classes.
+
+class PetshopBuildingAI:
+ def __init__(self, air, exteriorZone, interiorZone, blockNumber):
+ # While this is not a distributed object, it needs to know about
+ # the repository.
+ self.air = air
+ self.exteriorZone = exteriorZone
+ self.interiorZone = interiorZone
+
+ self.setup(blockNumber)
+
+ def cleanup(self):
+ for npc in self.npcs:
+ npc.requestDelete()
+ del self.npcs
+ self.door.requestDelete()
+ del self.door
+ self.insideDoor.requestDelete()
+ del self.insideDoor
+ self.interior.requestDelete()
+ del self.interior
+ return
+
+ def setup(self, blockNumber):
+ # The interior
+ self.interior=DistributedPetshopInteriorAI.DistributedPetshopInteriorAI(
+ blockNumber, self.air, self.interiorZone)
+
+ self.npcs = NPCToons.createNpcsInZone(self.air, self.interiorZone)
+
+ seeds = self.air.petMgr.getAvailablePets(1, len(self.npcs))
+ #for i in range(len(self.npcs)):
+ # self.wanderingPets = self.createPet(self.npcs[i].doId, seeds[i])
+
+ self.interior.generateWithRequired(self.interiorZone)
+ # Outside door
+ door=DistributedDoorAI.DistributedDoorAI(
+ self.air, blockNumber, DoorTypes.EXT_STANDARD)
+ # Inside door
+ insideDoor=DistributedDoorAI.DistributedDoorAI(
+ self.air,
+ blockNumber,
+ DoorTypes.INT_STANDARD)
+ # Tell them about each other:
+ door.setOtherDoor(insideDoor)
+ insideDoor.setOtherDoor(door)
+ # Put them in the right zones
+ door.zoneId=self.exteriorZone
+ insideDoor.zoneId=self.interiorZone
+ # Now that they both now about each other, generate them:
+ door.generateWithRequired(self.exteriorZone)
+ insideDoor.generateWithRequired(self.interiorZone)
+ # keep track of them:
+ self.door=door
+ self.insideDoor=insideDoor
+ return
+
+ def createPet(self, ownerId, seed):
+ zoneId = self.interiorZone
+ safeZoneId = ZoneUtil.getCanonicalSafeZoneId(zoneId)
+ name, dna, traitSeed = PetUtil.getPetInfoFromSeed(seed, safeZoneId)
+
+ pet = DistributedPetAI.DistributedPetAI(self.air, dna = dna)
+ pet.setOwnerId(ownerId)
+ pet.setPetName(name)
+ pet.traits = PetTraits.PetTraits(traitSeed=traitSeed, safeZoneId=safeZoneId)
+ pet.generateWithRequired(zoneId)
+ pet.setPos(0, 0, 0)
+ pet.b_setParent(ToontownGlobals.SPRender)
diff --git a/toontown/src/building/Sources.pp b/toontown/src/building/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/toontown/src/building/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/toontown/src/building/SuitBuildingGlobals.py b/toontown/src/building/SuitBuildingGlobals.py
new file mode 100644
index 0000000..d3c399e
--- /dev/null
+++ b/toontown/src/building/SuitBuildingGlobals.py
@@ -0,0 +1,149 @@
+from ElevatorConstants import *
+
+# floor and suit information for all suit buildings, organized by each
+# level of suit that originally took over the building (minus 1), used
+# to determine how many and what level of suits to create for the suit
+# interiors
+#
+# 1 number of floors for this level building
+# 2 suit level range, excluding the boss
+# 3 boss level range
+# 4 base level pool for total suits on each floor of the building
+# 5 multipliers for item 4 for each floor of the building, generally
+# each consecutive floor increases the base range of the level pool
+# 6 are they v2.0 cogs
+#
+SuitBuildingInfo = (
+ # building difficulty 0 (suit level 1)
+ ( ( 1, 1 ), # 1
+ ( 1, 3 ), # 2
+ ( 4, 4 ), # 3
+ ( 8, 10 ), # 4
+ ( 1, ) ), # 5
+ # building difficulty 1 (suit level 2)
+ ( ( 1, 2 ),
+ ( 2, 4 ),
+ ( 5, 5 ),
+ ( 8, 10 ),
+ ( 1, 1.2 ) ),
+ # building difficulty 2 (suit level 3)
+ ( ( 1, 3 ),
+ ( 3, 5 ),
+ ( 6, 6 ),
+ ( 8, 10 ),
+ ( 1, 1.3, 1.6 ) ),
+ # building difficulty 3 (suit level 4)
+ ( ( 2, 3 ),
+ ( 4, 6 ),
+ ( 7, 7 ),
+ ( 8, 10 ),
+ ( 1, 1.4, 1.8 ) ),
+ # building difficulty 4 (suit level 5)
+ ( ( 2, 4 ),
+ ( 5, 7 ),
+ ( 8, 8 ),
+ ( 8, 10 ),
+ ( 1, 1.6, 1.8, 2 ) ),
+ # building difficulty 5 (suit level 6)
+ ( ( 3, 4 ),
+ ( 6, 8 ),
+ ( 9, 9 ),
+ ( 10, 12 ),
+ ( 1, 1.6, 2, 2.4 ) ),
+ # building difficulty 6 (suit level 7)
+ ( ( 3, 5 ),
+ ( 7, 9 ),
+ ( 10, 10 ),
+ ( 10, 14 ),
+ ( 1, 1.6, 1.8, 2.2, 2.4 ) ),
+ # building difficulty 7 (suit level 8)
+ ( ( 4, 5 ),
+ ( 8, 10 ),
+ ( 11, 11 ),
+ ( 12, 16 ),
+ ( 1, 1.8, 2.4, 3, 3.2 ) ),
+ # building difficulty 8 (suit level 9)
+ ( ( 5, 5 ),
+ ( 9, 11 ),
+ ( 12, 12 ),
+ ( 14, 20 ),
+ ( 1.4, 1.8, 2.6, 3.4, 4 ) ),
+
+ # building difficulty 9. This is a special difficulty level that
+ # is used only for the first battle with the Sellbot V.P. No
+ # buildings in the world outside of CogHQ have difficulty level 9.
+ ( ( 1, 1 ),
+ ( 1, 12 ),
+ ( 12, 12 ),
+ ( 67, 67 ),
+ ( 1, 1, 1, 1, 1 ) ),
+
+ # building difficulty 10. Same as above, for the second battle with
+ # the Sellbot V.P. These are skelecogs.
+ ( ( 1, 1 ),
+ ( 8, 12 ),
+ ( 12, 12 ),
+ ( 100, 100 ),
+ ( 1, 1, 1, 1, 1 ) ),
+
+ # building difficulty 11, first battle with Cashbot V.P. These
+ # are normal cogs.
+ ( ( 1, 1 ),
+ ( 1, 12 ),
+ ( 12, 12 ),
+ ( 100, 100 ),
+ ( 1, 1, 1, 1, 1 ) ),
+
+ # building difficulty 12, first battle with Cashbot V.P., but
+ # these are the skelecogs. In the cashbot battle, both normal
+ # cogs and skelecogs are mixed up together.
+ ( ( 1, 1 ),
+ ( 8, 12 ),
+ ( 12, 12 ),
+ ( 150, 150 ),
+ ( 1, 1, 1, 1, 1 ) ),
+
+ # building difficulty 13, first battle with Lawbot Boss. These
+ # are normal cogs.
+ ( ( 1, 1 ),
+ ( 8, 12 ),
+ ( 12, 12 ),
+ ( 275, 275 ),
+ ( 1, 1, 1, 1, 1 ) ),
+
+ # building difficulty 14, first battle with Bossbot Boss. These
+ # are v2.0 cogs. Even though it has less total suit levels, they
+ # are fought twice!
+ ( ( 1, 1 ),
+ ( 9, 12 ),
+ ( 12, 12 ),
+ ( 206, 206 ),
+ ( 1, 1, 1, 1, 1),
+ ( 1, ) ),
+ )
+
+
+SUIT_BLDG_INFO_FLOORS = 0
+SUIT_BLDG_INFO_SUIT_LVLS = 1
+SUIT_BLDG_INFO_BOSS_LVLS = 2
+SUIT_BLDG_INFO_LVL_POOL = 3
+SUIT_BLDG_INFO_LVL_POOL_MULTS = 4
+SUIT_BLDG_INFO_REVIVES = 5
+
+
+# how long it takes for a building to transition from a toon
+# to suit building, and vice-versa
+#
+
+VICTORY_RUN_TIME = ElevatorData[ELEVATOR_NORMAL]['openTime'] + \
+ TOON_VICTORY_EXIT_TIME
+TO_TOON_BLDG_TIME = 8
+VICTORY_SEQUENCE_TIME = VICTORY_RUN_TIME + TO_TOON_BLDG_TIME
+CLEAR_OUT_TOON_BLDG_TIME = 4
+TO_SUIT_BLDG_TIME = 8
+
+
+# History
+#
+# 14Aug01 jlbutler created.
+
diff --git a/toontown/src/building/SuitInterior.py b/toontown/src/building/SuitInterior.py
new file mode 100644
index 0000000..e52d93a
--- /dev/null
+++ b/toontown/src/building/SuitInterior.py
@@ -0,0 +1,357 @@
+from pandac.PandaModules import *
+from toontown.toonbase.ToonBaseGlobal import *
+
+from direct.directnotify import DirectNotifyGlobal
+from toontown.hood import Place
+from direct.showbase import DirectObject
+from direct.fsm import StateData
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.town import TownBattle
+from toontown.suit import Suit
+import Elevator
+from direct.task.Task import Task
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import ToontownBattleGlobals
+
+class SuitInterior(Place.Place):
+ """SuitInterior class"""
+
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("SuitInterior")
+
+ # special methods
+
+ def __init__(self, loader, parentFSM, doneEvent):
+ """
+ SuitInterior constructor: create a play game ClassicFSM
+ """
+ Place.Place.__init__(self, loader, doneEvent)
+
+ self.fsm = ClassicFSM.ClassicFSM('SuitInterior',
+ [State.State('entrance',
+ self.enterEntrance,
+ self.exitEntrance,
+ ['battle', 'walk']),
+ State.State('Elevator',
+ self.enterElevator,
+ self.exitElevator,
+ ['battle', 'walk']),
+ State.State('battle',
+ self.enterBattle,
+ self.exitBattle,
+ ['walk', 'died']),
+ State.State('walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['stickerBook', 'stopped',
+ 'sit', 'died',
+ 'teleportOut',
+ 'Elevator',
+ 'DFA', 'trialerFA',]),
+ State.State('sit',
+ self.enterSit,
+ self.exitSit,
+ ['walk',]),
+ State.State('stickerBook',
+ self.enterStickerBook,
+ self.exitStickerBook,
+ ['walk', 'stopped', 'sit', 'died',
+ 'DFA', 'trialerFA',
+ 'teleportOut', 'Elevator',]),
+ # Trialer Force Acknowledge:
+ State.State('trialerFA',
+ self.enterTrialerFA,
+ self.exitTrialerFA,
+ ['trialerFAReject', 'DFA']),
+ State.State('trialerFAReject',
+ self.enterTrialerFAReject,
+ self.exitTrialerFAReject,
+ ['walk']),
+ State.State('DFA',
+ self.enterDFA,
+ self.exitDFA,
+ ['DFAReject', 'teleportOut']),
+ State.State('DFAReject',
+ self.enterDFAReject,
+ self.exitDFAReject,
+ ['walk']),
+ State.State('teleportIn',
+ self.enterTeleportIn,
+ self.exitTeleportIn,
+ ['walk']),
+ State.State('teleportOut',
+ self.enterTeleportOut,
+ self.exitTeleportOut,
+ ['teleportIn']),
+ State.State('stopped',
+ self.enterStopped,
+ self.exitStopped,
+ ['walk', 'elevatorOut']),
+ State.State('died',
+ self.enterDied,
+ self.exitDied,
+ []),
+ State.State('elevatorOut',
+ self.enterElevatorOut,
+ self.exitElevatorOut,
+ [])],
+ # Initial State
+ 'entrance',
+ # Final State
+ 'elevatorOut',
+ )
+ self.parentFSM = parentFSM
+ self.elevatorDoneEvent = "elevatorDoneSI"
+
+ # This is updated each floor by the DistributedSuitInterior.
+ self.currentFloor = 0
+
+ def enter(self, requestStatus):
+ assert(self.notify.debug("enter(requestStatus="+str(requestStatus)+")"))
+ self.fsm.enterInitialState()
+ # Let the safe zone manager know that we are here.
+ #messenger.send("enterToonInterior")
+
+ #self.geom.reparentTo(render)
+
+ self.zoneId = requestStatus['zoneId']
+ self.accept("DSIDoneEvent", self.handleDSIDoneEvent)
+
+ def exit(self):
+ assert(self.notify.debug("exit()"))
+ self.ignoreAll()
+ # Let the safe zone manager know that we are leaving
+ #messenger.send("exitToonInterior")
+ #self.geom.reparentTo(hidden)
+
+ # Turn off the little red arrows.
+ #NametagGlobals.setMasterArrowsOn(0)
+
+ def load(self):
+ assert(self.notify.debug("load()"))
+ # Call up the chain
+ Place.Place.load(self)
+ self.parentFSM.getStateNamed("suitInterior").addChild(self.fsm)
+ self.townBattle = TownBattle.TownBattle('town-battle-done')
+ self.townBattle.load()
+ for i in range(1, 3):
+ Suit.loadSuits(i)
+
+ def unload(self):
+ assert(self.notify.debug("unload()"))
+ # Call up the chain
+ Place.Place.unload(self)
+
+ self.parentFSM.getStateNamed("suitInterior").removeChild(self.fsm)
+ del self.parentFSM
+ del self.fsm
+ #self.geom.removeNode()
+ #del self.geom
+ self.ignoreAll()
+ # Get rid of any references to models or textures from this safe zone
+ ModelPool.garbageCollect()
+ TexturePool.garbageCollect()
+ self.townBattle.unload()
+ self.townBattle.cleanup()
+ del self.townBattle
+
+ for i in range(1, 3):
+ Suit.unloadSuits(i)
+
+ def setState(self, state, battleEvent=None):
+ assert(self.notify.debug("setState(state="+str(state)
+ +", battleEvent="+str(battleEvent)+")"))
+ if (battleEvent):
+ self.fsm.request(state, [battleEvent])
+ else:
+ self.fsm.request(state)
+
+ def getZoneId(self):
+ """
+ Returns the current zone ID.
+ """
+ return self.zoneId
+
+ def enterZone(self, zoneId):
+ assert(self.notify.debug('enterZone() - %d' % zoneId))
+ pass
+
+ def isPeriodTimerEffective(self):
+ """
+ Returns true if the period timer will be honored if it expires
+ in this kind of Place (and we're also in a suitable mode).
+ Generally, SuitInterior returns false, and other kinds of
+ Place return true.
+ """
+ return 0
+
+ def handleDSIDoneEvent(self, requestStatus):
+ self.doneStatus = requestStatus
+ messenger.send(self.doneEvent)
+
+ def doRequestLeave(self, requestStatus):
+ # when it's time to leave, check their trialer status first
+ self.fsm.request('trialerFA', [requestStatus])
+
+ # Elevator state
+
+ def enterEntrance(self):
+ return
+
+ def exitEntrance(self):
+ return
+
+ def enterElevator(self, distElevator):
+ assert(self.notify.debug('enterElevator()'))
+ self.accept(self.elevatorDoneEvent, self.handleElevatorDone)
+ self.elevator = Elevator.Elevator(self.fsm.getStateNamed("Elevator"),
+ self.elevatorDoneEvent,
+ distElevator)
+ self.elevator.load()
+ self.elevator.enter()
+ # Disable leave to pay / set parent password
+ base.localAvatar.cantLeaveGame = 1
+ return
+
+ def exitElevator(self):
+ base.localAvatar.cantLeaveGame = 0
+ self.ignore(self.elevatorDoneEvent)
+ self.elevator.unload()
+ self.elevator.exit()
+ del self.elevator
+ return None
+
+ def detectedElevatorCollision(self, distElevator):
+ assert(self.notify.debug("detectedElevatorCollision()"))
+ self.fsm.request("Elevator", [distElevator])
+ return None
+
+ def handleElevatorDone(self, doneStatus):
+ assert(self.notify.debug("handleElevatorDone()"))
+ self.notify.debug("handling elevator done event")
+ where = doneStatus['where']
+ if (where == 'reject'):
+ # If there has been a reject the Elevator should show an
+ # elevatorNotifier message and put the toon in the stopped state.
+ # Don't request the walk state here. Let the the toon be stuck in the
+ # stopped state till the player removes that message from his screen.
+ # Removing the message will automatically put him in the walk state there.
+ # Put the player in the walk state only if there is no elevator message.
+ if hasattr(base.localAvatar, "elevatorNotifier") and base.localAvatar.elevatorNotifier.isNotifierOpen():
+ pass
+ else:
+ self.fsm.request("walk")
+ elif (where == 'exit'):
+ self.fsm.request("walk")
+ elif (where == 'suitInterior'):
+ pass
+ else:
+ self.notify.error("Unknown mode: " + +
+ " in handleElevatorDone")
+
+ # Battle state
+
+ def enterBattle(self, event):
+ assert(self.notify.debug("enterBattle()"))
+
+ # Get the floor multiplier
+ mult = ToontownBattleGlobals.getCreditMultiplier(self.currentFloor)
+ self.townBattle.enter(event, self.fsm.getStateNamed("battle"),
+ bldg=1, creditMultiplier=mult)
+
+ # Make sure the toon's anim state gets reset
+ base.localAvatar.b_setAnimState('off', 1)
+
+ # Disable leave to pay / set parent password
+ base.localAvatar.cantLeaveGame = 1
+
+ def exitBattle(self):
+ assert(self.notify.debug("exitBattle()"))
+ self.townBattle.exit()
+ base.localAvatar.cantLeaveGame = 0
+
+ # walk state inherited from Place.py
+ def enterWalk(self, teleportIn=0):
+ Place.Place.enterWalk(self, teleportIn)
+ self.ignore('teleportQuery')
+ base.localAvatar.setTeleportAvailable(0)
+
+ # sticker book state inherited from Place.py
+ def enterStickerBook(self, page = None):
+ Place.Place.enterStickerBook(self, page)
+ self.ignore('teleportQuery')
+ base.localAvatar.setTeleportAvailable(0)
+
+ # sit state inherited from Place.py
+ def enterSit(self):
+ Place.Place.enterSit(self)
+ self.ignore('teleportQuery')
+ base.localAvatar.setTeleportAvailable(0)
+
+ # teleport in state
+
+ def enterTeleportIn(self, requestStatus):
+ # We can only teleport in if our goHome or teleport to toon
+ # request failed.
+ # Set localToon to the starting position within the
+ # interior
+ base.localAvatar.setPosHpr(2.5, 11.5, ToontownGlobals.FloorOffset,
+ 45.0, 0.0, 0.0)
+
+ Place.Place.enterTeleportIn(self, requestStatus)
+
+ # teleport out state
+
+ def enterTeleportOut(self, requestStatus):
+ assert(self.notify.debug('enterTeleportOut()'))
+ Place.Place.enterTeleportOut(self, requestStatus,
+ self.__teleportOutDone)
+
+ def __teleportOutDone(self, requestStatus):
+ # Get out of here.
+ hoodId = requestStatus["hoodId"]
+ if hoodId == ToontownGlobals.MyEstate:
+ # We are trying to go to an estate. This request might fail
+ # if we are going to a toon's estate that we are not friends with.
+ # So we don't want to tell the AI that we are leaving right away.
+ # We will rely on the Place.Place.goHome function to do that if
+ # the teleport to estate request is successful.
+ self.getEstateZoneAndGoHome(requestStatus)
+ else:
+ # Let the DSI know that we are leaving.
+ messenger.send("localToonLeft")
+ self.doneStatus = requestStatus
+ messenger.send(self.doneEvent)
+
+ def exitTeleportOut(self):
+ Place.Place.exitTeleportOut(self)
+
+ def goHomeFailed(self, task):
+ # it took too long to hear back from the server,
+ # or we tried going to a non-friends house
+ self.notifyUserGoHomeFailed()
+ # ignore the setLocalEstateZone message
+ self.ignore("setLocalEstateZone")
+ self.doneStatus["avId"] = -1
+ self.doneStatus["zoneId"] = self.getZoneId()
+ self.fsm.request("teleportIn", [self.doneStatus])
+ return Task.done
+
+
+ # elevatorOut state
+
+ def enterElevatorOut(self):
+ assert(self.notify.debug('enterElevatorOut()'))
+ # TODO: Eventually, we will have a sequence here (like iris out)
+ # and when it is done, it should find a way to call __elevatorOutDone.
+ # for now, we'll just call it directly.
+ #self.__elevatorOutDone(
+ return None
+
+ def __elevatorOutDone(self, requestStatus):
+ self.doneStatus = requestStatus
+ messenger.send(self.doneEvent)
+
+ def exitElevatorOut(self):
+ return None
diff --git a/toontown/src/building/SuitPlannerInteriorAI.py b/toontown/src/building/SuitPlannerInteriorAI.py
new file mode 100644
index 0000000..1a959e4
--- /dev/null
+++ b/toontown/src/building/SuitPlannerInteriorAI.py
@@ -0,0 +1,404 @@
+""" SuitPlannerInteriorAI module: contains the SuitPlannerInteriorAI
+ class which handles management of all suits within a suit building."""
+
+# AI code should not import ShowBaseGlobal because it creates a graphics window
+# Use AIBaseGlobal instead
+from otp.ai.AIBaseGlobal import *
+
+import random
+from toontown.suit import SuitDNA
+from direct.directnotify import DirectNotifyGlobal
+from toontown.suit import DistributedSuitAI
+import SuitBuildingGlobals
+import types
+
+class SuitPlannerInteriorAI:
+ """
+ // SuitPlannerInteriorAI class: manages all suits which exist within
+ // a single suit building. This object only exists on the server AI.
+ //
+ // Attributes:
+ // none
+ """
+
+ # load a config file value to see if we should print out information
+ # about this suit while it is thinking
+ #
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'SuitPlannerInteriorAI')
+
+ def __init__( self, numFloors, bldgLevel, bldgTrack, zone ):
+ # when the suit planner interior is created, create information
+ # about all suits that will exist in this building
+ #
+
+ self.dbg_4SuitsPerFloor = config.GetBool("4-suits-per-floor", 0)
+ self.dbg_1SuitPerFloor = config.GetBool("1-suit-per-floor", 0) # formerly called 'wuss-suits'
+
+ self.zoneId = zone
+ self.numFloors = numFloors
+
+ # By default, if an invasion is in progress we only generate
+ # suits of that kind. Set this false to turn off this
+ # behavior.
+ self.respectInvasions = 1
+
+ # This dbg var forces the creations of all 1 suit type (overrides level/type restrictions)
+ dbg_defaultSuitName = simbase.config.GetString('suit-type', 'random')
+ if (dbg_defaultSuitName == 'random'):
+ self.dbg_defaultSuitType = None
+ else:
+ self.dbg_defaultSuitType = SuitDNA.getSuitType(dbg_defaultSuitName)
+
+ if (isinstance(bldgLevel, types.StringType)):
+ self.notify.warning('bldgLevel is a string!')
+ bldgLevel = int(bldgLevel)
+ self._genSuitInfos( numFloors, bldgLevel, bldgTrack )
+ assert(len(self.suitInfos) > 0)
+
+ def __genJoinChances( self, num ):
+ joinChances = []
+ for currChance in range( num ):
+ joinChances.append( random.randint( 1, 100 ) )
+ joinChances.sort( cmp )
+ return joinChances
+
+ def _genSuitInfos( self, numFloors, bldgLevel, bldgTrack ):
+ """
+ // Function: create information about all suits that will exist
+ // in this building
+ // Parameters: numFloors, number of floors in the building
+ // bldgLevel, how difficult the building is, based on
+ // the suit that initially took the building
+ // bldgTrack, the track of the building, based on the
+ // track that initially took the building
+ // Changes:
+ """
+ self.suitInfos = []
+ self.notify.debug( "\n\ngenerating suitsInfos with numFloors (" +
+ str( numFloors ) +
+ ") bldgLevel (" +
+ str( bldgLevel ) +
+ "+1) and bldgTrack (" +
+ str( bldgTrack ) + ")" )
+
+ assert(bldgLevel >= 0 and
+ bldgLevel < len(SuitBuildingGlobals.SuitBuildingInfo))
+ assert(numFloors > 0)
+
+ # process each floor in the building and create all active and
+ # reserve suits
+ #
+ for currFloor in range(numFloors):
+ infoDict = {}
+ lvls = self.__genLevelList(bldgLevel, currFloor, numFloors)
+
+ # now randomly decide how many suits will be active and how
+ # many will be in reserve, create the active suit objects
+ # create the active suits in order of highest level to lowest
+ # this is only because we want to make sure the highest level
+ # active suit is in the first position in the list
+ #
+ activeDicts = []
+
+ if (self.dbg_4SuitsPerFloor):
+ numActive = 4
+ else:
+ numActive = random.randint( 1, min( 4, len( lvls ) ) )
+
+ if ((currFloor + 1) == numFloors and len(lvls) > 1):
+ # Make the boss be suit 1 (unless there is only 1 active suit)
+ origBossSpot = len(lvls) - 1
+ if (numActive == 1):
+ newBossSpot = numActive - 1
+ else:
+ newBossSpot = numActive - 2
+ tmp = lvls[newBossSpot]
+ lvls[newBossSpot] = lvls[origBossSpot]
+ lvls[origBossSpot] = tmp
+
+ bldgInfo = SuitBuildingGlobals.SuitBuildingInfo[ bldgLevel ]
+ if len(bldgInfo) > SuitBuildingGlobals.SUIT_BLDG_INFO_REVIVES:
+ revives = bldgInfo[ SuitBuildingGlobals.SUIT_BLDG_INFO_REVIVES ][0]
+ else:
+ revives = 0
+ for currActive in range( numActive - 1, -1, -1 ):
+ level = lvls[ currActive ]
+ type = self.__genNormalSuitType( level )
+ activeDict = {}
+ activeDict['type'] = type
+ activeDict['track'] = bldgTrack
+ activeDict['level'] = level
+ activeDict['revives'] = revives
+ activeDicts.append(activeDict)
+ infoDict['activeSuits'] = activeDicts
+
+ # now create the reserve suit objects, also assign each a
+ # % join restriction, this indicates when the reserve suit
+ # should join the battle based on how much damage has been
+ # done to the suits currently in the battle
+ #
+ reserveDicts = []
+ numReserve = len( lvls ) - numActive
+ joinChances = self.__genJoinChances( numReserve )
+ for currReserve in range( numReserve ):
+ level = lvls[ currReserve + numActive ]
+ type = self.__genNormalSuitType( level )
+ reserveDict = {}
+ reserveDict['type'] = type
+ reserveDict['track'] = bldgTrack
+ reserveDict['level'] = level
+ reserveDict['revives'] = revives
+ reserveDict['joinChance'] = joinChances[currReserve]
+ reserveDicts.append(reserveDict)
+ infoDict['reserveSuits'] = reserveDicts
+
+ self.suitInfos.append(infoDict)
+
+ #self.print()
+
+ def __genNormalSuitType( self, lvl ):
+ """
+ // Function: generate info for a normal suit that we might find
+ // in this particular building
+ // Parameters: none
+ // Changes:
+ // Returns: list containing the suit level, type, and track
+ """
+ # there is a similar formula in DistributedSuitPlannerAI used for
+ # picking suit types for the streets, based on the suit level we
+ # need to make sure we pick a valid suit type that can actually
+ # be this level (each suit type can be 1 of 5 levels)
+ #
+ # TODO: track this formula down and make it use
+ # SuitDNA.getRandomSuitType
+
+ if (self.dbg_defaultSuitType != None):
+ return self.dbg_defaultSuitType
+
+ return SuitDNA.getRandomSuitType(lvl)
+
+ def __genLevelList( self, bldgLevel, currFloor, numFloors ):
+ """
+ // Function: based on a few parameters from the building, create
+ // a list of suit levels for a specific floor
+ // Parameters: bldgLevel, the level of the current building (the
+ // level of the suit that took it over, this
+ // value is 0-based)
+ // currFloor, the current floor that we are calculating
+ // numFloors, the total number of floors in this bldg
+ // Changes:
+ // returns: list of suit levels
+ """
+ assert(self.notify.debug('genLevelList(): floor: %d numFloors: %d' % \
+ (currFloor + 1, numFloors)))
+ bldgInfo = SuitBuildingGlobals.SuitBuildingInfo[ bldgLevel ]
+
+ # For quick building battles during debug.
+ if (self.dbg_1SuitPerFloor):
+ return [1] # 1 suit of max level 1
+ elif (self.dbg_4SuitsPerFloor):
+ return [5,6,7,10] # a typical level with a higher level boss (must be at end)
+
+ lvlPoolRange = bldgInfo[ SuitBuildingGlobals.SUIT_BLDG_INFO_LVL_POOL ]
+ maxFloors = bldgInfo[ SuitBuildingGlobals.SUIT_BLDG_INFO_FLOORS ][1]
+
+ # now figure out which level pool multiplier to use
+ #
+ lvlPoolMults = bldgInfo[
+ SuitBuildingGlobals.SUIT_BLDG_INFO_LVL_POOL_MULTS ]
+ floorIdx = min(currFloor, maxFloors - 1)
+
+ # now adjust the min and max level pool range based on the multipliers
+ # we just got
+ #
+ lvlPoolMin = lvlPoolRange[ 0 ] * lvlPoolMults[ floorIdx ]
+ lvlPoolMax = lvlPoolRange[ 1 ] * lvlPoolMults[ floorIdx ]
+
+ # now randomly choose a level pool between the max and min
+ #
+ lvlPool = random.randint( int(lvlPoolMin), int(lvlPoolMax) )
+
+ # find the min and max possible suit levels that we can create
+ # for this level of building
+ #
+ lvlMin = bldgInfo[ SuitBuildingGlobals.SUIT_BLDG_INFO_SUIT_LVLS ][ 0 ]
+ lvlMax = bldgInfo[ SuitBuildingGlobals.SUIT_BLDG_INFO_SUIT_LVLS ][ 1 ]
+
+ # now randomly generate levels within our min and max, pulling
+ # from our pool until we run out
+ #
+ self.notify.debug( "Level Pool: " + str( lvlPool ) )
+ lvlList = []
+ while lvlPool >= lvlMin:
+ newLvl = random.randint( lvlMin, min( lvlPool, lvlMax ) )
+ lvlList.append( newLvl )
+ lvlPool -= newLvl
+
+ # now if we are on the top floor of the building, make sure to
+ # add in a slot for the building boss
+ #
+ if currFloor + 1 == numFloors:
+ bossLvlRange=bldgInfo[SuitBuildingGlobals.SUIT_BLDG_INFO_BOSS_LVLS]
+ newLvl = random.randint( bossLvlRange[ 0 ], bossLvlRange[ 1 ] )
+ assert(self.notify.debug('boss level: %d' % newLvl))
+ lvlList.append( newLvl )
+
+
+ lvlList.sort( cmp )
+ self.notify.debug( "LevelList: " + repr( lvlList ) )
+ return lvlList
+
+
+ def __setupSuitInfo( self, suit, bldgTrack, suitLevel, suitType ):
+ """
+ create dna information for the given suit with the given track
+ and suit type
+ """
+ suitName, skeleton = simbase.air.suitInvasionManager.getInvadingCog()
+ if suitName and self.respectInvasions:
+ # Override the suit type
+ suitType = SuitDNA.getSuitType(suitName)
+ # Override the building track
+ bldgTrack = SuitDNA.getSuitDept(suitName)
+ # if our type is already specified, we might need to
+ # constrain the level to fit.
+ suitLevel = min(max(suitLevel, suitType), suitType + 4)
+
+ dna = SuitDNA.SuitDNA()
+ dna.newSuitRandom( suitType, bldgTrack )
+ suit.dna = dna
+ self.notify.debug("Creating suit type " + suit.dna.name +
+ " of level " + str( suitLevel ) +
+ " from type " + str( suitType ) +
+ " and track " + str( bldgTrack ) )
+ suit.setLevel( suitLevel )
+
+ # We can't make a suit a skeleton until after generate.
+ # Pass this info back so we know whether to do it or not
+ return skeleton
+
+
+ def __genSuitObject(self, suitZone, suitType, bldgTrack, suitLevel, revives = 0):
+ """
+ // Function: generate a distributed suit object
+ // Parameters:
+ // Changes:
+ // Returns: the suit object created
+ """
+ newSuit = DistributedSuitAI.DistributedSuitAI( simbase.air, None )
+ skel = self.__setupSuitInfo( newSuit, bldgTrack, suitLevel, suitType )
+ if skel:
+ newSuit.setSkelecog(1)
+ newSuit.setSkeleRevives(revives)
+ newSuit.generateWithRequired( suitZone )
+
+ # Fill in the name so we can tell one suit from another in printouts.
+ newSuit.node().setName('suit-%s' % (newSuit.doId))
+ return newSuit
+
+ def myPrint(self):
+ """
+ // Function: print suit infos structure to see what and which
+ // suits exist on each floor of this building
+ // Parameters: suitInfos, structure containing all suits in this
+ // building
+ // Changes:
+ """
+ self.notify.info("Generated suits for building: ")
+ for currInfo in suitInfos:
+ whichSuitInfo = suitInfos.index( currInfo ) + 1
+ self.notify.debug( " Floor " + str( whichSuitInfo ) +
+ " has " +
+ str( len( currInfo[ 0 ] ) ) +
+ " active suits." )
+ for currActive in range( len( currInfo [ 0 ] ) ):
+ self.notify.debug( " Active suit " +
+ str( currActive + 1 ) +
+ " is of type " +
+ str( currInfo[ 0 ][ currActive ][ 0 ] ) +
+ " and of track " +
+ str( currInfo[ 0 ][ currActive ][ 1 ] ) +
+ " and of level " +
+ str( currInfo[ 0 ][ currActive ][ 2 ] ) )
+
+ self.notify.debug( " Floor " + str( whichSuitInfo ) +
+ " has " +
+ str( len( currInfo[ 1 ] ) ) +
+ " reserve suits." )
+ for currReserve in range( len( currInfo[ 1 ] ) ):
+ self.notify.debug( " Reserve suit " +
+ str( currReserve + 1 ) +
+ " is of type " +
+ str( currInfo[ 1 ][ currReserve ][ 0 ] ) +
+ " and of track " +
+ str( currInfo[ 1 ][ currReserve ][ 1 ] ) +
+ " and of lvel " +
+ str( currInfo[ 1 ][ currReserve ][ 2 ] ) +
+ " and has " +
+ str( currInfo[ 1 ][ currReserve ][ 3 ] ) +
+ "% join restriction." )
+
+ def genFloorSuits(self, floor):
+ """
+ """
+ assert(floor < len(self.suitInfos))
+ assert(self.notify.debug('generating suits for floor: %d' % floor))
+ suitHandles = {}
+ floorInfo = self.suitInfos[floor]
+
+ activeSuits = []
+ for activeSuitInfo in floorInfo['activeSuits']:
+ suit = self.__genSuitObject(self.zoneId,
+ activeSuitInfo['type'],
+ activeSuitInfo['track'],
+ activeSuitInfo['level'],
+ activeSuitInfo['revives'])
+
+ activeSuits.append(suit)
+ assert(len(activeSuits) > 0)
+ suitHandles['activeSuits'] = activeSuits
+
+ reserveSuits = []
+ for reserveSuitInfo in floorInfo['reserveSuits']:
+ suit = self.__genSuitObject(self.zoneId,
+ reserveSuitInfo['type'],
+ reserveSuitInfo['track'],
+ reserveSuitInfo['level'],
+ reserveSuitInfo['revives'])
+ reserveSuits.append((suit, reserveSuitInfo['joinChance']))
+ suitHandles['reserveSuits'] = reserveSuits
+
+ return suitHandles
+
+ def genSuits( self ):
+ """
+ // Function: for each floor, create a list of active and reserve
+ // suits that should exist inside of a suit building
+ // Parameters: none
+ // Changes:
+ // Returns: a map
+ """
+ assert(self.notify.debug('genSuits() for zone: %d' % self.zoneId))
+
+ suitHandles = []
+ # process each floor in the building and create all active and
+ # reserve suits
+ #
+ for floor in range(len(self.suitInfos)):
+ floorSuitHandles = self.genFloorSuits(floor)
+ suitHandles.append(floorSuitHandles)
+
+ return suitHandles
+
+
+# History
+#
+# 13Aug01 jlbutler created.
+# 14Aug01 jlbutler modified to have two separate steps, the first is done
+# automatically when the SuitPlannerInteriorAI is created,
+# which creates all suit information for the building (this way
+# the same suits will be in this building until it is taken back
+# by toons), the second step is done when genSuits is called, this
+# creates the actual suit objects for the entire building (only
+# needed to be done when a toon enters the building)
diff --git a/toontown/src/building/ToonInterior.py b/toontown/src/building/ToonInterior.py
new file mode 100644
index 0000000..5a9a705
--- /dev/null
+++ b/toontown/src/building/ToonInterior.py
@@ -0,0 +1,399 @@
+"""ToonInterior module: contains the ToonInterior class"""
+
+from pandac.PandaModules import *
+from toontown.toonbase.ToonBaseGlobal import *
+from direct.directnotify import DirectNotifyGlobal
+from toontown.hood import Place
+from toontown.hood import ZoneUtil
+from direct.showbase import DirectObject
+from direct.fsm import StateData
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from toontown.toon import NPCForceAcknowledge
+from toontown.toon import HealthForceAcknowledge
+class ToonInterior(Place.Place):
+ """ToonInterior class"""
+
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("ToonInterior")
+
+ # special methods
+
+ def __init__(self, loader, parentFSMState, doneEvent):
+ """
+ ToonInterior constructor: create a play game ClassicFSM
+ """
+ Place.Place.__init__(self, loader, doneEvent)
+ self.dnaFile="phase_7/models/modules/toon_interior"
+ self.isInterior=1
+ self.tfaDoneEvent = "tfaDoneEvent"
+ self.hfaDoneEvent = "hfaDoneEvent"
+ self.npcfaDoneEvent = "npcfaDoneEvent"
+ # shared state
+ self.fsm = ClassicFSM.ClassicFSM('ToonInterior',
+ [State.State('start',
+ self.enterStart,
+ self.exitStart,
+ ['doorIn', 'teleportIn', 'tutorial',]),
+ State.State('walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['sit', 'stickerBook', 'doorOut',
+ 'DFA', 'trialerFA',
+ 'teleportOut', 'quest',
+ 'purchase', 'phone','stopped', 'pet']),
+ State.State('sit',
+ self.enterSit,
+ self.exitSit,
+ ['walk',]),
+ State.State('stickerBook',
+ self.enterStickerBook,
+ self.exitStickerBook,
+ ['walk', 'DFA', 'trialerFA',
+ 'sit', 'doorOut',
+ 'teleportOut', 'quest',
+ 'purchase', 'phone', 'stopped', 'pet',
+ ]),
+ # Trialer Force Acknowledge:
+ State.State('trialerFA',
+ self.enterTrialerFA,
+ self.exitTrialerFA,
+ ['trialerFAReject', 'DFA']),
+ State.State('trialerFAReject',
+ self.enterTrialerFAReject,
+ self.exitTrialerFAReject,
+ ['walk']),
+ # Download Force Acknowlege:
+ State.State('DFA',
+ self.enterDFA,
+ self.exitDFA,
+ ['DFAReject', 'HFA', 'NPCFA', 'teleportOut',
+ 'doorOut']),
+ State.State('DFAReject',
+ self.enterDFAReject,
+ self.exitDFAReject,
+ ['walk']),
+ # NPC Force Acknowledge:
+ State.State('NPCFA',
+ self.enterNPCFA,
+ self.exitNPCFA,
+ ['NPCFAReject', 'HFA', 'teleportOut']),
+ State.State('NPCFAReject',
+ self.enterNPCFAReject,
+ self.exitNPCFAReject,
+ ['walk']),
+ # Health Force Acknowledge
+ State.State('HFA',
+ self.enterHFA,
+ self.exitHFA,
+ ['HFAReject', 'teleportOut', 'tunnelOut']),
+ State.State('HFAReject',
+ self.enterHFAReject,
+ self.exitHFAReject,
+ ['walk']),
+ State.State('doorIn',
+ self.enterDoorIn,
+ self.exitDoorIn,
+ ['walk']),
+ State.State('doorOut',
+ self.enterDoorOut,
+ self.exitDoorOut,
+ ['walk']), # 'final'
+ State.State('teleportIn',
+ self.enterTeleportIn,
+ self.exitTeleportIn,
+ ['walk']),
+ State.State('teleportOut',
+ self.enterTeleportOut,
+ self.exitTeleportOut,
+ ['teleportIn']), # 'final'
+ State.State('quest',
+ self.enterQuest,
+ self.exitQuest,
+ ['walk', 'doorOut',]),
+ State.State('tutorial',
+ self.enterTutorial,
+ self.exitTutorial,
+ ['walk', 'quest',]),
+ State.State('purchase',
+ self.enterPurchase,
+ self.exitPurchase,
+ ['walk', 'doorOut']),
+ State.State('pet',
+ self.enterPet,
+ self.exitPet,
+ ['walk']),
+ State.State('phone',
+ self.enterPhone,
+ self.exitPhone,
+ ['walk', 'doorOut']),
+ State.State('stopped',
+ self.enterStopped,
+ self.exitStopped,
+ ['walk', 'doorOut']),
+ State.State('final',
+ self.enterFinal,
+ self.exitFinal,
+ ['start'])],
+ # Initial State
+ 'start',
+ # Final State
+ 'final',
+ )
+ self.parentFSMState = parentFSMState
+
+
+ def load(self):
+ assert(self.notify.debug("load()"))
+ # Call up the chain
+ Place.Place.load(self)
+
+ self.parentFSMState.addChild(self.fsm)
+
+ def unload(self):
+ assert(self.notify.debug("unload()"))
+ # Call up the chain
+ Place.Place.unload(self)
+
+ self.parentFSMState.removeChild(self.fsm)
+ del self.parentFSMState
+ del self.fsm
+ #self.geom.removeNode()
+ #del self.geom
+ #self.ignoreAll()
+ # Get rid of any references to models or textures from this safe zone
+ ModelPool.garbageCollect()
+ TexturePool.garbageCollect()
+
+ def enter(self, requestStatus):
+ assert(self.notify.debug("enter(requestStatus="+str(requestStatus)+")"))
+ self.zoneId = requestStatus["zoneId"]
+ self.fsm.enterInitialState()
+ # Let the safe zone manager know that we are here.
+ messenger.send("enterToonInterior")
+ self.accept("doorDoneEvent", self.handleDoorDoneEvent)
+ self.accept("DistributedDoor_doorTrigger", self.handleDoorTrigger)
+ # Play music
+ volume = requestStatus.get('musicVolume', 0.7)
+ base.playMusic(self.loader.activityMusic, looping = 1, volume = volume)
+
+ #self.geom.reparentTo(render)
+
+ # Turn on the little red arrows.
+ NametagGlobals.setMasterArrowsOn(1)
+ # Request the state change:
+ self.fsm.request(requestStatus["how"], [requestStatus])
+
+ def exit(self):
+ assert(self.notify.debug("exit()"))
+ self.ignoreAll()
+ # Let the safe zone manager know that we are leaving
+ messenger.send("exitToonInterior")
+ #self.geom.reparentTo(hidden)
+
+ # Turn off the little red arrows.
+ NametagGlobals.setMasterArrowsOn(0)
+
+ # Stop music
+ self.loader.activityMusic.stop()
+
+ def setState(self, state):
+ assert(self.notify.debug("setState(state="+str(state)+")"))
+ self.fsm.request(state)
+
+ def enterTutorial(self, requestStatus):
+ self.fsm.request("walk")
+ base.localAvatar.b_setParent(ToontownGlobals.SPRender)
+ globalClock.tick()
+ base.transitions.irisIn()
+ messenger.send("enterTutorialInterior")
+
+
+ def exitTutorial(self):
+ pass
+
+ def doRequestLeave(self, requestStatus):
+ # when it's time to leave, check their trialer status first
+ self.fsm.request('trialerFA', [requestStatus])
+
+ # walk state inherited from Place.py
+
+ # sticker book state inherited from Place.py
+
+ # doorIn/Out state inherited from Place.py
+
+ # override DFA callback
+ def enterDFACallback(self, requestStatus, doneStatus):
+ """
+ Download Force Acknowledge
+ This function overrides Place.py because from the toon interior we need to
+ if you have ridden the trolley before letting you teleprt out.
+ """
+ assert(self.notify.debug("enterDFACallback()"))
+ self.dfa.exit()
+ del self.dfa
+ # Check the status from the dfa
+ # If the download force acknowledge tells us the download is complete, then
+ # we can enter the tunnel, otherwise for now we just stand there
+ ds = doneStatus['mode']
+ if (ds == 'complete'):
+ # Allowed, check your quests
+ # We no longer need the NPCFA with the new tutorial
+ self.fsm.request("NPCFA", [requestStatus])
+ # Skip straight to the HFA
+ #self.fsm.request("HFA", [requestStatus])
+ # Rejected
+ elif (ds == 'incomplete'):
+ self.fsm.request("DFAReject")
+ else:
+ # Some return code that is not handled
+ self.notify.error("Unknown done status for DownloadForceAcknowledge: "
+ + `doneStatus`)
+
+ # NPCFA state
+
+ def enterNPCFA(self, requestStatus):
+ """NPC Force Acknowledge"""
+ assert(self.notify.debug("enterNPCFA()"))
+
+ self.acceptOnce(self.npcfaDoneEvent, self.enterNPCFACallback, [requestStatus])
+ self.npcfa = NPCForceAcknowledge.NPCForceAcknowledge(self.npcfaDoneEvent)
+ self.npcfa.enter()
+
+ def exitNPCFA(self):
+ assert(self.notify.debug("exitNPCFA()"))
+ self.ignore(self.npcfaDoneEvent)
+
+ def enterNPCFACallback(self, requestStatus, doneStatus):
+ assert(self.notify.debug("enterNPCFACallback()"))
+ self.npcfa.exit()
+ del self.npcfa
+ # Check the status from the fda
+ # If the download force acknowledge tells us the download is complete, then
+ # we can enter the tunnel, otherwise for now we just stand there
+ # Allowed, do the tunnel transition
+ if (doneStatus["mode"] == "complete"):
+ # Allowed, let them leave
+ outHow={"teleportIn":"teleportOut", "tunnelIn":"tunnelOut", "doorIn":"doorOut"}
+ self.fsm.request(outHow[requestStatus["how"]], [requestStatus])
+ elif (doneStatus["mode"] == 'incomplete'):
+ self.fsm.request("NPCFAReject")
+ else:
+ # Some return code that is not handled
+ self.notify.error("Unknown done status for NPCForceAcknowledge: "
+ + `doneStatus`)
+
+ # npca reject state
+
+ def enterNPCFAReject(self):
+ assert(self.notify.debug("enterNPCFAReject()"))
+ # TODO: reject movie, turn toon around
+ self.fsm.request("walk")
+
+ def exitNPCFAReject(self):
+ assert(self.notify.debug("exitNPCFAReject()"))
+
+
+ # HFA state
+
+ def enterHFA(self, requestStatus):
+ """Hit Point Force Acknowledge"""
+ assert(self.notify.debug("enterHFA()"))
+ self.acceptOnce(self.hfaDoneEvent, self.enterHFACallback, [requestStatus])
+ # Make sure we have enough HP to leave the safe zone
+ # This enforces the so-called time out penalty
+ self.hfa = HealthForceAcknowledge.HealthForceAcknowledge(self.hfaDoneEvent)
+ self.hfa.enter(1)
+
+ def exitHFA(self):
+ assert(self.notify.debug("exitHFA()"))
+ self.ignore(self.hfaDoneEvent)
+
+ def enterHFACallback(self, requestStatus, doneStatus):
+ assert(self.notify.debug("enterHFACallback()"))
+ self.hfa.exit()
+ del self.hfa
+ # Check the status from the fda
+ # If the download force acknowledge tells us the download is complete, then
+ # we can enter the tunnel, otherwise for now we just stand there
+ # Allowed, do the tunnel transition
+ if (doneStatus["mode"] == "complete"):
+ outHow={"teleportIn":"teleportOut", "tunnelIn":"tunnelOut", "doorIn":"doorOut"}
+ self.fsm.request(outHow[requestStatus["how"]], [requestStatus])
+ # Rejected
+ elif (doneStatus["mode"] == 'incomplete'):
+ self.fsm.request("HFAReject")
+ else:
+ # Some return code that is not handled
+ self.notify.error("Unknown done status for HealthForceAcknowledge: "
+ + `doneStatus`)
+
+ # hfa reject state
+
+ def enterHFAReject(self):
+ assert(self.notify.debug("enterHFAReject()"))
+ # TODO: reject movie, turn toon around
+ self.fsm.request("walk")
+
+ def exitHFAReject(self):
+ assert(self.notify.debug("exitHFAReject()"))
+
+
+ # teleport in state
+
+ def enterTeleportIn(self, requestStatus):
+ # We want to set localToon to the starting position within the
+ # interior, even if we are teleporting to an avatar here.
+ # That way, if the teleport-to-avatar fails (for instance, if
+ # the avatar has moved on), we'll at least be in a sensible
+ # place.
+ if ZoneUtil.isPetshop(self.zoneId):
+ base.localAvatar.setPosHpr(0, 0, ToontownGlobals.FloorOffset,
+ 45.0, 0.0, 0.0)
+ else:
+ base.localAvatar.setPosHpr(2.5, 11.5, ToontownGlobals.FloorOffset,
+ 45.0, 0.0, 0.0)
+
+ Place.Place.enterTeleportIn(self, requestStatus)
+
+ # teleport out state
+
+ def enterTeleportOut(self, requestStatus):
+ Place.Place.enterTeleportOut(self, requestStatus,
+ self.__teleportOutDone)
+
+ def __teleportOutDone(self, requestStatus):
+ assert(self.notify.debug("__teleportOutDone(requestStatus="
+ +str(requestStatus)+")"))
+ hoodId = requestStatus["hoodId"]
+ zoneId = requestStatus["zoneId"]
+ shardId = requestStatus["shardId"]
+ if ((hoodId == self.loader.hood.id) and (zoneId == self.zoneId) and (shardId == None)):
+ # If you are teleporting to somebody in this zone
+ self.fsm.request("teleportIn", [requestStatus])
+ else:
+ # Different hood or zone, exit the zone
+ if (hoodId == ToontownGlobals.MyEstate):
+ self.getEstateZoneAndGoHome(requestStatus)
+ else:
+ self.doneStatus = requestStatus
+ messenger.send(self.doneEvent)
+
+ def goHomeFailed(self, task):
+ # it took too long to hear back from the server,
+ # or we tried going to a non-friends house
+ self.notifyUserGoHomeFailed()
+ # ignore the setLocalEstateZone message
+ self.ignore("setLocalEstateZone")
+ self.doneStatus["avId"] = -1
+ self.doneStatus["zoneId"] = self.getZoneId()
+ self.fsm.request("teleportIn", [self.doneStatus])
+ return Task.done
+
+ def exitTeleportOut(self):
+ Place.Place.exitTeleportOut(self)
+
+
diff --git a/toontown/src/building/ToonInteriorColors.py b/toontown/src/building/ToonInteriorColors.py
new file mode 100644
index 0000000..62eaeb3
--- /dev/null
+++ b/toontown/src/building/ToonInteriorColors.py
@@ -0,0 +1,98 @@
+
+from toontown.toonbase.ToontownGlobals import *
+
+wainscottingBase = [
+ # ( r, g, b, a)
+ Vec4(0.800, 0.500, 0.300, 1.0),
+ Vec4(0.699, 0.586, 0.473, 1.0),
+ Vec4(0.473, 0.699, 0.488, 1.0),
+]
+
+wallpaperBase = [
+ # ( r, g, b, a)
+ Vec4(1.0, 1.0, 0.7, 1.0),
+ Vec4(0.8, 1.0, 0.7, 1.0),
+ Vec4(0.4, 0.5, 0.4, 1.0),
+ Vec4(0.5, 0.7, 0.6, 1.0),
+# Vec4(1.000, 0.820, 0.699, 1.0),
+]
+
+wallpaperBorderBase = [
+ # ( r, g, b, a)
+ Vec4(1.0, 1.0, 0.7, 1.0),
+ Vec4(0.8, 1.0, 0.7, 1.0),
+ Vec4(0.4, 0.5, 0.4, 1.0),
+ Vec4(0.5, 0.7, 0.6, 1.0),
+# Vec4(1.000, 1.000, 0.700, 1.0),
+# Vec4(0.789, 1.000, 0.699, 1.0),
+# Vec4(1.000, 0.820, 0.699, 1.0),
+]
+
+doorBase = [
+ # ( r, g, b, a)
+ Vec4(1.000, 1.000, 0.700, 1.0),
+]
+
+floorBase = [
+ # ( r, g, b, a)
+ Vec4(0.746, 1.000, 0.477, 1.0),
+ Vec4(1.000, 0.684, 0.477, 1.0),
+]
+
+
+baseScheme = {
+ "TI_wainscotting":wainscottingBase,
+ "TI_wallpaper":wallpaperBase,
+ "TI_wallpaper_border":wallpaperBorderBase,
+ "TI_door":doorBase,
+ "TI_floor":floorBase,
+}
+
+
+colors={
+ DonaldsDock:{
+ "TI_wainscotting":wainscottingBase,
+ "TI_wallpaper":wallpaperBase,
+ "TI_wallpaper_border":wallpaperBorderBase,
+ "TI_door":doorBase,
+ "TI_floor":floorBase,
+ },
+ # NOTE: If you change ToontownCentral, change the Tutorial, too.
+ ToontownCentral:{
+ "TI_wainscotting":wainscottingBase,
+ "TI_wallpaper":wallpaperBase,
+ "TI_wallpaper_border":wallpaperBorderBase,
+ "TI_door":doorBase+[
+ Vec4(0.8, 0.5, 0.3, 1.0),
+ ],
+ "TI_floor":floorBase,
+ },
+ TheBrrrgh:baseScheme,
+ MinniesMelodyland:baseScheme,
+ DaisyGardens:baseScheme,
+ #ConstructionZone:baseScheme,
+ #FunnyFarm:baseScheme,
+ GoofySpeedway:baseScheme,
+ DonaldsDreamland:{
+ "TI_wainscotting":wainscottingBase,
+ "TI_wallpaper":wallpaperBase,
+ "TI_wallpaper_border":wallpaperBorderBase,
+ "TI_door":doorBase,
+ "TI_floor":floorBase,
+ },
+ # The tutorial is a cut and paste of Toontown Central
+ Tutorial:{
+ "TI_wainscotting":wainscottingBase,
+ "TI_wallpaper":wallpaperBase,
+ "TI_wallpaper_border":wallpaperBorderBase,
+ "TI_door":doorBase+[
+ Vec4(0.8, 0.5, 0.3, 1.0),
+ ],
+ "TI_floor":floorBase,
+ },
+ MyEstate:baseScheme,
+ #BossbotHQ:baseScheme,
+ #SellbotHQ:baseScheme,
+ #CashbotHQ:baseScheme,
+ #LawbotHQ:baseScheme,
+}
diff --git a/toontown/src/building/TutorialBuildingAI.py b/toontown/src/building/TutorialBuildingAI.py
new file mode 100644
index 0000000..07c2e5b
--- /dev/null
+++ b/toontown/src/building/TutorialBuildingAI.py
@@ -0,0 +1,91 @@
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+import DistributedDoorAI
+import DistributedTutorialInteriorAI
+import FADoorCodes
+import DoorTypes
+from toontown.toon import NPCToons
+from toontown.toonbase import TTLocalizer
+
+# This is not a distributed class... It just owns and manages some distributed
+# classes.
+
+class TutorialBuildingAI:
+ def __init__(self, air, exteriorZone, interiorZone, blockNumber):
+ # While this is not a distributed object, it needs to know about
+ # the repository.
+ self.air = air
+ self.exteriorZone = exteriorZone
+ self.interiorZone = interiorZone
+
+ # This is because we are "pretending" to be a DistributedBuilding.
+ # The DistributedTutorialInterior takes a peek at savedBy. It really
+ # should make a function call. Perhaps TutorialBuildingAI and
+ # DistributedBuildingAI should inherit from each other somehow,
+ # but I can't see an easy way to do that.
+ self.savedBy = None
+
+ self.setup(blockNumber)
+
+ def cleanup(self):
+ self.interior.requestDelete()
+ del self.interior
+ self.door.requestDelete()
+ del self.door
+ self.insideDoor.requestDelete()
+ del self.insideDoor
+ self.gagShopNPC.requestDelete()
+ del self.gagShopNPC
+ return
+
+ def setup(self, blockNumber):
+ # Put an NPC in here. Give him id# 20000. When he has assigned
+ # his quest, he will unlock the interior door.
+ self.gagShopNPC = NPCToons.createNPC(
+ self.air, 20000,
+ (self.interiorZone,
+ TTLocalizer.NPCToonNames[20000],
+ ("dll" ,"ms" ,"m" ,"m" ,7 ,0 ,7 ,7 ,2 ,6 ,2 ,6 ,2 ,16), "m", 1, NPCToons.NPC_REGULAR),
+ self.interiorZone,
+ questCallback=self.unlockInteriorDoor)
+ # Flag him as being part of tutorial
+ self.gagShopNPC.setTutorial(1)
+ npcId = self.gagShopNPC.getDoId()
+ # Toon interior (with tutorial flag set to 1)
+ self.interior=DistributedTutorialInteriorAI.DistributedTutorialInteriorAI(
+ blockNumber, self.air, self.interiorZone, self, npcId)
+ self.interior.generateWithRequired(self.interiorZone)
+ # Outside door:
+ door=DistributedDoorAI.DistributedDoorAI(self.air, blockNumber,
+ DoorTypes.EXT_STANDARD,
+ lockValue=FADoorCodes.DEFEAT_FLUNKY_TOM)
+ # Inside door. Locked until you get your gags.
+ insideDoor=DistributedDoorAI.DistributedDoorAI(
+ self.air,
+ blockNumber,
+ DoorTypes.INT_STANDARD,
+ lockValue=FADoorCodes.TALK_TO_TOM)
+ # Tell them about each other:
+ door.setOtherDoor(insideDoor)
+ insideDoor.setOtherDoor(door)
+ door.zoneId=self.exteriorZone
+ insideDoor.zoneId=self.interiorZone
+ # Now that they both now about each other, generate them:
+ door.generateWithRequired(self.exteriorZone)
+ #door.sendUpdate("setDoorIndex", [door.getDoorIndex()])
+ insideDoor.generateWithRequired(self.interiorZone)
+ #insideDoor.sendUpdate("setDoorIndex", [door.getDoorIndex()])
+ # keep track of them:
+ self.door=door
+ self.insideDoor=insideDoor
+ return
+
+ def unlockInteriorDoor(self):
+ self.insideDoor.setDoorLock(FADoorCodes.UNLOCKED)
+
+ def battleOverCallback(self):
+ # There is an if statement here because it is possible for
+ # the callback to get called after cleanup has already taken
+ # place.
+ if hasattr(self, "door"):
+ self.door.setDoorLock(FADoorCodes.TALK_TO_HQ_TOM)
diff --git a/toontown/src/building/TutorialHQBuildingAI.py b/toontown/src/building/TutorialHQBuildingAI.py
new file mode 100644
index 0000000..3243bd4
--- /dev/null
+++ b/toontown/src/building/TutorialHQBuildingAI.py
@@ -0,0 +1,128 @@
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+import DistributedDoorAI
+import DistributedHQInteriorAI
+import FADoorCodes
+import DoorTypes
+from toontown.toon import NPCToons
+from toontown.quest import Quests
+from toontown.toonbase import TTLocalizer
+
+# This is not a distributed class... It just owns and manages some distributed
+# classes.
+
+class TutorialHQBuildingAI:
+ def __init__(self, air, exteriorZone, interiorZone, blockNumber):
+ # While this is not a distributed object, it needs to know about
+ # the repository.
+ self.air = air
+ self.exteriorZone = exteriorZone
+ self.interiorZone = interiorZone
+
+ self.setup(blockNumber)
+
+ def cleanup(self):
+ self.interior.requestDelete()
+ del self.interior
+ self.npc.requestDelete()
+ del self.npc
+ self.door0.requestDelete()
+ del self.door0
+ self.door1.requestDelete()
+ del self.door1
+ self.insideDoor0.requestDelete()
+ del self.insideDoor0
+ self.insideDoor1.requestDelete()
+ del self.insideDoor1
+ return
+
+ def setup(self, blockNumber):
+ # The interior
+ self.interior=DistributedHQInteriorAI.DistributedHQInteriorAI(
+ blockNumber, self.air, self.interiorZone)
+
+ # We do not use a standard npc toon here becuase these npcs are created on
+ # the fly for as many tutorials as we need. The interior zone is not known
+ # until the ai allocates a zone, so we fabricate the description here.
+ desc = (self.interiorZone, TTLocalizer.TutorialHQOfficerName, ('dls', 'ms', 'm', 'm', 6,0,6,6,0,10,0,10,2,9), "m", 1, 0)
+ self.npc = NPCToons.createNPC(self.air, Quests.ToonHQ, desc,
+ self.interiorZone,
+ questCallback=self.unlockInsideDoor1)
+ # Flag npc as part of tutorial
+ self.npc.setTutorial(1)
+
+ self.interior.generateWithRequired(self.interiorZone)
+ # Outside door 0. Locked til you defeat the Flunky:
+ door0=DistributedDoorAI.DistributedDoorAI(
+ self.air, blockNumber, DoorTypes.EXT_HQ,
+ doorIndex=0,
+ lockValue=FADoorCodes.DEFEAT_FLUNKY_HQ)
+ # Outside door 1. Always locked.
+ door1=DistributedDoorAI.DistributedDoorAI(
+ self.air, blockNumber, DoorTypes.EXT_HQ,
+ doorIndex=1,
+ lockValue=FADoorCodes.GO_TO_PLAYGROUND)
+ # Inside door 0. Always locked, but the message will change.
+ insideDoor0=DistributedDoorAI.DistributedDoorAI(
+ self.air,
+ blockNumber,
+ DoorTypes.INT_HQ,
+ doorIndex=0,
+ lockValue=FADoorCodes.TALK_TO_HQ)
+ # Inside door 1. Locked til you get your HQ reward.
+ insideDoor1=DistributedDoorAI.DistributedDoorAI(
+ self.air,
+ blockNumber,
+ DoorTypes.INT_HQ,
+ doorIndex=1,
+ lockValue=FADoorCodes.TALK_TO_HQ)
+ # Tell them about each other:
+ door0.setOtherDoor(insideDoor0)
+ insideDoor0.setOtherDoor(door0)
+ door1.setOtherDoor(insideDoor1)
+ insideDoor1.setOtherDoor(door1)
+ # Put them in the right zones
+ door0.zoneId=self.exteriorZone
+ door1.zoneId=self.exteriorZone
+ insideDoor0.zoneId=self.interiorZone
+ insideDoor1.zoneId=self.interiorZone
+ # Now that they both now about each other, generate them:
+ door0.generateWithRequired(self.exteriorZone)
+ door1.generateWithRequired(self.exteriorZone)
+ door0.sendUpdate("setDoorIndex", [door0.getDoorIndex()])
+ door1.sendUpdate("setDoorIndex", [door1.getDoorIndex()])
+ insideDoor0.generateWithRequired(self.interiorZone)
+ insideDoor1.generateWithRequired(self.interiorZone)
+ insideDoor0.sendUpdate("setDoorIndex", [insideDoor0.getDoorIndex()])
+ insideDoor1.sendUpdate("setDoorIndex", [insideDoor1.getDoorIndex()])
+ # keep track of them:
+ self.door0=door0
+ self.door1=door1
+ self.insideDoor0=insideDoor0
+ self.insideDoor1=insideDoor1
+ # hide the periscope
+ self.interior.setTutorial(1)
+ return
+
+ def unlockDoor(self, door):
+ door.setDoorLock(FADoorCodes.UNLOCKED)
+
+ def battleOverCallback(self):
+ # There is an if statement here because it is possible for
+ # the callback to get called after cleanup has already taken
+ # place.
+ if hasattr(self, "door0"):
+ self.unlockDoor(self.door0)
+
+ # This callback type happens to give zoneId. We don't need it.
+ def unlockInsideDoor1(self):
+ # There is an if statement here because it is possible for
+ # the callback to get called after cleanup has already taken
+ # place.
+ if hasattr(self, "insideDoor1"):
+ self.unlockDoor(self.insideDoor1)
+ # Change the message on this locked door to tell you to go
+ # through the other door. Maybe this door should not be
+ # here at all?
+ if hasattr(self, "insideDoor0"):
+ self.insideDoor0.setDoorLock(FADoorCodes.WRONG_DOOR_HQ)
diff --git a/toontown/src/building/__init__.py b/toontown/src/building/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/catalog/.cvsignore b/toontown/src/catalog/.cvsignore
new file mode 100644
index 0000000..985f113
--- /dev/null
+++ b/toontown/src/catalog/.cvsignore
@@ -0,0 +1,4 @@
+.cvsignore
+Makefile
+pp.dep
+*.pyc
diff --git a/toontown/src/catalog/CatalogAnimatedFurnitureItem.py b/toontown/src/catalog/CatalogAnimatedFurnitureItem.py
new file mode 100644
index 0000000..3fd6179
--- /dev/null
+++ b/toontown/src/catalog/CatalogAnimatedFurnitureItem.py
@@ -0,0 +1,71 @@
+from CatalogFurnitureItem import *
+
+# The first 6 CatalogFurnitureItem properties are defined in CatalogFurnitureItem.py
+# They are:
+# FTModelName = 0
+# FTColor = 1
+# FTColorOptions = 2
+# FTBasePrice = 3
+# FTFlags = 4
+# FTScale = 5
+FTAnimRate = 6
+
+# IMPORTANT this list must be updated if any new animated furniture item gets added
+AnimatedFurnitureItemKeys = (
+ 10020, # winter tree
+ 270, # Trolley Bed
+ 990, # Gag Fan
+ 460, # Coral Fireplace with fire
+ 470, # Square Fireplace with fire
+ 480, # Round Fireplace with fire
+ 490, # Girly Fireplace with fire
+ 491, # Bug Room Fireplace with fire
+ 492, # Caramel Apple Fireplace with fire
+ )
+
+class CatalogAnimatedFurnitureItem(CatalogFurnitureItem):
+ """
+ This class represents a furniture item that has some kind or animation on it.
+ The animation could be in the form of a sequence node, in which case, will be
+ handled by this class, or it could be an animating actor, in which case, will
+ be handled by CatalogAnimatedFurnitureActor.
+ CatalogAnimatedFurnitureActor should derive from CatalogAnimatedFurnitureItem.
+
+ This class supports functions to start, stop or change play rate of the animation.
+ """
+
+ def loadModel(self):
+ model = CatalogFurnitureItem.loadModel(self)
+ self.setAnimRate(model, self.getAnimRate())
+ return model
+
+ def getAnimRate(self):
+ """
+ Returns the animation rate of a CatalogAnimatedFurnitureItem
+ from its definition found in the FurnitureTypes dict.
+ If there is no such it defaults to 1.0.
+ Only CatalogAnimatedFurnitureItem (or its derivatives) should use AnimationRate.
+ """
+ item = FurnitureTypes[self.furnitureType]
+ if (FTAnimRate < len(item)):
+ animRate = item[FTAnimRate]
+ if not (animRate == None):
+ return item[FTAnimRate]
+ else:
+ return 1
+ else:
+ return 1
+
+ def setAnimRate(self, model, rate):
+ """
+ This function should set the play rate of the animation.
+ We are assuming that a CatalogAnimatedFurnitureItem is animated using sequence nodes,
+ in which it will loop it's animation as soon as it is loaded. We have to only find
+ the sequence nodes and setPlayRate on them.
+ We are not handling actors here. For actor based animations make another
+ class called CatalogAnimatedFurnitureActor.
+ """
+ # Find all the sequence nodes in the model.
+ seqNodes = model.findAllMatches('**/seqNode*')
+ for seqNode in seqNodes:
+ seqNode.node().setPlayRate(rate)
diff --git a/toontown/src/catalog/CatalogAtticItem.py b/toontown/src/catalog/CatalogAtticItem.py
new file mode 100644
index 0000000..a77ca22
--- /dev/null
+++ b/toontown/src/catalog/CatalogAtticItem.py
@@ -0,0 +1,121 @@
+import CatalogItem
+from toontown.toonbase import TTLocalizer
+from direct.showbase import PythonUtil
+from direct.gui.DirectGui import *
+from toontown.toonbase import ToontownGlobals
+
+class CatalogAtticItem(CatalogItem.CatalogItem):
+ """CatalogAtticItem
+
+ This is a base class for a family of items (CatalogFurnitureItem,
+ CatalogWindowItem, CatalogWallpaperItem) that must all be stored
+ in the player's attic. It contains some common methods that check
+ for space available in the attic.
+
+ """
+
+ def storedInAttic(self):
+ # Returns true if this kind of item takes up space in the
+ # avatar's attic, false otherwise.
+ return 1
+
+ def isDeletable(self):
+ # Returns true if the item can be deleted from the attic,
+ # false otherwise.
+ return 1
+
+ def getHouseInfo(self, avatar):
+ # Looks up the house for the avatar, and checks the available
+ # space in the attic. A helper function for recordPurchase().
+
+ # Returns a pair: (house, retcode), where house is the house
+ # object if it can be determined, and retcode is the return
+ # code if there is a failure. If retcode is positive the
+ # object should be added to the attic.
+
+ houseId = avatar.houseId
+ if not houseId:
+ self.notify.warning("Avatar %s has no houseId associated." % (avatar.doId))
+ return (None, ToontownGlobals.P_InvalidIndex)
+ house = simbase.air.doId2do.get(houseId)
+ if not house:
+ self.notify.warning("House %s (for avatar %s) not instantiated." % (houseId, avatar.doId))
+ return (None, ToontownGlobals.P_InvalidIndex)
+
+ numAtticItems = len(house.atticItems) + len(house.atticWallpaper) + len(house.atticWindows)
+ numHouseItems = numAtticItems + len(house.interiorItems)
+ if numHouseItems >= ToontownGlobals.MaxHouseItems and \
+ not self.replacesExisting():
+ # No more room in the house.
+ return (house, ToontownGlobals.P_NoRoomForItem)
+
+ return (house, ToontownGlobals.P_ItemAvailable)
+
+ def requestPurchase(self, phone, callback):
+ # Orders the item via the indicated telephone. Some items
+ # will pop up a dialog querying the user for more information
+ # before placing the order; other items will order
+ # immediately.
+
+ # In either case, the function will return immediately before
+ # the transaction is finished, but the given callback will be
+ # called later with two parameters: the return code (one of
+ # the P_* symbols defined in ToontownGlobals.py), followed by the
+ # item itself.
+
+ # This method is only called on the client.
+ from toontown.toontowngui import TTDialog
+ avatar = base.localAvatar
+
+ itemsOnOrder = 0
+ for item in avatar.onOrder + avatar.mailboxContents:
+ if item.storedInAttic() and not item.replacesExisting():
+ itemsOnOrder += 1
+
+ numHouseItems = phone.numHouseItems + itemsOnOrder
+ if numHouseItems >= ToontownGlobals.MaxHouseItems and \
+ not self.replacesExisting():
+ # If the avatar's house is full, pop up a dialog warning
+ # the user, and give him a chance to bail out.
+ self.requestPurchaseCleanup()
+ buttonCallback = PythonUtil.Functor(
+ self.__handleFullPurchaseDialog, phone, callback)
+ self.dialog = TTDialog.TTDialog(
+ style = TTDialog.YesNo,
+ text = TTLocalizer.CatalogPurchaseHouseFull,
+ text_wordwrap = 15,
+ command = buttonCallback,
+ )
+ self.dialog.show()
+
+ else:
+ # The avatar's house isn't full; just buy it.
+ CatalogItem.CatalogItem.requestPurchase(self, phone, callback)
+
+ def requestPurchaseCleanup(self):
+ if hasattr(self, "dialog"):
+ self.dialog.cleanup()
+ del self.dialog
+
+ def __handleFullPurchaseDialog(self, phone, callback, buttonValue):
+ from toontown.toontowngui import TTDialog
+ self.requestPurchaseCleanup()
+ if buttonValue == DGG.DIALOG_OK:
+ # Go ahead and purchase it.
+ CatalogItem.CatalogItem.requestPurchase(self, phone, callback)
+ else:
+ # Don't purchase it.
+ callback(ToontownGlobals.P_UserCancelled, self)
+
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ return TTLocalizer.CatalogAcceptInAttic
+ elif retcode == ToontownGlobals.P_NoRoomForItem:
+ return TTLocalizer.CatalogAcceptHouseFull
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
diff --git a/toontown/src/catalog/CatalogBeanItem.py b/toontown/src/catalog/CatalogBeanItem.py
new file mode 100644
index 0000000..bbe4ad2
--- /dev/null
+++ b/toontown/src/catalog/CatalogBeanItem.py
@@ -0,0 +1,105 @@
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from direct.interval.IntervalGlobal import *
+
+class CatalogBeanItem(CatalogItem.CatalogItem):
+ """
+ This represents jellybeans sent in the gift system
+ """
+
+ sequenceNumber = 0
+
+ def makeNewItem(self, beanAmount, tagCode = 1):
+ self.beanAmount = beanAmount
+ self.giftCode = tagCode
+
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 0
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ if self in avatar.onOrder or self in avatar.mailboxContents or self in avatar.onGiftOrder \
+ or self in avatar.awardMailboxContents or self in avatar.onAwardOrder:
+ return 1
+ return 0
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ if self.giftCode == ToontownGlobals.GIFT_RAT:
+ return TTLocalizer.CatalogAcceptRATBeans
+ else:
+ return TTLocalizer.CatalogAcceptBeans
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 0
+
+ def getTypeName(self):
+ return TTLocalizer.BeanTypeName
+
+ def getName(self):
+ name = ("%s %s" % (self.beanAmount , TTLocalizer.BeanTypeName))
+ return name
+
+ def recordPurchase(self, avatar, optional):
+ if avatar:
+ avatar.addMoney(self.beanAmount)
+
+ #avatar.emoteAccess[self.emoteIndex] = 1
+ #avatar.d_setEmoteAccess(avatar.emoteAccess)
+ #TODO: increase the toon's money here
+ return ToontownGlobals.P_ItemAvailable
+
+ def getPicture(self, avatar):
+ #chatBalloon = loader.loadModel("phase_3/models/props/chatbox.bam")
+ beanJar = loader.loadModel("phase_3.5/models/gui/jar_gui")
+ #chatBalloon.find("**/top").setPos(1,0,5)
+ #chatBalloon.find("**/middle").setScale(1,1,3)
+ frame = self.makeFrame()
+ beanJar.reparentTo(frame)
+
+ beanJar.setPos(0,0,0)
+ beanJar.setScale(2.5)
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (frame, None)
+
+ def output(self, store = ~0):
+ return "CatalogBeanItem(%s%s)" % (
+ self.beanAmount,
+ self.formatOptionalData(store))
+
+ def compareTo(self, other):
+ return self.beanAmount - other.beanAmount
+
+ def getHashContents(self):
+ return self.beanAmount
+
+ def getBasePrice(self):
+ # equal to it's worth
+ return self.beanAmount
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ self.beanAmount = di.getUint16()
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.beanAmount)
+
diff --git a/toontown/src/catalog/CatalogChatItem.py b/toontown/src/catalog/CatalogChatItem.py
new file mode 100644
index 0000000..c5263fd
--- /dev/null
+++ b/toontown/src/catalog/CatalogChatItem.py
@@ -0,0 +1,243 @@
+from pandac.PandaModules import *
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from otp.otpbase import OTPLocalizer
+from toontown.toonbase import TTLocalizer
+
+# Scandalous phrases not appropriate to toontown
+bannedPhrases = [ 11009 ]
+
+class CatalogChatItem(CatalogItem.CatalogItem):
+ """CatalogChatItem
+
+ This represents a particular custom menu item in the SpeedChat
+ that a player may purchase.
+
+ """
+
+ def makeNewItem(self, customIndex):
+ self.customIndex = customIndex
+
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 1
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ #assert avatar.onGiftOrder, "on gift order is None"
+ #print avatar.onGiftOrder
+ #print "self in onGiftOrder: %s" % (self in avatar.onGiftOrder)
+ if self in avatar.onOrder or self in avatar.mailboxContents or self in avatar.onGiftOrder \
+ or self in avatar.awardMailboxContents or self in avatar.onAwardOrder:
+ return 1
+ #if avatar != localAvatar:
+ #pass
+ #import pdb; pdb.set_trace()
+ return avatar.customMessages.count(self.customIndex) != 0
+
+ def getTypeName(self):
+ return TTLocalizer.ChatTypeName
+
+ def getName(self):
+ return TTLocalizer.ChatItemQuotes % OTPLocalizer.CustomSCStrings[self.customIndex]
+
+ def getDisplayName(self):
+ return OTPLocalizer.CustomSCStrings[self.customIndex]
+
+ def recordPurchase(self, avatar, optional):
+ if avatar.customMessages.count(self.customIndex) != 0:
+ # We already have this chat item.
+ return ToontownGlobals.P_ReachedPurchaseLimit
+
+ if len(avatar.customMessages) >= ToontownGlobals.MaxCustomMessages:
+ # Oops, too many custom messages.
+
+ # Delete the old index if so requested by the client.
+ if optional >= 0 and optional < len(avatar.customMessages):
+ del avatar.customMessages[optional]
+
+ if len(avatar.customMessages) >= ToontownGlobals.MaxCustomMessages:
+ # Still too many.
+ return ToontownGlobals.P_NoRoomForItem
+
+ avatar.customMessages.append(self.customIndex)
+ avatar.d_setCustomMessages(avatar.customMessages)
+ return ToontownGlobals.P_ItemAvailable
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ return TTLocalizer.CatalogAcceptChat
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
+ def output(self, store = ~0):
+ return "CatalogChatItem(%s%s)" % (
+ self.customIndex,
+ self.formatOptionalData(store))
+
+ def compareTo(self, other):
+ return self.customIndex - other.customIndex
+
+ def getHashContents(self):
+ return self.customIndex
+
+ def getBasePrice(self):
+ # Special holiday-themed chat phrases (>= 10000) are a bit
+ # more expensive.
+ if self.customIndex >= 10000:
+ return 150
+
+ return 100
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ self.customIndex = di.getUint16()
+
+ # The following will generate an exception if self.customIndex
+ # is wrong.
+ text = OTPLocalizer.CustomSCStrings[self.customIndex]
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.customIndex)
+
+ def acceptItem(self, mailbox, index, callback):
+ # Accepts the item from the mailbox. Some items will pop up a
+ # dialog querying the user for more information before
+ # accepting the item; other items will accept it immediately.
+
+ # This method is only called on the client.
+ if (len(base.localAvatar.customMessages) < ToontownGlobals.MaxCustomMessages):
+ mailbox.acceptItem(self, index, callback)
+ else:
+ # else make them make a choice
+ self.showMessagePickerOnAccept(mailbox, index, callback)
+
+
+ def requestPurchase(self, phone, callback):
+ # make sure we have room for this in the chat menu
+ if (len(base.localAvatar.customMessages) < ToontownGlobals.MaxCustomMessages):
+ # if so request the purchase
+ CatalogItem.CatalogItem.requestPurchase(self, phone, callback)
+ else:
+ # else make them make a choice
+ self.showMessagePicker(phone, callback)
+
+ def showMessagePicker(self, phone, callback):
+ # we will need these later
+ self.phone = phone
+ self.callback = callback
+ # pop up a toontown dialog with message picker
+ import CatalogChatItemPicker
+ self.messagePicker = CatalogChatItemPicker.CatalogChatItemPicker(self.__handlePickerDone,
+ self.customIndex)
+ self.messagePicker.show()
+
+
+ def showMessagePickerOnAccept(self, mailbox, index, callback):
+ # we will need these later
+ self.mailbox = mailbox
+ self.callback = callback
+ self.index = index
+ # pop up a toontown dialog with message picker
+ import CatalogChatItemPicker
+ self.messagePicker = CatalogChatItemPicker.CatalogChatItemPicker(self.__handlePickerOnAccept,
+ self.customIndex)
+ self.messagePicker.show()
+
+ def __handlePickerOnAccept(self, status, pickedMessage=None):
+ print("Picker Status%s" % (status))
+ if (status == "pick"):
+ # user has deleted custom phrase, so add this one now
+ self.mailbox.acceptItem(self, self.index, self.callback, pickedMessage)
+ else:
+ print("picker canceled")
+ self.callback(ToontownGlobals.P_UserCancelled, None, self.index)
+
+ self.messagePicker.hide()
+ self.messagePicker.destroy()
+ del self.messagePicker
+ del self.callback
+ del self.mailbox
+
+
+ def __handlePickerDone(self, status, pickedMessage=None):
+ if (status == "pick"):
+ # user has deleted custom phrase, so add this one now
+ CatalogItem.CatalogItem.requestPurchase(self,
+ self.phone,
+ self.callback,
+ pickedMessage)
+ self.messagePicker.hide()
+ self.messagePicker.destroy()
+ del self.messagePicker
+ del self.callback
+ del self.phone
+
+ def getPicture(self, avatar):
+ chatBalloon = loader.loadModel("phase_3/models/props/chatbox.bam")
+ chatBalloon.find("**/top").setPos(1,0,5)
+ chatBalloon.find("**/middle").setScale(1,1,3)
+ frame = self.makeFrame()
+ chatBalloon.reparentTo(frame)
+
+ #chatBalloon.setPos(-1.92,0,-1.53)
+ chatBalloon.setPos(-2.19,0,-1.74)
+ chatBalloon.setScale(0.4)
+
+ #bMin, bMax = chatBalloon.getTightBounds()
+ #center = (bMin + bMax)/2.0
+ #chatBalloon.setPos(-center[0], -center[1], -center[2])
+ #corner = Vec3(bMax - center)
+ #print bMin, bMax, center, corner
+ #chatBalloon.setScale(1.0/corner[2])
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (frame, None)
+
+ # nametag = NametagGroup()
+ # nametag.setFont(ToontownGlobals.getInterfaceFont())
+ # nametag.manage(base.marginManager)
+ # nametag.setActive(1)
+ # nametag.getNametag3d().setContents(Nametag.CName | Nametag.CSpeech | Nametag.CThought)
+ # chatString = TTLocalizer.ChatItemQuotes % (OTPLocalizer.CustomSCStrings[self.customIndex])
+ # nametag.setChat(chatString, CFSpeech)
+ # nametagNodePath = NodePath(nametag.getNametag3d().upcastToPandaNode())
+ # return self.makeFrameModel(nametagNodePath, spin=0)
+
+
+
+def getChatRange(fromIndex, toIndex, *otherRanges):
+ # This function returns a list of the chat items within the
+ # indicated range(s).
+
+ # Make sure we got an even number of otherRanges
+ assert(len(otherRanges)%2 == 0)
+
+ list = []
+
+ froms = [fromIndex,]
+ tos = [toIndex,]
+
+ i = 0
+ while i < len(otherRanges):
+ froms.append(otherRanges[i])
+ tos.append(otherRanges[i+1])
+ i += 2
+
+ for chatId in OTPLocalizer.CustomSCStrings.keys():
+ for fromIndex, toIndex in zip(froms, tos):
+ if chatId >= fromIndex and chatId <= toIndex and (chatId not in bannedPhrases):
+ list.append(CatalogChatItem(chatId))
+
+ return list
diff --git a/toontown/src/catalog/CatalogChatItemPicker.py b/toontown/src/catalog/CatalogChatItemPicker.py
new file mode 100644
index 0000000..76c6291
--- /dev/null
+++ b/toontown/src/catalog/CatalogChatItemPicker.py
@@ -0,0 +1,181 @@
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from direct.showbase import DirectObject
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from toontown.toontowngui import TTDialog
+
+NUM_ITEMS_SHOWN = 15
+
+class CatalogChatItemPicker(DirectObject.DirectObject):
+ """CatalogChatItemPicker
+
+ This represents a gui element that prompts the user to choose
+ a phrase from their custom phrase list to remove when it is full.
+
+ """
+
+ def __init__(self, callback, newMsg):
+ self.confirmDelete = None
+
+ # this callback is how we will let the caller know we are done
+ self.doneCallback = callback
+
+ # make a panel to hold everything
+ self.panel = DirectFrame(
+ relief = None,
+ geom = DGG.getDefaultDialogGeom(),
+ geom_color = ToontownGlobals.GlobalDialogColor,
+ geom_scale = (1.4, 1, 1.6),
+ text = TTLocalizer.MessagePickerTitle % OTPLocalizer.CustomSCStrings[newMsg],
+ text_pos = (0, 0.68),
+ text_scale = 0.05,
+ text_wordwrap = 24,
+ pos = (0, 0, 0),
+ )
+ #self.panel.hide()
+
+ # make the buttons for the picker
+ msgStrings = []
+ for msg in base.localAvatar.customMessages:
+ msgStrings.append(OTPLocalizer.CustomSCStrings[msg])
+
+ # make the scrolling pick list for the custom phrases
+ gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui")
+ self.picker = DirectScrolledList(
+ parent = self.panel,
+ relief = None,
+ pos = (0, 0, 0),
+ # inc and dec are DirectButtons
+ 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,0,-0.5),
+ # Make the disabled button fade out
+ 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,0,0.5),
+ # Make the disabled button fade out
+ decButton_image3_color = Vec4(1,1,1,0.2),
+ # itemFrame is a DirectFrame
+ itemFrame_pos = (0, 0, 0.39),
+ itemFrame_scale = 1.0,
+ itemFrame_relief = DGG.SUNKEN,
+ itemFrame_frameSize = (-0.55,0.55,-0.85,0.06),
+ itemFrame_frameColor = (0.85,0.95,1,1),
+ itemFrame_borderWidth = (0.01, 0.01),
+ itemMakeFunction = self.makeMessageButton,
+ itemMakeExtraArgs=[base.localAvatar.customMessages],
+ numItemsVisible = NUM_ITEMS_SHOWN,
+ items = msgStrings,
+ )
+
+ # Set up a clipping plane to truncate phrases that would extend
+ # off the right end of the scrolled list.
+ clipper = PlaneNode('clipper')
+ clipper.setPlane(Plane(Vec3(-1, 0, 0), Point3(0.55, 0, 0)))
+ clipNP = self.picker.attachNewNode(clipper)
+ self.picker.setClipPlane(clipNP)
+
+ gui.removeNode()
+
+ # make a cancel button
+ buttonModels = loader.loadModel("phase_3.5/models/gui/inventory_gui")
+ upButton = buttonModels.find("**/InventoryButtonUp")
+ downButton = buttonModels.find("**/InventoryButtonDown")
+ rolloverButton = buttonModels.find("**/InventoryButtonRollover")
+
+ # setup exit button
+ exitButton = DirectButton(
+ parent = self.panel,
+ relief = None,
+ pos = (0, 0, -0.7),
+ text = TTLocalizer.MessagePickerCancel,
+ text_scale = TTLocalizer.CCIPmessagePickerCancel,
+ text_pos = (-0.005,-0.01),
+ text_fg = Vec4(1,1,1,1),
+ textMayChange = 0,
+ image = (upButton,
+ downButton,
+ rolloverButton,
+ ),
+ image_scale = 1.1,
+ image_color = (0, 0.6, 1, 1),
+ command = self.__handleCancel,
+ )
+ buttonModels.removeNode()
+
+ def hide(self):
+ base.transitions.noTransitions()
+ self.panel.hide()
+
+ def show(self):
+ base.transitions.fadeScreen(0.5)
+ self.panel.setBin('gui-popup', 0)
+ self.panel.show()
+
+ def destroy(self):
+ # clean up gui items
+ base.transitions.noTransitions()
+ self.panel.destroy()
+ del self.panel
+ del self.picker
+ del self.doneCallback
+ if self.confirmDelete:
+ self.confirmDelete.cleanup()
+ del self.confirmDelete
+ self.confirmDelete = None
+
+ def makeMessageButton(self, name, number, *extraArgs):
+ # index into our particular msg
+ msg = extraArgs[0][0][number]
+ return DirectButton(
+ relief = None,
+ text = OTPLocalizer.CustomSCStrings[msg],
+ text_pos = (-0.5, 0, 0),
+ text_scale = 0.05,
+ text_align = TextNode.ALeft,
+ text1_bg = Vec4(1,1,0,1),
+ text2_bg = Vec4(0.5,0.9,1,1),
+ text3_fg = Vec4(0.4,0.8,0.4,1),
+ command = self.__handleDelete,
+ extraArgs = [msg],
+ )
+
+ def __handleDelete(self, msg):
+ # prompt the user to verify purchase
+ self.confirmDelete = TTDialog.TTGlobalDialog(
+ doneEvent = "confirmDelete",
+ message = TTLocalizer.MessageConfirmDelete % (OTPLocalizer.CustomSCStrings[msg]),
+ style = TTDialog.TwoChoice)
+ self.confirmDelete.msg = msg
+ self.hide()
+ self.confirmDelete.show()
+ self.accept("confirmDelete", self.__handleDeleteConfirm)
+
+ def __handleCancel(self):
+ self.doneCallback("cancel")
+
+ def __handleDeleteConfirm(self):
+ status = self.confirmDelete.doneStatus
+ msg = self.confirmDelete.msg
+ self.ignore("confirmDelete")
+ self.confirmDelete.cleanup()
+ del self.confirmDelete
+ self.confirmDelete = None
+ if (status == "ok"):
+ self.doneCallback("pick", base.localAvatar.customMessages.index(msg))
+ else:
+ self.show()
diff --git a/toontown/src/catalog/CatalogClothingItem.py b/toontown/src/catalog/CatalogClothingItem.py
new file mode 100644
index 0000000..06b89cd
--- /dev/null
+++ b/toontown/src/catalog/CatalogClothingItem.py
@@ -0,0 +1,685 @@
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from toontown.toon import ToonDNA
+import random
+from direct.showbase import PythonUtil
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+
+CTArticle = 0
+CTString = 1
+CTBasePrice = 2
+
+ABoysShirt = 0
+AGirlsShirt = 1
+AShirt = 2
+ABoysShorts = 3
+AGirlsShorts = 4
+AGirlsSkirt = 5
+AShorts = 6
+
+
+# These index numbers are written to the database. Don't mess with them.
+ClothingTypes = {
+ # Basic boy's shirts (also available from Make-A-Toon)
+ 101 : (ABoysShirt, 'bss1', 40),
+ 102 : (ABoysShirt, 'bss2', 40),
+ 103 : (ABoysShirt, 'bss3', 40),
+ 105 : (ABoysShirt, 'bss4', 40),
+ 104 : (ABoysShirt, 'bss5', 40),
+ 106 : (ABoysShirt, 'bss6', 40),
+ 107 : (ABoysShirt, 'bss7', 40),
+ 108 : (ABoysShirt, 'bss8', 40),
+ 109 : (ABoysShirt, 'bss9', 40),
+ 111 : (ABoysShirt, 'bss11', 40),
+ 115 : (ABoysShirt, 'bss15', 40),
+ # Catalog exclusive shirts are a bit pricier
+ 116 : (ABoysShirt, 'c_ss1', 80), # Series 1
+ 117 : (ABoysShirt, 'c_ss2', 80), # Series 1
+ 118 : (ABoysShirt, 'c_bss1', 80), # Series 1
+ 119 : (ABoysShirt, 'c_bss2', 80), # Series 1
+
+ 120 : (ABoysShirt, 'c_ss3', 80), # Series 2
+ 121 : (ABoysShirt, 'c_bss3', 80), # Series 2
+ 122 : (ABoysShirt, 'c_bss4', 80), # Series 2
+
+ 123 : (ABoysShirt, 'c_ss4', 120), # Series 3
+ 124 : (ABoysShirt, 'c_ss5', 120), # Series 3
+
+ 125 : (AShirt, 'c_ss6', 120), # Series 4
+ 126 : (AShirt, 'c_ss7', 120), # Series 4
+ 127 : (AShirt, 'c_ss8', 120), # Series 4
+ 128 : (AShirt, 'c_ss9', 120), # Series 4
+ 129 : (AShirt, 'c_ss10', 120), # Series 4
+ 130 : (AShirt, 'c_ss11', 120), # Series 4
+
+ 131 : (ABoysShirt, 'c_ss12', 160), # Series 7
+
+ # Basic girl's shirts (also available from Make-A-Toon)
+ 201 : (AGirlsShirt, 'gss1', 40),
+ 202 : (AGirlsShirt, 'gss2', 40),
+ 203 : (AGirlsShirt, 'gss3', 40),
+ 205 : (AGirlsShirt, 'gss4', 40),
+ 204 : (AGirlsShirt, 'gss5', 40),
+ 206 : (AGirlsShirt, 'gss6', 40),
+ 207 : (AGirlsShirt, 'gss7', 40),
+ 208 : (AGirlsShirt, 'gss8', 40),
+ 209 : (AGirlsShirt, 'gss9', 40),
+ 211 : (AGirlsShirt, 'gss11', 40),
+ 215 : (AGirlsShirt, 'gss15', 40),
+
+ # Catalog exclusive shirts are a bit pricier
+ 216 : (AGirlsShirt, 'c_ss1', 80), # Series 1
+ 217 : (AGirlsShirt, 'c_ss2', 80), # Series 1
+ 218 : (AGirlsShirt, 'c_gss1', 80), # Series 1
+ 219 : (AGirlsShirt, 'c_gss2', 80), # Series 1
+ 220 : (AGirlsShirt, 'c_ss3', 80), # Series 2
+ 221 : (AGirlsShirt, 'c_gss3', 80), # Series 2
+ 222 : (AGirlsShirt, 'c_gss4', 80), # Series 2
+ 223 : (AGirlsShirt, 'c_gss5', 80), # UNUSED
+ 224 : (AGirlsShirt, 'c_ss4', 120), # Series 3
+ 225 : (AGirlsShirt, 'c_ss13', 160), # Series 7
+
+ # Basic shorts for boys (available from Make-A-Toon)
+ 301 : (ABoysShorts, 'bbs1', 50),
+ 302 : (ABoysShorts, 'bbs2', 50),
+ 303 : (ABoysShorts, 'bbs3', 50),
+ 304 : (ABoysShorts, 'bbs4', 50),
+ 305 : (ABoysShorts, 'bbs5', 50),
+ 308 : (ABoysShorts, 'bbs8', 50),
+ # Catalog exclusive shorts
+ 310 : (ABoysShorts, 'c_bs1', 120), # Series 3
+ 311 : (ABoysShorts, 'c_bs2', 120), # Series 3
+ 312 : (ABoysShorts, 'c_bs3', 120), # Series 4
+ 313 : (ABoysShorts, 'c_bs4', 120), # Series 4
+ 314 : (ABoysShorts, 'c_bs5', 160), # Series 7
+
+ # Basic shorts/skirts for girls (available from Make-A-Toon)
+ 401 : (AGirlsSkirt, 'gsk1', 50),
+ 403 : (AGirlsSkirt, 'gsk3', 50),
+ 404 : (AGirlsSkirt, 'gsk4', 50),
+ 405 : (AGirlsSkirt, 'gsk5', 50),
+ 407 : (AGirlsSkirt, 'gsk7', 50),
+ # Catalog exclusive skirts are a bit pricier
+ 408 : (AGirlsSkirt, 'c_gsk1', 100),
+ 409 : (AGirlsSkirt, 'c_gsk2', 100),
+ 410 : (AGirlsSkirt, 'c_gsk3', 100),
+ 411 : (AGirlsSkirt, 'c_gsk4', 120),
+ 412 : (AGirlsSkirt, 'c_gsk5', 120), # Series 4
+ 413 : (AGirlsSkirt, 'c_gsk6', 120), # Series 4
+ 414 : (AGirlsSkirt, 'c_gsk7', 160), # Series 7
+ # Shorts
+ 451 : (AGirlsShorts, 'gsh1', 50),
+ 452 : (AGirlsShorts, 'gsh2', 50),
+ 453 : (AGirlsShorts, 'gsh3', 50),
+
+ # Halloween-themed clothes.
+ 1001 : (AShirt, 'hw_ss1', 200),
+ 1002 : (AShirt, 'hw_ss2', 200),
+ # Winter Holiday clothes.
+ 1100 : (AShirt, 'wh_ss1', 200),
+ 1101 : (AShirt, 'wh_ss2', 200),
+ 1102 : (AShirt, 'wh_ss3', 200),
+ 1103 : (AShirt, 'wh_ss4', 200),
+ 1104 : (ABoysShorts, 'wh_bs1', 200),
+ 1105 : (ABoysShorts, 'wh_bs2', 200),
+ 1106 : (ABoysShorts, 'wh_bs3', 200),
+ 1107 : (ABoysShorts, 'wh_bs4', 200),
+ 1108 : (AGirlsSkirt, 'wh_gsk1', 200),
+ 1109 : (AGirlsSkirt, 'wh_gsk2', 200),
+ 1110 : (AGirlsSkirt, 'wh_gsk3', 200),
+ 1111 : (AGirlsSkirt, 'wh_gsk4', 200),
+ # Valentines clothes.
+ 1200 : (AGirlsShirt, 'vd_ss1', 200),
+ 1201 : (AShirt, 'vd_ss2', 200),
+ 1202 : (ABoysShirt, 'vd_ss3', 200),
+ 1203 : (AGirlsShirt, 'vd_ss4', 200),
+ 1204 : (AGirlsSkirt, 'vd_gs1', 200),
+ 1205 : (ABoysShorts, 'vd_bs1', 200),
+ 1206 : (AShirt, 'vd_ss5', 200),
+ 1207 : (AShirt, 'vd_ss6', 200),
+ 1208 : (ABoysShorts, 'vd_bs2', 200),
+ 1209 : (ABoysShorts, 'vd_bs3', 200),
+ 1210 : (AGirlsSkirt, 'vd_gs2', 200),
+ 1211 : (AGirlsSkirt, 'vd_gs3', 200),
+ 1212 : (AShirt, 'vd_ss7', 200),
+ # St. Patricks Day clothes
+ 1300 : (AShirt, 'sd_ss1', 200),
+ 1301 : (AShirt, 'sd_ss2', 225),
+ 1302 : (AGirlsShorts, 'sd_gs1', 200),
+ 1303 : (ABoysShorts, 'sd_bs1', 200),
+ # T-Shirt Contest Shirts
+ 1400 : (AShirt, 'tc_ss1', 200),
+ 1401 : (AShirt, 'tc_ss2', 200),
+ 1402 : (AShirt, 'tc_ss3', 200),
+ 1403 : (AShirt, 'tc_ss4', 200),
+ 1404 : (AShirt, 'tc_ss5', 200),
+ 1405 : (AShirt, 'tc_ss6', 200),
+ 1406 : (AShirt, 'tc_ss7', 200),
+ # July 4th clothes
+ 1500 : (AShirt, 'j4_ss1', 200),
+ 1501 : (AShirt, 'j4_ss2', 200),
+ 1502 : (ABoysShorts, 'j4_bs1', 200),
+ 1503 : (AGirlsSkirt, 'j4_gs1', 200),
+ # Loyalty Gag Pajamas
+ 1600 : (AShirt, 'pj_ss1', 500),
+ 1601 : (AShirt, 'pj_ss2', 500),
+ 1602 : (AShirt, 'pj_ss3', 500),
+ 1603 : (ABoysShorts, 'pj_bs1', 500),
+ 1604 : (ABoysShorts, 'pj_bs2', 500),
+ 1605 : (ABoysShorts, 'pj_bs3', 500),
+ 1606 : (AGirlsShorts, 'pj_gs1', 500),
+ 1607 : (AGirlsShorts, 'pj_gs2', 500),
+ 1608 : (AGirlsShorts, 'pj_gs3', 500),
+ # Special Award Clothes
+ 1700 : (AShirt, 'sa_ss1', 200),
+ 1701 : (AShirt, 'sa_ss2', 200),
+ 1702 : (AShirt, 'sa_ss3', 200),
+ 1703 : (AShirt, 'sa_ss4', 200),
+ 1704 : (AShirt, 'sa_ss5', 200),
+ 1705 : (AShirt, 'sa_ss6', 200),
+ 1706 : (AShirt, 'sa_ss7', 200),
+ 1707 : (AShirt, 'sa_ss8', 200),
+ 1708 : (AShirt, 'sa_ss9', 200),
+ 1709 : (AShirt, 'sa_ss10', 200),
+ 1710 : (AShirt, 'sa_ss11', 200),
+
+ 1711 : (ABoysShorts, 'sa_bs1', 200),
+ 1712 : (ABoysShorts, 'sa_bs2', 200),
+ 1713 : (ABoysShorts, 'sa_bs3', 200),
+ 1714 : (ABoysShorts, 'sa_bs4', 200),
+ 1715 : (ABoysShorts, 'sa_bs5', 200),
+
+ 1716 : (AGirlsSkirt, 'sa_gs1', 200),
+ 1717 : (AGirlsSkirt, 'sa_gs2', 200),
+ 1718 : (AGirlsSkirt, 'sa_gs3', 200),
+ 1719 : (AGirlsSkirt, 'sa_gs4', 200),
+ 1720 : (AGirlsSkirt, 'sa_gs5', 200),
+
+ 1721 : (AShirt, 'sa_ss12', 200),
+ 1722 : (AShirt, 'sa_ss13', 200),
+ 1723 : (AShirt, 'sa_ss14', 250),
+ 1724 : (AShirt, 'sa_ss15', 250),
+ 1725 : (AShirt, 'sa_ss16', 200),
+ 1726 : (AShirt, 'sa_ss17', 200),
+ 1727 : (AShirt, 'sa_ss18', 200),
+ 1728 : (AShirt, 'sa_ss19', 200),
+ 1729 : (AShirt, 'sa_ss20', 200),
+ 1730 : (AShirt, 'sa_ss21', 200),
+ 1731 : (AShirt, 'sa_ss22', 200),
+ 1732 : (AShirt, 'sa_ss23', 200),
+
+ 1733 : (ABoysShorts, 'sa_bs6', 200),
+ 1734 : (ABoysShorts, 'sa_bs7', 250),
+ 1735 : (ABoysShorts, 'sa_bs8', 250),
+ 1736 : (ABoysShorts, 'sa_bs9', 200),
+ 1737 : (ABoysShorts, 'sa_bs10', 200),
+
+ 1738 : (AGirlsSkirt, 'sa_gs6', 200),
+ 1739 : (AGirlsSkirt, 'sa_gs7', 250),
+ 1740 : (AGirlsSkirt, 'sa_gs8', 250),
+ 1741 : (AGirlsSkirt, 'sa_gs9', 200),
+ 1742 : (AGirlsSkirt, 'sa_gs10', 200),
+
+ 1743 : (AShirt, 'sa_ss24', 250),
+ 1744 : (AShirt, 'sa_ss25', 250),
+ 1745 : (ABoysShorts, 'sa_bs11', 250),
+ 1746 : (ABoysShorts, 'sa_bs12', 250),
+ 1747 : (AGirlsSkirt, 'sa_gs11', 250),
+ 1748 : (AGirlsSkirt, 'sa_gs12', 250),
+
+ # I'm setting the cost of any Code Redemption clothing item to an obvious
+ # 5000 jellybeans. They should never be offered for sale in the catalog.
+ # They should only be available through the code redemption system.
+ 1749 : (AShirt, 'sil_1', 1), # Silly Mailbox Shirt
+ 1750 : (AShirt, 'sil_2', 1), # Silly Trash Can Shirt
+ 1751 : (AShirt, 'sil_3', 1), # Loony Labs Shirt
+ 1752 : (AShirt, 'sil_4', 5000), # Silly Hydrant Shirt
+ 1753 : (AShirt, 'sil_5', 5000), # Sillymeter Whistle Shirt
+ 1754 : (AShirt, 'sil_6', 1), # Silly Cogbuster Shirt
+ 1755 : (ABoysShorts, 'sil_bs1', 1), # Silly Cogbuster Shorts
+ 1756 : (AGirlsShorts, 'sil_gs1', 1), # Silly Cogbuster Shorts
+ 1757 : (AShirt, 'sil_7', 20), # Victory Party Shirt 1
+ 1758 : (AShirt, 'sil_8', 20), # Victory Party Shirt 2
+
+ 1762 : (AShirt, 'sa_ss26', 200),
+ }
+
+# A list of clothes that are loyalty items, needed by award manager
+LoyaltyClothingItems = (1600, 1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608)
+
+class CatalogClothingItem(CatalogItem.CatalogItem):
+ """CatalogClothingItem
+
+ This corresponds to an item of clothing, either a shirt or a
+ bottom (shorts or skirt). The clothingType indexes into the
+ above map, which returns the appropriate clothing string for
+ either a girl or a boy toon.
+
+ """
+
+ def makeNewItem(self, clothingType, colorIndex, loyaltyDays = 0):
+ self.clothingType = clothingType
+ self.colorIndex = colorIndex
+ self.loyaltyDays = loyaltyDays
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def storedInCloset(self):
+ # Returns true if this kind of item takes up space in the
+ # avatar's closet, false otherwise.
+ return 1
+
+ def notOfferedTo(self, avatar):
+ # Boys can only buy boy clothing, and girls can only buy girl
+ # clothing. Sorry.
+ article = ClothingTypes[self.clothingType][CTArticle]
+
+ if article == AShirt or article == AShorts:
+ # This article is androgynous.
+ return 0
+
+ forBoys = (article == ABoysShirt or article == ABoysShorts)
+ if avatar.getStyle().getGender() == 'm':
+ return not forBoys
+ else:
+ return forBoys
+
+ def forBoysOnly(self):
+ article = ClothingTypes[self.clothingType][CTArticle]
+ if (article == ABoysShirt or article == ABoysShorts):
+ return 1
+ else:
+ return 0
+
+ def forGirlsOnly(self):
+ article = ClothingTypes[self.clothingType][CTArticle]
+ if (article == AGirlsShirt or article == AGirlsSkirt or article == AGirlsShorts):
+ return 1
+ else:
+ return 0
+
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 1
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+
+ if avatar.onOrder.count(self) != 0:
+ # It's on the way.
+ return 1
+
+ if avatar.onGiftOrder.count(self) != 0:
+ # someone has given it to you
+ return 1
+
+ if avatar.mailboxContents.count(self) != 0:
+ # It's waiting in the mailbox.
+ return 1
+
+ if self in avatar.awardMailboxContents or self in avatar.onAwardOrder:
+ # check award queue and award mailbox too
+ return 1
+
+ str = ClothingTypes[self.clothingType][CTString]
+
+ dna = avatar.getStyle()
+ if self.isShirt():
+ # Check if the avatar is already wearing this shirt.
+ defn = ToonDNA.ShirtStyles[str]
+ if (dna.topTex == defn[0] and
+ dna.topTexColor == defn[2][self.colorIndex][0] and
+ dna.sleeveTex == defn[1] and
+ dna.sleeveTexColor == defn[2][self.colorIndex][1]):
+ return 1
+
+ # Check if the shirt is in the avatar's closet.
+ l = avatar.clothesTopsList
+ for i in range(0, len(l), 4):
+ if (l[i] == defn[0] and
+ l[i + 1] == defn[2][self.colorIndex][0] and
+ l[i + 2] == defn[1] and
+ l[i + 3] == defn[2][self.colorIndex][1]):
+ return 1
+ else:
+ # Check if the avatar is already wearing these shorts/skirt.
+ defn = ToonDNA.BottomStyles[str]
+ if (dna.botTex == defn[0] and
+ dna.botTexColor == defn[1][self.colorIndex]):
+ return 1
+
+ # Check if the shorts/skirt is in the avatar's closet.
+ l = avatar.clothesBottomsList
+ for i in range(0, len(l), 2):
+ if (l[i] == defn[0] and
+ l[i + 1] == defn[1][self.colorIndex]):
+ return 1
+
+ # Not found anywhere; go ahead and buy it.
+ return 0
+
+
+ def getTypeName(self):
+ # e.g. "shirt", "shorts", etc.
+ #article = ClothingTypes[self.clothingType][CTArticle]
+ #return TTLocalizer.ClothingArticleNames[article]
+
+ # Until we have descriptive names per-item below, just return
+ # "Clothing" here.
+ return TTLocalizer.ClothingTypeName
+
+ def getName(self):
+ typeName = TTLocalizer.ClothingTypeNames.get(self.clothingType, 0)
+ # check for a specific item name
+ if typeName:
+ return typeName
+ # otherwise use a generic name
+ else:
+ article = ClothingTypes[self.clothingType][CTArticle]
+ return TTLocalizer.ClothingArticleNames[article]
+
+ def recordPurchase(self, avatar, optional):
+ # Updates the appropriate field on the avatar to indicate the
+ # purchase (or delivery). This makes the item available to
+ # use by the avatar. This method is only called on the AI side.
+
+ if avatar.isClosetFull():
+ return ToontownGlobals.P_NoRoomForItem
+
+ str = ClothingTypes[self.clothingType][CTString]
+
+ # Save the avatar's current clothes in his closet.
+ dna = avatar.getStyle()
+ if self.isShirt():
+ added = avatar.addToClothesTopsList(dna.topTex, dna.topTexColor,
+ dna.sleeveTex, dna.sleeveTexColor)
+ if added:
+ avatar.b_setClothesTopsList(avatar.getClothesTopsList())
+ self.notify.info('Avatar %s put shirt %d,%d,%d,%d in closet.' % (avatar.doId,
+ dna.topTex, dna.topTexColor,
+ dna.sleeveTex, dna.sleeveTexColor))
+ else:
+ self.notify.warning('Avatar %s %s lost current shirt; closet full.' % (avatar.doId, dna.asTuple()))
+
+ defn = ToonDNA.ShirtStyles[str]
+ dna.topTex = defn[0]
+ dna.topTexColor = defn[2][self.colorIndex][0]
+ dna.sleeveTex = defn[1]
+ dna.sleeveTexColor = defn[2][self.colorIndex][1]
+
+ else:
+ added = avatar.addToClothesBottomsList(dna.botTex, dna.botTexColor)
+ if added:
+ avatar.b_setClothesBottomsList(avatar.getClothesBottomsList())
+ self.notify.info('Avatar %s put bottoms %d,%d in closet.' % (avatar.doId,
+ dna.botTex, dna.botTexColor))
+ else:
+ self.notify.warning('Avatar %s %s lost current bottoms; closet full.' % (avatar.doId, dna.asTuple()))
+
+ defn = ToonDNA.BottomStyles[str]
+ dna.botTex = defn[0]
+ dna.botTexColor = defn[1][self.colorIndex]
+
+ # Store the new clothes on the avatar.
+ avatar.b_setDNAString(dna.makeNetString())
+ # need to call this to make sure generateToonClothes is called on client
+ avatar.d_catalogGenClothes()
+ return ToontownGlobals.P_ItemAvailable
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ return 60 # 1 hour.
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+
+ # Don't import this at the top of the file, since this code
+ # must run on the AI.
+ from toontown.toon import Toon
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ # Make an ToonDNA suitable for showing this clothing.
+ # First, we start with a copy of the avatar's dna.
+ dna = ToonDNA.ToonDNA(type = 't', dna = avatar.style)
+
+ # Now we apply the properties from this clothing.
+ str = ClothingTypes[self.clothingType][CTString]
+
+ if self.isShirt():
+ # It's a shirt.
+ defn = ToonDNA.ShirtStyles[str]
+ dna.topTex = defn[0]
+ dna.topTexColor = defn[2][self.colorIndex][0]
+ dna.sleeveTex = defn[1]
+ dna.sleeveTexColor = defn[2][self.colorIndex][1]
+ pieceNames = ('**/1000/**/torso-top', '**/1000/**/sleeves')
+ else:
+ # It's a skirt or shorts.
+ defn = ToonDNA.BottomStyles[str]
+ dna.botTex = defn[0]
+ dna.botTexColor = defn[1][self.colorIndex]
+ pieceNames = ('**/1000/**/torso-bot',)
+
+ # Create a toon wearing the clothing, then pull out the
+ # appropriate clothes and throw the rest away.
+ toon = Toon.Toon()
+ toon.setDNA(dna)
+
+ model = NodePath('clothing')
+
+ for name in pieceNames:
+ for piece in toon.findAllMatches(name):
+ piece.wrtReparentTo(model)
+
+ model.setH(180)
+
+ toon.delete()
+
+
+ return self.makeFrameModel(model)
+
+ def requestPurchase(self, phone, callback):
+ # Orders the item via the indicated telephone. Some items
+ # will pop up a dialog querying the user for more information
+ # before placing the order; other items will order
+ # immediately.
+
+ # In either case, the function will return immediately before
+ # the transaction is finished, but the given callback will be
+ # called later with two parameters: the return code (one of
+ # the P_* symbols defined in ToontownGlobals.py), followed by the
+ # item itself.
+
+ # This method is only called on the client.
+ from toontown.toontowngui import TTDialog
+ avatar = base.localAvatar
+
+ clothesOnOrder = 0
+ for item in avatar.onOrder + avatar.mailboxContents:
+ if item.storedInCloset():
+ clothesOnOrder += 1
+
+ if avatar.isClosetFull(clothesOnOrder):
+ # If the avatar's closet is full, pop up a dialog warning
+ # the user, and give him a chance to bail out.
+ self.requestPurchaseCleanup()
+ buttonCallback = PythonUtil.Functor(
+ self.__handleFullPurchaseDialog, phone, callback)
+ self.dialog = TTDialog.TTDialog(
+ style = TTDialog.YesNo,
+ text = TTLocalizer.CatalogPurchaseClosetFull,
+ text_wordwrap = 15,
+ command = buttonCallback,
+ )
+ self.dialog.show()
+
+ else:
+ # The avatar's closet isn't full; just buy it.
+ CatalogItem.CatalogItem.requestPurchase(self, phone, callback)
+
+ def requestPurchaseCleanup(self):
+ if hasattr(self, "dialog"):
+ self.dialog.cleanup()
+ del self.dialog
+
+ def __handleFullPurchaseDialog(self, phone, callback, buttonValue):
+ from toontown.toontowngui import TTDialog
+ self.requestPurchaseCleanup()
+ if buttonValue == DGG.DIALOG_OK:
+ # Go ahead and purchase it.
+ CatalogItem.CatalogItem.requestPurchase(self, phone, callback)
+ else:
+ # Don't purchase it.
+ callback(ToontownGlobals.P_UserCancelled, self)
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ if self.isShirt():
+ return TTLocalizer.CatalogAcceptShirt
+ elif self.isSkirt():
+ return TTLocalizer.CatalogAcceptSkirt
+ else:
+ return TTLocalizer.CatalogAcceptShorts
+ elif retcode == ToontownGlobals.P_NoRoomForItem:
+ return TTLocalizer.CatalogAcceptClosetFull
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
+
+ def getColorChoices(self):
+ # Returns the list from ToonDNA that defines the clothing
+ # item and its color options.
+ str = ClothingTypes[self.clothingType][CTString]
+
+ if self.isShirt():
+ # It's a shirt.
+ return ToonDNA.ShirtStyles[str][2]
+ else:
+ # It's a skirt or shorts.
+ return ToonDNA.BottomStyles[str][1]
+
+ def isShirt(self):
+ # Returns true if the article is a shirt, false if it is a
+ # pair of shorts or a skirt.
+ article = ClothingTypes[self.clothingType][CTArticle]
+ return article < ABoysShorts
+
+ def isSkirt(self):
+ # Returns true if the article is a skirt, false if it is a
+ # pair of shorts or a shirt.
+ article = ClothingTypes[self.clothingType][CTArticle]
+ return article == AGirlsSkirt
+
+ def output(self, store = ~0):
+ return "CatalogClothingItem(%s, %s%s)" % (
+ self.clothingType, self.colorIndex,
+ self.formatOptionalData(store))
+
+ def getFilename(self):
+ str = ClothingTypes[self.clothingType][CTString]
+ if self.isShirt():
+ # It's a shirt.
+ defn = ToonDNA.ShirtStyles[str]
+ topTex = defn[0]
+ return ToonDNA.Shirts[topTex]
+ else:
+ # It's a skirt or shorts.
+ defn = ToonDNA.BottomStyles[str]
+ botTex = defn[0]
+ article = ClothingTypes[self.clothingType][CTArticle]
+ if article == ABoysShorts:
+ return ToonDNA.BoyShorts[botTex]
+ else:
+ return ToonDNA.GirlBottoms[botTex][0]
+
+ def getColor(self):
+ str = ClothingTypes[self.clothingType][CTString]
+ if self.isShirt():
+ # It's a shirt.
+ defn = ToonDNA.ShirtStyles[str]
+ topTexColor = defn[2][self.colorIndex][0]
+ return ToonDNA.ClothesColors[topTexColor]
+ else:
+ # It's a skirt or shorts.
+ defn = ToonDNA.BottomStyles[str]
+ botTexColor = defn[1][self.colorIndex]
+ return ToonDNA.ClothesColors[botTexColor]
+
+
+ def compareTo(self, other):
+ if self.clothingType != other.clothingType:
+ return self.clothingType - other.clothingType
+ return self.colorIndex - other.colorIndex
+
+ def getHashContents(self):
+ return (self.clothingType, self.colorIndex)
+
+ def getBasePrice(self):
+ return ClothingTypes[self.clothingType][CTBasePrice]
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ self.clothingType = di.getUint16()
+ self.colorIndex = di.getUint8()
+ if versionNumber >= 6:
+ self.loyaltyDays = di.getUint16()
+ else:
+ #RAU this seeems safe, as an old user would never have the new loyalty items
+ self.loyaltyDays = 0
+
+ # Now validate the indices by assigning into a variable,
+ # color, which we don't care about other than to prove the
+ # clothingType and colorIndex map to a valid definition. If
+ # they don't, the following will raise an exception.
+ str = ClothingTypes[self.clothingType][CTString]
+ if self.isShirt():
+ color = ToonDNA.ShirtStyles[str][2][self.colorIndex]
+ else:
+ color = ToonDNA.BottomStyles[str][1][self.colorIndex]
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.clothingType)
+ dg.addUint8(self.colorIndex)
+ dg.addUint16(self.loyaltyDays)
+
+ def isGift(self):
+ if (self.loyaltyRequirement() > 0):
+ return 0
+ else:
+ if self.clothingType in LoyaltyClothingItems:
+ # we can get this case through award manager
+ # catalog generator is not creating the catalog item, hence no loyalty days
+ return 0
+ else:
+ return 1
+
+def getAllClothes(*clothingTypes):
+ # This function returns a list of all possible
+ # CatalogClothingItems (that is, all color variants) for the
+ # indicated type index(es).
+
+ list = []
+ for clothingType in clothingTypes:
+ base = CatalogClothingItem(clothingType, 0)
+
+ list.append(base)
+ for n in range(1, len(base.getColorChoices())):
+ list.append(CatalogClothingItem(clothingType, n))
+
+ return list
+
diff --git a/toontown/src/catalog/CatalogEmoteItem.py b/toontown/src/catalog/CatalogEmoteItem.py
new file mode 100644
index 0000000..fce3ee4
--- /dev/null
+++ b/toontown/src/catalog/CatalogEmoteItem.py
@@ -0,0 +1,191 @@
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from direct.interval.IntervalGlobal import *
+
+# A list of emotesthat are loyalty items, needed by award manager
+LoyaltyEmoteItems = (20, 21, 22, 23, 24)
+
+class CatalogEmoteItem(CatalogItem.CatalogItem):
+ """
+ This represents a particular emote animation.
+ """
+
+ sequenceNumber = 0
+ pictureToon = None
+ def makeNewItem(self, emoteIndex, loyaltyDays = 0):
+ self.emoteIndex = emoteIndex
+ self.loyaltyDays = loyaltyDays
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 1
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ if self in avatar.onOrder or self in avatar.mailboxContents or self in avatar.onGiftOrder \
+ or self in avatar.awardMailboxContents or self in avatar.onAwardOrder:
+ return 1
+ if self.emoteIndex >= len(avatar.emoteAccess):
+ return 0
+ return avatar.emoteAccess[self.emoteIndex] != 0
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ return TTLocalizer.CatalogAcceptEmote
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def getTypeName(self):
+ return TTLocalizer.EmoteTypeName
+
+ def getName(self):
+ return OTPLocalizer.EmoteList[self.emoteIndex]
+
+ def recordPurchase(self, avatar, optional):
+ if self.emoteIndex < 0 or self.emoteIndex > len(avatar.emoteAccess):
+ self.notify.warning("Invalid emote access: %s for avatar %s" % (self.emoteIndex, avatar.doId))
+ return ToontownGlobals.P_InvalidIndex
+
+ avatar.emoteAccess[self.emoteIndex] = 1
+ avatar.d_setEmoteAccess(avatar.emoteAccess)
+ return ToontownGlobals.P_ItemAvailable
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+
+ # Don't import this at the top of the file, since this code
+ # must run on the AI.
+ from toontown.toon import Toon
+ from toontown.toon import ToonHead
+ from toontown.toon import TTEmote
+ from otp.avatar import Emote
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ if self.emoteIndex in Emote.globalEmote.getHeadEmotes():
+ toon = ToonHead.ToonHead()
+ toon.setupHead(avatar.style, forGui = 1)
+ else:
+ toon = Toon.Toon()
+ toon.setDNA(avatar.style)
+ toon.loop('neutral')
+
+ toon.setH(180)
+ model, ival = self.makeFrameModel(toon, 0)
+
+
+ # Discard the ival from makeFrameModel, since we don't want to
+ # spin.
+
+ track, duration = Emote.globalEmote.doEmote(toon, self.emoteIndex, volume = self.volume)
+
+ if duration == None:
+ duration = 0
+ name = "emote-item-%s" % (self.sequenceNumber)
+ CatalogEmoteItem.sequenceNumber += 1
+ if track != None:
+ track = Sequence(Sequence(track, duration = 0),
+ Wait(duration + 2),
+ name = name)
+ else:
+ track = Sequence(Func(Emote.globalEmote.doEmote, toon, self.emoteIndex),
+ Wait(duration + 4),
+ name = name)
+ self.pictureToon = toon
+ return (model, track)
+
+ def changeIval(self, volume):
+ """Return an interval of the toon doing the emote, with a possible change in volume."""
+ # Don't import this at the top of the file, since this code
+ # must run on the AI.
+ from toontown.toon import Toon
+ from toontown.toon import ToonHead
+ from toontown.toon import TTEmote
+ from otp.avatar import Emote
+
+ self.volume = volume
+
+ # assumes getPicture has been called previously
+ if not hasattr(self, 'pictureToon'):
+ return Sequence()
+ track, duration = Emote.globalEmote.doEmote(self.pictureToon, self.emoteIndex, volume = self.volume)
+ if duration == None:
+ duration = 0
+ name = "emote-item-%s" % (self.sequenceNumber)
+ CatalogEmoteItem.sequenceNumber += 1
+ if track != None:
+ track = Sequence(Sequence(track, duration = 0),
+ Wait(duration + 2),
+ name = name)
+ else:
+ track = Sequence(Func(Emote.globalEmote.doEmote, toon, self.emoteIndex),
+ Wait(duration + 4),
+ name = name)
+ return track
+
+ def cleanupPicture(self):
+ CatalogItem.CatalogItem.cleanupPicture(self)
+ assert self.pictureToon
+ self.pictureToon.emote.finish()
+ self.pictureToon.emote = None
+ self.pictureToon.delete()
+ self.pictureToon = None
+
+ def output(self, store = ~0):
+ return "CatalogEmoteItem(%s%s)" % (
+ self.emoteIndex,
+ self.formatOptionalData(store))
+
+ def compareTo(self, other):
+ return self.emoteIndex - other.emoteIndex
+
+ def getHashContents(self):
+ return self.emoteIndex
+
+ def getBasePrice(self):
+ # All emotes are the same price for now.
+ return 550
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ self.emoteIndex = di.getUint8()
+ if versionNumber >= 6:
+ self.loyaltyDays = di.getUint16()
+ else:
+ #RAU this seeems safe, as an old user would never have the new loyalty items
+ self.loyaltyDays = 0
+ if self.emoteIndex > len(OTPLocalizer.EmoteList):
+ raise ValueError
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint8(self.emoteIndex)
+ dg.addUint16(self.loyaltyDays)
+
+ def isGift(self):
+ if (self.loyaltyRequirement() > 0):
+ return 0
+ else:
+ if self.emoteIndex in LoyaltyEmoteItems:
+ return 0
+ else:
+ return 1
+
diff --git a/toontown/src/catalog/CatalogFlooringItem.py b/toontown/src/catalog/CatalogFlooringItem.py
new file mode 100644
index 0000000..ca4d321
--- /dev/null
+++ b/toontown/src/catalog/CatalogFlooringItem.py
@@ -0,0 +1,261 @@
+from CatalogSurfaceItem import *
+
+# Indicies into Flooring Textures Dictionary
+FTTextureName = 0
+FTColor = 1
+FTBasePrice = 2
+
+# These index numbers are written to the database. Don't mess with them.
+# Also see TTLocalizer.FlooringNames.
+FlooringTypes = {
+ ## Series 1 ##
+ 1000 : ("phase_5.5/maps/floor_wood_neutral.jpg",
+ CTBasicWoodColorOnWhite, 150),
+ 1010 : ("phase_5.5/maps/flooring_carpetA_neutral.jpg",
+ CTFlatColorDark, 150),
+ 1020 : ("phase_4/maps/flooring_tile_neutral.jpg", # In phase 4 because it's also on the PetShopInterior model.
+ CTFlatColorDark, 150),
+ 1030 : ("phase_5.5/maps/flooring_tileB2.jpg",
+ None, 150),
+ # Grass, just for fun
+ 1040 : ("phase_4/maps/grass.jpg", None, 150),
+ # Beige bricks
+ 1050 : ("phase_4/maps/floor_tile_brick_diagonal2.jpg", None, 150),
+ # Red bricks
+ 1060 : ("phase_4/maps/floor_tile_brick_diagonal.jpg", None, 150),
+ # Square beige tiles
+ 1070 : ("phase_4/maps/plazz_tile.jpg", None, 150),
+ # Sidewalk with colors
+ 1080 : ("phase_4/maps/sidewalk.jpg", CTFlatColorDark, 150),
+ # Boardwalk
+ 1090 : ("phase_3.5/maps/boardwalk_floor.jpg", None, 150),
+ # Dirt
+ 1100 : ("phase_3.5/maps/dustroad.jpg", None, 150),
+
+ ## Series 2 ##
+ # Wood Tile
+ 1110 : ("phase_5.5/maps/floor_woodtile_neutral.jpg",
+ CTBasicWoodColorOnWhite, 150),
+ # Floor Tile
+ 1120 : ("phase_5.5/maps/floor_tile_neutral.jpg",
+ CTBasicWoodColorOnWhite + CTFlatColorDark, 150),
+ # Floor Tile Honeycomb
+ 1130 : ("phase_5.5/maps/floor_tile_honeycomb_neutral.jpg",
+ CTBasicWoodColorOnWhite, 150),
+
+ ## Series 3 ##
+ # Water floor
+ 1140 : ("phase_5.5/maps/UWwaterFloor1.jpg",
+ None, 150),
+
+ # Peach conch tile
+ 1150 : ("phase_5.5/maps/UWtileFloor4.jpg",
+ None, 150),
+
+ # Peach shell tile
+ 1160 : ("phase_5.5/maps/UWtileFloor3.jpg",
+ None, 150),
+
+ # Sand shell tile
+ 1170 : ("phase_5.5/maps/UWtileFloor2.jpg",
+ None, 150),
+
+ # Sand conch tile
+ 1180 : ("phase_5.5/maps/UWtileFloor1.jpg",
+ None, 150),
+
+ # Sandy floor
+ 1190 : ("phase_5.5/maps/UWsandyFloor1.jpg",
+ None, 150),
+
+
+ ## WINTER HOLIDAY ##
+ # Ice cube
+ 10000 : ("phase_5.5/maps/floor_icecube.jpg", CTWhite, 225),
+ 10010 : ("phase_5.5/maps/floor_snow.jpg", CTWhite, 225),
+
+ ## St. Patricks Day ##
+ # Gold shamrock
+ 11000 : ("phase_5.5/maps/StPatsFloor1.jpg", CTWhite, 225),
+ # Green shamrock
+ 11010 : ("phase_5.5/maps/StPatsFloor2.jpg", CTWhite, 225),
+ }
+
+class CatalogFlooringItem(CatalogSurfaceItem):
+ """CatalogFlooringItem
+
+ This represents a texture/color combination for floors.
+
+ """
+
+ def makeNewItem(self, patternIndex, colorIndex = None):
+ self.patternIndex = patternIndex
+ self.colorIndex = colorIndex
+ CatalogSurfaceItem.makeNewItem(self)
+
+ def needsCustomize(self):
+ # Returns true if the item still needs to be customized by the
+ # user (e.g. by choosing a color).
+ return self.colorIndex == None
+
+ def getTypeName(self):
+ # e.g. "wallpaper", "wainscoting", etc.
+ return TTLocalizer.SurfaceNames[STFlooring]
+
+ def getName(self):
+ name = TTLocalizer.FlooringNames.get(self.patternIndex)
+ if name:
+ return name
+ return self.getTypeName()
+
+ def getSurfaceType(self):
+ # Returns a value reflecting the type of surface this
+ # pattern is intended to be applied to.
+ return STFlooring
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+ frame = self.makeFrame()
+
+ sample = loader.loadModel('phase_5.5/models/estate/wallpaper_sample')
+ a = sample.find('**/a')
+ b = sample.find('**/b')
+ c = sample.find('**/c')
+
+ # Flooring gets applied to the whole thing.
+ a.setTexture(self.loadTexture(), 1)
+ a.setColorScale(*self.getColor())
+ b.setTexture(self.loadTexture(), 1)
+ b.setColorScale(*self.getColor())
+ c.setTexture(self.loadTexture(), 1)
+ c.setColorScale(*self.getColor())
+
+ sample.reparentTo(frame)
+
+## assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (frame, None)
+
+ def output(self, store = ~0):
+ return "CatalogFlooringItem(%s, %s%s)" % (
+ self.patternIndex, self.colorIndex,
+ self.formatOptionalData(store))
+
+ def getFilename(self):
+ return FlooringTypes[self.patternIndex][FTTextureName]
+
+ def compareTo(self, other):
+ if self.patternIndex != other.patternIndex:
+ return self.patternIndex - other.patternIndex
+ return 0
+
+ def getHashContents(self):
+ return self.patternIndex
+
+ def getBasePrice(self):
+ return FlooringTypes[self.patternIndex][FTBasePrice]
+
+ def loadTexture(self):
+ from pandac.PandaModules import Texture
+ filename = FlooringTypes[self.patternIndex][FTTextureName]
+ texture = loader.loadTexture(filename)
+ texture.setMinfilter(Texture.FTLinearMipmapLinear)
+ texture.setMagfilter(Texture.FTLinear)
+ return texture
+
+ def getColor(self):
+ if self.colorIndex == None:
+ # If no color index is set yet, use first color in color list
+ colorIndex = 0
+ else:
+ colorIndex = self.colorIndex
+ colors = FlooringTypes[self.patternIndex][FTColor]
+ if colors:
+ if colorIndex < len(colors):
+ return colors[colorIndex]
+ else:
+ print "Warning: colorIndex not in colors. Returning white."
+ return CT_WHITE
+ else:
+ return CT_WHITE
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogAtticItem.CatalogAtticItem.decodeDatagram(self, di, versionNumber, store)
+ if versionNumber < 3:
+ self.patternIndex = di.getUint8()
+ else:
+ self.patternIndex = di.getUint16()
+ if (versionNumber < 4) or (store & CatalogItem.Customization):
+ self.colorIndex = di.getUint8()
+ else:
+ self.colorIndex = None
+
+ # The following will generate an exception if
+ # self.patternIndex is invalid. The other fields can take
+ # care of themselves.
+ wtype = FlooringTypes[self.patternIndex]
+
+ def encodeDatagram(self, dg, store):
+ CatalogAtticItem.CatalogAtticItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.patternIndex)
+ if (store & CatalogItem.Customization):
+ dg.addUint8(self.colorIndex)
+
+def getFloorings(*indexList):
+ # This function returns a list of CatalogFlooringItems
+ # The returned items will all need to be customized (i.e
+ # have a color chosen by the user. Until customization,
+ # use a default color index of 0 (if the pattern has a color
+ # list) or CT_WHITE if the pattern has no color list
+ list = []
+ for index in indexList:
+ list.append(CatalogFlooringItem(index))
+ return list
+
+def getAllFloorings(*indexList):
+ # This function returns a list of all possible
+ # CatalogFlooringItems (that is, all color variants) for the
+ # indicated type index(es).
+ list = []
+ for index in indexList:
+ colors = FlooringTypes[index][FTColor]
+ if colors:
+ for n in range(len(colors)):
+ list.append(CatalogFlooringItem(index, n))
+ else:
+ list.append(CatalogFlooringItem(index, 0))
+ return list
+
+def getFlooringRange(fromIndex, toIndex, *otherRanges):
+ # This function returns a list of all possible
+ # CatalogFlooringItems (that is, all color variants) for the
+ # indicated type index(es).
+
+ # Make sure we got an even number of otherRanges
+ assert(len(otherRanges)%2 == 0)
+
+ list = []
+
+ froms = [fromIndex,]
+ tos = [toIndex,]
+
+ i = 0
+ while i < len(otherRanges):
+ froms.append(otherRanges[i])
+ tos.append(otherRanges[i+1])
+ i += 2
+
+ for patternIndex in FlooringTypes.keys():
+ for fromIndex, toIndex in zip(froms,tos):
+ if patternIndex >= fromIndex and patternIndex <= toIndex:
+ colors = FlooringTypes[patternIndex][FTColor]
+ if colors:
+ for n in range(len(colors)):
+ list.append(CatalogFlooringItem(patternIndex, n))
+ else:
+ list.append(CatalogFlooringItem(patternIndex, 0))
+ return list
diff --git a/toontown/src/catalog/CatalogFurnitureItem.py b/toontown/src/catalog/CatalogFurnitureItem.py
new file mode 100644
index 0000000..ce1d2c5
--- /dev/null
+++ b/toontown/src/catalog/CatalogFurnitureItem.py
@@ -0,0 +1,1143 @@
+import CatalogAtticItem
+import CatalogItem
+import random
+from toontown.toonbase import TTLocalizer
+
+FTModelName = 0
+FTColor = 1
+FTColorOptions = 2
+FTBasePrice = 3
+FTFlags = 4
+FTScale = 5
+
+FLBank = 0x0001
+FLCloset = 0x0002
+FLRug = 0x0004
+FLPainting = 0x0008
+FLOnTable = 0x0010
+FLIsTable = 0x0020
+FLPhone = 0x0040
+FLBillboard = 0x0080
+
+# this is essentially the same as HouseGlobals.houseColors2 with the addition of alpha = 1
+furnitureColors = [
+ (0.792, 0.353, 0.290, 1.0), # red
+ (0.176, 0.592, 0.439, 1.0), # green
+ (0.439, 0.424, 0.682, 1.0), # purple
+ (0.325, 0.580, 0.835, 1.0), # blue
+ (0.753, 0.345, 0.557, 1.0), # pink
+ (0.992, 0.843, 0.392, 1.0), # yellow
+ ]
+
+woodColors = [
+ (0.9330, 0.7730, 0.5690, 1.0), # burly wood
+ (0.9333, 0.6785, 0.0550, 1.0), # dark goldenrod
+ (0.5450, 0.4510, 0.3330, 1.0), # peach puff
+ (0.5410, 0.0000, 0.0000, 1.0), # deep red
+ (0.5451, 0.2706, 0.0745, 1.0), # chocolate
+ (0.5451, 0.4118, 0.4118, 1.0), # rosey brown
+ ]
+
+
+# This table maps the various bank ID's to the amount of jellybeans
+# they hold.
+BankToMoney = {
+ 1300 : 1000,
+ 1310 : 2500,
+ 1320 : 5000,
+ 1330 : 7500,
+ 1340 : 10000,
+ }
+MoneyToBank = {}
+for bankId, maxMoney in BankToMoney.items():
+ MoneyToBank[maxMoney] = bankId
+MaxBankId = 1340
+
+# This table maps the various closet ID's to the amount of clothes
+# they hold.
+ClosetToClothes = {
+ 500 : 10,
+ 502 : 15,
+ 504 : 20,
+ 506 : 25,
+ 510 : 10,
+ 512 : 15,
+ 514 : 20,
+ 516 : 25,
+ }
+ClothesToCloset = {}
+for closetId, maxClothes in ClosetToClothes.items():
+ # There is not a 1-to-1 mapping like the banks since there are boys
+ # and girls closets, so we'll store a bank Id tuple.
+ if not ClothesToCloset.has_key(maxClothes):
+ ClothesToCloset[maxClothes] = (closetId,)
+ else:
+ ClothesToCloset[maxClothes] += (closetId,)
+MaxClosetIds = (506, 516)
+
+
+# These index numbers are written to the database. Don't mess with them.
+# Also see TTLocalizer.FurnitureNames and TTLocalizer.AwardManagerFurnitureNames
+FurnitureTypes = {
+
+ # These are examples to illustrate how we might apply color
+ # options to furniture, and/or hide and show pieces or replace
+ # textures to extend a single model for multiple purposes.
+
+## # Wooden chair
+## 100 : ("phase_5.5/models/estate/cushionChair",
+## (("**/cushion*", None)),
+## None,
+## 50),
+
+## # Cushioned chair
+## 110 : ("phase_5.5/models/estate/cushionChair",
+## None,
+## None,
+## 100),
+
+## # Velvet chair
+## 120 : ("phase_5.5/models/estate/cushionChair",
+## (("**/cushion*", "phase_5.5/maps/velvet_cushion.jpg")),
+## { 0 : (("**/cushion*", (1, 0, 0, 1)),),
+## 1 : (("**/cushion*", (0.6, 0.2, 1, 1)),),
+## 2 : (("**/cushion*", (0.2, 0.2, 0.6, 1)),),
+## },
+## 250),
+
+## # Library chair
+## 130 : ("phase_5.5/models/estate/libraryChair",
+## None,
+## None,
+## 500),
+
+ ## CHAIRS ##
+ # Chair A - Series 1
+ 100 : ("phase_5.5/models/estate/chairA",
+ None, None, 80),
+
+ # Chair A desaturated - Series 7
+ 105 : ("phase_5.5/models/estate/chairAdesat",
+ None,
+ { 0 : (("**/cushion*", furnitureColors[0]), ("**/arm*", furnitureColors[0]),),
+ 1 : (("**/cushion*", furnitureColors[1]), ("**/arm*", furnitureColors[1]),),
+ 2 : (("**/cushion*", furnitureColors[2]), ("**/arm*", furnitureColors[2]),),
+ 3 : (("**/cushion*", furnitureColors[3]), ("**/arm*", furnitureColors[3]),),
+ 4 : (("**/cushion*", furnitureColors[4]), ("**/arm*", furnitureColors[4]),),
+ 5 : (("**/cushion*", furnitureColors[5]), ("**/arm*", furnitureColors[5]),),
+ },
+ 160),
+
+ # Chair - Series 1
+ 110 : ("phase_3.5/models/modules/chair",
+ None, None, 40),
+
+ # Desk chair - Series 2
+ 120 : ("phase_5.5/models/estate/deskChair",
+ None, None, 60),
+
+ # Bug room chair - Series 2
+ 130 : ("phase_5.5/models/estate/BugRoomChair",
+ None, None, 160),
+
+ # Underwater lobster chair - Series 3
+ 140 : ("phase_5.5/models/estate/UWlobsterChair",
+ None, None, 200),
+
+ # Underwater lifesaver chair - Series 3
+ 145 : ("phase_5.5/models/estate/UWlifeSaverChair",
+ None, None, 200),
+
+ # Western saddle stool - Series 4
+ 150 : ("phase_5.5/models/estate/West_saddleStool2",
+ None, None, 160),
+
+ # Western native chair - Series 4
+ 160 : ("phase_5.5/models/estate/West_nativeChair",
+ None, None, 160),
+
+ # Candy cupcake Chair - Series 6
+ 170 : ("phase_5.5/models/estate/cupcakeChair",
+ None, None, 240),
+
+
+ ## BEDS ##
+ # Boy's bed - Initial Furniture
+ 200 : ("phase_5.5/models/estate/regular_bed",
+ None, None, 400),
+
+ # Boy's bed destaturated - Series 7
+ 205 : ("phase_5.5/models/estate/regular_bed_desat",
+ None,
+ { 0 : (("**/bar*", woodColors[0]),("**/post*", woodColors[0]),("**/*support", woodColors[0]),
+ ("**/top", woodColors[0]),("**/bottom", woodColors[0]),("**/pPlane*", woodColors[0]),),
+ 1 : (("**/bar*", woodColors[1]),("**/post*", woodColors[1]),("**/*support", woodColors[1]),
+ ("**/top", woodColors[1]),("**/bottom", woodColors[1]),("**/pPlane*", woodColors[1]),),
+ 2 : (("**/bar*", woodColors[2]),("**/post*", woodColors[2]),("**/*support", woodColors[2]),
+ ("**/top", woodColors[2]),("**/bottom", woodColors[2]),("**/pPlane*", woodColors[2]),),
+ 3 : (("**/bar*", woodColors[3]),("**/post*", woodColors[3]),("**/*support", woodColors[3]),
+ ("**/top", woodColors[3]),("**/bottom", woodColors[3]),("**/pPlane*", woodColors[3]),),
+ 4 : (("**/bar*", woodColors[4]),("**/post*", woodColors[4]),("**/*support", woodColors[4]),
+ ("**/top", woodColors[4]),("**/bottom", woodColors[4]),("**/pPlane*", woodColors[4]),),
+ 5 : (("**/bar*", woodColors[5]),("**/post*", woodColors[5]),("**/*support", woodColors[5]),
+ ("**/top", woodColors[5]),("**/bottom", woodColors[5]),("**/pPlane*", woodColors[5]),),
+ },
+ 800),
+
+ # Girl's bed - Series 1
+ 210 : ("phase_5.5/models/estate/girly_bed",
+ None, None, 450),
+
+ # Bathtub bed - Series 1
+ 220 : ("phase_5.5/models/estate/bathtub_bed",
+ None, None, 550),
+
+ # Bug Room Bed - Series 2
+ 230 : ("phase_5.5/models/estate/bugRoomBed",
+ None, None, 600),
+
+ # Underwater Boat bed - Series 3
+ 240 : ("phase_5.5/models/estate/UWBoatBed",
+ None, None, 600),
+
+ # Western Cactus Hammoc bed - Series 4
+ 250 : ("phase_5.5/models/estate/West_cactusHammoc",
+ None, None, 550),
+
+ # Candy ice cream bed - Series 6
+ 260 : ("phase_5.5/models/estate/icecreamBed",
+ None, None, 700),
+
+ # Trolley bed - CatalogAnimatedFurnitureItem
+ 270 : ("phase_5.5/models/estate/trolley_bed",
+ None, None, 1200, None, None, 0.25),
+
+ ## MUSICAL INSTRUMENTS
+ # Piano - Series 2
+ 300 : ("phase_5.5/models/estate/Piano",
+ None, None, 1000, FLIsTable),
+
+ # Organ - Series 2
+ 310 : ("phase_5.5/models/estate/Organ",
+ None, None, 2500),
+
+
+ ## FIREPLACES ##
+ # Square Fireplace - Initial Furniture
+ 400 : ("phase_5.5/models/estate/FireplaceSq",
+ None, None, 800),
+
+ # Girly Fireplace - Series 1
+ 410 : ("phase_5.5/models/estate/FireplaceGirlee",
+ None, None, 800),
+
+ # Round Fireplace - Series 2
+ 420 : ("phase_5.5/models/estate/FireplaceRound",
+ None, None, 800),
+
+ # Bug Room Fireplace - Series 2
+ 430 : ("phase_5.5/models/estate/bugRoomFireplace",
+ None, None, 800),
+
+ # Candy Carmel Apple Fireplace - Series 6
+ 440 : ("phase_5.5/models/estate/CarmelAppleFireplace",
+ None, None, 800),
+
+ # Coral Fireplace
+ 450 : ("phase_5.5/models/estate/fireplace_coral",
+ None, None, 950),
+
+ # Coral Fireplace with fire
+ 460 : ("phase_5.5/models/estate/tt_m_prp_int_fireplace_coral",
+ None, None, 1250, None, None, 0.5),
+
+ # Square Fireplace with fire
+ 470 : ("phase_5.5/models/estate/tt_m_prp_int_fireplace_square",
+ None, None, 1100, None, None, 0.5),
+
+ # Round Fireplace with fire
+ 480 : ("phase_5.5/models/estate/tt_m_prp_int_fireplace_round",
+ None, None, 1100, None, None, 0.5),
+
+ # Girly Fireplace with fire
+ 490 : ("phase_5.5/models/estate/tt_m_prp_int_fireplace_girlee",
+ None, None, 1100, None, None, 0.5),
+
+ # Bug Room Fireplace with fire
+ 491 : ("phase_5.5/models/estate/tt_m_prp_int_fireplace_bugRoom",
+ None, None, 1100, None, None, 0.5),
+
+ # Candy Caramel Apple Fireplace with fire
+ 492 : ("phase_5.5/models/estate/tt_m_prp_int_fireplace_caramelApple",
+ None, None, 1100, None, None, 0.5),
+
+ ## WARDROBES ##
+ # Boy's Wardrobe, 10 items - Initial Furniture
+ 500 : ("phase_5.5/models/estate/closetBoy",
+ None, None, 500, FLCloset, 0.85),
+
+ # Boy's Wardrobe, 15 items - Series 1
+ 502 : ("phase_5.5/models/estate/closetBoy",
+ None, None, 500, FLCloset, 1.0),
+
+ # Boy's Wardrobe, 20 items
+ 504 : ("phase_5.5/models/estate/closetBoy",
+ None, None, 500, FLCloset, 1.15),
+
+ # Boy's Wardrobe, 25 items
+ 506 : ("phase_5.5/models/estate/closetBoy",
+ None, None, 500, FLCloset, 1.3),
+
+
+ # Girl's Wardrobe, 10 items - Initial Furniture
+ 510 : ("phase_5.5/models/estate/closetGirl",
+ None, None, 500, FLCloset, 0.85),
+
+ # Girl's Wardrobe, 15 items - Series 1
+ 512 : ("phase_5.5/models/estate/closetGirl",
+ None, None, 500, FLCloset, 1.0),
+
+ # Girl's Wardrobe, 20 items
+ 514 : ("phase_5.5/models/estate/closetGirl",
+ None, None, 500, FLCloset, 1.15),
+
+ # Girl's Wardrobe, 25 items
+ 516 : ("phase_5.5/models/estate/closetGirl",
+ None, None, 500, FLCloset, 1.3),
+
+ ## LAMPS ##
+ # Short lamp - Series 1
+ 600 : ("phase_3.5/models/modules/lamp_short",
+ None, None, 45, FLOnTable),
+
+ # Tall lamp - Series 2
+ 610 : ("phase_3.5/models/modules/lamp_tall",
+ None, None, 45),
+
+ # Lamp A - Series 1
+ 620 : ("phase_5.5/models/estate/lampA",
+ None, None, 35, FLOnTable),
+
+ # Lamp A Desaturated - Series 7
+ 625 : ("phase_5.5/models/estate/lampADesat",
+ None,
+ { 0 : (("**/top", furnitureColors[0]),),
+ 1 : (("**/top", furnitureColors[1]),),
+ 2 : (("**/top", furnitureColors[2]),),
+ 3 : (("**/top", furnitureColors[3]),),
+ 4 : (("**/top", furnitureColors[4]),),
+ 5 : (("**/top", furnitureColors[5]),),
+ },
+ 70, FLOnTable),
+
+ # Bug Room Daisy Lamp 1 - Series 2
+ 630 : ("phase_5.5/models/estate/bugRoomDaisyLamp1",
+ None, None, 55),
+
+ # Bug Room Daisy Lamp 2 - Series 2
+ 640 : ("phase_5.5/models/estate/bugRoomDaisyLamp2",
+ None, None, 55),
+
+ # Underwater Lamp 1 - Series 3
+ 650 : ("phase_5.5/models/estate/UWlamp_jellyfish",
+ None, None, 55, FLOnTable),
+
+ # Underwater Lamp 2 - Series 3
+ 660 : ("phase_5.5/models/estate/UWlamps_jellyfishB",
+ None, None, 55, FLOnTable),
+
+ # Cowboy Lamp - series 4
+ 670 : ("phase_5.5/models/estate/West_cowboyLamp",
+ None, None, 55, FLOnTable),
+
+ # Lamps
+ 680: ("phase_5.5/models/estate/tt_m_ara_int_candlestick",
+ None,
+ { 0 : (("**/candlestick/candlestick", (1.0, 1.0, 1.0, 1.0),),),
+ 1 : (("**/candlestick/candlestick", furnitureColors[1]),),
+ 2 : (("**/candlestick/candlestick", furnitureColors[2]),),
+ 3 : (("**/candlestick/candlestick", furnitureColors[3]),),
+ 4 : (("**/candlestick/candlestick", furnitureColors[4]),),
+ 5 : (("**/candlestick/candlestick", furnitureColors[5]),),
+ 6 : (("**/candlestick/candlestick", furnitureColors[0]),),
+ },
+ 20, FLOnTable),
+
+ 681: ("phase_5.5/models/estate/tt_m_ara_int_candlestickLit",
+ None,
+ { 0 : (("**/candlestick/candlestick", (1.0, 1.0, 1.0, 1.0),),),
+ 1 : (("**/candlestickLit/candlestick", furnitureColors[1]),),
+ 2 : (("**/candlestickLit/candlestick", furnitureColors[2]),),
+ 3 : (("**/candlestickLit/candlestick", furnitureColors[3]),),
+ 4 : (("**/candlestickLit/candlestick", furnitureColors[4]),),
+ 5 : (("**/candlestickLit/candlestick", furnitureColors[5]),),
+ 6 : (("**/candlestickLit/candlestick", furnitureColors[0]),),
+ },
+ 25, FLOnTable),
+
+ ## COUCHES ##
+ # 1-person couch - Series 1
+ 700 : ("phase_3.5/models/modules/couch_1person",
+ None, None, 230),
+
+ # 1-person couch desaturated - Series 7
+ 705 : ("phase_5.5/models/estate/couch_1personDesat",
+ None,
+ { 0 : (("**/*couch", furnitureColors[0]),),
+ 1 : (("**/*couch", furnitureColors[1]),),
+ 2 : (("**/*couch", furnitureColors[2]),),
+ 3 : (("**/*couch", furnitureColors[3]),),
+ 4 : (("**/*couch", furnitureColors[4]),),
+ 5 : (("**/*couch", furnitureColors[5]),),
+ },
+ 460),
+
+ # 2-person couch - Series 1
+ 710 : ("phase_3.5/models/modules/couch_2person",
+ None, None, 230),
+
+ # 2-person couch desaturated - Series 7
+ 715 : ("phase_5.5/models/estate/couch_2personDesat",
+ None,
+ { 0 : (("**/*couch", furnitureColors[0]),),
+ 1 : (("**/*couch", furnitureColors[1]),),
+ 2 : (("**/*couch", furnitureColors[2]),),
+ 3 : (("**/*couch", furnitureColors[3]),),
+ 4 : (("**/*couch", furnitureColors[4]),),
+ 5 : (("**/*couch", furnitureColors[5]),),
+ },
+ 460),
+
+ # Western Hay couch - Series 4
+ 720 : ("phase_5.5/models/estate/West_HayCouch",
+ None, None, 420),
+
+ # Candy Twinkie couch - Series 6
+ 730 : ("phase_5.5/models/estate/twinkieCouch",
+ None, None, 480),
+
+
+ ## DESKS ##
+ # Desk - Series 1
+ 800 : ("phase_3.5/models/modules/desk_only_wo_phone",
+ None, None, 65, FLIsTable),
+
+ # Bug Room Desk - Series 2
+ 810 : ("phase_5.5/models/estate/BugRoomDesk",
+ None, None, 125, FLIsTable),
+
+
+ ## MISC PROPS ##
+ # Umbrella stand - Series 1
+ 900 : ("phase_3.5/models/modules/umbrella_stand",
+ None, None, 30),
+
+ # Coat rack - Series 1
+ 910 : ("phase_3.5/models/modules/coatrack",
+ None, None, 75),
+
+ # Trashcan - Series 2
+ 920 : ("phase_3.5/models/modules/paper_trashcan",
+ None, None, 30),
+
+ # Bug Room Red Pot - Series 2
+ 930 : ("phase_5.5/models/estate/BugRoomRedMushroomPot",
+ None, None, 60),
+
+ # Bug Room Yellow Pot - Series 2
+ 940 : ("phase_5.5/models/estate/BugRoomYellowMushroomPot",
+ None, None, 60),
+
+ # Underwater coat rack - Series 3
+ 950 : ("phase_5.5/models/estate/UWcoralClothRack",
+ None, None, 75),
+
+ # Western barrel stand - Series 4
+ 960 : ("phase_5.5/models/estate/west_barrelStand",
+ None, None, 75),
+
+ # Western fat cactus plant - Series 4
+ 970 : ("phase_5.5/models/estate/West_fatCactus",
+ None, None, 75),
+
+ # Western tepee - Series 4
+ 980 : ("phase_5.5/models/estate/West_Tepee",
+ None, None, 150),
+
+ # Gag fan - CatalogAnimatedFurnitureItem
+ 990 : ("phase_5.5/models/estate/gag_fan",
+ None, None, 500, None, None, 0.5),
+
+ ## RUGS ##
+ # Square Rug - Series 1
+ 1000 : ("phase_3.5/models/modules/rug",
+ None, None, 75, FLRug),
+
+ # Round Rug A - Series 1
+ 1010 : ("phase_5.5/models/estate/rugA",
+ None, None, 75, FLRug),
+
+ # Round Rug A desaturated - Series 7
+ 1015 : ("phase_5.5/models/estate/rugADesat",
+ None,
+ { 0 : (("**/pPlane*", furnitureColors[0]),),
+ 1 : (("**/pPlane*", furnitureColors[1]),),
+ 2 : (("**/pPlane*", furnitureColors[2]),),
+ 3 : (("**/pPlane*", furnitureColors[3]),),
+ 4 : (("**/pPlane*", furnitureColors[4]),),
+ 5 : (("**/pPlane*", furnitureColors[5]),),
+ },
+ 150, FLRug),
+
+ # Round Rug B - Series 1
+ 1020 : ("phase_5.5/models/estate/rugB",
+ None, None, 75, FLRug, 2.5),
+
+ # Bug Room Leaf Mat - Series 2
+ 1030 : ("phase_5.5/models/estate/bugRoomLeafMat",
+ None, None, 75, FLRug),
+
+ # Presents
+ 1040 : ("phase_5.5/models/estate/tt_m_ara_int_presents",
+ None, None, 300),
+
+ # Sled
+ 1050 : ("phase_5.5/models/estate/tt_m_ara_int_sled",
+ None, None, 400),
+
+ ## CABINETS ##
+ # Red Wood Cabinet - Series 1
+ 1100 : ("phase_5.5/models/estate/cabinetRwood",
+ None, None, 825),
+
+ # Yellow Wood Cabinet - Series 2
+ 1110 : ("phase_5.5/models/estate/cabinetYwood",
+ None, None, 825),
+
+ # Bookcase - Series 2
+ 1120 : ("phase_3.5/models/modules/bookcase",
+ None, None, 650, FLIsTable),
+
+ # Bookcase - Series 2
+ 1130 : ("phase_3.5/models/modules/bookcase_low",
+ None, None, 650, FLIsTable),
+
+ # Candy ice cream chest - Series 6
+ 1140 : ("phase_5.5/models/estate/icecreamChest",
+ None, None, 750),
+
+
+ ## TABLES ##
+ # End table - Series 1
+ 1200 : ("phase_3.5/models/modules/ending_table",
+ None, None, 60, FLIsTable),
+
+ # Radio table - Series 1
+ 1210 : ("phase_5.5/models/estate/table_radio",
+ None, None, 60, FLIsTable, 50.0),
+
+ # Radio table desaturated - Series 7
+ 1215 : ("phase_5.5/models/estate/table_radioDesat",
+ None,
+ { 0 : (("**/RADIOTABLE_*", woodColors[0]),),
+ 1 : (("**/RADIOTABLE_*", woodColors[1]),),
+ 2 : (("**/RADIOTABLE_*", woodColors[2]),),
+ 3 : (("**/RADIOTABLE_*", woodColors[3]),),
+ 4 : (("**/RADIOTABLE_*", woodColors[4]),),
+ 5 : (("**/RADIOTABLE_*", woodColors[5]),),
+ },
+ 120, FLIsTable, 50.0),
+
+ # Coffee table - Series 2
+ 1220 : ("phase_5.5/models/estate/coffeetableSq",
+ None, None, 180, FLIsTable),
+
+ # Coffee table - Series 2
+ 1230 : ("phase_5.5/models/estate/coffeetableSq_BW",
+ None, None, 180, FLIsTable),
+
+ # Underwater coffee table - Series 3
+ 1240 : ("phase_5.5/models/estate/UWtable",
+ None, None, 180, FLIsTable),
+
+ # Candy cookie table - Series 6
+ 1250 : ("phase_5.5/models/estate/cookieTableA",
+ None, None, 220, FLIsTable),
+
+ # Desaturated bedside table - Series 7
+ 1260 : ("phase_5.5/models/estate/TABLE_Bedroom_Desat",
+ None,
+ { 0 : (("**/Bedroom_Table", woodColors[0]),),
+ 1 : (("**/Bedroom_Table", woodColors[1]),),
+ 2 : (("**/Bedroom_Table", woodColors[2]),),
+ 3 : (("**/Bedroom_Table", woodColors[3]),),
+ 4 : (("**/Bedroom_Table", woodColors[4]),),
+ 5 : (("**/Bedroom_Table", woodColors[5]),),
+ },
+ 220, FLIsTable),
+
+
+ ## IN GAME INTERFACE DEVICES ##
+ # Jellybean Bank, 1000 beans - Initial Furniture
+ 1300 : ("phase_5.5/models/estate/jellybeanBank",
+ None, None, 0, FLBank, 0.75),
+
+ # Jellybean Bank, 2500 beans - Series 1
+ 1310 : ("phase_5.5/models/estate/jellybeanBank",
+ None, None, 400, FLBank, 1.0),
+
+ # Jellybean Bank, 5000 beans - Series 1
+ 1320 : ("phase_5.5/models/estate/jellybeanBank",
+ None, None, 800, FLBank, 1.125),
+
+ # Jellybean Bank, 7500 beans - Series 1
+ 1330 : ("phase_5.5/models/estate/jellybeanBank",
+ None, None, 1600, FLBank, 1.25),
+
+ # Jellybean Bank, 10000 beans - Series 1
+ 1340 : ("phase_5.5/models/estate/jellybeanBank",
+ None, None, 3200, FLBank, 1.5),
+
+ # Phone - Initial Furniture
+ 1399 : ("phase_5.5/models/estate/prop_phone-mod",
+ None, None, 0, FLPhone),
+
+
+ ## PAINTINGS ##
+ # Painting: Cezanne Toon - Series 1
+ 1400 : ("phase_5.5/models/estate/cezanne_toon",
+ None, None, 425, FLPainting, 2.0),
+
+ # Painting: Flowers - Series 1
+ 1410 : ("phase_5.5/models/estate/flowers",
+ None, None, 425, FLPainting, 2.0),
+
+ # Painting: Modern Mickey - Series 1
+ 1420 : ("phase_5.5/models/estate/modernistMickey",
+ None, None, 425, FLPainting, 2.0),
+
+ # Painting: Rembrandt Toon - Series 1
+ 1430 : ("phase_5.5/models/estate/rembrandt_toon",
+ None, None, 425, FLPainting, 2.0),
+
+ # Painting: Toon Landscape - Series 2
+ 1440 : ("phase_5.5/models/estate/landscape",
+ None, None, 425, FLPainting, 100.0),
+
+ # Painting: Whistler's Horse - Series 2
+ 1441 : ("phase_5.5/models/estate/whistler-horse",
+ None, None, 425, FLPainting, 2.0),
+
+ # Painting: Degas Toon Star - Series 2
+ 1442 : ("phase_5.5/models/estate/degasHorseStar",
+ None, None, 425, FLPainting, 2.5),
+
+ # Painting: Toon Pie - Series 2
+ 1443 : ("phase_5.5/models/estate/MagPie",
+ None, None, 425, FLPainting, 2.0),
+
+ # Painting: Valentines Day - Mickey and Minney
+ 1450 : ("phase_5.5/models/estate/tt_m_prp_int_painting_valentine",
+ None, None, 425, FLPainting),
+
+
+ ## APPLIANCES ##
+ # Radio A - Series 2
+ 1500 : ("phase_5.5/models/estate/RADIO_A",
+ None, None, 25, FLOnTable, 15.0),
+
+ # Radio B - Series 1
+ 1510 : ("phase_5.5/models/estate/RADIO_B",
+ None, None, 25, FLOnTable, 15.0),
+
+ # Radio C - Series 2
+ 1520 : ("phase_5.5/models/estate/radio_c",
+ None, None, 25, FLOnTable, 15.0),
+
+ # Bug Room TV - Series 2
+ 1530 : ("phase_5.5/models/estate/bugRoomTV",
+ None, None, 675),
+
+
+ ## VASES ##
+ # Vase A short - Series 1
+ 1600 : ("phase_5.5/models/estate/vaseA_short",
+ None, None, 120, FLOnTable),
+
+ # Vase A tall - Series 1
+ 1610 : ("phase_5.5/models/estate/vaseA_tall",
+ None, None, 120, FLOnTable),
+
+ # Vase B short - Series 2
+ 1620 : ("phase_5.5/models/estate/vaseB_short",
+ None, None, 120, FLOnTable),
+
+ # Vase B tall - Series 2
+ 1630 : ("phase_5.5/models/estate/vaseB_tall",
+ None, None, 120, FLOnTable),
+
+ # Vase C short - Series 2
+ 1640 : ("phase_5.5/models/estate/vaseC_short",
+ None, None, 120, FLOnTable),
+
+ # Vase D short - Series 2
+ 1650 : ("phase_5.5/models/estate/vaseD_short",
+ None, None, 120, FLOnTable),
+
+ # Underwater coral vase - Series 3
+ 1660 : ("phase_5.5/models/estate/UWcoralVase",
+ None, None, 120, (FLOnTable | FLBillboard)),
+
+ # Underwater shell vase - Series 3
+ 1661 : ("phase_5.5/models/estate/UWshellVase",
+ None, None, 120, (FLOnTable | FLBillboard) ),
+
+ # Valentines Day Vase - Rose Vase
+ 1670 : ("phase_5.5/models/estate/tt_m_prp_int_roseVase_valentine",
+ None, None, 200, (FLOnTable)),
+
+ # Valentines Day Vase - Rose Water Can
+ 1680 : ("phase_5.5/models/estate/tt_m_prp_int_roseWatercan_valentine",
+ None, None, 200, (FLOnTable)),
+
+
+ ## KITSCH ##
+ # Popcorn cart - Series 2
+ 1700 : ("phase_5.5/models/estate/popcornCart",
+ None, None, 400),
+
+ # Bug Room Ladybug - Series 2
+ 1710 : ("phase_5.5/models/estate/bugRoomLadyBug",
+ None, None, 260),
+
+ # Underwater skateboarder statue - Series 3
+ 1720 : ("phase_5.5/models/estate/UWfountain",
+ None, None, 450),
+
+ # Underwater clothes dryer - Series 3
+ 1725 : ("phase_5.5/models/estate/UWOceanDryer",
+ None, None, 400),
+
+
+ ## Fishbowls ##
+ # Underwater skull fish bowl - Series 3
+ 1800 : ("phase_5.5/models/estate/UWskullBowl",
+ None, None, 120, FLOnTable),
+
+ # Underwater lizard fish bowl - Series 3
+ 1810 : ("phase_5.5/models/estate/UWlizardBowl",
+ None, None, 120, FLOnTable),
+
+
+ ## Wall hangings ##
+ # Underwater swordFish wall hanging - Series 3
+ 1900 : ("phase_5.5/models/estate/UWswordFish",
+ None, None, 425, FLPainting, .5),
+
+ # Underwater hammerhead wall hanging - Series 3
+ 1910 : ("phase_5.5/models/estate/UWhammerhead",
+ None, None, 425, FLPainting),
+
+ # Western hanging horns - Series 4
+ 1920 : ("phase_5.5/models/estate/West_hangingHorns",
+ None, None, 475, FLPainting),
+
+ # Western sombrero - Series 4
+ 1930 : ("phase_5.5/models/estate/West_Sombrero",
+ None, None, 425, FLPainting),
+
+ # Western fancy sombrero - Series 4
+ 1940 : ("phase_5.5/models/estate/West_fancySombrero",
+ None, None, 450, FLPainting),
+
+ # Western coyote paw - Series 4
+ 1950 : ("phase_5.5/models/estate/West_CoyotePawdecor",
+ None, None, 475, FLPainting),
+
+ # Western horse shoe - Series 4
+ 1960 : ("phase_5.5/models/estate/West_Horseshoe",
+ None, None, 475, FLPainting),
+
+ # Western bison portrait - Series 4
+ 1970 : ("phase_5.5/models/estate/West_bisonPortrait",
+ None, None, 475, FLPainting),
+
+
+
+ ## Play Area Items ##
+ # Candy swingset - Series 6
+ 2000 : ("phase_5.5/models/estate/candySwingSet",
+ None, None, 300),
+
+ # Candy cake slide - Series 6
+ 2010 : ("phase_5.5/models/estate/cakeSlide",
+ None, None, 200),
+
+
+ ## Bathing Items ##
+ # Candy banana split shower - Series 6
+ 3000 : ("phase_5.5/models/estate/BanannaSplitShower",
+ None, None, 400),
+
+
+ ## SPECIAL HOLIDAY THEMED ITEMS FOLLOW ##
+ # short pumpkin - Halloween
+ 10000 : ("phase_4/models/estate/pumpkin_short",
+ None, None, 200, FLOnTable),
+
+ # tall pumpkin - Halloween
+ 10010 : ("phase_4/models/estate/pumpkin_tall",
+ None, None, 250, FLOnTable),
+
+ # winter tree
+ 10020 : ("phase_5.5/models/estate/tt_m_prp_int_winter_tree",
+ None, None, 500, None, None, 0.1),
+
+ # winter wreath
+ 10030 : ("phase_5.5/models/estate/tt_m_prp_int_winter_wreath",
+ None, None, 200, FLPainting),
+
+ }
+# If you add any new Animated furniture, update CatalogAnimatedFurniture.AnimatedFurnitureItemKeys
+
+
+class CatalogFurnitureItem(CatalogAtticItem.CatalogAtticItem):
+ """CatalogFurnitureItem
+
+ This represents a piece of furniture that the player may purchase
+ and store in his house or possibly in his lawn. Each item of
+ furniture corresponds to a particular model file (possibly with
+ some pieces hidden and/or texture swapped); there may also be a
+ number of user-customizable options for a given piece of furniture
+ (e.g. changing colors).
+
+ """
+
+ def makeNewItem(self, furnitureType, colorOption = None, posHpr = None):
+ self.furnitureType = furnitureType
+ self.colorOption = colorOption
+ self.posHpr = posHpr
+
+ CatalogAtticItem.CatalogAtticItem.makeNewItem(self)
+
+ def needsCustomize(self):
+ # Returns true if the item still needs to be customized by the
+ # user (e.g. by choosing a color).
+ return self.colorOption == None and \
+ FurnitureTypes[self.furnitureType][FTColorOptions] != None
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def replacesExisting(self):
+ # Returns true if an item of this type will, when purchased,
+ # replace an existing item of the same type, or false if items
+ # accumulate.
+ return (self.getFlags() & (FLCloset | FLBank)) != 0
+
+ def hasExisting(self):
+ # If replacesExisting returns true, this returns true if an
+ # item of this class is already owned by the avatar, false
+ # otherwise. If replacesExisting returns false, this is
+ # undefined.
+
+ # We always have a closet and bank.
+ return 1
+
+ def getYourOldDesc(self):
+ # If replacesExisting returns true, this returns the name of
+ # the already existing object, in sentence construct: "your
+ # old ...". If replacesExisting returns false, this is undefined.
+
+ if (self.getFlags() & FLCloset):
+ return TTLocalizer.FurnitureYourOldCloset
+ elif (self.getFlags() & FLBank):
+ return TTLocalizer.FurnitureYourOldBank
+ else:
+ return None
+
+ def notOfferedTo(self, avatar):
+ if (self.getFlags() & FLCloset):
+ # Boys can only buy boy wardrobes, and girls can only buy
+ # girl wardrobes. Sorry.
+ decade = self.furnitureType - (self.furnitureType % 10)
+ forBoys = (decade == 500)
+ if avatar.getStyle().getGender() == 'm':
+ return not forBoys
+ else:
+ return forBoys
+
+ # All other items are completely androgynous.
+ return 0
+
+ def isDeletable(self):
+ # Returns true if the item can be deleted from the attic,
+ # false otherwise.
+ return (self.getFlags() & (FLBank | FLCloset | FLPhone)) == 0
+
+ def getMaxBankMoney(self):
+ # This special method is only defined for bank type items,
+ # and returns the capacity of the bank in jellybeans.
+ return BankToMoney.get(self.furnitureType)
+
+ def getMaxClothes(self):
+ # This special method is only defined for wardrobe type items,
+ # and returns the number of clothing items the wardrobe holds.
+ index = self.furnitureType % 10
+ if index == 0:
+ return 10
+ elif index == 2:
+ return 15
+ elif index == 4:
+ return 20
+ elif index == 6:
+ return 25
+ else:
+ return None
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ if self.getFlags() & FLBank:
+ # No point in buying an equal or smaller bank.
+ if self.getMaxBankMoney() <= avatar.getMaxBankMoney():
+ return 1
+
+ # Also if this particular bank is on order, we don't need
+ # another one.
+ if self in avatar.onOrder or self in avatar.mailboxContents:
+ return 1
+
+ if self.getFlags() & FLCloset:
+ # No point in buying an equal or smaller wardrobe.
+ if self.getMaxClothes() <= avatar.getMaxClothes():
+ return 1
+
+ # Also if this particular wardrobe is on order, we don't need
+ # another one.
+ if self in avatar.onOrder or self in avatar.mailboxContents:
+ return 1
+
+ return 0
+
+ def getTypeName(self):
+ flags = self.getFlags()
+ if flags & FLPainting:
+ return TTLocalizer.PaintingTypeName
+ else:
+ return TTLocalizer.FurnitureTypeName
+
+ def getName(self):
+ return TTLocalizer.FurnitureNames[self.furnitureType]
+
+ def getFlags(self):
+ # Returns the special flag word associated with this furniture
+ # item. This controls special properties of the item, and is
+ # one or more of the bits defined above with the symbols FL*.
+ defn = FurnitureTypes[self.furnitureType]
+ if FTFlags < len(defn):
+ flag = defn[FTFlags]
+ if (flag == None):
+ return 0
+ else:
+ return flag
+ else:
+ return 0
+
+ def isGift(self):
+ if self.getFlags() & (FLCloset | FLBank):
+ return 0
+ else:
+ return 1
+
+ def recordPurchase(self, avatar, optional):
+ # Updates the appropriate field on the avatar to indicate the
+ # purchase (or delivery). This makes the item available to
+ # use by the avatar. This method is only called on the AI side.
+ house, retcode = self.getHouseInfo(avatar)
+ self.giftTag = None
+ if retcode >= 0:
+ house.addAtticItem(self)
+ if (self.getFlags() & FLBank):
+ # A special case: if we just bought a new bank, change
+ # our maximum bank money accordingly. This property
+ # is stored on the toon.
+ avatar.b_setMaxBankMoney(self.getMaxBankMoney())
+ if (self.getFlags() & FLCloset):
+ # Another special case: if we just bought a new
+ # wardrobe, change our maximum clothing items
+ # accordingly. This property is also stored on the
+ # toon.
+ avatar.b_setMaxClothes(self.getMaxClothes())
+
+ return retcode
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ return 24 * 60 # 24 hours.
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+ model = self.loadModel()
+ spin = 1
+
+ flags = self.getFlags()
+ if flags & FLRug:
+ spin = 0
+ model.setP(90)
+ elif flags & FLPainting:
+ spin = 0
+ elif flags & FLBillboard:
+ spin = 0
+ model.setBin('unsorted', 0, 1)
+
+## assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return self.makeFrameModel(model, spin)
+
+ def output(self, store = ~0):
+ return "CatalogFurnitureItem(%s%s)" % (
+ self.furnitureType,
+ self.formatOptionalData(store))
+
+ def getFilename(self):
+ type = FurnitureTypes[self.furnitureType]
+ return type[FTModelName]
+
+ def compareTo(self, other):
+ return self.furnitureType - other.furnitureType
+
+ def getHashContents(self):
+ return self.furnitureType
+
+ def getBasePrice(self):
+ return FurnitureTypes[self.furnitureType][FTBasePrice]
+
+ def loadModel(self):
+ type = FurnitureTypes[self.furnitureType]
+ model = loader.loadModel(type[FTModelName])
+ self.applyColor(model, type[FTColor])
+ if type[FTColorOptions] != None:
+ if self.colorOption == None:
+ # The user hasn't picked a color option; choose a
+ # random one.
+ option = random.choice(type[FTColorOptions].values())
+ else:
+ # Use the user's specified color option.
+ option = type[FTColorOptions].get(self.colorOption)
+
+ self.applyColor(model, option)
+
+ if (FTScale < len(type)):
+ scale = type[FTScale]
+ if not (scale == None):
+ # Also apply a scale.
+ model.setScale(scale)
+ model.flattenLight()
+ return model
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogAtticItem.CatalogAtticItem.decodeDatagram(self, di, versionNumber, store)
+ self.furnitureType = di.getInt16()
+ self.colorOption = None
+
+ # The following will raise an exception if self.furnitureType
+ # is not valid.
+ type = FurnitureTypes[self.furnitureType]
+
+ if type[FTColorOptions]:
+ if store & CatalogItem.Customization:
+ self.colorOption = di.getUint8()
+
+ # The following will raise an exception if
+ # self.colorOption is not valid.
+ option = type[FTColorOptions][self.colorOption]
+
+ def encodeDatagram(self, dg, store):
+ CatalogAtticItem.CatalogAtticItem.encodeDatagram(self, dg, store)
+ dg.addInt16(self.furnitureType)
+ if FurnitureTypes[self.furnitureType][FTColorOptions]:
+ if store & CatalogItem.Customization:
+ dg.addUint8(self.colorOption)
+
+
+def nextAvailableBank(avatar, duplicateItems):
+ bankId = MoneyToBank.get(avatar.getMaxBankMoney())
+ if bankId == None or bankId == MaxBankId:
+ # No more banks for this avatar.
+ return None
+
+ bankId += 10
+ item = CatalogFurnitureItem(bankId)
+
+ # But if this bank is already on order, don't offer the same bank
+ # again. Skip to the next one instead.
+ while item in avatar.onOrder or \
+ item in avatar.mailboxContents:
+ bankId += 10
+ if bankId > MaxBankId:
+ return None
+ item = CatalogFurnitureItem(bankId)
+
+ return item
+
+def getAllBanks():
+ list = []
+ for bankId in BankToMoney.keys():
+ list.append(CatalogFurnitureItem(bankId))
+ return list
+
+def nextAvailableCloset(avatar, duplicateItems):
+ # detemine which closet index in the tuple to use
+ if avatar.getStyle().getGender() == 'm':
+ index = 0
+ else:
+ index = 1
+ # handle a race a condition - dist toon ai cleaned up?
+ if not hasattr(avatar, "maxClothes"):
+ return None
+ closetIds = ClothesToCloset.get(avatar.getMaxClothes())
+ # we can't guarantee order on these dict values, so we'll sort them as a list
+ closetIds = list(closetIds)
+ closetIds.sort()
+ closetId = closetIds[index]
+ if closetId == None or closetId == MaxClosetIds[index]:
+ # No more closets for this avatar.
+ return None
+
+ closetId += 2
+ item = CatalogFurnitureItem(closetId)
+
+ # But if this closet is already on order, don't offer the same bank
+ # again. Skip to the next one instead.
+ while item in avatar.onOrder or \
+ item in avatar.mailboxContents:
+ closetId += 2
+ if closetId > MaxClosetIds[index]:
+ return None
+ item = CatalogFurnitureItem(closetId)
+
+ return item
+
+def getAllClosets():
+ list = []
+ for closetId in ClosetsToClothes.keys():
+ list.append(CatalogFurnitureItem(closetId))
+ return list
+
+def getAllFurnitures(index):
+ # This function returns a list of all possible
+ # CatalogFurnitureItems (that is, all color variants)
+ # for the indicated type index(es).
+ list = []
+ colors = FurnitureTypes[index][FTColorOptions]
+ for n in range(len(colors)):
+ list.append(CatalogFurnitureItem(index, n))
+ return list
diff --git a/toontown/src/catalog/CatalogGardenItem.py b/toontown/src/catalog/CatalogGardenItem.py
new file mode 100644
index 0000000..1c33a6c
--- /dev/null
+++ b/toontown/src/catalog/CatalogGardenItem.py
@@ -0,0 +1,219 @@
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from direct.interval.IntervalGlobal import *
+from toontown.estate import GardenGlobals
+
+class CatalogGardenItem(CatalogItem.CatalogItem):
+ """
+ Represents a Garden Item on the Delivery List
+ """
+
+ sequenceNumber = 0
+
+ def makeNewItem(self, itemIndex = 0, count = 3, tagCode = 1):
+ #import pdb; pdb.set_trace()
+ self.gardenIndex = itemIndex
+ self.numItems = count
+ self.giftCode = tagCode
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ if self.gardenIndex == GardenGlobals.GardenAcceleratorSpecial:
+ return 1
+ else:
+ return 100
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ if self in avatar.onOrder or self in avatar.mailboxContents or self in avatar.onGiftOrder \
+ or self in avatar.awardMailboxContents or self in avatar.onAwardOrder:
+ return 1
+ return 0
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ return TTLocalizer.CatalogAcceptGarden
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def getTypeName(self):
+ return TTLocalizer.GardenTypeName
+
+ def getName(self):
+ #name = ("Garden Item %s %s" % (self.gardenIndex, self.numItems))
+ name = GardenGlobals.Specials[self.gardenIndex]['photoName']
+ return name
+
+ def recordPurchase(self, avatar, optional):
+ #import pdb; pdb.set_trace()
+ #if self.gardenIndex == GardenGlobals.GardenAcceleratorSpecial:
+ # return ToontownGlobals.P_ItemOnOrder
+ #else:
+ if 1:
+ if avatar:
+ pass
+ avatar.addGardenItem(self.gardenIndex, self.numItems)
+ #TODO modify the toon's GardenSpecials field
+ return ToontownGlobals.P_ItemAvailable
+
+ def getPicture(self, avatar):
+ photoModel = GardenGlobals.Specials[self.gardenIndex]['photoModel']
+ beanJar = loader.loadModel(photoModel)
+ frame = self.makeFrame()
+ beanJar.reparentTo(frame)
+
+ photoPos = GardenGlobals.Specials[self.gardenIndex]['photoPos']
+ beanJar.setPos(*photoPos)
+ photoScale = GardenGlobals.Specials[self.gardenIndex]['photoScale']
+ #beanJar.setScale(2.5)
+ beanJar.setScale(photoScale)
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+ return (frame, None)
+
+ def output(self, store = ~0):
+ return "CatalogGardenItem(%s%s)" % (
+ self.gardenIndex,
+ self.formatOptionalData(store))
+
+ def compareTo(self, other):
+ return 0
+
+ def getHashContents(self):
+ return self.gardenIndex
+
+ def getBasePrice(self):
+ # equal to it's worth
+ beanCost = GardenGlobals.Specials[self.gardenIndex]['beanCost']
+ return beanCost
+
+ def decodeDatagram(self, di, versionNumber, store):
+ #import pdb; pdb.set_trace()
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ self.gardenIndex = di.getUint8()
+ self.numItems = di.getUint8()
+
+ def encodeDatagram(self, dg, store):
+ #import pdb; pdb.set_trace()
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint8(self.gardenIndex)
+ dg.addUint8(self.numItems)
+
+ def getRequestPurchaseErrorText(self, retcode):
+ """
+ Tell the user the item is available and what jelly beans to use it with.
+ """
+ retval = CatalogItem.CatalogItem.getRequestPurchaseErrorText(self,retcode)
+ origText =retval
+
+ if retval == TTLocalizer.CatalogPurchaseItemAvailable or \
+ retval == TTLocalizer.CatalogPurchaseItemOnOrder:
+ #now lets tell them what other beans to plant it with
+ recipeKey = GardenGlobals.getRecipeKeyUsingSpecial(self.gardenIndex)
+ if not recipeKey == -1:
+ retval += GardenGlobals.getPlantItWithString(self.gardenIndex)
+
+ if self.gardenIndex == GardenGlobals.GardenAcceleratorSpecial:
+ if GardenGlobals.ACCELERATOR_USED_FROM_SHTIKER_BOOK:
+ retval = origText
+ retval += TTLocalizer.UseFromSpecialsTab
+ retval += TTLocalizer.MakeSureWatered
+
+ return retval
+
+ def getRequestPurchaseErrorTextTimeout(self):
+ """
+ #RAU How long do we display RequestPurchaseErrorText.
+ Created since we need to display the text longer for garden supplies
+ """
+ return 20
+
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ if self.gardenIndex == GardenGlobals.GardenAcceleratorSpecial:
+ return 24 * 60 #24 hrs
+ else:
+ return 0
+
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ if self.gardenIndex == GardenGlobals.GardenAcceleratorSpecial:
+ return 1
+ else:
+ return 0
+ #return 1
+
+ def compareTo(self, other):
+ if self.gardenIndex != other.gardenIndex:
+ return self.gardenIndex - other.gardenIndex
+ return self.gardenIndex - other.gardenIndex
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+
+ if avatar.onOrder.count(self) != 0:
+ # It's on the way.
+ return 1
+
+ if avatar.mailboxContents.count(self) != 0:
+ # It's waiting in the mailbox.
+ return 1
+
+ for specials in avatar.getGardenSpecials():
+ if specials[0] == self.gardenIndex:
+ if self.gardenIndex == GardenGlobals.GardenAcceleratorSpecial:
+ #we're already carrying it
+ return 1
+
+ # Not found anywhere; go ahead and buy it.
+ return 0
+
+ def isSkillTooLow(self, avatar):
+ recipeKey = GardenGlobals.getRecipeKeyUsingSpecial(self.gardenIndex)
+ recipe = GardenGlobals.Recipes[recipeKey]
+ numBeansRequired = len(recipe['beans'])
+
+ canPlant = avatar.getBoxCapability()
+ result = False
+ if canPlant < numBeansRequired:
+ result = True
+ # make the Toon Statue special, requiring 639 skill
+ if not result and \
+ GardenGlobals.Specials.has_key(self.gardenIndex) and \
+ GardenGlobals.Specials[self.gardenIndex].has_key('minSkill'):
+ minSkill = GardenGlobals.Specials[self.gardenIndex]['minSkill']
+ if avatar.shovelSkill < minSkill:
+ result = True
+ else:
+ result = False
+ return result;
+
+ def noGarden(self, avatar):
+ """
+ if we don't have a garden, we can't buy the fertilizer and statues
+ """
+ return not avatar.getGardenStarted()
+
+ def isGift(self):
+ return 0
diff --git a/toontown/src/catalog/CatalogGardenStarterItem.py b/toontown/src/catalog/CatalogGardenStarterItem.py
new file mode 100644
index 0000000..1891371
--- /dev/null
+++ b/toontown/src/catalog/CatalogGardenStarterItem.py
@@ -0,0 +1,164 @@
+import CatalogItem
+import time
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from direct.interval.IntervalGlobal import *
+from toontown.toontowngui import TTDialog
+from toontown.estate import GardenTutorial
+
+class CatalogGardenStarterItem(CatalogItem.CatalogItem):
+ """CatalogGardenStarterItem
+
+ This is an item that goes away after a period of time.
+
+ """
+
+ def makeNewItem(self):
+ # this will need to be persistant (db?)
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 0
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ if self in avatar.onOrder or self in avatar.mailboxContents or self in avatar.onGiftOrder \
+ or self in avatar.awardMailboxContents or self in avatar.onAwardOrder or (hasattr(avatar, "gardenStarted") and avatar.getGardenStarted()):
+ return 1
+ return 0
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def getTypeName(self):
+ # Returns the name of the general type of item.
+ return TTLocalizer.GardenStarterTypeName
+
+
+ def getName(self):
+ return TTLocalizer.GardenStarterTypeName
+
+
+ def recordPurchase(self, avatar, optional):
+ print("rental-- record purchase")
+ #if avatar:
+ # avatar.addMoney(self.beanAmount)
+
+ if avatar:
+ print("starter garden-- has avater")
+ #zoneId = avatar.zoneId
+ #estateOwnerDoId = simbase.air.estateMgr.zone2owner.get(zoneId)
+ estate = simbase.air.estateMgr.estate.get(avatar.doId)
+ if estate:
+ print("starter garden-- has estate")
+ estate.placeStarterGarden(avatar.doId)
+ else:
+ pass
+ print("starter garden-- something not there")
+ #import pdb; pdb.set_trace()
+
+ return ToontownGlobals.P_ItemAvailable
+
+ def getPicture(self, avatar):
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ scale = 1
+ heading = 0
+ pitch = 30
+ roll = 0
+ spin = 1
+ down = -1
+ #chatBalloon = loader.loadModel("phase_3/models/props/chatbox.bam")
+ modelParent = loader.loadModel('phase_5.5/models/estate/watering_cans')
+ model = modelParent.find('**/water_canA')
+ scale = .5
+ heading = 45
+
+ return self.makeFrameModel(model, spin)
+
+ def output(self, store = ~0):
+ return "CatalogGardenStarterItem(%s)" % (
+ self.formatOptionalData(store))
+
+ def compareTo(self, other):
+ return 0
+
+ def getHashContents(self):
+ return 0
+
+ def getBasePrice(self):
+ return 50
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ return 1 # 1 minute.
+
+ def isRental(self):
+ return 0
+
+ def isGift(self):
+ return 0
+
+ def acceptItem(self, mailbox, index, callback):
+ # Accepts the item from the mailbox. Some items will pop up a
+ # dialog querying the user for more information before
+ # accepting the item; other items will accept it immediately.
+
+ # In either case, the function will return immediately before
+ # the transaction is finished, but the given callback will be
+ # called later with three parameters: the return code (one of
+ # the P_* symbols defined in ToontownGlobals.py), followed by
+ # the item itself, and the supplied index number.
+
+ # The index is the position of this item within the avatar's
+ # mailboxContents list, which is used by the AI to know which
+ # item to remove from the list (and also to doublecheck that
+ # we're accepting the expected item).
+
+ # This method is only called on the client.
+ self.confirmGarden = TTDialog.TTGlobalDialog(
+ doneEvent = "confirmGarden",
+ message = TTLocalizer.MessageConfirmGarden,
+ command = Functor(self.handleGardenConfirm, mailbox, index, callback),
+ style = TTDialog.TwoChoice)
+ #self.confirmRent.msg = msg
+ self.confirmGarden.show()
+ #self.accept("confirmRent", Functor(self.handleRentConfirm, mailbox, index, callback))
+ #self.__handleRentConfirm)
+ #self.mailbox = mailbox
+ #self.mailIndex = index
+ #self.mailcallback = callback
+
+ def handleGardenConfirm(self, mailbox, index, callback, choice):
+ #def handleRentConfirm(self, *args):
+ #print(args)
+ if choice > 0:
+ def handleTutorialDone():
+ self.gardenTutorial.destroy()
+ self.gardenTutorial = None
+
+ self.gardenTutorial = GardenTutorial.GardenTutorial(callback=handleTutorialDone)
+ if hasattr(mailbox, "mailboxGui") and mailbox.mailboxGui:
+ mailbox.acceptItem(self, index, callback)
+ mailbox.mailboxGui.justExit()
+ else:
+ callback(ToontownGlobals.P_UserCancelled, self, index)
+ if self.confirmGarden:
+ self.confirmGarden.cleanup()
+ self.confirmGarden = None
diff --git a/toontown/src/catalog/CatalogGenerator.py b/toontown/src/catalog/CatalogGenerator.py
new file mode 100644
index 0000000..76de5c7
--- /dev/null
+++ b/toontown/src/catalog/CatalogGenerator.py
@@ -0,0 +1,1809 @@
+from direct.directnotify import DirectNotifyGlobal
+import CatalogItem
+import CatalogItemList
+from CatalogFurnitureItem import CatalogFurnitureItem, nextAvailableBank, getAllBanks, nextAvailableCloset, getAllClosets
+from CatalogAnimatedFurnitureItem import CatalogAnimatedFurnitureItem
+from CatalogClothingItem import CatalogClothingItem, getAllClothes
+from CatalogChatItem import CatalogChatItem, getChatRange
+from CatalogEmoteItem import CatalogEmoteItem
+from CatalogWallpaperItem import CatalogWallpaperItem, getWallpapers
+from CatalogFlooringItem import CatalogFlooringItem, getFloorings
+from CatalogMouldingItem import CatalogMouldingItem, getAllMouldings
+from CatalogWainscotingItem import CatalogWainscotingItem, getAllWainscotings
+from CatalogWindowItem import CatalogWindowItem
+from CatalogPoleItem import nextAvailablePole, getAllPoles
+from CatalogPetTrickItem import CatalogPetTrickItem, getAllPetTricks
+from CatalogGardenItem import CatalogGardenItem
+from CatalogToonStatueItem import CatalogToonStatueItem
+from CatalogRentalItem import CatalogRentalItem
+from CatalogGardenStarterItem import CatalogGardenStarterItem
+from CatalogNametagItem import CatalogNametagItem
+from direct.actor import Actor
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownGlobals
+import types
+import random
+import time
+from pandac.PandaModules import *
+
+# Index numbers that stand for catalog items within this file,
+# e.g. for MetaItems, are local to this file only. These index
+# numbers are not saved in the database or stored on a toon; they are
+# used only to define the relationship between catalog items chosen at
+# random.
+
+MetaItems = {
+ # Basic shirts
+ 100: getAllClothes(101, 102, 103, 104, 105, 106, 107, 108, 109,
+ 109, 111, 115,
+ 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 209, 211, 215),
+
+ # Basic shorts and/or skirts
+ 300: getAllClothes(301, 302, 303, 304, 305, 308,
+ 401, 403, 404, 405, 407, 451, 452, 453),
+
+ # Series 1 chat
+ 2000: getChatRange(0, 1999),
+ # Series 2 chat
+ 2010: getChatRange(2000, 2999),
+ # Series 3 chat
+ 2020: getChatRange(3000, 3999),
+ # Series 4 chat
+ 2030: getChatRange(4000, 4999),
+ # Series 6 chat
+ 2040: getChatRange(6000, 6999),
+ # Series 7 chat
+ 2050: getChatRange(7000, 7999),
+
+ # Halloween chat
+ 2900: getChatRange(10000, 10099),
+ # Fall Festivus chat
+ 2910: getChatRange(11000, 11099),
+ # Valentines love chat
+ 2920: getChatRange(12000, 12049),
+ # Valentines love chat
+ 2921: getChatRange(12050, 12099),
+ # St Patrick's Day chat
+ 2930: getChatRange(13000, 13099),
+ # Estate Party Chat
+ 2940: getChatRange(14000, 14099),
+
+ # Wallpaper Series 1
+ 3000: getWallpapers(1000, 1100, 1200, 1300,
+ 1400, 1500, 1600, 1700,
+ 1800, 1900, 2000, 2100),
+
+ # Wallpaper Series 2
+ 3010: getWallpapers(2200, 2300, 2400, 2500,
+ 2600, 2700, 2800),
+
+ # Wallpaper Series 3
+ 3020: getWallpapers(2900,3000,3100,3200,3300,3400,3500,3600),
+
+ # Wallpaper Series 4
+ 3030: getWallpapers(3700, 3800, 3900),
+
+ # Basic wainscoting - Series 1
+ 3500 : getAllWainscotings(1000, 1010),
+ # Basic wainscoting - Series 2
+ 3510 : getAllWainscotings(1020),
+ # Valentines wainscoting
+ 3520 : getAllWainscotings(1030),
+ # Underwater wainscoting
+ 3530 : getAllWainscotings(1040),
+
+ # Basic flooring Series 1
+ 4000: getFloorings(1000, 1010, 1020, 1030,
+ 1040, 1050, 1060, 1070,
+ 1080, 1090, 1100),
+
+ # Basic flooring Series 2
+ 4010: getFloorings(1110, 1120, 1130),
+
+ # Basic flooring Series 3
+ 4020: getFloorings(1140, 1150, 1160, 1170, 1180, 1190),
+
+ # Basic moulding
+ 4500: getAllMouldings(1000, 1010),
+ 4510: getAllMouldings(1020, 1030, 1040),
+ 4520: getAllMouldings(1070,),
+
+ # A random pet trick. Presently this selects from the entire pool
+ # of available tricks; one day we may have level 1 pet tricks and
+ # level 2 pet tricks (and level 3 tricks, etc.)
+ 5000: getAllPetTricks(),
+
+ }
+
+# some of the chat keys above are not sold, these keys are the ones sold in the catalog
+MetaItemChatKeysSold = (2000, 2010, 2020, 2030, 2040, 2050, 2900, 2910, 2920, 2921, 2930)
+
+
+def getAllChatItemsSold():
+ """Give me a list of every single catalog chat item we offer."""
+ result = []
+ for key in MetaItemChatKeysSold:
+ result += MetaItems[key]
+ return result
+
+# This class is used in the below schedules to wrap around a catalog
+# week or a particular item to indicate that it is a "sale item" or
+# that all items in the week are "sale items".
+class Sale:
+ def __init__(self, *args):
+ self.args = args
+
+MonthlySchedule = (
+
+ # startMM, startDD, endMM, endDD, (item, item, item, ...)
+
+ # Halloween items -- on sale 10/1 through 10/31.
+ (10, 1, 10, 31,
+ ((3, 2900),
+ CatalogChatItem(10003),
+ CatalogClothingItem(1001, 0),
+ CatalogClothingItem(1002, 0),
+ CatalogClothingItem(1743, 0),
+ CatalogClothingItem(1744, 0),
+ CatalogClothingItem(1745, 0),
+ CatalogClothingItem(1746, 0),
+ CatalogClothingItem(1747, 0),
+ CatalogClothingItem(1748, 0),
+ CatalogClothingItem(1739, 0),
+ CatalogClothingItem(1740, 0),
+ CatalogClothingItem(1734, 0),
+ CatalogClothingItem(1735, 0),
+ CatalogClothingItem(1723, 0),
+ CatalogClothingItem(1724, 0),
+ CatalogWallpaperItem(10100),
+ CatalogWallpaperItem(10200),
+ CatalogFurnitureItem(10000),
+ CatalogFurnitureItem(10010),
+ CatalogNametagItem(9),
+
+
+ )),
+
+ # Winter items -- on sale 11/18 through 12/31
+ # moved a little earlier to get thanksgiving phrases
+ # before thanksgiving happens
+ (11, 18, 1, 1,
+ ((3, 2910),
+ CatalogChatItem(11020), # Have a Wonderful Winter!
+ CatalogClothingItem(1100, 0),
+ CatalogClothingItem(1101, 0),
+ CatalogClothingItem(1102, 0),
+ CatalogClothingItem(1103, 0),
+ CatalogWallpaperItem(11000),
+ CatalogWallpaperItem(11100),
+ CatalogWallpaperItem(11200),
+ CatalogFlooringItem(10000),
+ CatalogFlooringItem(10010),
+ CatalogGardenItem(130, 1), # snowman
+ CatalogAnimatedFurnitureItem(10020), # winter tree
+ CatalogFurnitureItem(10030, 0), # winter wreath
+ )),
+
+ # Valentines items -- on sale 2/1 through 2/16
+ (2, 1, 2, 16,
+ ((3, 2920),
+ (2, 2921),
+ CatalogClothingItem(1200, 0),
+ CatalogClothingItem(1201, 0),
+ CatalogClothingItem(1202, 0),
+ CatalogClothingItem(1203, 0),
+ CatalogClothingItem(1204, 0),
+ CatalogClothingItem(1205, 0),
+ CatalogWallpaperItem(12000),
+ CatalogWallpaperItem(12100),
+ CatalogWallpaperItem(12200),
+ CatalogWallpaperItem(12300),
+ CatalogWainscotingItem(1030, 0),
+ CatalogWainscotingItem(1030, 1),
+ CatalogMouldingItem(1060, 0),
+ CatalogMouldingItem(1060, 1),
+
+ # 2009 Valentines Day Items
+ CatalogClothingItem(1206, 0), # Valentines Day Shirt 1
+ CatalogClothingItem(1207, 0), # Valentines Day Shirt 2
+ CatalogClothingItem(1208, 0), # Valentines Day Shorts 1
+ CatalogClothingItem(1209, 0), # Valentines Day Shorts 2
+ CatalogClothingItem(1210, 0), # Valentines Day Skirt 1
+ CatalogClothingItem(1211, 0), # Valentines Day Skirt 2
+ CatalogClothingItem(1212, 0), # Valentines Day Shirt 3 - 2010 VDay Shirt
+ CatalogFurnitureItem(1670), # Valentines Day Vase - Rose Vase
+ CatalogFurnitureItem(1680), # Valentines Day Vase - Rose Water Can
+ CatalogFurnitureItem(1450), # Valentines Day Painting - Mickey and Minnie
+ CatalogMouldingItem(1100, 0), # Valentines Day Moulding - Cupid
+ CatalogMouldingItem(1110, 0), # Valentines Day Moulding - Hearts 1
+ CatalogMouldingItem(1120, 0), # Valentines Day Moulding - Hearts 2
+ )),
+
+ # St Patrick's items -- on sale 3/8 through 3/21
+ (3, 8, 3, 21,
+ ((3, 2930),
+ CatalogClothingItem(1300, 0),
+ CatalogClothingItem(1301, 0),
+ CatalogClothingItem(1302, 0),
+ CatalogClothingItem(1303, 0),
+ CatalogWallpaperItem(13000),
+ CatalogWallpaperItem(13100),
+ CatalogWallpaperItem(13200),
+ CatalogWallpaperItem(13300),
+ CatalogFlooringItem(11000),
+ CatalogFlooringItem(11010),
+ )),
+
+ # T-Shirt Contest items -- on sale 5/25 through 6/25
+ (5, 25, 6, 25,
+ (
+ CatalogClothingItem(1400, 0),
+ CatalogClothingItem(1401, 0),
+ CatalogClothingItem(1402, 0),
+ )
+ ),
+
+ # T-Shirt 2 Contest items -- on sale 8/1 through 8/31
+ (8, 1, 8, 31,
+ (
+ CatalogClothingItem(1403, 0),
+ CatalogClothingItem(1404, 0),
+ CatalogClothingItem(1405, 0),
+ CatalogClothingItem(1406, 0),
+ )
+ ),
+
+ # Furniture Contest items -- on sale 9/24 through 10/24
+ (9, 24, 10, 24,
+ (
+ CatalogFurnitureItem(450), # Coral Fireplace
+ CatalogAnimatedFurnitureItem(460), # Coral Fireplace with fire
+ CatalogAnimatedFurnitureItem(270), # Trolley Bed
+ CatalogAnimatedFurnitureItem(990), # Gag Fan
+ )
+ ),
+
+ # Estate Party speedchat items -- on sale 6/15 through 8/15
+ (6, 15, 8, 15,
+ ((4, 2940),
+ )
+ ),
+
+ # July 4th clothing items -- on sale 6/18 through 7/16
+ (6, 18, 7, 16,
+ (
+ CatalogClothingItem(1500, 0),
+ CatalogClothingItem(1501, 0),
+ CatalogClothingItem(1502, 0),
+ CatalogClothingItem(1503, 0),
+ )
+ ),
+
+ # Winter Holiday items - on sale 12/17 to 1/20
+ (12, 8, 1, 20,
+ (
+ CatalogClothingItem(1104, 0), # Winter Holiday Shorts Style 1
+ CatalogClothingItem(1105, 0), # Winter Holiday Shorts Style 2
+ CatalogClothingItem(1106, 0), # Winter Holiday Shorts Style 3
+ CatalogClothingItem(1107, 0), # Winter Holiday Shorts Style 4
+ CatalogClothingItem(1108, 0), # Winter Holiday Skirt Style 1
+ CatalogClothingItem(1109, 0), # Winter Holiday Skirt Style 2
+ CatalogClothingItem(1110, 0), # Winter Holiday Skirt Style 3
+ CatalogClothingItem(1111, 0), # Winter Holiday Skirt Style 4
+
+ CatalogMouldingItem(1080, 0), # Winter String Lights Moulding 1
+ CatalogMouldingItem(1085, 0), # Winter String Lights Moulding 2
+ CatalogMouldingItem(1090, 0), # Winter String Lights Moulding 3
+
+ CatalogFurnitureItem(680), # Candle
+
+ CatalogFurnitureItem(681), # Lit Candle
+
+ CatalogFurnitureItem(1040), # Presents
+ CatalogFurnitureItem(1050), # Sled
+ )
+ ),
+
+ # Silly Story Loony Labs Atom Shirt - on sale 6/9 to 7/15
+ (6, 9, 7, 15,
+ (
+ CatalogClothingItem(1751, 0), # Silly Story Loony Labs Atom Shirt
+ )
+ ),
+
+ # Silly Story Cogbuster Outfit - on sale 6/14 to 7/15
+ (6, 14, 7, 15,
+ (
+ CatalogClothingItem(1754, 0), # Silly Story Silly Cogbuster Shirt
+ CatalogClothingItem(1755, 0), # Silly Story Silly Cogbuster Shorts
+ CatalogClothingItem(1756, 0), # Silly Story Silly Cogbuster Shorts
+
+ )
+ ),
+
+ # Victory Party and Silly Story shirts - on sale 7/21 to 8/17
+ (7, 21, 8, 17,
+ (
+ CatalogClothingItem(1749, 0), # Silly Mailbox Shirt
+ CatalogClothingItem(1750, 0), # Silly Trash Can Shirt
+ CatalogClothingItem(1757, 0), # Victory Party Shirt 1
+ CatalogClothingItem(1758, 0), # Victory Party Shirt 2
+ )
+ ),
+
+ # Items - on sale 1/1 through 12/31, always available
+ (1, 1, 12, 31,
+ (
+ # Gardening Items
+ CatalogGardenItem(100, 1),
+ CatalogGardenItem(101, 1),
+ # save accelerator for later
+ #CatalogGardenItem(102, 1),
+ CatalogGardenItem(103, 1),
+ CatalogGardenItem(104, 1),
+ CatalogToonStatueItem(105, endPoseIndex = 108),
+
+ # Rental Items
+ CatalogRentalItem(1, 2880, 1000), # Renatl Cannon
+## CatalogRentalItem(2, 2880, 1000), # Rental Game Table
+ CatalogGardenStarterItem(),
+
+ # Basic Nametags
+ CatalogNametagItem(100),
+ CatalogNametagItem(0),
+
+ # Loyalty Items # WARNING update CatalogClothingItem.LoyaltyItems if you add more
+ CatalogClothingItem(1608, 0, 720), # Purple Pajama girl pants
+ CatalogClothingItem(1605, 0, 720), # Purple Pajama boy pants
+ CatalogClothingItem(1602, 0, 720), # Purple Glasses Pajama
+ CatalogClothingItem(1607, 0, 540), # Red Pajama girl pants
+ CatalogClothingItem(1604, 0, 540), # Red Pajama boy pants
+ CatalogClothingItem(1601, 0, 540), # Red Horn Pajama
+ CatalogClothingItem(1606, 0, 360), # Blue Pajama girl pants
+ CatalogClothingItem(1603, 0, 360), # Blue Pajama boy pants
+ CatalogClothingItem(1600, 0, 360), # Blue Banana Pajama
+
+ # WARNING update CatalogEmoteItem.LoyaltyItems if you add more loyalty emotes
+ CatalogEmoteItem(20, 90), # surprise
+ CatalogEmoteItem(21, 180), # cry
+ CatalogEmoteItem(22, 360), # delighted
+ CatalogEmoteItem(23, 540), # furious
+ CatalogEmoteItem(24, 720), # laugh
+ )
+ ),
+ )
+
+WeeklySchedule = (
+
+ ############################# SERIES 1 #############################
+
+ # Series 1, week 1 (overall week 1)
+ (100, # Basic shirt
+ (5, 2000), # Basic chat
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogEmoteItem(5), # Shrug
+ CatalogFurnitureItem(210, 0), # Girly bed
+ CatalogFurnitureItem(220, 0), # Bathtub bed
+ ),
+
+ # Series 1, week 2 (overall week 2)
+ (100, # Basic shirt
+ (5, 2000), # Basic chat
+ CatalogFurnitureItem(1400), # Painting: Cezanne Toon
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(600), # Short lamp
+ CatalogFurnitureItem(610), # Tall lamp
+ CatalogClothingItem(116, 0), # Exclusive boy shirt (yellow hooded sweatshirt)
+ CatalogClothingItem(216, 0), # Exclusive girl shirt (yellow hooded sweatshirt)
+ ),
+
+ # Series 1, week 3 (overall week 3)
+ (300, # Basic bottoms
+ (5, 2000), # Basic chat
+ CatalogFurnitureItem(1410), # Painting: Flowers
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(1100), # Cabinet Red Wood
+ CatalogFurnitureItem(1020), # Rug Round B
+ CatalogClothingItem(408, 0), # Exclusive girl skirt (blue and tan skirt)
+ 5000, # Pet trick
+ ),
+
+ # Series 1, week 4 (overall week 4)
+ (100, # Basic shirt
+ (5, 2000), # Basic chat
+ CatalogWindowItem(40), # Window view: City
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(110), # Chair
+ CatalogFurnitureItem(100), # Chair A
+ nextAvailablePole,
+ ),
+
+ # Series 1, week 5 (overall week 5)
+ (100, # Basic shirt
+ (5, 2000), # Basic chat
+ CatalogFurnitureItem(1420), # Painting: Modern Mickey
+ CatalogEmoteItem(9), # Applause
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(700), # Small couch
+ CatalogFurnitureItem(710), # Large couch
+ ),
+
+ # Series 1, week 6 (overall week 6)
+ (300, # Basic bottoms
+ (5, 2000), # Basic chat
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(410), # Girly Fireplace
+ CatalogAnimatedFurnitureItem(490), # Girly Fireplace with fire
+ CatalogFurnitureItem(1000), # Rug square
+ nextAvailableBank, # Bank
+ CatalogClothingItem(117, 0), # Exclusive boy shirt (yellow with palm)
+ CatalogClothingItem(217, 0), # Exclusive girl shirt (yellow with palm)
+ ),
+
+ # Series 1, week 7 (overall week 7)
+ (100, # Basic shirt
+ (5, 2000), # Basic chat
+ CatalogFurnitureItem(1430), # Painting: Rembrandt Toon
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(1510), # Radio B
+ CatalogFurnitureItem(1610), # Vase B
+ 5000, # Pet trick
+ CatalogNametagItem(1),
+ ),
+
+ # Series 1, week 8 (overall week 8)
+ (100, # Basic shirt
+ (5, 2000), # Basic chat
+ CatalogWindowItem(70), # Window view: Tropical Island
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(1210), # Table
+ CatalogClothingItem(409, 0), # Exclusive girl shirt (pink and purple skirt)
+ nextAvailablePole,
+ ),
+
+ # Series 1, week 9 (overall week 9)
+ (300, # Basic bottoms
+ (5, 2000), # Basic chat
+ CatalogEmoteItem(13), # Bow
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(1200), # Night Stand (end table)
+ CatalogFurnitureItem(900), # Umbrella Stand
+ ),
+
+ # Series 1, week 10 (overall week 10)
+ (100, # Basic shirt
+ (5, 2000), # Basic chat
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(910), # Coat Rack
+ CatalogFurnitureItem(1600), # Vase A
+ CatalogClothingItem(118, 0), # Exclusive boy shirt (blue with blue and white stripes)
+ CatalogClothingItem(218, 0), # Exclusive girl shirt (blue with 3 yellow stripes)
+ ),
+
+ # Series 1, week 11 (overall week 11)
+ (100, # Basic shirt
+ (5, 2000), # Basic chat
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(800), # Desk
+ CatalogFurnitureItem(1010), # Round Rug A
+ CatalogClothingItem(410, 0), # Exclusive girl shirt (green and yellow with star)
+ 5000, # Pet trick
+ ),
+
+ # Series 1, week 12 (overall week 12)
+ (300, # Basic bottoms
+ (5, 2000), # Basic chat
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogFurnitureItem(620), # Lamp A
+ nextAvailableBank, # Bank
+ nextAvailablePole, # Pole
+ nextAvailableCloset, # Wardrobe
+ ),
+
+ # Series 1, week 13 (overall week 13)
+ (300, # Basic bottoms
+ (5, 2000), # Basic chat
+ 3000, # Wallpaper
+ 3500, # Basic wainscoting
+ 4000, # Basic flooring
+ 4500, # Basic moulding
+ CatalogClothingItem(119, 0), # Exclusive boy shirt (orange)
+ CatalogClothingItem(219, 0), # Exclusive girl shirt (pink and beige)
+ ),
+
+ ############################# SERIES 2 #############################
+
+ # Series 2, week 1 (overall week 14)
+ (100, # Basic shirt
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(1110), # Yellow Wood Cabinet
+ CatalogFurnitureItem(630), # Bug Room Daisy Lamp 1
+ CatalogFurnitureItem(1630), # Vase B tall
+ CatalogEmoteItem(11), # Emote: Confused
+ CatalogNametagItem(11),
+ ),
+
+ # Series 2, week 2 (overall week 15)
+ (100, # Basic shirt
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(230), # Bug Room Bed
+ CatalogFurnitureItem(920), # Trashcan
+ CatalogFurnitureItem(1440), # Painting: Toon Landscape
+ ),
+
+ # Series 2, week 3 (overall week 16)
+ (300, # Basic bottoms
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(420), # Round Fireplace
+ CatalogAnimatedFurnitureItem(480), # Round Fireplace with fire
+ CatalogFurnitureItem(120), # Desk chair
+ CatalogClothingItem(120, 0),# Exclusive boy shirt
+ CatalogClothingItem(220, 0),# Exclusive girl shirt
+ nextAvailablePole, # Next Fishing pole
+ 5000, # Pet trick
+ ),
+
+ # Series 2, week 4 (overall week 17)
+ (100, # Basic shirt
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(1700), # Popcorn cart
+ CatalogFurnitureItem(640), # Bug Room Daisy Lamp 2
+ CatalogWindowItem(50), # Window view: Western
+ ),
+
+ # Series 2, week 5 (overall week 18)
+ (100, # Basic shirt
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(1120), # Bookcase - Tall
+ CatalogFurnitureItem(930), # Bug Room Red Pot
+ CatalogFurnitureItem(1500), # Radio A
+ CatalogEmoteItem(6), # Emote: Victory Dance
+ nextAvailableCloset, # Wardrobe
+ ),
+
+ # Series 2, week 6 (overall week 19)
+ (300, # Basic bottoms
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(430), # Bug Room Fireplace
+ CatalogAnimatedFurnitureItem(491), # Bug Room Fireplace with fire
+ CatalogFurnitureItem(1620), # Vase B short
+ CatalogFurnitureItem(1442), # Painting: Degas Toon Star
+ nextAvailableBank, # Bank
+ ),
+
+ # Series 2, week 7 (overall week 20)
+ (100, # Basic shirt
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(610), # Tall lamp
+ CatalogFurnitureItem(940), # Bug Room Yellow Pot
+ CatalogClothingItem(121, 0),# Exclusive boy shirt
+ CatalogClothingItem(221, 0),# Exclusive girl shirt
+ nextAvailablePole, # Next Fishing pole
+ 5000, # Pet trick
+ ),
+
+ # Series 2, week 8 (overall week 21)
+ (100, # Basic shirt
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(1710), # Bug Room Ladybug
+ CatalogFurnitureItem(1030), # Bug Room Leaf Mat
+ CatalogWindowItem(60), # Window view: Underwater
+ CatalogNametagItem(7),
+ ),
+
+ # Series 2, week 9 (overall week 22)
+ (300, # Basic bottoms
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(1130), # Bookcase - Low
+ CatalogFurnitureItem(130), # Bug room chair
+ CatalogEmoteItem(8), # Emote: Bored
+ ),
+
+ # Series 2, week 10 (overall week 23)
+ (100, # Basic shirt
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(1530), # Bug Room TV
+ CatalogFurnitureItem(1640), # Vase C short
+ CatalogFurnitureItem(1441), # Painting: Whistler's horse
+ ),
+
+ # Series 2, week 11 (overall week 24)
+ (100, # Basic shirt
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(300), # Piano
+ CatalogFurnitureItem(1220), # Coffee table
+ nextAvailablePole, # Next Fishing pole
+ 5000, # Pet trick
+ ),
+
+ # Series 2, week 12 (overall week 25)
+ (300, # Basic bottoms
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(810), # Bug Room Desk
+ CatalogFurnitureItem(1230), # Coffee table
+ CatalogFurnitureItem(1443), # Painting: Magritte Toon Pie
+ nextAvailableBank, # Bank
+ ),
+
+ # Series 2, week 13 (overall week 26)
+ (300, # Basic bottoms
+ (2, 2000), # Basic chat from series 1
+ (3, 2010), # Basic chat from series 2
+ 3010, # Wallpaper
+ 3510, # Basic wainscoting
+ 4010, # Basic flooring
+ 4510, # Basic moulding
+ CatalogFurnitureItem(310), # Organ
+ CatalogFurnitureItem(1520), # Radio C
+ CatalogFurnitureItem(1650), # Vase D short
+ CatalogWindowItem(80), # Window view: Starry night
+ #CatalogClothingItem(120, 0),# Exclusive boy shirt
+ CatalogClothingItem(222, 0),# Exclusive girl shirt
+ nextAvailableCloset, # Wardrobe
+ ),
+
+ ############################# SERIES 3 #############################
+
+ # Series 3, week 1 (overall week 27)
+ (100, # Basic shirt
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(1240), # Snorkelers Table
+ CatalogFurnitureItem(1661), # Shell Vase
+ CatalogEmoteItem(5), # Shrug
+ ),
+ # Series 3, week 2 (overall week 28)
+ (100, # Basic shirt
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(1800), # Fish Bowl 1
+ CatalogFurnitureItem(240), # Boat bed
+ CatalogFurnitureItem(1200), # Night Stand (end table)
+ CatalogNametagItem(12),
+ ),
+ # Series 3, week 3 (overall week 29)
+ (300, # Basic bottoms
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(145), # Lifejacket chair
+ CatalogClothingItem(123, 0),# Exclusive shirt (tie dye boy)
+ CatalogClothingItem(224, 0),# Exclusive shirt (tie dye girl)
+ nextAvailablePole, # Next Fishing pole
+ 5000, # Pet trick
+ ),
+ # Series 3, week 4 (overall week 30)
+ (100, # Basic shirt
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogWindowItem(100), # Window view: Snow
+ CatalogFurnitureItem(1810), # Fish Bowl 2
+ nextAvailableCloset, # Wardrobe
+ ),
+ # Series 3, week 5 (overall week 31)
+ (100, # Basic shirt
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(650), # Jellyfish Lamp 1
+ CatalogFurnitureItem(1900), # Swordfish Trophy
+ ),
+ # Series 3, week 6 (overall week 32)
+ (300, # Basic bottoms
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(1725), # Washing Machine
+ nextAvailableBank, # Bank
+ ),
+ # Series 3, week 7 (overall week 33)
+ (100, # Basic shirt
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogWindowItem(90), # Window view: Pool
+ CatalogClothingItem(124, 0),# Exclusive boy shirt
+ CatalogClothingItem(411, 0),# Exclusive girl skirt, rainbow
+ nextAvailablePole, # Next Fishing pole
+ ),
+ # Series 3, week 8 (overall week 34)
+ (100, # Basic shirt
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(140), # Lobster chair
+ CatalogFurnitureItem(1020), # Rug Round B
+ CatalogEmoteItem(13), # Bow
+ ),
+ # Series 3, week 9 (overall week 35)
+ (300, # Basic bottoms
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(950), # Coral Coat Rack
+ CatalogFurnitureItem(1660), # Coral Vase
+ CatalogClothingItem(310, 0),# Exclusive shorts (orange w/ blue side stripes)
+ CatalogNametagItem(2),
+ ),
+ # Series 3, week 10 (overall week 36)
+ (100, # Basic shirt
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(400), # Square Fireplace
+ CatalogAnimatedFurnitureItem(470), # Square Fireplace with fire
+ CatalogFurnitureItem(660), # Jellyfish Lamp 2
+ CatalogFurnitureItem(1200), # Night Stand (end table)
+ nextAvailableCloset, # Wardrobe
+ 5000, # Pet trick
+ ),
+ # Series 3, week 11 (overall week 37)
+ (100, # Basic shirt
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(1910), # Hammerhead trophy
+ nextAvailablePole, # Next Fishing pole
+ CatalogFurnitureItem(1000), # Rug square
+ ),
+ # Series 3, week 12 (overall week 38)
+ (300, # Basic bottoms
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(1720), # Fountain
+ nextAvailableBank, # Bank
+ CatalogEmoteItem(9), # Applause
+ ),
+ # Series 3, week 13 (overall week 39)
+ (300, # Basic bottoms
+ (1, 2000), # Basic chat from series 1
+ (2, 2010), # Basic chat from series 2
+ (3, 2020), # Basic chat from series 3
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogWindowItem(110), # Window view: Farm
+ CatalogClothingItem(311, 0),# Exclusive shorts (blue with yellow cuff)
+ ),
+
+ ############################# SERIES 4 #############################
+
+ # Series 4, week 1 (overall week 40)
+ (100, # Basic shirt
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogWindowItem(120), # Window view: Native Camp.
+ CatalogClothingItem(125, 0),# Cowboy shirts.
+ 5000, # Pet trick
+ ),
+ # Series 4, week 2 (overall week 41)
+ (300, # Basic bottoms
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogClothingItem(412, 0),# Girls western skirts.
+ CatalogClothingItem(312, 0),# Boys cowboy shorts.
+ CatalogFurnitureItem(1920), # Hanging Horns.
+ ),
+ # Series 4, week 3 (overall week 42)
+ (100, # Basic shirt
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ nextAvailablePole, # Next Fishing pole
+ CatalogWallpaperItem(3900), # Hat Wallpaper.
+ CatalogFurnitureItem(980), # Tepee.
+ CatalogNametagItem(13),
+ ),
+ # Series 4, week 4 (overall week 43)
+ (300, # Basic bottoms
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogClothingItem(130, 0),# Cowboy shirts.
+ CatalogFurnitureItem(150), # Saddle Stool.
+ nextAvailableCloset, # Wardrobe
+ ),
+ # Series 4, week 5 (overall week 44)
+ (100, # Basic shirt
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogClothingItem(128, 0),# Cowboy shirts.
+ CatalogWallpaperItem(3700), # Boot Wallpaper.
+ CatalogFurnitureItem(160), # Native Chair.
+ ),
+ # Series 4, week 6 (overall week 45)
+ (300, # Basic bottoms
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ nextAvailableBank, # Bank
+ CatalogClothingItem(313, 0),# Boys cowboy shorts.
+ CatalogClothingItem(413, 0),# Girls western skirts.
+ CatalogFurnitureItem(960), # Barrel Stand.
+ CatalogEmoteItem(7), # Think
+ ),
+ # Series 4, week 7 (overall week 46)
+ (100, # Basic shirt
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # gBasic moulding
+ nextAvailablePole, # Next Fishing pole
+ CatalogFurnitureItem(1930), # Simple Sombrero.
+ CatalogFurnitureItem(670), # Cowboy Lamp.
+ ),
+ # Series 4, week 8 (overall week 47)
+ (300, # Basic bottoms
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogClothingItem(126, 0),# Cowboy shirts.
+ CatalogFurnitureItem(1970), # Bison portrait.
+ 5000, # Pet trick
+ ),
+ # Series 4, week 9 (overall week 48)
+ (100, # Basic shirt
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(720), # Hay Couch.
+ CatalogFurnitureItem(970), # Fat Cactus.
+ nextAvailableCloset, # Wardrobe
+ ),
+ # Series 4, week 10 (overall week 49)
+ (300, # Basic bottoms
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogClothingItem(127, 0),# Cowboy shirts.
+ CatalogFurnitureItem(1950), # Coyote paw wall hanging.
+ CatalogNametagItem(4),
+ ),
+ # Series 4, week 11 (overall week 50)
+ (100, # Basic shirt
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ nextAvailablePole, # Next Fishing pole
+ CatalogFurnitureItem(1940), # Fancy Sombrero.
+ CatalogWindowItem(130), # Main Street View.
+ ),
+ # Series 4, week 12 (overall week 51)
+ (300, # Basic bottoms
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ nextAvailableBank, # Bank
+ CatalogWallpaperItem(3800), # Cactus Wallpaper.
+ CatalogClothingItem(129, 0),# Cowboy shirts.
+ CatalogEmoteItem(10), # Cringe
+ ),
+ # Series 4, week 13 (overall week 52)
+ (100, # Basic shirt
+ (1, 2010), # Basic chat from series 2
+ (2, 2020), # Basic chat from series 3
+ (3, 2030), # Basic chat from series 4
+ 3020, # Wallpaper
+ 3530, # Basic wainscoting
+ 4020, # Basic flooring
+ 4520, # Basic moulding
+ CatalogFurnitureItem(250), # Cactus Hammoc.
+ CatalogFurnitureItem(1960), # Horseshoe wall hanging.
+ nextAvailablePole, # Next Fishing pole
+ ),
+
+ ############################# SERIES 5 #############################
+
+ ## NOTE: This is a short catalog (only 5 weeks). The remainder of this
+ ## thirteen week period is provided by Catalog Series 6 (8 weeks).
+
+ # Series 5, week 1 (overall week 53) - Furniture Sale
+ Sale(
+ CatalogFurnitureItem(210, 0), # Girly bed
+ CatalogFurnitureItem(220, 0), # Bathtub bed
+ CatalogFurnitureItem(1100), # Cabinet Red Wood
+ CatalogFurnitureItem(110), # Chair
+ CatalogFurnitureItem(100), # Chair A
+ CatalogFurnitureItem(700), # Small couch
+ CatalogFurnitureItem(710), # Large couch
+ CatalogFurnitureItem(410), # Girly Fireplace
+ CatalogAnimatedFurnitureItem(490), # Girly Fireplace with fire
+ CatalogFurnitureItem(1210), # Table
+ CatalogFurnitureItem(1200), # Night Stand (end table)
+ CatalogFurnitureItem(800), # Desk
+ CatalogFurnitureItem(1110), # Yellow Wood Cabinet
+ CatalogFurnitureItem(230), # Bug Room Bed
+ CatalogFurnitureItem(420), # Round Fireplace
+ CatalogAnimatedFurnitureItem(480), # Round Fireplace with fire
+ CatalogFurnitureItem(120), # Desk chair
+ CatalogFurnitureItem(1700), # Popcorn cart
+ CatalogFurnitureItem(1120), # Bookcase - Tall
+ CatalogFurnitureItem(430), # Bug Room Fireplace
+ CatalogAnimatedFurnitureItem(491), # Bug Room Fireplace
+ CatalogFurnitureItem(1130), # Bookcase - Low
+ CatalogFurnitureItem(130), # Bug room chair
+ CatalogFurnitureItem(300), # Piano
+ CatalogFurnitureItem(1220), # Coffee table
+ CatalogFurnitureItem(810), # Bug Room Desk
+ CatalogFurnitureItem(1230), # Coffee table
+ CatalogFurnitureItem(310), # Organ
+ CatalogFurnitureItem(1240), # Snorkelers Table
+ CatalogFurnitureItem(240), # Boat bed
+ CatalogFurnitureItem(145), # Lifejacket chair
+ CatalogFurnitureItem(1725), # Washing Machine
+ CatalogFurnitureItem(140), # Lobster chair
+ CatalogFurnitureItem(950), # Coral Coat Rack
+ CatalogFurnitureItem(1720), # Fountain
+ ),
+
+ # Series 5, week 2 (overall week 54) - Clothing Sale
+ Sale(
+ CatalogClothingItem(116, 0), # Exclusive boy shirt (yellow hooded sweatshirt)
+ CatalogClothingItem(216, 0), # Exclusive girl shirt (yellow hooded sweatshirt)
+ CatalogClothingItem(408, 0), # Exclusive girl skirt (blue and tan skirt)
+ CatalogClothingItem(117, 0), # Exclusive boy shirt (yellow with palm)
+ CatalogClothingItem(217, 0), # Exclusive girl shirt (yellow with palm)
+ CatalogClothingItem(409, 0), # Exclusive girl shirt (pink and purple skirt)
+ CatalogClothingItem(118, 0), # Exclusive boy shirt (blue with blue and white stripes)
+ CatalogClothingItem(218, 0), # Exclusive girl shirt (blue with 3 yellow stripes)
+ CatalogClothingItem(410, 0), # Exclusive girl shirt (green and yellow with star)
+ CatalogClothingItem(119, 0), # Exclusive boy shirt (orange)
+ CatalogClothingItem(219, 0), # Exclusive girl shirt (pink and beige)
+ CatalogClothingItem(120, 0), # Exclusive boy shirt
+ CatalogClothingItem(220, 0), # Exclusive girl shirt
+ CatalogClothingItem(121, 0), # Exclusive boy shirt
+ CatalogClothingItem(221, 0), # Exclusive girl shirt
+ CatalogClothingItem(222, 0), # Exclusive girl shirt
+ CatalogClothingItem(123, 0), # Exclusive shirt (tie dye boy)
+ CatalogClothingItem(224, 0), # Exclusive shirt (tie dye girl)
+ CatalogClothingItem(411, 0), # Exclusive girl skirt, rainbow
+ CatalogClothingItem(311, 0), # Exclusive shorts (blue with yellow cuff)
+ CatalogClothingItem(310, 0), # Exclusive shorts (orange w/ blue side stripes)
+ ),
+
+ # Series 5, week 3 (overall week 55) - Window View Sale
+ Sale(
+ CatalogWindowItem(40), # Window view: City
+ CatalogWindowItem(70), # Window view: Tropical Island
+ CatalogWindowItem(50), # Window view: Western
+ CatalogWindowItem(60), # Window view: Underwater
+ CatalogWindowItem(80), # Window view: Starry night
+ CatalogWindowItem(100), # Window view: Snow
+ CatalogWindowItem(90), # Window view: Pool
+ CatalogWindowItem(110), # Window view: Farm
+ ),
+
+ # Series 5, week 4 (overall week 56) - Emote Sale
+ Sale(
+ CatalogEmoteItem(5), # Shrug
+ CatalogEmoteItem(9), # Applause
+ CatalogEmoteItem(13), # Bow
+ CatalogEmoteItem(11), # Confused
+ CatalogEmoteItem(6), # Victory Dance
+ CatalogEmoteItem(8), # Bored
+ CatalogNametagItem(10),
+ ),
+
+ # Series 5, week 5 (overall week 57) - Knick-knack Sale
+ Sale(
+ CatalogFurnitureItem(600), # Short lamp
+ CatalogFurnitureItem(610), # Tall lamp
+ CatalogFurnitureItem(620), # Lamp A
+ CatalogFurnitureItem(630), # Bug Room Daisy Lamp 1
+ CatalogFurnitureItem(640), # Bug Room Daisy Lamp 2
+ CatalogFurnitureItem(650), # Jellyfish Lamp 1
+ CatalogFurnitureItem(660), # Jellyfish Lamp 2
+ CatalogFurnitureItem(900), # Umbrella Stand
+ CatalogFurnitureItem(910), # Coat Rack
+ CatalogFurnitureItem(920), # Trashcan
+ CatalogFurnitureItem(930), # Bug Room Red Pot
+ CatalogFurnitureItem(940), # Bug Room Yellow Pot
+ CatalogFurnitureItem(1000), # Rug square
+ CatalogFurnitureItem(1010), # Round Rug A
+ CatalogFurnitureItem(1020), # Rug Round B
+ CatalogFurnitureItem(1030), # Bug Room Leaf Mat
+ CatalogFurnitureItem(1400), # Painting: Cezanne Toon
+ CatalogFurnitureItem(1410), # Painting: Flowers
+ CatalogFurnitureItem(1420), # Painting: Modern Mickey
+ CatalogFurnitureItem(1430), # Painting: Rembrandt Toon
+ CatalogFurnitureItem(1440), # Painting: Toon Landscape
+ CatalogFurnitureItem(1441), # Painting: Whistler's horse
+ CatalogFurnitureItem(1442), # Painting: Degas Toon Star
+ CatalogFurnitureItem(1443), # Painting: Magritte Toon Pie
+ CatalogFurnitureItem(1500), # Radio A
+ CatalogFurnitureItem(1510), # Radio B
+ CatalogFurnitureItem(1520), # Radio C
+ CatalogFurnitureItem(1530), # Bug Room TV
+ CatalogFurnitureItem(1600), # Vase A
+ CatalogFurnitureItem(1610), # Vase B
+ CatalogFurnitureItem(1620), # Vase B short
+ CatalogFurnitureItem(1630), # Vase B tall
+ CatalogFurnitureItem(1640), # Vase C short
+ CatalogFurnitureItem(1650), # Vase D short
+ CatalogFurnitureItem(1660), # Coral Vase
+ CatalogFurnitureItem(1661), # Shell Vase
+ CatalogFurnitureItem(1710), # Bug Room gLadybug
+ CatalogFurnitureItem(1800), # Fish Bowl 1
+ CatalogFurnitureItem(1810), # Fish Bowl 2
+ CatalogFurnitureItem(1900), # Swordfish Trophy
+ CatalogFurnitureItem(1910), # Hammerhead trophy
+ ),
+
+ ############################# SERIES 6 #############################
+
+ ## NOTE: This is a short catalog (only 8 weeks). This series makes up the
+ ## difference between the sale catalog (Series 5) and a full catalog.
+
+ # Series 6, week 1 (overall week 58) - Candy Items
+ (300, # Basic bottoms
+ (1, 2020), # Basic chat from series 3
+ (2, 2030), # Basic chat from series 4
+ (3, 2040), # Basic chat from series 6
+ CatalogFurnitureItem(730), # Twinkie Couch
+ nextAvailablePole, # Next Fishing pole
+ ),
+
+ # Series 6, week 2 (overall week 59) - Candy Items
+ (100, # Basic shirt
+ (1, 2020), # Basic chat from series 3
+ (2, 2030), # Basic chat from series 4
+ (3, 2040), # Basic chat from series 6
+ CatalogFurnitureItem(260), # Ice Cream Bed
+ nextAvailableBank, # Bank
+ ),
+
+ # Series 6, week 3 (overall week 60) - Candy Items
+ (300, # Basic bottoms
+ (1, 2020), # Basic chat from series 3
+ (2, 2030), # Basic chat from series 4
+ (3, 2040), # Basic chat from series 6
+ CatalogFurnitureItem(440), # Caramel Apple Fireplace
+ CatalogAnimatedFurnitureItem(492), # Caramel Apple Fireplace with fire
+ nextAvailableCloset, # Wardrobe
+ 5000, # Pet trick
+ ),
+
+ # Series 6, week 4 (overall week 61) - Candy Items
+ (100, # Basic shirt
+ (1, 2020), # Basic chat from series 3
+ (2, 2030), # Basic chat from series 4
+ (3, 2040), # Basic chat from series 6
+ CatalogFurnitureItem(170), # Cupcake Chair
+ CatalogFurnitureItem(1250), # Cookie Table
+ ),
+
+ # Series 6, week 5 (overall week 62) - Candy Items
+ (300, # Basic bottoms
+ (1, 2020), # Basic chat from series 3
+ (2, 2030), # Basic chat from series 4
+ (3, 2040), # Basic chat from series 6
+ CatalogFurnitureItem(1140), # Ice Cream Chest
+ nextAvailablePole, # Next Fishing pole
+ ),
+
+ # Series 6, week 6 (overall week 63) - Candy Items
+ (100, # Basic shirt
+ (1, 2020), # Basic chat from series 3
+ (2, 2030), # Basic chat from series 4
+ (3, 2040), # Basic chat from series 6
+ CatalogFurnitureItem(2010), # Candy Cake Slide
+ CatalogNametagItem(8),
+ ),
+
+ # Series 6, week 7 (overall week 64) - Candy Items
+ (300, # Basic bottoms
+ (1, 2020), # Basic chat from series 3
+ (2, 2030), # Basic chat from series 4
+ (3, 2040), # Basic chat from series 6
+ CatalogFurnitureItem(2000), # Candy Swing Set
+ 5000, # Pet trick
+ ),
+
+ # Series 6, week 8 (overall week 65) - Candy Items
+ (100, # Basic shirt
+ (1, 2020), # Basic chat from series 3
+ (2, 2030), # Basic chat from series 4
+ (3, 2040), # Basic chat from series 6
+ CatalogFurnitureItem(3000), # Candy Banana Split Shower
+ nextAvailableBank, # Bank
+ ),
+
+ ############################# SERIES 7 #############################
+
+ # Series 7, week 1 (overall week 66) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogClothingItem(131, 0), # Green shirt w/ yellow buttons
+ CatalogClothingItem(225, 0), # Purple shirt w/ big flower
+ nextAvailablePole, # Next Fishing pole
+ ),
+
+ # Series 7, week 2 (overall week 67) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogFurnitureItem(105), # Desat Chair A
+ nextAvailableCloset, # Wardrobe
+ ),
+
+ # Series 7, week 3 (overall week 68) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogFurnitureItem(205), # Desat Boy's Bed
+ ),
+
+ # Series 7, week 4 (overall week 69) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogFurnitureItem(625), # Desat Lamp A
+ ),
+
+ # Series 7, week 5 (overall week 70) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ nextAvailablePole, # Next Fishing pole
+ CatalogEmoteItem(12), # Belly Flop
+ CatalogNametagItem(5),
+ ),
+
+ # Series 7, week 6 (overall week 71) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogClothingItem(314, 0), # Green striped shorts
+ CatalogClothingItem(414, 0), # Blue skirt w/ big flower
+ nextAvailableBank, # Bank
+ ),
+
+ # Series 7, week 7 (overall week 72) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogFurnitureItem(715), # Desat 2-person couch
+ nextAvailableCloset, # Wardrobe
+ ),
+
+ # Series 7, week 8 (overall week 73) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogFurnitureItem(1015), # Desat Round Rug
+ CatalogNametagItem(6),
+ ),
+
+ # Series 7, week 9 (overall week 74) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogFurnitureItem(1215), # Desat Radio table
+ nextAvailablePole, # Next Fishing pole
+ ),
+
+ # Series 7, week 10 (overall week 75) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogEmoteItem(14), # Banana Peel
+ ),
+
+ # Series 7, week 11 (overall week 76) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogFurnitureItem(1260), # Desat Bedroom table
+ ),
+
+ # Series 7, week 12 (overall week 77) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ CatalogFurnitureItem(705), # Desat 1-person couch
+ CatalogNametagItem(3),
+ ),
+
+ # Series 7, week 13 (overall week 78) - Color Setable Items
+ (300, # Basic bottoms
+ (1, 2030), # Basic chat from series 4
+ (2, 2040), # Basic chat from series 5
+ (3, 2050), # Basic chat from series 7
+ nextAvailableBank, # Bank
+ nextAvailablePole, # Next Fishing pole
+ nextAvailableCloset, # Wardrobe
+ ),
+
+ )
+
+assert(len(WeeklySchedule) == ToontownGlobals.CatalogNumWeeks)
+
+class CatalogGenerator:
+ """CatalogGenerator
+
+ This class is responsible for constructing a catalog of available
+ items for a particular avatar. It normally exists only on the AI.
+
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("CatalogGenerator")
+
+ def __init__(self):
+ self.__itemLists = {}
+
+ def generateMonthlyCatalog(self, avatar, weekStart):
+ # Generates the list of items that should be offered to the
+ # given avatar based on the seasonal specials this month.
+
+ # weekStart is the date at which the catalog is considered to
+ # have been generated, as minutes elapsed since the epoch.
+
+ # This method is designed for use on the AI, but will function
+ # properly on the client, given LocalToon (which is mainly
+ # useful for testing).
+
+ # Get the day offset.
+ dayNumber = int(weekStart / (24 * 60))
+ itemLists = self.__getMonthlyItemLists(dayNumber, weekStart)
+
+ # Now build a list of items for this avatar.
+
+ monthlyCatalog = CatalogItemList.CatalogItemList()
+
+ for list in itemLists:
+ for item in list:
+ monthlyCatalog += self.__selectItem(avatar, item, [])
+ return monthlyCatalog
+
+ def generateWeeklyCatalog(self, avatar, week, monthlyCatalog):
+ # Generates the list of items that should be offered to the
+ # given avatar for the current week. We must have the actual
+ # DistributedAvatar object handy so we can query it.
+
+ # This method is designed for use on the AI, but will function
+ # properly on the client, given LocalToon (which is mainly
+ # useful for testing).
+
+ weeklyCatalog = CatalogItemList.CatalogItemList()
+
+ self.notify.debug("Generating catalog for %s for week %s." % (avatar.doId, week))
+ if week >= 1 and week <= len(WeeklySchedule):
+ saleItem = 0
+ schedule = WeeklySchedule[week - 1]
+ if isinstance(schedule, Sale):
+ schedule = schedule.args
+ saleItem = 1
+
+ for item in schedule:
+ weeklyCatalog += self.__selectItem(avatar, item, monthlyCatalog,
+ saleItem = saleItem)
+
+ # Here is an ugly hack for ensuring that everyone gets at
+ # least one pet trick offered in their first catalog when pets
+ # goes live for the first time. If it is not yet the end of
+ # the month of September 2004 (the month in which pets goes
+ # live), and neither the newly-generated catalog nor the
+ # avatar's current catalog or back catalog includes a pet
+ # trick already, then we will artificially add a random pet
+ # trick.
+ if time.time() < 1096617600.0: # 'Fri Oct 1 01:00:00 2004'
+ def hasPetTrick(catalog):
+ for item in catalog:
+ if isinstance(item, CatalogPetTrickItem):
+ return 1
+ return 0
+
+ if not hasPetTrick(weeklyCatalog) and not \
+ hasPetTrick(avatar.weeklyCatalog) and not \
+ hasPetTrick(avatar.backCatalog):
+ self.notify.debug("Artificially adding pet trick to catalog")
+ weeklyCatalog += self.__selectItem(avatar, 5000, monthlyCatalog, saleItem = saleItem)
+
+ self.notify.debug("Generated catalog: %s" % (weeklyCatalog))
+
+ return weeklyCatalog
+
+ def generateBackCatalog(self, avatar, week, previousWeek, weeklyCatalog):
+ # Generates the list of items that the avatar has seen offered
+ # on previous catalogs.
+ backCatalog = CatalogItemList.CatalogItemList()
+ lastBackCatalog = avatar.backCatalog[:]
+
+ # Add in the items for weeks we may have skipped over, in
+ # reverse order.
+ thisWeek = min(len(WeeklySchedule), week - 1)
+ lastWeek = min(len(WeeklySchedule), previousWeek)
+ for week in range(thisWeek, lastWeek, -1):
+ self.notify.debug("Adding items from week %s to back catalog" % (week))
+ schedule = WeeklySchedule[week - 1]
+ if not isinstance(schedule, Sale): # Don't bother with a sale week.
+ for item in schedule:
+ for item in self.__selectItem(avatar, item, weeklyCatalog + backCatalog):
+ item.putInBackCatalog(backCatalog, lastBackCatalog)
+
+ # Add the items in our current catalog.
+ if previousWeek < week:
+ self.notify.debug("Adding current items from week %s to back catalog" % (previousWeek))
+ for item in avatar.weeklyCatalog:
+ item.putInBackCatalog(backCatalog, lastBackCatalog)
+
+ # Also add in all of the items already on the back catalog.
+ backCatalog += lastBackCatalog
+
+ # Remove any repeated items we just generated this week from
+ # the newly-generated back catalog. It's confusing if an item
+ # shows up both in the new catalog and also in the back
+ # catalog.
+ for item in weeklyCatalog:
+ while item in backCatalog:
+ backCatalog.remove(item)
+
+ return backCatalog
+
+ def __getMonthlyItemLists(self, dayNumber, weekStart):
+ # Returns a list of lists of seasonal items that should be
+ # selected from for monthlyCatalogs generated on the indicated
+ # day. Since the answer is always the same for a particular
+ # day, we save some time by computing this only once for each
+ # different day.
+
+ itemLists = self.__itemLists.get(dayNumber)
+ if itemLists != None:
+ return itemLists
+
+ # Hasn't been generated yet today; do so now.
+ nowtuple = time.localtime(weekStart * 60)
+ month = nowtuple[1]
+ day = nowtuple[2]
+
+ self.notify.debug("Generating seasonal itemLists for %s/%s." % (month, day))
+ itemLists = []
+
+ for startMM, startDD, endMM, endDD, list in MonthlySchedule:
+ pastStart = (month > startMM) or (month == startMM and day >= startDD)
+ beforeEnd = (month < endMM) or (month == endMM and day <= endDD)
+
+ # We are within the range if we are pastStart and
+ # beforeEnd (the normal case), or (pastStart or
+ # beforeEnd) (if end < start, and we're
+ # wrapping around at the end of the year).
+
+ if endMM < startMM:
+ if (pastStart or beforeEnd):
+ itemLists.append(list)
+ else:
+ if (pastStart and beforeEnd):
+ itemLists.append(list)
+
+ self.__itemLists[dayNumber] = itemLists
+ return itemLists
+
+
+ def __selectItem(self, avatar, item, duplicateItems, saleItem = 0):
+ # Evaluates the item code into a list of CatalogItem objects that
+ # are suitable for purchase by the avatar.
+
+ chooseCount = 1
+
+ # If the item is wrapped in a sale wrapper, it's a sale item.
+ if isinstance(item, Sale):
+ assert(len(item.args) == 1)
+ item = item.args[0]
+ saleItem = 1
+
+ # If the item is a function, call it. It should then return
+ # an actual item, a list of items, or a (chooseCount, list).
+ if callable(item):
+ item = item(avatar, duplicateItems)
+
+ if isinstance(item, types.TupleType):
+ # Unpack a 2-tuple into a (chooseCount, list).
+ chooseCount, item = item
+
+ if isinstance(item, types.IntType):
+ # If the item is a MetaItem, it's really a list.
+ item = MetaItems[item]
+
+ selection = []
+
+ if isinstance(item, CatalogItem.CatalogItem):
+ if not item.notOfferedTo(avatar):
+ item.saleItem = saleItem
+ selection.append(item)
+
+ elif item != None:
+ # Choose n of the given list. That means we should make a
+ # copy of the list first.
+ list = item[:]
+ for i in range(chooseCount):
+ if len(list) == 0:
+ return selection
+ item = self.__chooseFromList(avatar, list, duplicateItems)
+ if item != None:
+ item.saleItem = saleItem
+ selection.append(item)
+
+ return selection
+
+ def __chooseFromList(self, avatar, list, duplicateItems):
+ index = random.randrange(len(list))
+ item = list[index]
+ del list[index]
+
+ # Is this an acceptable item?
+ # Make sure you can offer this item to the given avatar,
+ # that the avatar hasn't reached his/her purchase limit,
+ # and that the item isn't already in the duplicate or
+ # backorder list
+ while item.notOfferedTo(avatar) or \
+ item.reachedPurchaseLimit(avatar) or \
+ item in duplicateItems or \
+ item in avatar.backCatalog or \
+ item in avatar.weeklyCatalog:
+ # The current item is unacceptable, see if there is another one
+ if len(list) == 0:
+ return None
+ index = random.randrange(len(list))
+ item = list[index]
+ del list[index]
+
+ return item
+
+ def outputSchedule(self, filename):
+ out = open(Filename(filename).toOsSpecific(), "w")
+
+ sched = self.generateScheduleDictionary()
+
+ # Now put the items into sorted order, which will collect
+ # similar items together for the user's convenience.
+ items = sched.keys()
+ items.sort()
+
+ for item in items:
+ weeklist, maybeWeeklist = sched[item]
+
+ color = self.__formatColor(item.getColor())
+
+ # Figure out which series(es) in which the item is
+ # offered.
+ seriesDict = {}
+ self.__determineSeries(seriesDict, weeklist)
+ self.__determineSeries(seriesDict, maybeWeeklist)
+ seriesList = seriesDict.keys()
+ seriesList.sort()
+ series = str(seriesList)[1:-1]
+
+ week = self.__formatWeeklist(weeklist)
+ maybeWeek = self.__formatWeeklist(maybeWeeklist)
+
+ line = '"%s"\t"%s"\t"%s"\t%s\t"%s"\t"%s"\t"%s"\t"%s"\t"%s"' % (
+ item.output(store = 0),
+ item.getTypeName(),
+ item.getDisplayName(),
+ item.getBasePrice(),
+ item.getFilename(),
+ color,
+ series,
+ week,
+ maybeWeek,
+ )
+ out.write(line + '\n')
+
+ out.close()
+
+ def __formatColor(self, color):
+ if color == None:
+ return ""
+ else:
+ return "(%0.2f, %0.2f, %0.2f)" % (color[0], color[1], color[2])
+
+ def __determineSeries(self, seriesDict, weeklist):
+ for week in weeklist:
+ if isinstance(week, types.IntType):
+ # If the week is an integer, it's the week number (as
+ # opposed to a string, which represents a season).
+ series = ((week - 1) / ToontownGlobals.CatalogNumWeeksPerSeries) + 1
+ seriesDict[series] = None
+
+ def __formatWeeklist(self, weeklist):
+ # Returns a string representing a friendly way to represent
+ # the list of weeks.
+
+ str = ''
+ for week in weeklist:
+ str += ', %s' % (week)
+ return str[2:]
+
+
+ def generateScheduleDictionary(self):
+ # Build up a dictionary of item to:
+ # [weeklist, maybeWeeklist]
+
+ # where each weeklist is a list of week numbers and/or season
+ # strings. A season string is of the form "10/01 - 10/31". The
+ # first list is the list of weeks/seasons in which the item is
+ # definitely offered to everyone; the second list is the list
+ # of weeks/seasons in which the item is just one of a larger
+ # pool of items that is offered to everyone (so each player
+ # may or may not be offered any one particular item).
+
+ sched = {}
+
+ for index in range(len(WeeklySchedule)):
+ week = index + 1
+
+ schedule = WeeklySchedule[index]
+ if isinstance(schedule, Sale):
+ schedule = schedule.args
+
+ self.__recordSchedule(sched, week, schedule)
+
+ for startMM, startDD, endMM, endDD, list in MonthlySchedule:
+ string = "%02d/%02d - %02d/%02d" % (startMM, startDD, endMM, endDD)
+ self.__recordSchedule(sched, string, list)
+
+ return sched
+
+ def __recordSchedule(self, sched, weekCode, schedule):
+ for item in schedule:
+
+ # If the item is a function, we have to handle it as a
+ # special case.
+ if callable(item):
+ if item == nextAvailablePole:
+ item = getAllPoles()
+
+ elif item == nextAvailableBank:
+ item = getAllBanks()
+
+ elif item == nextAvailableCloset:
+ item = getAllClosets()
+
+ else:
+ self.notify.warning("Don't know how to interpret function " % (repr(name)))
+ item = None
+
+ elif isinstance(item, types.TupleType):
+ # A tuple is (chooseCount, list). We don't care about
+ # the chooseCount here.
+ item = item[1]
+
+ if isinstance(item, types.IntType):
+ # If the item is a MetaItem, it's really a list.
+ item = MetaItems[item]
+
+ if isinstance(item, CatalogItem.CatalogItem):
+ # Just one item, definitely offered.
+ self.__recordScheduleItem(sched, weekCode, None, item)
+
+ elif item != None:
+ # Multiple items, each of which may be offered.
+ #if item == MetaItems[3020] or item == MetaItems[3010]:
+ # print "%s: %s" % (weekCode, item)
+ for i in item:
+ self.__recordScheduleItem(sched, None, weekCode, i)
+
+ def __recordScheduleItem(self, sched, weekCode, maybeWeekCode, item):
+ if not sched.has_key(item):
+ sched[item] = [[], []]
+
+ #if item == CatalogWallpaperItem(2900) or item == CatalogWallpaperItem(2210):
+ # print "%s,%s: %s" % (item, maybeWeekCode, sched[item])
+ if weekCode != None:
+ sched[item][0].append(weekCode)
+ if maybeWeekCode != None:
+ sched[item][1].append(maybeWeekCode)
+
diff --git a/toontown/src/catalog/CatalogGui.py b/toontown/src/catalog/CatalogGui.py
new file mode 100644
index 0000000..8e6bb60
--- /dev/null
+++ b/toontown/src/catalog/CatalogGui.py
@@ -0,0 +1,88 @@
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+import CatalogItemPanel
+
+# how many items of master list are visible at once in the scrolling list
+NUM_ITEMS_SHOWN = 3
+
+class CatalogGui:
+ """CatalogGui
+
+ This class presents the user interface to an individual catalog. It consists of
+ a title and a scrolling list of catalog items.
+
+ """
+
+ def __init__(self, type, list=[], parent=None):
+
+ self.type = type
+ self.itemList = list
+ self.parent = parent
+
+ self.panelPicker = None
+ self.frame = DirectFrame(parent=parent, relief=None)
+ self.load()
+
+ def show(self):
+ self.frame.show()
+
+ def hide(self):
+ self.frame.hide()
+
+ def load(self):
+ # scrolled list wants a list of strings
+ itemStrings = []
+ for i in range(0, len(self.itemList)):
+ itemStrings.append(self.itemList[i].getName())
+
+ gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui")
+ # make a scrolled list of item panels
+ self.panelPicker = DirectScrolledList(
+ parent = self.frame,
+ items = itemStrings,
+ command = self.scrollItem,
+ itemMakeFunction = CatalogItemPanel.CatalogItemPanel,
+ itemMakeExtraArgs=[self.itemList, self.type],
+ numItemsVisible = NUM_ITEMS_SHOWN,
+ pos = (0.5, 0, 0.4),
+ # inc and dec are DirectButtons
+ 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,0,-1.05),
+ # Make the disabled button fade out
+ incButton_image3_color = Vec4(1,1,1,0.3),
+ 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,0,0.25),
+ # Make the disabled button fade out
+ decButton_image3_color = Vec4(1,1,1,0.3),
+ )
+
+
+ def unload(self):
+ # remove all graphical elements
+ del self.parent
+ del self.itemList
+ del self.panelPicker
+ self.frame.destroy()
+
+
+ def update(self):
+ # call this when toon's money count changes to update the buy buttons
+ for item in self.panelPicker['items']:
+ if (type(item) != type("")):
+ item.updateBuyButton()
+
+ def scrollItem(self):
+ pass
+
diff --git a/toontown/src/catalog/CatalogInvalidItem.py b/toontown/src/catalog/CatalogInvalidItem.py
new file mode 100644
index 0000000..6ea49fe
--- /dev/null
+++ b/toontown/src/catalog/CatalogInvalidItem.py
@@ -0,0 +1,25 @@
+import CatalogItem
+from toontown.toonbase import TTLocalizer
+from direct.showbase import PythonUtil
+from toontown.toonbase import ToontownGlobals
+
+class CatalogInvalidItem(CatalogItem.CatalogItem):
+ """CatalogInvalidItem
+
+ This special item type may be returned by CatalogItem.getItem()
+ and similar functions. It represents a CatalogItem that was not
+ correctly decoded from the encoded blob; its purposes is to stand
+ in as a placeholder for the broken item, so that AI code will not
+ necessarily crash when an invalid message is received from a
+ client.
+
+ """
+
+ def requestPurchase(self, phone, callback):
+ self.notify.error("Attempt to purchase invalid item.")
+
+ def acceptItem(self, mailbox, index, callback):
+ self.notify.error("Attempt to accept invalid item.")
+
+ def output(self, store = ~0):
+ return "CatalogInvalidItem()"
diff --git a/toontown/src/catalog/CatalogItem.py b/toontown/src/catalog/CatalogItem.py
new file mode 100644
index 0000000..78a8687
--- /dev/null
+++ b/toontown/src/catalog/CatalogItem.py
@@ -0,0 +1,741 @@
+from direct.directnotify import DirectNotifyGlobal
+from pandac.PandaModules import *
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownGlobals
+from direct.interval.IntervalGlobal import *
+from direct.distributed.PyDatagram import PyDatagram
+from direct.distributed.PyDatagramIterator import PyDatagramIterator
+
+
+import types
+import sys
+
+# This is created in decodeCatalogItem, below, as a map that reverses
+# the lookup for the indexes defined in CatalogItemTypes.py.
+CatalogReverseType = None
+
+# This is the current version number that is written to the datagram
+# stream describing how the CatalogItems are formatted. It is similar
+# to the bam minor version number. When you introduce a change to the
+# code that makes old records invalid, increment the version number,
+# and put the appropriate conditionals in the decodeDatagram() methods
+# based on the version number. This helps us avoid having to do
+# database patches every time something changes here.
+CatalogItemVersion = 8
+# version 2: adds pr to posHpr
+# version 3: makes wallpaper type 16-bit
+# version 4: make wallpaper color index a customization option
+# version 5: use new hprs instead of old hprs (temp-hpr-fix)
+# version 6: add loyaltyDays to Clothing, Emote,
+# version 7: add cost to RentalItem
+# version 8: add specialEventId
+
+# How much default markup for backorder items?
+CatalogBackorderMarkup = 1.2
+
+# How much to reduce for sale items?
+CatalogSaleMarkdown = 0.75
+
+# These are bits that represent the additional data that might be
+# stored in the blob along with the CatalogItem. This must be known
+# in order to properly decode the CatalogItem into or from a blob;
+# context will indicate which values are appropriate to store along
+# with the CatalogItem.
+Customization = 0x01
+DeliveryDate = 0x02
+Location = 0x04
+WindowPlacement = 0x08
+GiftTag = 0x10 #usually contains the name of the sender
+
+# These are flags that indicate which kind of catalog the item is
+# stored on. This is not stored on the item itself, but is rather
+# stored on the list.
+CatalogTypeUnspecified = 0
+CatalogTypeWeekly = 1
+CatalogTypeBackorder = 2
+CatalogTypeMonthly = 3
+CatalogTypeLoyalty = 4
+
+class CatalogItem:
+ """CatalogItem"""
+ notify = DirectNotifyGlobal.directNotify.newCategory("CatalogItem")
+
+ def __init__(self, *args, **kw):
+ # This init function is designed to be directly inherited (not
+ # overridden) by each of the base classes. It performs a
+ # dispatch based on the parameter types to construct a
+ # CatalogItem either from a datagram or directly from its
+ # parameters.
+
+ self.saleItem = 0
+ self.deliveryDate = None
+ self.posHpr = None
+ self.giftTag = None
+ self.giftCode = 0
+ self.hasPicture = False
+ self.volume = 0
+ self.specialEventId = 0 # Code assumes that if non zero, then this item is an award
+ if (len(args) >= 1 and isinstance(args[0], DatagramIterator)):
+ # If we are called with di, versionNumber, store then we
+ # meant to decode the CatalogItem from a datagram.
+ self.decodeDatagram(*args, **kw)
+ else:
+ # Otherwise, we are creating a new CatalogItem.
+ self.makeNewItem(*args, **kw)
+
+ def isAward(self):
+ """Return true if this catalog item is an award."""
+ result = self.specialEventId != 0
+ return result
+
+ def makeNewItem(self):
+ # This is to be used as the primary constructor-from-arguments
+ # method for CatalogItem derivatives.
+ pass
+
+ def needsCustomize(self):
+ # Returns true if the item still needs to be customized by the
+ # user (e.g. by choosing a color).
+ return 0
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 0
+
+ def getBackSticky(self):
+ #some items should hang around in the back catalog
+ itemType = 0 #the types that should stick around
+ numSticky = 0 #how many should stick around
+ return itemType, numSticky
+
+ def putInBackCatalog(self, backCatalog, lastBackCatalog):
+ # Appends the item to the backCatalog. For reference, the
+ # current back catalog is passed in as well; this list will be
+ # appended to the backCatalog after all items have been added
+ # (so that older items appear at the end of the list). It is
+ # legal to modify the lastBackCatalog list.
+
+ if self.saveHistory() and not self.isSaleItem():
+ # There should be only one of a given item in the back
+ # catalog at any given time. If we get more than one, we
+ # should remove the old one.
+ if not self in backCatalog:
+ if self in lastBackCatalog:
+ lastBackCatalog.remove(self)
+ backCatalog.append(self)
+
+ def replacesExisting(self):
+ # Returns true if an item of this type will, when purchased,
+ # replace an existing item of the same type, or false if items
+ # accumulate.
+ return 0
+
+ def hasExisting(self):
+ # If replacesExisting returns true, this returns true if an
+ # item of this class is already owned by the avatar, false
+ # otherwise. If replacesExisting returns false, this is
+ # undefined.
+ return 0
+
+ def getYourOldDesc(self):
+ # If replacesExisting returns true, this returns the name of
+ # the already existing object, in sentence construct: "your
+ # old ...". If replacesExisting returns false, this is undefined.
+ return None
+
+ def storedInCloset(self):
+ # Returns true if this kind of item takes up space in the
+ # avatar's closet, false otherwise.
+ return 0
+
+ def storedInAttic(self):
+ # Returns true if this kind of item takes up space in the
+ # avatar's attic, false otherwise.
+ return 0
+
+ def notOfferedTo(self, avatar):
+ # Returns true if the item cannot be bought by the indicated
+ # avatar (pass in the actual DistributdAvatarAI object), for
+ # instance because the avatar already has one and cannot have
+ # two. This is normally called only on the AI side.
+ return 0
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 0
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ return 0
+
+ def hasBeenGifted(self, avatar):
+ # returns true if this item is on your onGiftOrderList
+ if avatar.onGiftOrder.count(self) != 0:
+ # someone has given it to you
+ return 1
+ return 0
+
+ def getTypeName(self):
+ # Returns the name of the general type of item.
+ # No need to localize this string; it should never be
+ # displayed except in case of error.
+ return "Unknown Type Item"
+
+ def getName(self):
+ # Returns the name of the item.
+
+ # No need to localize this string; it should never be
+ # displayed except in case of error.
+ return "Unnamed Item"
+
+ def getDisplayName(self):
+ # Used in the catalog gui display
+ return self.getName()
+
+ def recordPurchase(self, avatar, optional):
+ # Updates the appropriate field on the avatar to indicate the
+ # purchase (or delivery). This makes the item available to
+ # use by the avatar. This method is only called on the AI side.
+
+ # The optional parameter may be 0 or some number passed up
+ # from the client which has different meaning to the various
+ # different kinds of CatalogItems (and may indicate, for
+ # instance, the index number of the item to replace).
+
+ # This should return one of the P_* tokens from ToontownGlobals.
+ # If the return value is zero or positive, the item is removed
+ # from the mailbox.
+
+ self.notify.warning("%s has no purchase method." % (self))
+ return ToontownGlobals.P_NoPurchaseMethod
+
+ def isSaleItem(self):
+ # Returns true if this particular item was tagged as a "sale"
+ # item in the catalog generator. This means (a) it has a
+ # reduced price, and (b) it does not get placed in the
+ # backorder catalog.
+ return self.saleItem
+
+ def isGift(self):
+ return 1
+
+ def isRental(self):
+ return 0
+
+ def forBoysOnly(self):
+ return 0
+
+ def forGirlsOnly(self):
+ return 0
+
+ def setLoyaltyRequirement(self, days):
+ self.loyaltyDays = days
+
+ def loyaltyRequirement(self):
+ """Return. the number of days an account must have to purchase."""
+ if not hasattr(self, "loyaltyDays"):
+ return 0
+ else:
+ return self.loyaltyDays
+
+ def getPrice(self, catalogType):
+ assert(catalogType != CatalogTypeUnspecified)
+ if catalogType == CatalogTypeBackorder:
+ return self.getBackPrice()
+ elif self.isSaleItem():
+ return self.getSalePrice()
+ else:
+ return self.getCurrentPrice()
+
+ def getCurrentPrice(self):
+ # Returns the price of the item when it is listed in the
+ # current catalog.
+ return int(self.getBasePrice())
+
+ def getBackPrice(self):
+ # Returns the price of the item when it is listed in the back
+ # catalog.
+ return int(self.getBasePrice() * CatalogBackorderMarkup)
+
+ def getSalePrice(self):
+ # Returns the price of the item when it is listed in the
+ # current catalog as a sale item.
+ return int(self.getBasePrice() * CatalogSaleMarkdown)
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ return 0
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+ assert (not self.hasPicture)
+ self.hasPicture=True
+ return (None, None)
+
+ def cleanupPicture(self):
+ assert self.hasPicture
+ self.hasPicture=False
+
+
+
+ def requestPurchase(self, phone, callback, optional=-1):
+ # Orders the item via the indicated telephone. Some items
+ # will pop up a dialog querying the user for more information
+ # before placing the order; other items will order
+ # immediately.
+
+ # In either case, the function will return immediately before
+ # the transaction is finished, but the given callback will be
+ # called later with two parameters: the return code (one of
+ # the P_* symbols defined in ToontownGlobals.py), followed by the
+ # item itself.
+
+ # This method is only called on the client.
+
+ assert(not self.needsCustomize())
+ phone.requestPurchase(self, callback, optional)
+
+ def requestGiftPurchase(self, phone, targetDoID,callback, optional=-1):
+ # Orders the item via the indicated telephone. Some items
+ # will pop up a dialog querying the user for more information
+ # before placing the order; other items will order
+ # immediately.
+
+ # In either case, the function will return immediately before
+ # the transaction is finished, but the given callback will be
+ # called later with two parameters: the return code (one of
+ # the P_* symbols defined in ToontownGlobals.py), followed by the
+ # item itself.
+
+ # This method is only called on the client.
+
+ # assert 0, "Gift Purchase"
+
+ assert(not self.needsCustomize())
+ phone.requestGiftPurchase(self, targetDoID, callback, optional)
+ #base.cr.deliveryManager.sendRequestPurchaseGift(self, targetDoID, callback)
+
+
+ def requestPurchaseCleanup(self):
+ # This will be called on the client side to clean up any
+ # objects created in requestPurchase(), above.
+ pass
+
+ def getRequestPurchaseErrorText(self, retcode):
+ # PLEASE NOTE: this is not usually an error message, usually this returns a confiramtion -JML
+ # Returns a string describing the error that occurred on
+ # attempting to order the item from the phone. The input
+ # parameter is the retcode returned by
+ # phone.requestPurchase().
+ if retcode == ToontownGlobals.P_ItemAvailable: # worked can use right away
+ return TTLocalizer.CatalogPurchaseItemAvailable
+ elif retcode == ToontownGlobals.P_ItemOnOrder: # worked and will be delivered
+ return TTLocalizer.CatalogPurchaseItemOnOrder
+ elif retcode == ToontownGlobals.P_MailboxFull: #failure messages
+ return TTLocalizer.CatalogPurchaseMailboxFull
+ elif retcode == ToontownGlobals.P_OnOrderListFull:
+ return TTLocalizer.CatalogPurchaseOnOrderListFull
+ else:
+ return TTLocalizer.CatalogPurchaseGeneralError % (retcode)
+
+ def getRequestGiftPurchaseErrorText(self, retcode):
+ # PLEASE NOTE: this is not usually an error message, usually this returns a confiramtion -JML
+ # Returns a string describing the error that occurred on
+ # attempting to order the item from the phone. The input
+ # parameter is the retcode returned by
+ # phone.requestPurchase().
+ if retcode == ToontownGlobals.P_ItemAvailable: # worked can use right away
+ return TTLocalizer.CatalogPurchaseGiftItemAvailable
+ elif retcode == ToontownGlobals.P_ItemOnOrder: # worked and will be delivered
+ return TTLocalizer.CatalogPurchaseGiftItemOnOrder
+ elif retcode == ToontownGlobals.P_MailboxFull: #failure messages
+ return TTLocalizer.CatalogPurchaseGiftMailboxFull
+ elif retcode == ToontownGlobals.P_OnOrderListFull:
+ return TTLocalizer.CatalogPurchaseGiftOnOrderListFull
+ elif retcode == ToontownGlobals.P_NotAGift:
+ return TTLocalizer.CatalogPurchaseGiftNotAGift
+ elif retcode == ToontownGlobals.P_WillNotFit:
+ return TTLocalizer.CatalogPurchaseGiftWillNotFit
+ elif retcode == ToontownGlobals.P_ReachedPurchaseLimit:
+ return TTLocalizer.CatalogPurchaseGiftLimitReached
+ elif retcode == ToontownGlobals.P_NotEnoughMoney:
+ return TTLocalizer.CatalogPurchaseGiftNotEnoughMoney
+ else:
+ return TTLocalizer.CatalogPurchaseGiftGeneralError % {'friend' : "%s",'error' : retcode}
+
+ def acceptItem(self, mailbox, index, callback):
+ # Accepts the item from the mailbox. Some items will pop up a
+ # dialog querying the user for more information before
+ # accepting the item; other items will accept it immediately.
+
+ # In either case, the function will return immediately before
+ # the transaction is finished, but the given callback will be
+ # called later with three parameters: the return code (one of
+ # the P_* symbols defined in ToontownGlobals.py), followed by
+ # the item itself, and the supplied index number.
+
+ # The index is the position of this item within the avatar's
+ # mailboxContents list, which is used by the AI to know which
+ # item to remove from the list (and also to doublecheck that
+ # we're accepting the expected item).
+
+ # This method is only called on the client.
+
+ mailbox.acceptItem(self, index, callback)
+
+ def discardItem(self, mailbox, index, callback):
+ print("Item discardItem")
+ # Discards the item from the mailbox.
+ # This method is only called on the client.
+
+ mailbox.discardItem(self, index, callback)
+
+ def acceptItemCleanup(self):
+ # This will be called on the client side to clean up any
+ # objects created in acceptItem(), above.
+ pass
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_NoRoomForItem:
+ return TTLocalizer.CatalogAcceptRoomError
+ elif retcode == ToontownGlobals.P_ReachedPurchaseLimit:
+ return TTLocalizer.CatalogAcceptLimitError
+ elif retcode == ToontownGlobals.P_WillNotFit:
+ return TTLocalizer.CatalogAcceptFitError
+ elif retcode == ToontownGlobals.P_InvalidIndex:
+ return TTLocalizer.CatalogAcceptInvalidError
+ else:
+ return TTLocalizer.CatalogAcceptGeneralError % (retcode)
+
+ def output(self, store = ~0):
+ return "CatalogItem"
+
+ def getFilename(self):
+ # This returns a filename if it makes sense for the particular
+ # item. This is only used for documentation purposes.
+ return ""
+
+ def getColor(self):
+ # This returns a VBase4 color which is applied to the above
+ # filename, if it makes sense for the particular item. This
+ # may be used for documentation purposes, but some item types
+ # define this specifically for their own use.
+ return None
+
+ def formatOptionalData(self, store = ~0):
+ # This is used within output() to format optional data
+ # (according to the bits indicated in store).
+ result = ""
+ if (store & Location) and self.posHpr != None:
+ result += ", posHpr = (%s, %s, %s, %s, %s, %s)" % (self.posHpr)
+ return result
+
+ def __str__(self):
+ return self.output()
+
+ def __repr__(self):
+ return self.output()
+
+ def compareTo(self, other):
+ # All CatalogItem type objects are equivalent.
+ # Specializations of this class will redefine this method
+ # appropriately.
+ return 0
+
+ def getHashContents(self):
+ # Specializations of this class will redefine this method to
+ # return whatever pieces of the class are uniquely different
+ # to each instance.
+ return None
+
+ def __cmp__(self, other):
+ # If the classes are different, they must be different objects.
+ c = cmp(self.__class__, other.__class__)
+ if c != 0:
+ return c
+
+ # Otherwise, they are the same class; use compareTo.
+ return self.compareTo(other)
+
+ def __hash__(self):
+ return hash((self.__class__, self.getHashContents()))
+
+ def getBasePrice(self):
+ return 0
+
+ def loadModel(self):
+ return None
+
+ def decodeDatagram(self, di, versionNumber, store):
+ if store & DeliveryDate:
+ self.deliveryDate = di.getUint32()
+ if store & Location:
+ x = di.getArg(STInt16, 10)
+ y = di.getArg(STInt16, 10)
+ z = di.getArg(STInt16, 100)
+ if versionNumber < 2:
+ h = di.getArg(STInt16, 10)
+ p = 0.0
+ r = 0.0
+ elif versionNumber < 5:
+ h = di.getArg(STInt8, 256.0/360.0)
+ p = di.getArg(STInt8, 256.0/360.0)
+ r = di.getArg(STInt8, 256.0/360.0)
+ hpr = oldToNewHpr(VBase3(h, p, r))
+ h = hpr[0]
+ p = hpr[1]
+ r = hpr[2]
+ else:
+ h = di.getArg(STInt8, 256.0/360.0)
+ p = di.getArg(STInt8, 256.0/360.0)
+ r = di.getArg(STInt8, 256.0/360.0)
+
+ self.posHpr = (x, y, z, h, p, r)
+ if store & GiftTag:
+ self.giftTag = di.getString()
+ if versionNumber >= 8:
+ self.specialEventId = di.getUint8()
+ else:
+ self.specialEventId = 0
+
+ def encodeDatagram(self, dg, store):
+ if store & DeliveryDate:
+ dg.addUint32(self.deliveryDate)
+ if store & Location:
+ dg.putArg(self.posHpr[0], STInt16, 10)
+ dg.putArg(self.posHpr[1], STInt16, 10)
+ dg.putArg(self.posHpr[2], STInt16, 100)
+ dg.putArg(self.posHpr[3], STInt8, 256.0/360.0)
+ dg.putArg(self.posHpr[4], STInt8, 256.0/360.0)
+ dg.putArg(self.posHpr[5], STInt8, 256.0/360.0)
+ if store & GiftTag:
+ dg.addString(self.giftTag)
+ dg.addUint8(self.specialEventId)
+
+ def getTypeCode(self):
+ import CatalogItemTypes
+ return CatalogItemTypes.CatalogItemTypes[self.__class__]
+
+ def applyColor(self, model, colorDesc):
+ """
+
+ This method is used to apply a color/texture description to
+ a model. The colorDesc is a list of tuples, each of which has
+ one of the forms:
+
+ (partName, None) - hide the given part(s)
+ (partName, "texture name") - apply the texture to the given part(s)
+ (partName, (r, g, b, a)) - apply the color to the given part(s)
+
+ """
+ if model == None or colorDesc == None:
+ return
+ for partName, color in colorDesc:
+ matches = model.findAllMatches(partName)
+ if (color == None):
+ matches.hide()
+
+ elif isinstance(color, types.StringType):
+ tex = loader.loadTexture(color)
+ tex.setMinfilter(Texture.FTLinearMipmapLinear)
+ tex.setMagfilter(Texture.FTLinear)
+ for i in range(matches.getNumPaths()):
+ matches.getPath(i).setTexture(tex, 1)
+
+ else:
+ needsAlpha = (color[3] != 1)
+ color = VBase4(color[0], color[1], color[2], color[3])
+ for i in range(matches.getNumPaths()):
+ #matches.getPath(i).setColor(color, 1)
+ matches.getPath(i).setColorScale(color, 1)
+ if needsAlpha:
+ matches.getPath(i).setTransparency(1)
+
+ def makeFrame(self):
+ # Returns a DirectFrame suitable for holding models returned
+ # by getPicture().
+
+ # Don't import this at the top of the file, since this code
+ # must run on the AI.
+ from direct.gui.DirectGui import DirectFrame
+
+ frame = DirectFrame(parent = hidden,
+ frameSize = (-1.0, 1.0, -1.0, 1.0),
+ relief = None,
+ )
+ return frame
+
+ def makeFrameModel(self, model, spin = 1):
+ # Returns a (DirectWidget, Interval) pair to spin the
+ # indicated model, an arbitrary NodePath, on a panel. Called
+ # only on the client, from getPicture(), by derived classes
+ # like CatalogFurnitureItem and CatalogClothingItem.
+
+ frame = self.makeFrame()
+ ival = None
+ if model:
+ # This 3-d model will be drawn in the 2-d scene.
+ model.setDepthTest(1)
+ model.setDepthWrite(1)
+
+ if spin:
+ # We need two nested nodes: one to pitch down to the user,
+ # and one to rotate.
+ pitch = frame.attachNewNode('pitch')
+ rotate = pitch.attachNewNode('rotate')
+ scale = rotate.attachNewNode('scale')
+ model.reparentTo(scale)
+ # Translate model to the center.
+ bMin,bMax = model.getTightBounds()
+ center = (bMin + bMax)/2.0
+ model.setPos(-center[0], -center[1], -center[2])
+ pitch.setP(20)
+ # Scale the model to fit within a 2x2 box
+ bMin,bMax = pitch.getTightBounds()
+ center = (bMin + bMax)/2.0
+ corner = Vec3(bMax - center)
+ #scale.setScale(1.0/corner[2])
+ scale.setScale(1.0/max(corner[0],corner[1],corner[2]))
+ pitch.setY(2)
+ ival = LerpHprInterval(rotate, 10, VBase3(-270, 0, 0),
+ startHpr = VBase3(90, 0, 0))
+ else:
+ # This case is simpler, we do not need all the extra nodes
+ scale = frame.attachNewNode('scale')
+ model.reparentTo(scale)
+ # Translate model to the center.
+ bMin,bMax = model.getTightBounds()
+ center = (bMin + bMax)/2.0
+ model.setPos(-center[0], 2, -center[2])
+ corner = Vec3(bMax - center)
+ #scale.setScale(1.0/corner[2])
+ scale.setScale(1.0/max(corner[0],corner[1],corner[2]))
+
+ return (frame, ival)
+
+ def getBlob(self, store = 0):
+ dg = PyDatagram()
+ dg.addUint8(CatalogItemVersion)
+ encodeCatalogItem(dg, self, store)
+ return dg.getMessage()
+
+
+ def getRequestPurchaseErrorTextTimeout(self):
+ """
+ #RAU How long do we display RequestPurchaseErrorText.
+ Created since we need to display the text longer for garden supplies
+ """
+ return 6
+
+ def getDaysToGo(self, avatar):
+ """Return the number of days the toon has to wait before he can buy this."""
+ accountDays = avatar.getAccountDays()
+ daysToGo = self.loyaltyRequirement() - accountDays
+ if daysToGo <0:
+ daysToGo = 0
+ return int(daysToGo)
+
+
+def encodeCatalogItem(dg, item, store):
+ """encodeCatalogItem
+
+ Encodes a CatalogItem of some type into the datagram stream,
+ writing the type number first so that decodeCatalogItem() can
+ successfully decode it.
+ """
+ import CatalogItemTypes
+ flags = item.getTypeCode()
+ if item.isSaleItem():
+ flags |= CatalogItemTypes.CatalogItemSaleFlag
+ if item.giftTag != None:
+ flags |= CatalogItemTypes.CatalogItemGiftTag
+ dg.addUint8(flags)
+ if item.giftTag != None:
+ dg.addUint32(item.giftTag)
+ if not item.giftCode:
+ item.giftCode = 0
+ dg.addUint8(item.giftCode)
+ else:
+ pass
+ #print("No Gift Tag")
+
+ item.encodeDatagram(dg, store)
+
+
+def decodeCatalogItem(di, versionNumber, store):
+ """decodeCatalogItem
+
+ This function decodes a CatalogItem of an unknown type from the
+ given datagram stream, reversing the logic used by
+ encodeCatalogItem(). The new catalog item is returned.
+ """
+
+ import CatalogItemTypes
+ global CatalogReverseType
+ if CatalogReverseType == None:
+ # First, we have to create the reverse lookup.
+ CatalogReverseType = {}
+ for itemClass, index in CatalogItemTypes.CatalogItemTypes.items():
+ CatalogReverseType[index] = itemClass
+
+ startIndex = di.getCurrentIndex()
+ try:
+ flags = di.getUint8()
+ typeIndex = flags & CatalogItemTypes.CatalogItemTypeMask
+ gift = None
+ code = None
+ if flags & CatalogItemTypes.CatalogItemGiftTag:
+
+ gift = di.getUint32()
+ code = di.getUint8()
+ else:
+
+ pass
+ itemClass = CatalogReverseType[typeIndex]
+ item = itemClass(di, versionNumber, store = store)
+
+ except Exception, e:
+ CatalogItem.notify.warning("Invalid catalog item in stream: %s, %s" % (
+ sys.exc_info()[0], e))
+ d = Datagram(di.getDatagram().getMessage()[startIndex:])
+ d.dumpHex(Notify.out())
+ #import pdb; pdb.set_trace()#debug on invalid catalog items
+ import CatalogInvalidItem
+ return CatalogInvalidItem.CatalogInvalidItem()
+
+ if flags & CatalogItemTypes.CatalogItemSaleFlag:
+ item.saleItem = 1
+ item.giftTag = gift
+ item.giftCode = code
+
+ return item
+
+
+def getItem(blob, store = 0):
+ """getItem
+
+ Returns the CatalogItem written by a previous call to item.getBlob().
+ """
+ dg = PyDatagram(blob)
+ di = PyDatagramIterator(dg)
+ try:
+ versionNumber = di.getUint8()
+ return decodeCatalogItem(di, versionNumber, store)
+ except Exception, e:
+ CatalogItem.notify.warning("Invalid catalog item: %s, %s" % (
+ sys.exc_info()[0], e))
+ dg.dumpHex(Notify.out())
+ import CatalogInvalidItem
+ return CatalogInvalidItem.CatalogInvalidItem()
diff --git a/toontown/src/catalog/CatalogItemList.py b/toontown/src/catalog/CatalogItemList.py
new file mode 100644
index 0000000..2862109
--- /dev/null
+++ b/toontown/src/catalog/CatalogItemList.py
@@ -0,0 +1,290 @@
+import CatalogItem
+from pandac.PandaModules import *
+import types
+from direct.distributed.PyDatagram import PyDatagram
+from direct.distributed.PyDatagramIterator import PyDatagramIterator
+
+class CatalogItemList:
+ """CatalogItemList
+
+ This class presents itself as a list of CatalogItem objects. It
+ behaves like a normal Python list, except it supports lazy
+ decoding: it can be initialized from a blob received by the
+ network system; it won't attempt to decode the blob until some
+ properties of the catalog list are requested. This saves some CPU
+ time for the majority of cases when we download the
+ CatalogItemList but don't care about inspecting it.
+ """
+
+ def __init__(self, source = None, store = 0):
+ # The source may be either a list, a string blob, or another
+ # CatalogItemList object.
+
+ # The store parameter indicates the bitmask of additional
+ # properties we will store in (or decode from) the blob along
+ # with each CatalogItem. See CatalogItem.py.
+ self.store = store
+
+ # The data is stored in either or both of self.__blob and
+ # self.__list. If either one is None, the current data is
+ # stored in the other. If both are None, the data represents
+ # an empty list.
+ self.__blob = None
+ self.__list = None
+
+ if isinstance(source, types.StringType):
+ self.__blob = source
+ elif isinstance(source, types.ListType):
+ self.__list = source[:]
+ elif isinstance(source, CatalogItemList):
+ # Copy from another CatalogItemList.
+ if source.store == store:
+ # If the store types are the same, we can get away
+ # with copying the list (if it is defined), and also
+ # copying the blob.
+ if source.__list != None:
+ self.__list = source.__list[:]
+ self.__blob = source.__blob
+ else:
+ # If the store types are different, we must copy the list.
+ self.__list = source[:]
+ else:
+ assert(source == None)
+
+ def markDirty(self):
+ # Call this whenever you know one of the items has changed
+ # internally and you need to regenerate a new blob that
+ # reflects that change.
+ if self.__list:
+ self.__blob = None
+
+ def getBlob(self, store = None):
+ if store == None or store == self.store:
+ # If we are asking for a blob that matches our store type,
+ # we can just return our cached value.
+ if self.__blob == None:
+ self.__encodeList()
+ return self.__blob
+
+ # Otherwise, we must compute a new blob according to the
+ # indicated store type.
+ return self.__makeBlob(store)
+
+ def getNextDeliveryDate(self):
+ # Returns the minimum of all the listed items' delivery times,
+ # or None if the list is empty.
+ if len(self) == 0:
+ return None
+
+ nextDeliveryDate = None
+ for item in self:
+ #print ("item %s" %(item))
+ if item:
+ if nextDeliveryDate == None or \
+ item.deliveryDate < nextDeliveryDate:
+ nextDeliveryDate = item.deliveryDate
+
+ return nextDeliveryDate
+
+ def getNextDeliveryItem(self):
+ # Returns the minimum of all the listed items' delivery times,
+ # or None if the list is empty.
+ if len(self) == 0:
+ return None
+
+ nextDeliveryDate = None
+ nextDeliveryItem = None
+ for item in self:
+ if item:
+ if nextDeliveryDate == None or \
+ item.deliveryDate < nextDeliveryDate:
+ nextDeliveryDate = item.deliveryDate
+ nextDeliveryItem = item
+
+ return nextDeliveryItem
+
+ def extractDeliveryItems(self, cutoffTime):
+ # Extracts from the list the set of items whose delivery time
+ # is on or before the cutoff time. Returns a list of items to
+ # be delivered and a list of items still on the way.
+
+ beforeTime = []
+ afterTime = []
+ for item in self:
+ if item.deliveryDate <= cutoffTime:
+ beforeTime.append(item)
+ else:
+ afterTime.append(item)
+
+ return (CatalogItemList(beforeTime, store = self.store),
+ CatalogItemList(afterTime, store = self.store))
+
+ def extractOldestItems(self, count):
+ # Extracts from the list the count oldest items. Returns a
+ # list of the extracted items and a list of remaining items.
+
+ # Actually, we can cheat because we know new items are always
+ # appended to the end of the list. So just extract the first
+ # n items.
+ return (self[0:count], self[count:])
+
+ def __encodeList(self):
+ # We shouldn't try to call this function twice.
+ assert(self.__blob == None)
+ self.__blob = self.__makeBlob(self.store)
+
+ def __makeBlob(self, store):
+ # Construct a new datagram and fill it up with the items in
+ # the list.
+ dg = PyDatagram()
+ if self.__list: # empty list or None means nothing on the list.
+ dg.addUint8(CatalogItem.CatalogItemVersion)
+ for item in self.__list:
+ CatalogItem.encodeCatalogItem(dg, item, store)
+ return dg.getMessage()
+
+ def __decodeList(self):
+ # We shouldn't try to call this function twice.
+ assert(self.__list == None)
+ self.__list = self.__makeList(self.store)
+
+ def __makeList(self, store):
+ # Construct a new list and populate it with the items decoded
+ # from the blob.
+ list = []
+ if self.__blob: # empty string or None means nothing on the list.
+ dg = PyDatagram(self.__blob)
+ di = PyDatagramIterator(dg)
+ versionNumber = di.getUint8()
+ while di.getRemainingSize() > 0:
+ item = CatalogItem.decodeCatalogItem(di, versionNumber, store)
+ list.append(item)
+ return list
+
+
+ # Functions to make this act just like a Python list.
+
+ def append(self, item):
+ if self.__list == None:
+ self.__decodeList()
+ self.__list.append(item)
+ self.__blob = None
+
+ def extend(self, items):
+ self += items
+
+ def count(self, item):
+ if self.__list == None:
+ self.__decodeList()
+ return self.__list.count(item)
+
+ def index(self, item):
+ if self.__list == None:
+ self.__decodeList()
+ return self.__list.index(item)
+
+ def insert(self, index, item):
+ if self.__list == None:
+ self.__decodeList()
+ self.__list.insert(index, item)
+ self.__blob = None
+
+ def pop(self, index = None):
+ if self.__list == None:
+ self.__decodeList()
+ self.__blob = None
+ if index == None:
+ return self.__list.pop()
+ else:
+ return self.__list.pop(index)
+
+ def remove(self, item):
+ if self.__list == None:
+ self.__decodeList()
+ self.__list.remove(item)
+ self.__blob = None
+
+ def reverse(self):
+ if self.__list == None:
+ self.__decodeList()
+ self.__list.reverse()
+ self.__blob = None
+
+ def sort(self, cmpfunc = None):
+ if self.__list == None:
+ self.__decodeList()
+ if cmpfunc == None:
+ self.__list.sort()
+ else:
+ self.__list.sort(cmpfunc)
+ self.__blob = None
+
+ def __len__(self):
+ if self.__list == None:
+ self.__decodeList()
+ return len(self.__list)
+
+ def __getitem__(self, index):
+ if self.__list == None:
+ self.__decodeList()
+ return self.__list[index]
+
+ def __setitem__(self, index, item):
+ if self.__list == None:
+ self.__decodeList()
+ self.__list[index] = item
+ self.__blob = None
+
+ def __delitem__(self, index):
+ if self.__list == None:
+ self.__decodeList()
+ del self.__list[index]
+ self.__blob = None
+
+ def __getslice__(self, i, j):
+ if self.__list == None:
+ self.__decodeList()
+ return CatalogItemList(self.__list[i : j], store = self.store)
+
+ def __setslice__(self, i, j, s):
+ if self.__list == None:
+ self.__decodeList()
+ if isinstance(s, CatalogItemList):
+ self.__list[i : j] = s.__list
+ else:
+ self.__list[i : j] = s
+ self.__blob = None
+
+ def __delslice__(self, i, j):
+ if self.__list == None:
+ self.__decodeList()
+ del self.__list[i : j]
+ self.__blob = None
+
+ def __iadd__(self, other):
+ if self.__list == None:
+ self.__decodeList()
+ self.__list += list(other)
+ self.__blob = None
+ return self
+
+ def __add__(self, other):
+ copy = CatalogItemList(self, store = self.store)
+ copy += other
+ return copy
+
+
+ def __repr__(self):
+ return self.output()
+
+ def __str__(self):
+ return self.output()
+
+ def output(self, store = ~0):
+ if self.__list == None:
+ self.__decodeList()
+ inner = ""
+ for item in self.__list:
+ inner += ", %s" % (item.output(store))
+ return "CatalogItemList([%s])" % (inner[2:])
+
diff --git a/toontown/src/catalog/CatalogItemPanel.py b/toontown/src/catalog/CatalogItemPanel.py
new file mode 100644
index 0000000..555a945
--- /dev/null
+++ b/toontown/src/catalog/CatalogItemPanel.py
@@ -0,0 +1,665 @@
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from toontown.toonbase import ToontownGlobals
+from toontown.toontowngui import TTDialog
+from toontown.toonbase import TTLocalizer
+import CatalogItemTypes
+import CatalogItem
+from CatalogWallpaperItem import getAllWallpapers
+from CatalogFlooringItem import getAllFloorings
+from CatalogMouldingItem import getAllMouldings
+from CatalogWainscotingItem import getAllWainscotings
+from CatalogFurnitureItem import getAllFurnitures
+from toontown.toontowngui.TeaserPanel import TeaserPanel
+from otp.otpbase import OTPGlobals
+
+CATALOG_PANEL_WORDWRAP = 10
+CATALOG_PANEL_CHAT_WORDWRAP = 9
+
+class CatalogItemPanel(DirectFrame):
+ """
+ CatalogItemPanel
+
+ This class presents the graphical represntation of a catalog item in the
+ Catalog GUI.
+ """
+ def __init__(self, parent=aspect2d, parentCatalogScreen = None ,**kw):
+ optiondefs = (
+ # Define type of DirectGuiWidget
+ ('item', None, DGG.INITOPT),
+ ('type', CatalogItem.CatalogTypeUnspecified, DGG.INITOPT),
+ ('relief', None, None),
+ )
+ self.parentCatalogScreen = parentCatalogScreen;
+ # Merge keyword options with default options
+ self.defineoptions(kw, optiondefs)
+ # Initialize superclasses
+ DirectFrame.__init__(self, parent)
+ self.loaded = 0
+
+ # Loading is done lazily as we display each item.
+ # This reduces the initial load time when first opening the catalog
+ # and nicely spreads it out
+ # self.load()
+ # Call initialization methods
+ self.initialiseoptions(CatalogItemPanel)
+ assert parentCatalogScreen != None, "parentCatalogScreen: is none"
+
+ def load(self):
+ # Loading is guarded so things only get loaded once
+ if self.loaded:
+ return
+ self.loaded = 1
+ # Init variables
+ self.verify = None
+ # Some items know how to draw themselves. Put this first so
+ # it will be below any text on the frame.
+ # Add a picture Frame so draw order is preserved if you change
+ # the picture
+ self.pictureFrame = self.attachNewNode('pictureFrame')
+ self.pictureFrame.setScale(0.15)
+ self.itemIndex = 0
+ self.ival = None
+ # If this is an item that needs customization, add buttons
+ # to scroll through variations
+ typeCode = self['item'].getTypeCode()
+ if (self['item'].needsCustomize() and
+ ((typeCode == CatalogItemTypes.WALLPAPER_ITEM) or
+ (typeCode == CatalogItemTypes.FLOORING_ITEM) or
+ (typeCode == CatalogItemTypes.MOULDING_ITEM) or
+ (typeCode == CatalogItemTypes.FURNITURE_ITEM) or
+ (typeCode == CatalogItemTypes.WAINSCOTING_ITEM) or
+ (typeCode == CatalogItemTypes.TOON_STATUE_ITEM))):
+ if typeCode == CatalogItemTypes.WALLPAPER_ITEM:
+ self.items = getAllWallpapers(self['item'].patternIndex)
+ elif typeCode == CatalogItemTypes.FLOORING_ITEM:
+ self.items = getAllFloorings(self['item'].patternIndex)
+ elif typeCode == CatalogItemTypes.MOULDING_ITEM:
+ self.items = getAllMouldings(self['item'].patternIndex)
+ elif typeCode == CatalogItemTypes.FURNITURE_ITEM:
+ self.items = getAllFurnitures(self['item'].furnitureType)
+ elif typeCode == CatalogItemTypes.TOON_STATUE_ITEM:
+ self.items = self['item'].getAllToonStatues()
+ elif typeCode == CatalogItemTypes.WAINSCOTING_ITEM:
+ self.items = getAllWainscotings(self['item'].patternIndex)
+ self.numItems = len(self.items)
+ if self.numItems > 1:
+ guiItems = loader.loadModel('phase_5.5/models/gui/catalog_gui')
+ nextUp = guiItems.find('**/arrow_up')
+ nextRollover = guiItems.find('**/arrow_Rollover')
+ nextDown = guiItems.find('**/arrow_Down')
+ prevUp = guiItems.find('**/arrowUp')
+ prevDown = guiItems.find('**/arrowDown1')
+ prevRollover = guiItems.find('**/arrowRollover')
+ self.nextVariant = DirectButton(
+ parent = self,
+ relief = None,
+ image = (nextUp, nextDown, nextRollover, nextUp),
+ image3_color = (1,1,1,.4),
+ pos = (0.13,0,0),
+ command = self.showNextVariant,
+ )
+ self.prevVariant = DirectButton(
+ parent = self,
+ relief = None,
+ image = (prevUp, prevDown, prevRollover, prevUp),
+ image3_color = (1,1,1,.4),
+ pos = (-0.13,0,0),
+ command = self.showPrevVariant,
+ state = DGG.DISABLED,
+ )
+ self.variantPictures = [(None, None)] * self.numItems
+ else:
+ self.variantPictures = [(None, None)]
+ self.showCurrentVariant()
+ else:
+ # Some items know how to draw themselves. Put this first so
+ # it will be below any text on the frame.
+
+ picture,self.ival = self['item'].getPicture(base.localAvatar)
+ if picture:
+ picture.reparentTo(self)
+ picture.setScale(0.15)
+
+ self.items = [self['item']]
+ self.variantPictures = [(picture, self.ival)]
+ # type label
+ self.typeLabel = DirectLabel(
+ parent = self,
+ relief = None,
+ pos = (0,0,0.24),
+ scale = 0.075,
+ text = self['item'].getTypeName(),
+ text_fg = (0.95, 0.95, 0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getInterfaceFont(),
+ text_wordwrap = CATALOG_PANEL_WORDWRAP,
+ )
+ # aux text label
+ self.auxText = DirectLabel(
+ parent = self,
+ relief = None,
+ scale = 0.05,
+ pos = (-0.20, 0, 0.16),
+ )
+ # Put this one at a jaunty angle
+ self.auxText.setHpr(0,0,-30)
+
+ self.nameLabel = DirectLabel(
+ parent = self,
+ relief = None,
+ text = self['item'].getDisplayName(),
+ text_fg = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getInterfaceFont(),
+ text_scale = TTLocalizer.CIPnameLabel,
+ text_wordwrap = CATALOG_PANEL_WORDWRAP + TTLocalizer.CIPwordwrapOffset,
+ )
+
+ if self['item'].getTypeCode() == CatalogItemTypes.CHAT_ITEM:
+ # adjust wordwrap as the bubbles are narrower
+ self.nameLabel['text_wordwrap'] = CATALOG_PANEL_CHAT_WORDWRAP
+ # Center the text vertically on the chat balloon based
+ # on the number of rows of text
+ numRows = self.nameLabel.component('text0').textNode.getNumRows()
+ if numRows == 1:
+ namePos = (0,0,-0.06)
+ elif numRows == 2:
+ namePos = (0,0,-0.03)
+ else:
+ namePos = (0,0,0)
+ nameScale = 0.063
+ else:
+ namePos = (0,0,-.22)
+ nameScale = 0.06
+ self.nameLabel.setPos(*namePos)
+ self.nameLabel.setScale(nameScale)
+
+ priceStr = str(self['item'].getPrice(self['type'])) + " " + TTLocalizer.CatalogCurrency
+ priceScale = 0.07
+ # prepend sale sign if necessary
+ if self['item'].isSaleItem():
+ priceStr = TTLocalizer.CatalogSaleItem + priceStr
+ priceScale = 0.06
+ # price label
+ self.priceLabel = DirectLabel(
+ parent = self,
+ relief = None,
+ pos = (0,0,-0.3),
+ scale = priceScale,
+ text = priceStr,
+ text_fg = (0.95, 0.95, 0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getSignFont(),
+ text_align = TextNode.ACenter,
+ )
+
+ # buy button
+ buttonModels = loader.loadModel(
+ "phase_3.5/models/gui/inventory_gui")
+ upButton = buttonModels.find("**/InventoryButtonUp")
+ downButton = buttonModels.find("**/InventoryButtonDown")
+ rolloverButton = buttonModels.find("**/InventoryButtonRollover")
+
+ buyText = TTLocalizer.CatalogBuyText
+
+ if self['item'].isRental():
+ buyText = TTLocalizer.CatalogRentText
+
+ self.buyButton = DirectButton(
+ parent = self,
+ relief = None,
+ pos = (0.2, 0, 0.15),
+ scale = (0.7,1,0.8),
+ text = buyText,
+ text_scale = (0.06, 0.05),
+ text_pos = (-0.005,-0.01),
+ image = (upButton,
+ downButton,
+ rolloverButton,
+ upButton,
+ ),
+ image_color = (1.0, 0.2, 0.2, 1),
+ # Make the rollover button pop out
+ image0_color = Vec4(1.0, 0.4, 0.4, 1),
+ # Make the disabled button fade out
+ image3_color = Vec4(1.0, 0.4, 0.4, 0.4),
+ command = self.__handlePurchaseRequest,
+ )
+ #self.updateBuyButton()
+
+ soundIcons = loader.loadModel('phase_5.5/models/gui/catalogSoundIcons')
+ soundOn = soundIcons.find('**/sound07')
+
+ soundOff = soundIcons.find('**/sound08')
+
+ self.soundOnButton = DirectButton(
+ parent = self,
+ relief = None,
+ pos = (0.2, 0, -0.15),
+ scale = (0.7,1,0.8),
+ #geom = soundOn,
+ #text = TTLocalizer.CatalogSndOnText,
+ text_scale = (0.06, 0.05),
+ text_pos = (-0.005,-0.01),
+ image = (upButton,
+ downButton,
+ rolloverButton,
+ upButton,
+ ),
+ image_color = (0.2, 0.5, 0.2, 1),
+ # Make the rollover button pop out
+ image0_color = Vec4(0.4, 0.5, 0.4, 1),
+ # Make the disabled button fade out
+ image3_color = Vec4(0.4, 0.5, 0.4, 0.4),
+ command = self.handleSoundOnButton,
+ )
+ self.soundOnButton.hide()
+ soundOn.setScale(0.1)
+ soundOn.reparentTo(self.soundOnButton)
+
+ self.soundOffButton = DirectButton(
+ parent = self,
+ relief = None,
+ pos = (0.2, 0, -0.15),
+ scale = (0.7,1,0.8),
+ #text = TTLocalizer.CatalogSndOffText,
+ text_scale = (0.06, 0.05),
+ text_pos = (-0.005,-0.01),
+ image = (upButton,
+ downButton,
+ rolloverButton,
+ upButton,
+ ),
+ image_color = (0.2, 1.0, 0.2, 1),
+ # Make the rollover button pop out
+ image0_color = Vec4(0.4, 1.0, 0.4, 1),
+ # Make the disabled button fade out
+ image3_color = Vec4(0.4, 1.0, 0.4, 0.4),
+ command = self.handleSoundOffButton,
+ )
+ self.soundOffButton.hide()
+ soundOff = self.soundOffButton.attachNewNode('soundOff')
+ soundOn.copyTo(soundOff)
+ #soundOff.setScale(0.1)
+ soundOff.reparentTo(self.soundOffButton)
+
+ upGButton = buttonModels.find("**/InventoryButtonUp")
+ downGButton = buttonModels.find("**/InventoryButtonDown")
+ rolloverGButton = buttonModels.find("**/InventoryButtonRollover")
+
+ ##if len(base.localAvatar.friendsList) > 0:
+ self.giftButton = DirectButton(
+ parent = self,
+ relief = None,
+ pos = (0.2, 0, 0.15),
+ scale = (0.7,1,0.8),
+ text = TTLocalizer.CatalogGiftText,
+ text_scale = (0.06, 0.05),
+ text_pos = (-0.005,-0.01),
+ image = (upButton,
+ downButton,
+ rolloverButton,
+ upButton,
+ ),
+ image_color = (1.0, 0.2, 0.2, 1),
+ # Make the rollover button pop out
+ image0_color = Vec4(1.0, 0.4, 0.4, 1),
+ # Make the disabled button fade out
+ image3_color = Vec4(1.0, 0.4, 0.4, 0.4),
+ command = self.__handleGiftRequest,
+ )
+ self.updateButtons()
+
+ def showNextVariant(self):
+ messenger.send('wakeup')
+ self.hideCurrentVariant()
+ self.itemIndex += 1
+ if self.itemIndex >= self.numItems - 1:
+ self.itemIndex = self.numItems - 1
+ self.nextVariant['state'] = DGG.DISABLED
+ else:
+ self.nextVariant['state'] = DGG.NORMAL
+ self.prevVariant['state'] = DGG.NORMAL
+ self.showCurrentVariant()
+
+ def showPrevVariant(self):
+ messenger.send('wakeup')
+ self.hideCurrentVariant()
+ self.itemIndex -= 1
+ if self.itemIndex < 0:
+ self.itemIndex = 0
+ self.prevVariant['state'] = DGG.DISABLED
+ else:
+ self.prevVariant['state'] = DGG.NORMAL
+ self.nextVariant['state'] = DGG.NORMAL
+ self.showCurrentVariant()
+
+ def showCurrentVariant(self):
+ newPic, self.ival = self.variantPictures[self.itemIndex]
+ if self.ival:
+ self.ival.finish()
+ if not newPic:
+ variant = self.items[self.itemIndex]
+ newPic, self.ival = variant.getPicture(base.localAvatar)
+ self.variantPictures[self.itemIndex] = (newPic, self.ival)
+ newPic.reparentTo(self.pictureFrame)
+ if self.ival:
+ self.ival.loop()
+ if (self['item'].getTypeCode() == CatalogItemTypes.TOON_STATUE_ITEM):
+ if hasattr(self, 'nameLabel'):
+ self.nameLabel['text'] = self.items[self.itemIndex].getDisplayName()
+ self['item'].gardenIndex = self.items[self.itemIndex].gardenIndex
+
+ def hideCurrentVariant(self):
+ currentPic = self.variantPictures[self.itemIndex][0]
+ if currentPic:
+ currentPic.detachNode()
+
+ def unload(self):
+ if not self.loaded:
+ DirectFrame.destroy(self)
+ return
+ self.loaded = 0
+
+ # Getting Toon Statue to the first pose before exiting.
+ if (self['item'].getTypeCode() == CatalogItemTypes.TOON_STATUE_ITEM):
+ self['item'].deleteAllToonStatues()
+ self['item'].gardenIndex = self['item'].startPoseIndex
+ self.nameLabel['text'] = self['item'].getDisplayName()
+
+ # Cleanup any items created during requestPurchase
+ self['item'].requestPurchaseCleanup()
+ # Clear out instance variables
+ for picture, ival in self.variantPictures:
+ if picture:
+ picture.destroy()
+ if ival:
+ ival.finish()
+ self.variantPictures=None
+ if self.ival:
+ self.ival.finish()
+ self.ival = None
+ if(len(self.items)):
+ self.items[0].cleanupPicture()
+
+ self.pictureFrame.remove()
+ self.pictureFrame=None
+ self.items=[]
+ if self.verify:
+ self.verify.cleanup()
+ DirectFrame.destroy(self)
+
+ def destroy(self):
+ # this is only so the DirectGui code cleans us up properly
+ self.parentCatalogScreen=None
+ self.unload()
+
+ def getTeaserPanel(self):
+ # display the appropriate page of the feature browser for this catalog item type
+ typeName = self['item'].getTypeName()
+ if (typeName == TTLocalizer.EmoteTypeName) or (typeName == TTLocalizer.ChatTypeName):
+ page = 'emotions'
+ elif (typeName == TTLocalizer.GardenTypeName) or (typeName == TTLocalizer.GardenStarterTypeName):
+ page = 'gardening'
+ else:
+ # default
+ page = 'clothing'
+ def showTeaserPanel():
+ TeaserPanel(pageName=page)
+ return showTeaserPanel
+
+ def updateBuyButton(self):
+ # display the correct button based on item status
+ # if reached purchase limit
+ if not self.loaded:
+ return
+
+ # Only paid members can purchase
+ if not base.cr.isPaid():
+ self.buyButton['command'] = self.getTeaserPanel()
+
+ #print (self['item'].getName())
+ # Show if on order
+ self.buyButton.show()
+
+ typeCode = self['item'].getTypeCode()
+ orderCount = base.localAvatar.onOrder.count(self['item'])
+ if (orderCount > 0):
+ if orderCount > 1:
+ auxText = "%d %s" % (orderCount, TTLocalizer.CatalogOnOrderText)
+ else:
+ auxText = TTLocalizer.CatalogOnOrderText
+ #self.buyButton.hide()
+ else:
+ auxText = ""
+ # else if you have the current nametag
+ isNameTag = (typeCode == CatalogItemTypes.NAMETAG_ITEM)
+ if isNameTag and not (localAvatar.getGameAccess() == OTPGlobals.AccessFull):
+ # If the item panel is the free player nametag
+ if (self['item'].nametagStyle == 100):
+ if (localAvatar.getFont() == ToontownGlobals.getToonFont()):
+ auxText = TTLocalizer.CatalogCurrent
+ self.buyButton['state'] = DGG.DISABLED
+ elif isNameTag and (self['item'].nametagStyle == localAvatar.getNametagStyle()):
+ auxText = TTLocalizer.CatalogCurrent
+ self.buyButton['state'] = DGG.DISABLED
+ elif (self['item'].reachedPurchaseLimit(base.localAvatar)):
+ max = self['item'].getPurchaseLimit()
+ # Override aux text
+ if max <= 1:
+ auxText = TTLocalizer.CatalogPurchasedText
+ if self['item'].hasBeenGifted(base.localAvatar):
+ auxText = TTLocalizer.CatalogGiftedText
+ else:
+ auxText = TTLocalizer.CatalogPurchasedMaxText
+ self.buyButton['state'] = DGG.DISABLED
+ #self.buyButton.hide()
+ # else if can afford
+ elif ( hasattr(self['item'], 'noGarden') and
+ self['item'].noGarden(base.localAvatar)):
+ auxText = TTLocalizer.NoGarden
+ self.buyButton['state'] = DGG.DISABLED
+ elif ( hasattr(self['item'], 'isSkillTooLow') and
+ self['item'].isSkillTooLow(base.localAvatar)):
+ auxText = TTLocalizer.SkillTooLow
+ self.buyButton['state'] = DGG.DISABLED
+ elif ( hasattr(self['item'], 'getDaysToGo') and
+ self['item'].getDaysToGo(base.localAvatar)):
+ auxText = TTLocalizer.DaysToGo % self['item'].getDaysToGo(base.localAvatar)
+ self.buyButton['state'] = DGG.DISABLED
+ elif ( self['item'].getPrice(self['type']) <=
+ (base.localAvatar.getMoney() +
+ base.localAvatar.getBankMoney()) ):
+ self.buyButton['state'] = DGG.NORMAL
+ self.buyButton.show()
+ # else ghosted buy button
+ else:
+ self.buyButton['state'] = DGG.DISABLED
+ self.buyButton.show()
+ self.auxText['text'] = auxText
+
+ def __handlePurchaseRequest(self):
+ # prompt the user to verify purchase
+ if self['item'].replacesExisting() and self['item'].hasExisting():
+ message = TTLocalizer.CatalogOnlyOnePurchase % {
+ 'old' : self['item'].getYourOldDesc(),
+ 'item' : self['item'].getName(),
+ 'price' : self['item'].getPrice(self['type']),
+ }
+ else:
+ if self['item'].isRental():
+ message = TTLocalizer.CatalogVerifyRent % {
+ 'item' : self['item'].getName(),
+ 'price' : self['item'].getPrice(self['type']),
+ }
+ else:
+ message = TTLocalizer.CatalogVerifyPurchase % {
+ 'item' : self['item'].getName(),
+ 'price' : self['item'].getPrice(self['type']),
+ }
+
+ self.verify = TTDialog.TTGlobalDialog(
+ doneEvent = "verifyDone",
+ message = message,
+ style = TTDialog.TwoChoice)
+ self.verify.show()
+ self.accept("verifyDone", self.__handleVerifyPurchase)
+
+ def __handleVerifyPurchase(self):
+ # prompt the user to verify purchase
+ status = self.verify.doneStatus
+ self.ignore("verifyDone")
+ self.verify.cleanup()
+ del self.verify
+ self.verify = None
+ if (status == "ok"):
+ # ask the AI to clear this purchase
+ item = self.items[self.itemIndex]
+ messenger.send("CatalogItemPurchaseRequest", [item])
+ self.buyButton['state'] = DGG.DISABLED
+
+ def __handleGiftRequest(self):
+ # prompt the user to verify purchase
+ if self['item'].replacesExisting() and self['item'].hasExisting():
+ message = TTLocalizer.CatalogOnlyOnePurchase % {
+ 'old' : self['item'].getYourOldDesc(),
+ 'item' : self['item'].getName(),
+ 'price' : self['item'].getPrice(self['type']),
+ }
+ else:
+ friendIndex = self.parentCatalogScreen.friendGiftIndex
+ friendText = "Error";
+ numFriends = len(base.localAvatar.friendsList) + len(base.cr.avList) - 1
+ if numFriends > 0:
+ #friendPair = base.localAvatar.friendsList[self.parentCatalogScreen.friendGiftIndex]
+ #handle = base.cr.identifyFriend(friendPair[0])
+ #friendText = self.parentCatalogScreen.friendGiftHandle.getName()
+ friendText = self.parentCatalogScreen.receiverName
+ message = TTLocalizer.CatalogVerifyGift % {
+ 'item' : self['item'].getName(),
+ 'price' : self['item'].getPrice(self['type']),
+ 'friend' : friendText,
+ }
+
+ self.verify = TTDialog.TTGlobalDialog(
+ doneEvent = "verifyGiftDone",
+ message = message,
+ style = TTDialog.TwoChoice)
+ self.verify.show()
+ self.accept("verifyGiftDone", self.__handleVerifyGift)
+
+ def __handleVerifyGift(self):
+ # prompt the user to verify purchase
+ status = self.verify.doneStatus
+ self.ignore("verifyGiftDone")
+ self.verify.cleanup()
+ del self.verify
+ self.verify = None
+ if (status == "ok"):
+ # ask the AI to clear this purchase
+ self.giftButton['state'] = DGG.DISABLED
+ item = self.items[self.itemIndex]
+ messenger.send("CatalogItemGiftPurchaseRequest", [item])
+
+ def updateButtons(self, giftActivate = 0):
+ if self.parentCatalogScreen.gifting == -1:
+ self.updateBuyButton()
+ if self.loaded:
+ self.giftButton.hide()
+ else:
+ #print("update gift button")
+ self.updateGiftButton(giftActivate)
+ if self.loaded:
+ self.buyButton.hide()
+
+ def updateGiftButton(self, giftUpdate = 0):
+ # display the correct button based on item status
+ # if reached purchase limit
+ if not self.loaded:
+ return
+ self.giftButton.show()
+ if giftUpdate == 0:
+ return
+
+ # Only paid members can purchase gifts
+ if not base.cr.isPaid():
+ self.giftButton['command'] = self.getTeaserPanel()
+
+ self.auxText['text'] = " "
+ numFriends = len(base.localAvatar.friendsList) + len(base.cr.avList) - 1
+ if numFriends > 0:
+ # Start as ghosted and disabled
+ self.giftButton['state'] = DGG.DISABLED
+ #self.giftButton['state'] = DGG.NORMAL #REMOVE ME
+ self.giftButton.show()
+ #return # REMOVE ME
+ #friendPair = base.localAvatar.friendsList[self.parentCatalogScreen.friendGiftIndex]
+ #if it's not a gift hide it
+ auxText = " "
+ if (self['item'].isGift() <= 0):
+ self.giftButton.show()
+ self.giftButton['state'] = DGG.DISABLED
+ auxText = TTLocalizer.CatalogNotAGift
+ self.auxText['text'] = auxText
+ return
+ #print "not a gift"
+ #self.giftButton.hide()
+ #otherwise if you have a valid avater for your friend attempt to activate the button
+ elif (self.parentCatalogScreen.gotAvatar == 1):
+ avatar = self.parentCatalogScreen.giftAvatar #aliasing the giftAvater
+ #if it is for the other gender then fail
+ if((self['item'].forBoysOnly() and avatar.getStyle().getGender() == 'f') or (self['item'].forGirlsOnly() and avatar.getStyle().getGender() == 'm')):
+ self.giftButton.show()
+ self.giftButton['state'] = DGG.DISABLED
+ auxText = TTLocalizer.CatalogNoFit
+ self.auxText['text'] = auxText
+ #print "fit"
+ return
+ #if it's purchase limit is reached fail
+ elif(self['item'].reachedPurchaseLimit(avatar)):
+ self.giftButton.show()
+ self.giftButton['state'] = DGG.DISABLED
+ auxText = TTLocalizer.CatalogPurchasedGiftText
+ self.auxText['text'] = auxText
+ #print "limit"
+ return
+ #if their onGiftorder box is full
+ elif len(avatar.mailboxContents) + len(avatar.onGiftOrder) >= ToontownGlobals.MaxMailboxContents:
+ self.giftButton.show()
+ self.giftButton['state'] = DGG.DISABLED
+ auxText = TTLocalizer.CatalogMailboxFull
+ self.auxText['text'] = auxText
+ #print "full"
+ return
+ #if can you afford it activate the gift button
+ elif ( self['item'].getPrice(self['type']) <=
+ (base.localAvatar.getMoney() +
+ base.localAvatar.getBankMoney()) ):
+ self.giftButton['state'] = DGG.NORMAL
+ self.giftButton.show()
+
+ def handleSoundOnButton(self):
+ """Handle the user clicking on the sound."""
+ #import pdb; pdb.set_trace()
+ item = self.items[self.itemIndex]
+ self.soundOnButton.hide()
+ self.soundOffButton.show()
+ if hasattr(item, 'changeIval'):
+ if self.ival:
+ self.ival.finish()
+ self.ival = None
+ self.ival = item.changeIval(volume = 1)
+ self.ival.loop()
+
+ def handleSoundOffButton(self):
+ """Handle the user clicking off the sound."""
+ #import pdb; pdb.set_trace()
+ item = self.items[self.itemIndex]
+ self.soundOffButton.hide()
+ self.soundOnButton.show()
+ if hasattr(item, 'changeIval'):
+ if self.ival:
+ self.ival.finish()
+ self.ival = None
+ self.ival = item.changeIval(volume = 0)
+ self.ival.loop()
diff --git a/toontown/src/catalog/CatalogItemTypes.py b/toontown/src/catalog/CatalogItemTypes.py
new file mode 100644
index 0000000..84988dc
--- /dev/null
+++ b/toontown/src/catalog/CatalogItemTypes.py
@@ -0,0 +1,108 @@
+import CatalogFurnitureItem
+import CatalogChatItem
+import CatalogClothingItem
+import CatalogEmoteItem
+import CatalogWallpaperItem
+import CatalogFlooringItem
+import CatalogWainscotingItem
+import CatalogMouldingItem
+import CatalogWindowItem
+import CatalogPoleItem
+import CatalogPetTrickItem
+import CatalogBeanItem
+import CatalogGardenItem
+import CatalogInvalidItem
+import CatalogRentalItem
+import CatalogGardenStarterItem
+import CatalogNametagItem
+import CatalogToonStatueItem
+import CatalogAnimatedFurnitureItem
+
+# Catalog item type codes. These code numbers are written to the
+# database to represent each particular type of catalog item; you may
+# add to this list, but don't change previous numbers once they have
+# been published.
+# As you add more item types, please add to TTLocalizer.CatalogItemTypeNames too
+INVALID_ITEM = 0
+FURNITURE_ITEM = 1
+CHAT_ITEM = 2
+CLOTHING_ITEM = 3
+EMOTE_ITEM = 4
+WALLPAPER_ITEM = 5
+WINDOW_ITEM = 6
+FLOORING_ITEM = 7
+MOULDING_ITEM = 8
+WAINSCOTING_ITEM = 9
+POLE_ITEM = 10
+PET_TRICK_ITEM = 11
+BEAN_ITEM = 12
+GARDEN_ITEM = 13
+RENTAL_ITEM = 14
+GARDENSTARTER_ITEM = 15
+NAMETAG_ITEM = 16
+TOON_STATUE_ITEM = 17
+ANIMATED_FURNITURE_ITEM = 18
+
+NonPermanentItemTypes = (RENTAL_ITEM, )
+
+CatalogItemTypes = {
+ CatalogInvalidItem.CatalogInvalidItem : INVALID_ITEM,
+ CatalogFurnitureItem.CatalogFurnitureItem : FURNITURE_ITEM,
+ CatalogChatItem.CatalogChatItem : CHAT_ITEM,
+ CatalogClothingItem.CatalogClothingItem : CLOTHING_ITEM,
+ CatalogEmoteItem.CatalogEmoteItem : EMOTE_ITEM,
+ CatalogWallpaperItem.CatalogWallpaperItem : WALLPAPER_ITEM,
+ CatalogWindowItem.CatalogWindowItem : WINDOW_ITEM,
+ CatalogFlooringItem.CatalogFlooringItem : FLOORING_ITEM,
+ CatalogMouldingItem.CatalogMouldingItem : MOULDING_ITEM,
+ CatalogWainscotingItem.CatalogWainscotingItem : WAINSCOTING_ITEM,
+ CatalogPoleItem.CatalogPoleItem : POLE_ITEM,
+ CatalogPetTrickItem.CatalogPetTrickItem : PET_TRICK_ITEM,
+ CatalogBeanItem.CatalogBeanItem : BEAN_ITEM,
+ CatalogGardenItem.CatalogGardenItem : GARDEN_ITEM,
+ CatalogRentalItem.CatalogRentalItem : RENTAL_ITEM,
+ CatalogGardenStarterItem.CatalogGardenStarterItem : GARDENSTARTER_ITEM,
+ CatalogNametagItem.CatalogNametagItem : NAMETAG_ITEM,
+ CatalogToonStatueItem.CatalogToonStatueItem : TOON_STATUE_ITEM,
+ CatalogAnimatedFurnitureItem.CatalogAnimatedFurnitureItem : ANIMATED_FURNITURE_ITEM,
+ }
+
+# for each catalog item type, indicates whether or not toons are allowed to have more than one
+# of any particular item of that type
+CatalogItemType2multipleAllowed = {
+ INVALID_ITEM : False,
+ FURNITURE_ITEM : True,
+ CHAT_ITEM : False,
+ CLOTHING_ITEM : False,
+ EMOTE_ITEM : False,
+ WALLPAPER_ITEM : True,
+ WINDOW_ITEM : True,
+ FLOORING_ITEM : True,
+ MOULDING_ITEM : True,
+ WAINSCOTING_ITEM : True,
+ POLE_ITEM : False,
+ PET_TRICK_ITEM : False,
+ BEAN_ITEM : True,
+ GARDEN_ITEM : False,
+ RENTAL_ITEM : False,
+ GARDENSTARTER_ITEM : False,
+ NAMETAG_ITEM : False,
+ TOON_STATUE_ITEM : False,
+ ANIMATED_FURNITURE_ITEM : True,
+ }
+
+assert len(CatalogItemType2multipleAllowed) == len(CatalogItemTypes)
+
+# We only use the bottom n bits of the type byte to encode the type
+# number. For now, we reserve 5 bits, which gives us type codes up to
+# 31. We can change this if we need more type codes or more
+# high-order bits later.
+CatalogItemTypeMask = 0x1f
+assert(CatalogItemTypeMask > max(CatalogItemTypes.values()))
+
+# The remaining high-order bits are reserved for esoteric flags on the
+# catalog items.
+CatalogItemSaleFlag = 0x80
+CatalogItemGiftTag = 0x40
+
+
diff --git a/toontown/src/catalog/CatalogManager.py b/toontown/src/catalog/CatalogManager.py
new file mode 100644
index 0000000..b581a50
--- /dev/null
+++ b/toontown/src/catalog/CatalogManager.py
@@ -0,0 +1,57 @@
+from direct.distributed import DistributedObject
+from direct.directnotify import DirectNotifyGlobal
+
+class CatalogManager(DistributedObject.DistributedObject):
+ notify = DirectNotifyGlobal.directNotify.newCategory("CatalogManager")
+
+ # We should never disable this guy.
+ neverDisable = 1
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+
+ ### Interface methods ###
+
+ ### DistributedObject methods ###
+
+ def generate(self):
+ """
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ if base.cr.catalogManager != None:
+ base.cr.catalogManager.delete()
+ base.cr.catalogManager = self
+ DistributedObject.DistributedObject.generate(self)
+
+ # The first time a particular toon enters the world, start its
+ # catalog system running.
+ # We only want Toons to start the catalog manager running, however,
+ # not gateway avatars, etc.
+ if (hasattr(base.localAvatar, "catalogScheduleNextTime") and
+ base.localAvatar.catalogScheduleNextTime == 0):
+ self.d_startCatalog()
+
+ def disable(self):
+ """
+ This method is called when the DistributedObject is removed from
+ active duty and stored in a cache.
+ """
+ #self.notify.warning("Hey! The CatalogManager was disabled!")
+ base.cr.catalogManager = None
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ """
+ This method is called when the DistributedObject is permanently
+ removed from the world and deleted from the cache.
+ """
+ #self.notify.warning("Hey! The CatalogManager was deleted!")
+ base.cr.catalogManager = None
+ DistributedObject.DistributedObject.delete(self)
+
+ def d_startCatalog(self):
+ self.sendUpdate("startCatalog", [])
+
+
+
diff --git a/toontown/src/catalog/CatalogManagerAI.py b/toontown/src/catalog/CatalogManagerAI.py
new file mode 100644
index 0000000..f9d5753
--- /dev/null
+++ b/toontown/src/catalog/CatalogManagerAI.py
@@ -0,0 +1,325 @@
+# This file will hold the object that handles setting up long-term
+# (multiple day) do-later tasks for announcing a new catalog to a
+# player and/or scheduling deliveries of recently-ordered catalog
+# items. It also handles the actual purchase of items from the
+# catalog.
+
+from direct.distributed import DistributedObjectAI
+from direct.directnotify import DirectNotifyGlobal
+import CatalogGenerator
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+import time
+import math
+from toontown.ai.RepairAvatars import AvatarGetter
+from direct.showbase.PythonUtil import Functor
+from toontown.catalog import CatalogItemList
+from toontown.catalog import CatalogItem
+
+
+CatalogInterval = 7 * 24 * 60 # 1 week (in minutes)
+
+class CatalogManagerAI(DistributedObjectAI.DistributedObjectAI):
+ notify = DirectNotifyGlobal.directNotify.newCategory("CatalogManagerAI")
+
+ timeScale = simbase.config.GetFloat('catalog-time-scale', 1.0)
+ catalogInterval = CatalogInterval / timeScale
+
+ # If this is true, the catalog manager will deliver catalogs based
+ # on real time elapsed, even if the user has not played for more
+ # than one week. If false, each next catalog delivered will be no
+ # more than one more than the previous catalog delivered, no
+ # matter how much time has elapsed in the interim.
+ skipWeeks = simbase.config.GetBool('catalog-skip-weeks', 0)
+
+ def __init__(self, air):
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.generator = CatalogGenerator.CatalogGenerator()
+ self.uniqueIdToReturnCode = {} #cache for return phone calls
+
+ self.notify.info("Catalog time scale %s." % (self.timeScale))
+
+ #DATA holders for gifting an item. So item can be handled on response
+
+
+ def generate(self):
+ DistributedObjectAI.DistributedObjectAI.generate(self)
+
+ def delete(self):
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def forceCatalog(self, avatar, week, afterMinutes = 0):
+ # Forces the catalog to the indicated week for the given
+ # avatar by fiddling with the avatar's start date. Mainly
+ # used for testing and magic words.
+ weekDelta = week - avatar.catalogScheduleCurrentWeek - 1
+ now = (int)(time.time() / 60 + 0.5)
+ avatar.catalogScheduleNextTime = now - (weekDelta * self.catalogInterval) + afterMinutes
+
+ # Temporarily force skipWeeks to true by setting a local
+ # instance variable.
+ self.skipWeeks = 1
+
+ self.deliverCatalogFor(avatar)
+
+ # Delete the instance variable to re-expose the class variable.
+ del self.skipWeeks
+
+ def forceMonthlyCatalog(self, avatar, month, day):
+ # Forces the avatar's monthly catalog to be delivered as if it
+ # were the indicated month and day. Used for testing and
+ # magic words.
+ nowtuple = time.localtime(time.time())
+ thentuple = list(nowtuple)
+ thentuple[1] = month
+ thentuple[2] = day
+ then = time.mktime(thentuple)
+
+ monthlyCatalog = self.generator.generateMonthlyCatalog(avatar, then / 60)
+ avatar.b_setCatalog(monthlyCatalog, avatar.weeklyCatalog, avatar.backCatalog)
+
+
+ def deliverCatalogFor(self, avatar):
+ # Computes the next catalog time for and delivers the catalog
+ # to the indicated avatar.
+
+ # Get the current time in minutes.
+ now = (int)(time.time() / 60 + 0.5)
+
+ if avatar.catalogScheduleNextTime == 0:
+ # This avatar has never received a catalog before; this
+ # becomes the first one.
+ currentWeek = 1
+
+ weekStart = now
+ nextTime = weekStart + self.catalogInterval
+
+ else:
+ # This avatar has received a catalog before, so determine
+ # which week it is for him.
+
+ # We start the week at the avatar's next scheduled
+ # delivery time; this represents the start of the next
+ # following week. Usually (if the player logs in often
+ # enough) this will be the correct value to keep.
+ weekStart = avatar.catalogScheduleNextTime
+ currentWeek = avatar.catalogScheduleCurrentWeek + 1
+
+ # However, perhaps the player hasn't logged in for some
+ # time, in which case we also need to skip the intervening
+ # weeks.
+
+ # On second thought, that seems to be vexing to users.
+ # Maybe the right thing to do is not to skip any
+ # intervening weeks.
+ interval = now - weekStart
+ weekDelta = int(math.floor(float(interval) / float(self.catalogInterval)))
+ if self.skipWeeks:
+ currentWeek += weekDelta
+ weekStart += weekDelta * self.catalogInterval
+
+ nextTime = weekStart + self.catalogInterval
+
+ assert(weekStart <= now)
+
+ # We might come up with nextTime within one minute of now due
+ # to small roundoff error in the above. If so, just jump to
+ # the next week.
+
+ if nextTime <= now + 1:
+ if self.skipWeeks:
+ currentWeek += 1
+ weekStart += self.catalogInterval
+ nextTime += self.catalogInterval
+
+ assert(nextTime > now)
+
+ newMonthlyCatalog = (currentWeek != avatar.catalogScheduleCurrentWeek)
+
+ previousWeek = avatar.catalogScheduleCurrentWeek
+
+ newWeeklyCatalog = (currentWeek != avatar.catalogScheduleCurrentWeek)
+
+ self.notify.debug("Avatar %s at catalog week %s (previous %s)." % (
+ avatar.doId, currentWeek, previousWeek))
+
+ # Now schedule the next week's catalog delivery.
+ avatar.b_setCatalogSchedule(currentWeek, nextTime)
+
+ # We decided that we should wrap around to the beginning of the
+ # catalogs instead of holding on the last.
+ modCurrentWeek = ((currentWeek - 1) % ToontownGlobals.CatalogNumWeeks) + 1
+
+ if newMonthlyCatalog or newWeeklyCatalog:
+ # Finally, generate a new catalog for the avatar.
+ monthlyCatalog = self.generator.generateMonthlyCatalog(avatar, weekStart)
+ if newWeeklyCatalog:
+ weeklyCatalog = self.generator.generateWeeklyCatalog(avatar, modCurrentWeek, monthlyCatalog)
+ backCatalog = self.generator.generateBackCatalog(avatar, modCurrentWeek, previousWeek, weeklyCatalog)
+ # Truncate the old items in the back catalog if it's too
+ # long.
+
+
+ #import pdb; pdb.set_trace()
+
+ if len(backCatalog) > ToontownGlobals.MaxBackCatalog:
+ stickDict = {}
+ #print ("Back Catalog \n\n%s\n\nend back" % (backCatalog))
+ for item in backCatalog:
+ itemType, numSticky = item.getBackSticky()
+ #print ("type %s numSticky %s" % (itemType, numSticky))
+ if numSticky > 0:
+ if stickDict.has_key(itemType):
+ #print("has Key")
+ if (len(stickDict[itemType]) < numSticky):
+ stickDict[itemType].append(item)
+ else:
+ #print ("no key")
+ stickDict[itemType] = [item]
+ #print ("stickDict %s" % (stickDict))
+ backCatalog.remove(item)
+ backCatalog = backCatalog[ : ToontownGlobals.MaxBackCatalog]
+ for key in stickDict:
+ stickList = stickDict[key]
+ for item in stickList:
+ backCatalog.append(item)
+ #import pdb; pdb.set_trace()
+ else:
+ weeklyCatalog = avatar.weeklyCatalog
+ backCatalog = avatar.backCatalog
+
+ avatar.b_setCatalog(monthlyCatalog, weeklyCatalog, backCatalog)
+ if (len(monthlyCatalog) + len(weeklyCatalog) != 0):
+ avatar.b_setCatalogNotify(ToontownGlobals.NewItems, avatar.mailboxNotify)
+
+ self.air.writeServerEvent(
+ 'issue-catalog', avatar.doId, "%s" % (modCurrentWeek))
+
+ def purchaseItem(self, avatar, item, optional):
+ # Purchases the item for the given avatar. Returns the
+ # appropriate status code from ToontownGlobals.py. If the item
+ # requires a delayed delivery, this will schedule the
+ # delivery; otherwise, it will be purchased immediately.
+
+ retcode = None
+
+ if item in avatar.monthlyCatalog:
+ catalogType = CatalogItem.CatalogTypeMonthly
+ elif item in avatar.weeklyCatalog:
+ catalogType = CatalogItem.CatalogTypeWeekly
+ elif item in avatar.backCatalog:
+ catalogType = CatalogItem.CatalogTypeBackorder
+ else:
+ self.air.writeServerEvent('suspicious', avatar.doId, 'purchaseItem %s not in catalog' % (item))
+ self.notify.warning("Avatar %s attempted to purchase %s, not on catalog." % (avatar.doId, item))
+ self.notify.warning("Avatar %s weekly: %s" % (avatar.doId, avatar.weeklyCatalog))
+ return ToontownGlobals.P_NotInCatalog
+
+ price = item.getPrice(catalogType)
+ if price > avatar.getTotalMoney():
+ self.air.writeServerEvent('suspicious', avatar.doId, 'purchaseItem %s not enough money' % (item))
+ self.notify.warning("Avatar %s attempted to purchase %s, not enough money." % (avatar.doId, item))
+ return ToontownGlobals.P_NotEnoughMoney
+
+ deliveryTime = item.getDeliveryTime() / self.timeScale
+ if deliveryTime == 0:
+ # Deliver the item immediately.
+ self.notify.debug("Avatar %s purchased %s, delivered immediately." % (avatar.doId, item))
+ retcode = item.recordPurchase(avatar, optional)
+
+ else:
+ # Schedule a future delivery.
+ retcode = self.setDelivery(avatar, item, deliveryTime, retcode, doUpdateLater = True)
+
+ if retcode >= 0:
+ # Now deduct the avatar's money. We do this last, since there
+ # is a tiny window in which the player might log out between
+ # these transactions and miss the last transaction; we'd
+ # rather err in the player's favor.
+ self.deductMoney(avatar, price, item)
+
+ return retcode
+
+ def deductMoney(self, avatar, price, item):
+
+ bankPrice = min(avatar.getBankMoney(), price)
+ walletPrice = price - bankPrice
+
+ avatar.b_setBankMoney(avatar.getBankMoney() - bankPrice)
+ avatar.b_setMoney(avatar.getMoney() - walletPrice)
+
+ self.air.writeServerEvent(
+ 'catalog-purchase', avatar.doId, "%s|%s" %
+ (price, item))
+ #pdb.set_trace()
+
+ def refundMoney(self, avatarId, refund):
+ avatar = self.air.doId2do.get(avatarId)
+ if avatar:
+ avatar.addMoney(refund)
+ self.air.writeServerEvent(
+ 'refunded-money', avatar.doId, "%s" %
+ (refund))
+ #pdb.set_trace()
+
+ def setDelivery(self, avatar, item, deliveryTime, retcode, doUpdateLater):
+ if len(avatar.mailboxContents) + len(avatar.onOrder) >= ToontownGlobals.MaxMailboxContents:
+ self.notify.debug("Avatar %s has %s in mailbox and %s on order, too many." % (avatar.doId, len(avatar.mailboxContents), len(avatar.onOrder)))
+ if len(avatar.mailboxContents) == 0:
+ # If the mailbox is empty, don't tell the user to
+ # delete something out of his mailbox--he just has
+ # to wait for some of the stuff on his delivery
+ # list to arrive.
+ retcode = ToontownGlobals.P_OnOrderListFull
+ else:
+ # If there's stuff in the mailbox to delete,
+ # advise the user to do so.
+ retcode = ToontownGlobals.P_MailboxFull
+ else:
+ self.notify.debug("Avatar %s purchased %s, to be delivered later." % (avatar.doId, item))
+ now = (int)(time.time() / 60 + 0.5)
+ item.deliveryDate = int(now + deliveryTime)
+ avatar.onOrder.append(item)
+ avatar.b_setDeliverySchedule(avatar.onOrder, doUpdateLater = doUpdateLater)
+ retcode = ToontownGlobals.P_ItemOnOrder
+ #pdb.set_trace()
+ return retcode
+
+
+ def payForGiftItem(self, avatar, item, retcode):
+ print("in pay for Gift Item")
+ if item in avatar.monthlyCatalog:
+ catalogType = CatalogItem.CatalogTypeMonthly
+ elif item in avatar.weeklyCatalog:
+ catalogType = CatalogItem.CatalogTypeWeekly
+ elif item in avatar.backCatalog:
+ catalogType = CatalogItem.CatalogTypeBackorder
+ else:
+ self.air.writeServerEvent('suspicious', avatar.doId, 'purchaseItem %s not in catalog' % (item))
+ self.notify.warning("Avatar %s attempted to purchase %s, not on catalog." % (avatar.doId, item))
+ self.notify.warning("Avatar %s weekly: %s" % (avatar.doId, avatar.weeklyCatalog))
+ retcode = ToontownGlobals.P_NotInCatalog
+ return 0
+
+ price = item.getPrice(catalogType)
+ if price > avatar.getTotalMoney():
+ self.air.writeServerEvent('suspicious', avatar.doId, 'purchaseItem %s not enough money' % (item))
+ self.notify.warning("Avatar %s attempted to purchase %s, not enough money." % (avatar.doId, item))
+ retcode = ToontownGlobals.P_NotEnoughMoney
+ return 0
+
+ self.deductMoney(avatar, price, item)
+ return 1
+
+
+
+ def startCatalog(self):
+ # This message is sent by the client when he believes it is
+ # time to start the catalog system for himself.
+ avId = self.air.getAvatarIdFromSender()
+ avatar = self.air.doId2do.get(avId)
+
+ if avatar and avatar.catalogScheduleNextTime == 0:
+ print("starting catalog for %s" % (avatar.getName()))
+ self.deliverCatalogFor(avatar)
+
diff --git a/toontown/src/catalog/CatalogMouldingItem.py b/toontown/src/catalog/CatalogMouldingItem.py
new file mode 100644
index 0000000..2ac1a30
--- /dev/null
+++ b/toontown/src/catalog/CatalogMouldingItem.py
@@ -0,0 +1,209 @@
+from CatalogSurfaceItem import *
+
+# Indicies into Moulding Textures Dictionary
+MTTextureName = 0
+MTColor = 1
+MTBasePrice = 2
+
+# These index numbers are written to the database. Don't mess with them.
+# Also see TTLocalizer.MouldingNames.
+MouldingTypes = {
+ # Wood - Series 1
+ 1000 : ("phase_3.5/maps/molding_wood1.jpg", CTBasicWoodColorOnWhite, 150),
+ # Plain with colors - Series 1
+ 1010 : ("phase_5.5/maps/bd_grey_border1.jpg", CTFlatColorDark, 150),
+ # Dental wood - Series 2
+ 1020 : ("phase_5.5/maps/dental_Border_wood_neutral.jpg", CTFlatColorDark, 150),
+ # Flowers - Series 2
+ 1030 : ("phase_5.5/maps/littleFlowers_border.jpg", CTWhite, 150),
+ 1040 : ("phase_5.5/maps/littleFlowers_border_neutral.jpg", CTFlatColorDark, 150),
+ # Ladybug - Unused
+ 1050 : ("phase_5.5/maps/ladybugs2_Border.jpg", CTFlatColorDark, 150),
+ # Valentines
+ 1060 : ("phase_5.5/maps/bd_grey_border1.jpg", CTValentinesColors, 150),
+ # Beach
+ 1070 : ("phase_5.5/maps/bd_grey_border1.jpg", CTUnderwaterColors, 150),
+ # Winter String Lights 1
+ 1080 : ("phase_5.5/maps/tt_t_ara_int_border_winterLights1.jpg", CTWhite, 150),
+ # Winter String Lights 2
+ 1085 : ("phase_5.5/maps/tt_t_ara_int_border_winterLights2.jpg", CTWhite, 150),
+ # Winter String Lights 3
+ 1090 : ("phase_5.5/maps/tt_t_ara_int_border_winterLights3.jpg", CTWhite, 150),
+ # Valentines Day - Cupid
+ 1100 : ("phase_5.5/maps/tt_t_ara_int_border_valentine_cupid.jpg", CTWhite, 150),
+ # Valentines Day - Heart 1
+ 1110 : ("phase_5.5/maps/tt_t_ara_int_border_valentine_heart1.jpg", CTWhite, 150),
+ # Valentines Day - Heart 2
+ 1120 : ("phase_5.5/maps/tt_t_ara_int_border_valentine_heart2.jpg", CTWhite, 150),
+ }
+
+class CatalogMouldingItem(CatalogSurfaceItem):
+ """CatalogMouldingItem
+
+ This represents a texture/color combination for moulding.
+ """
+
+ def makeNewItem(self, patternIndex, colorIndex):
+ self.patternIndex = patternIndex
+ self.colorIndex = colorIndex
+ CatalogSurfaceItem.makeNewItem(self)
+
+ def getTypeName(self):
+ # e.g. "wallpaper", "wainscoting", etc.
+ return TTLocalizer.SurfaceNames[STMoulding]
+
+ def getName(self):
+ name = TTLocalizer.MouldingNames.get(self.patternIndex)
+ if name:
+ return name
+ return self.getTypeName()
+
+ def getSurfaceType(self):
+ # Returns a value reflecting the type of surface this
+ # pattern is intended to be applied to.
+ return STMoulding
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+
+## assert (not self.hasPicture)
+ self.hasPicture=True
+
+ frame = self.makeFrame()
+
+ sample = loader.loadModel('phase_5.5/models/estate/wallpaper_sample')
+ a = sample.find('**/a')
+ b = sample.find('**/b')
+ c = sample.find('**/c')
+
+ # Moulding gets applied to the top 1/3, with the bottom
+ # 2/3 hidden.
+ a.setTexture(self.loadTexture(), 1)
+ a.setColorScale(*self.getColor())
+ b.hide()
+ c.hide()
+
+ sample.reparentTo(frame)
+
+ return (frame, None)
+
+ def output(self, store = ~0):
+ return "CatalogMouldingItem(%s, %s%s)" % (
+ self.patternIndex, self.colorIndex,
+ self.formatOptionalData(store))
+
+ def getFilename(self):
+ return MouldingTypes[self.patternIndex][MTTextureName]
+
+ def compareTo(self, other):
+ if self.patternIndex != other.patternIndex:
+ return self.patternIndex - other.patternIndex
+ return self.colorIndex - other.colorIndex
+
+ def getHashContents(self):
+ return (self.patternIndex, self.colorIndex)
+
+ def getBasePrice(self):
+ return MouldingTypes[self.patternIndex][MTBasePrice]
+
+ def loadTexture(self):
+ from pandac.PandaModules import Texture
+ filename = MouldingTypes[self.patternIndex][MTTextureName]
+ texture = loader.loadTexture(filename)
+ texture.setMinfilter(Texture.FTLinearMipmapLinear)
+ texture.setMagfilter(Texture.FTLinear)
+ return texture
+
+ def getColor(self):
+ if self.colorIndex == None:
+ # If no color index is set yet, use first color in color list
+ colorIndex = 0
+ else:
+ colorIndex = self.colorIndex
+ colors = MouldingTypes[self.patternIndex][MTColor]
+ if colors:
+ if colorIndex < len(colors):
+ return colors[colorIndex]
+ else:
+ print "Warning: colorIndex not in colors. Returning white."
+ return CT_WHITE
+ else:
+ return CT_WHITE
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogAtticItem.CatalogAtticItem.decodeDatagram(self, di, versionNumber, store)
+ if versionNumber < 3:
+ self.patternIndex = di.getUint8()
+ else:
+ self.patternIndex = di.getUint16()
+ self.colorIndex = di.getUint8()
+
+ # The following will generate an exception if
+ # self.patternIndex is invalid. The other fields can take
+ # care of themselves.
+ wtype = MouldingTypes[self.patternIndex]
+
+ def encodeDatagram(self, dg, store):
+ CatalogAtticItem.CatalogAtticItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.patternIndex)
+ dg.addUint8(self.colorIndex)
+
+def getMouldings(*indexList):
+ # This function returns a list of CatalogMouldingItems
+ # The returned items will all need to be customized (i.e
+ # have a color chosen by the user. Until customization,
+ # use a default color index of 0 (if the pattern has a color
+ # list) or CT_WHITE if the pattern has no color list
+ list = []
+ for index in indexList:
+ list.append(CatalogMouldingItem(index))
+ return list
+
+
+def getAllMouldings(*indexList):
+ # This function returns a list of all possible
+ # CatalogMouldingItems (that is, all color variants) for the
+ # indicated type index(es).
+ list = []
+ for index in indexList:
+ colors = MouldingTypes[index][MTColor]
+ if colors:
+ for n in range(len(colors)):
+ list.append(CatalogMouldingItem(index, n))
+ else:
+ list.append(CatalogMouldingItem(index, 0))
+ return list
+
+
+def getMouldingRange(fromIndex, toIndex, *otherRanges):
+ # This function returns a list of all possible
+ # CatalogMouldingItems (that is, all color variants) for the
+ # indicated type index(es).
+
+ # Make sure we got an even number of otherRanges
+ assert(len(otherRanges)%2 == 0)
+
+ list = []
+
+ froms = [fromIndex,]
+ tos = [toIndex,]
+
+ i = 0
+ while i < len(otherRanges):
+ froms.append(otherRanges[i])
+ tos.append(otherRanges[i+1])
+ i += 2
+
+ for patternIndex in MouldingTypes.keys():
+ for fromIndex, toIndex in zip(froms,tos):
+ if patternIndex >= fromIndex and patternIndex <= toIndex:
+ colors = MouldingTypes[patternIndex][MTColor]
+ if colors:
+ for n in range(len(colors)):
+ list.append(CatalogMouldingItem(patternIndex, n))
+ else:
+ list.append(CatalogMouldingItem(patternIndex, 0))
+ return list
diff --git a/toontown/src/catalog/CatalogNametagItem.py b/toontown/src/catalog/CatalogNametagItem.py
new file mode 100644
index 0000000..49b7919
--- /dev/null
+++ b/toontown/src/catalog/CatalogNametagItem.py
@@ -0,0 +1,149 @@
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from direct.interval.IntervalGlobal import *
+from direct.gui.DirectGui import *
+
+class CatalogNametagItem(CatalogItem.CatalogItem):
+ """
+ This represents nametags sent
+ """
+
+ sequenceNumber = 0
+
+ def makeNewItem(self, nametagStyle):
+ self.nametagStyle = nametagStyle
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 1
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ if self in avatar.onOrder or self in avatar.mailboxContents or self in avatar.onGiftOrder \
+ or self in avatar.awardMailboxContents or self in avatar.onAwardOrder:
+ return 1
+ if avatar.nametagStyle == self.nametagStyle:
+ return 1
+ return 0
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ return TTLocalizer.CatalogAcceptNametag
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def getTypeName(self):
+ return TTLocalizer.NametagTypeName
+
+ def getName(self):
+ if self.nametagStyle == 100:
+ name = TTLocalizer.UnpaidNameTag
+ else:
+ name = TTLocalizer.NametagFontNames[self.nametagStyle]
+ name = name + TTLocalizer.NametagLabel
+ return name
+ if self.nametagStyle == 0:
+ name = TTLocalizer.NametagPaid
+ elif self.nametagStyle == 1:
+ name = TTLocalizer.NametagAction
+ elif self.nametagStyle == 2:
+ name = TTLocalizer.NametagFrilly
+
+
+ def recordPurchase(self, avatar, optional):
+ if avatar:
+ avatar.b_setNametagStyle(self.nametagStyle)
+
+ #avatar.emoteAccess[self.emoteIndex] = 1
+ #avatar.d_setEmoteAccess(avatar.emoteAccess)
+ #TODO: increase the toon's money here
+ return ToontownGlobals.P_ItemAvailable
+
+ def getPicture(self, avatar):
+ #chatBalloon = loader.loadModel("phase_3/models/props/chatbox.bam")
+ frame = self.makeFrame()
+ if self.nametagStyle == 100:
+ inFont = ToontownGlobals.getToonFont()
+ else:
+ inFont = ToontownGlobals.getNametagFont(self.nametagStyle)
+
+ #nametagJar = loader.loadModel("phase_3.5/models/gui/jar_gui")
+ nameTagDemo = DirectLabel(
+ parent = frame,
+ relief = None,
+ pos = (0,0,0.24),
+ scale = 0.5,
+ text = localAvatar.getName(),
+ text_fg = (1.0, 1.0, 1.0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = inFont,
+ text_wordwrap = 9,
+ )
+ #chatBalloon.find("**/top").setPos(1,0,5)
+ #chatBalloon.find("**/middle").setScale(1,1,3)
+
+ #nametagJar.reparentTo(frame)
+
+ #nametagJar.setPos(0,0,0)
+ #nametagJar.setScale(2.5)
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (frame, None)
+
+ def output(self, store = ~0):
+ return "CatalogNametagItem(%s%s)" % (
+ self.nametagStyle,
+ self.formatOptionalData(store))
+
+ def compareTo(self, other):
+ return self.nametagStyle - other.nametagStyle
+
+ def getHashContents(self):
+ return self.nametagStyle
+
+ def getBasePrice(self):
+ return 500
+ cost = 500
+ if self.nametagStyle == 0:
+ cost = 600
+ elif self.nametagStyle == 1:
+ cost = 600
+ elif self.nametagStyle == 2:
+ cost = 600
+ elif self.nametagStyle == 100:
+ cost = 50
+ return cost
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ self.nametagStyle = di.getUint16()
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.nametagStyle)
+
+ def isGift(self):
+ return 0
+
+ def getBackSticky(self):
+ #some items should hang around in the back catalog
+ itemType = 1 #the types that should stick around
+ numSticky = 4 #how many should stick around
+ return itemType, numSticky
+
diff --git a/toontown/src/catalog/CatalogNotifyDialog.py b/toontown/src/catalog/CatalogNotifyDialog.py
new file mode 100644
index 0000000..d5940ca
--- /dev/null
+++ b/toontown/src/catalog/CatalogNotifyDialog.py
@@ -0,0 +1,96 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+
+class CatalogNotifyDialog:
+ """CatalogNotifyDialog:
+
+ Pops up to tell you when you have a new catalog, or a new delivery
+ from the catalog.
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("CatalogNotifyDialog")
+
+ def __init__(self, message):
+ self.message = message
+ self.messageIndex = 0
+
+ framePosX = 0.40
+ from toontown.toon import LocalToon # import here to stop cyclic import
+ if LocalToon.WantNewsPage:
+ framePosX += LocalToon.AdjustmentForNewsButton
+ self.frame = DirectFrame(
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = ToontownGlobals.GlobalDialogColor,
+ image_scale = (1.2, 1.0, 0.4),
+ text = message[0],
+ text_wordwrap = 16,
+ text_scale = 0.06,
+ text_pos = (-0.1, 0.1),
+ pos = (framePosX, 0, 0.78),
+ )
+
+
+ buttons = loader.loadModel(
+ 'phase_3/models/gui/dialog_box_buttons_gui')
+ cancelImageList = (buttons.find('**/CloseBtn_UP'),
+ buttons.find('**/CloseBtn_DN'),
+ buttons.find('**/CloseBtn_Rllvr'))
+ okImageList = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+
+ self.nextButton = DirectButton(
+ parent = self.frame,
+ relief = None,
+ image = okImageList,
+ command = self.handleButton,
+ pos = (0, 0, -0.14),
+ )
+
+ self.doneButton = DirectButton(
+ parent = self.frame,
+ relief = None,
+ image = cancelImageList,
+ command = self.handleButton,
+ pos = (0, 0, -0.14),
+ )
+ if len(message) == 1:
+ self.nextButton.hide()
+ else:
+ self.doneButton.hide()
+
+ def handleButton(self):
+ self.messageIndex += 1
+ if self.messageIndex >= len(self.message):
+ # That was the last message.
+ self.cleanup()
+ return
+
+ # There's more text to display.
+ self.frame['text'] = self.message[self.messageIndex]
+ if self.messageIndex + 1 == len(self.message):
+ # That's the last message.
+ self.nextButton.hide()
+ self.doneButton.show()
+
+ def cleanup(self):
+ """cleanup(self):
+ Cancels any pending request and removes the panel from the
+ screen, unanswered.
+ """
+ if self.frame:
+ self.frame.destroy()
+ self.frame = None
+ if self.nextButton:
+ self.nextButton.destroy()
+ self.nextButton = None
+ if self.doneButton:
+ self.doneButton.destroy()
+ self.doneButton = None
+
+ def __handleButton(self, value):
+ self.cleanup()
diff --git a/toontown/src/catalog/CatalogPetTrickItem.py b/toontown/src/catalog/CatalogPetTrickItem.py
new file mode 100644
index 0000000..cc6fc81
--- /dev/null
+++ b/toontown/src/catalog/CatalogPetTrickItem.py
@@ -0,0 +1,146 @@
+import CatalogItem
+from toontown.pets import PetTricks
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from direct.interval.IntervalGlobal import *
+
+class CatalogPetTrickItem(CatalogItem.CatalogItem):
+ """
+ This represents a phrase you can say to teach your pet to do a
+ particular trick.
+ """
+
+ sequenceNumber = 0
+ petPicture = None
+ def makeNewItem(self, trickId):
+ self.trickId = trickId
+
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 1
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ if self in avatar.onOrder or self in avatar.mailboxContents or self in avatar.onGiftOrder \
+ or self in avatar.awardMailboxContents or self in avatar.onAwardOrder:
+ return 1
+ return self.trickId in avatar.petTrickPhrases
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ return TTLocalizer.CatalogAcceptPet
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def getTypeName(self):
+ return TTLocalizer.PetTrickTypeName
+
+ def getName(self):
+ phraseId = PetTricks.TrickId2scIds[self.trickId][0]
+ return OTPLocalizer.SpeedChatStaticText[phraseId]
+
+ def recordPurchase(self, avatar, optional):
+ avatar.petTrickPhrases.append(self.trickId)
+ avatar.d_setPetTrickPhrases(avatar.petTrickPhrases)
+ return ToontownGlobals.P_ItemAvailable
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+
+ # Don't import this at the top of the file, since this code
+ # must run on the AI.
+ from toontown.pets import PetDNA, Pet
+
+ pet = Pet.Pet(forGui = 1)
+
+ # We use the avatar's own pet if he/she has a pet (and we know
+ # its DNA), otherwise use a random pet.
+ dna = avatar.petDNA
+ if dna == None:
+ dna = PetDNA.getRandomPetDNA()
+ pet.setDNA(dna)
+
+ pet.setH(180)
+ model, ival = self.makeFrameModel(pet, 0)
+ pet.setScale(2.0)
+ pet.setP(-40)
+
+ # Discard the ival from makeFrameModel, since we don't want to
+ # spin.
+
+ track = PetTricks.getTrickIval(pet, self.trickId)
+ name = "petTrick-item-%s" % (self.sequenceNumber)
+ CatalogPetTrickItem.sequenceNumber += 1
+ if track != None:
+ track = Sequence(Sequence(track),
+ ActorInterval(pet, 'neutral', duration = 2),
+ name = name)
+ else:
+ pet.animFSM.request('neutral')
+ track = Sequence(Wait(4),
+ name = name)
+ self.petPicture = pet
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (model, track)
+
+ def cleanupPicture(self):
+ CatalogItem.CatalogItem.cleanupPicture(self)
+ assert self.petPicture
+ self.petPicture.delete()
+ self.petPicture = None
+
+ def output(self, store = ~0):
+ return "CatalogPetTrickItem(%s%s)" % (
+ self.trickId,
+ self.formatOptionalData(store))
+
+ def compareTo(self, other):
+ return self.trickId - other.trickId
+
+ def getHashContents(self):
+ return self.trickId
+
+ def getBasePrice(self):
+ # All petTricks are the same price for now.
+ return 500
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ self.trickId = di.getUint8()
+ self.dna = None
+ if self.trickId not in PetTricks.TrickId2scIds:
+ raise ValueError
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint8(self.trickId)
+
+
+def getAllPetTricks():
+ # Returns a list of all valid CatalogPetTrickItems.
+ list = []
+ for trickId in PetTricks.TrickId2scIds.keys():
+ list.append(CatalogPetTrickItem(trickId))
+
+ return list
+
diff --git a/toontown/src/catalog/CatalogPoleItem.py b/toontown/src/catalog/CatalogPoleItem.py
new file mode 100644
index 0000000..7bd64a4
--- /dev/null
+++ b/toontown/src/catalog/CatalogPoleItem.py
@@ -0,0 +1,178 @@
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from toontown.fishing import FishGlobals
+from direct.actor import Actor
+from toontown.toonbase import TTLocalizer
+from direct.interval.IntervalGlobal import *
+
+class CatalogPoleItem(CatalogItem.CatalogItem):
+ """CatalogPoleItem
+
+ This represents any of the fishing pole models you might be able
+ to buy. We assume that you can buy fishing poles only in
+ sequence, and that the rodId number increases with each better
+ pole.
+
+ """
+
+ sequenceNumber = 0
+
+ def makeNewItem(self, rodId):
+ self.rodId = rodId
+
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 1
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ return avatar.getFishingRod() >= self.rodId or \
+ self in avatar.onOrder or \
+ self in avatar.mailboxContents
+
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def getTypeName(self):
+ return TTLocalizer.PoleTypeName
+
+ def getName(self):
+ return TTLocalizer.FishingRod % (TTLocalizer.FishingRodNameDict[self.rodId])
+
+ def recordPurchase(self, avatar, optional):
+ if self.rodId < 0 or self.rodId > FishGlobals.MaxRodId:
+ self.notify.warning("Invalid fishing pole: %s for avatar %s" % (self.rodId, avatar.doId))
+ return ToontownGlobals.P_InvalidIndex
+
+ if self.rodId < avatar.getFishingRod():
+ self.notify.warning("Avatar already has pole: %s for avatar %s" % (self.rodId, avatar.doId))
+ return ToontownGlobals.P_ItemUnneeded
+
+ avatar.b_setFishingRod(self.rodId)
+ return ToontownGlobals.P_ItemAvailable
+
+ def isGift(self):
+ return 0
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ return 24 * 60 # 24 hours.
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+
+ rodPath = FishGlobals.RodFileDict.get(self.rodId)
+
+ pole = Actor.Actor(rodPath, {'cast' : 'phase_4/models/props/fishing-pole-chan'})
+
+ pole.setPosHpr(
+ 1.47, 0, -1.67,
+ 90, 55, -90)
+ pole.setScale(0.8)
+
+ pole.setDepthTest(1)
+ pole.setDepthWrite(1)
+
+ frame = self.makeFrame()
+ frame.attachNewNode(pole.node())
+
+ name = "pole-item-%s" % (self.sequenceNumber)
+ CatalogPoleItem.sequenceNumber += 1
+
+ # Not sure if this looks good or not. This interval makes the
+ # pole slowly wind and unwind. For now, we'll just put in an
+ # interval that leaves the pole posed at its wound frame. (We
+ # have to use an interval instead of just posing the actor and
+ # forgetting it, because otherwise the Actor python object
+ # will destruct and forget about the pose.)
+
+## track = Sequence(ActorInterval(pole, 'cast', startFrame=84, endFrame=130),
+## ActorInterval(pole, 'cast', startFrame=130, endFrame=84),
+## Wait(2),
+## name = name)
+ track = Sequence(Func(pole.pose, 'cast', 130),
+ Wait(100),
+ name = name)
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (frame, track)
+
+ def getAcceptItemErrorText(self, retcode):
+ # Returns a string describing the error that occurred on
+ # attempting to accept the item from the mailbox. The input
+ # parameter is the retcode returned by recordPurchase() or by
+ # mailbox.acceptItem().
+ if retcode == ToontownGlobals.P_ItemAvailable:
+ return TTLocalizer.CatalogAcceptPole
+ elif retcode == ToontownGlobals.P_ItemUnneeded:
+ return TTLocalizer.CatalogAcceptPoleUnneeded
+
+ return CatalogItem.CatalogItem.getAcceptItemErrorText(self, retcode)
+
+ def output(self, store = ~0):
+ return "CatalogPoleItem(%s%s)" % (
+ self.rodId,
+ self.formatOptionalData(store))
+
+ def getFilename(self):
+ return FishGlobals.RodFileDict.get(self.rodId)
+
+ def compareTo(self, other):
+ return self.rodId - other.rodId
+
+ def getHashContents(self):
+ return self.rodId
+
+ def getBasePrice(self):
+ return FishGlobals.RodPriceDict[self.rodId]
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ self.rodId = di.getUint8()
+
+ # The following will generate an exception if self.rodId is
+ # invalid.
+ price = FishGlobals.RodPriceDict[self.rodId]
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint8(self.rodId)
+
+
+def nextAvailablePole(avatar, duplicateItems):
+ rodId = avatar.getFishingRod() + 1
+ if rodId > FishGlobals.MaxRodId:
+ # No more fishing rods for this avatar.
+ return None
+
+ item = CatalogPoleItem(rodId)
+
+ # But if this rod is already on order, don't offer the same rod
+ # again. Skip to the next one instead.
+ while item in avatar.onOrder or \
+ item in avatar.mailboxContents:
+ rodId += 1
+ if rodId > FishGlobals.MaxRodId:
+ return None
+ item = CatalogPoleItem(rodId)
+
+ return item
+
+def getAllPoles():
+ list = []
+ for rodId in range(0, FishGlobals.MaxRodId + 1):
+ list.append(CatalogPoleItem(rodId))
+ return list
diff --git a/toontown/src/catalog/CatalogRentalItem.py b/toontown/src/catalog/CatalogRentalItem.py
new file mode 100644
index 0000000..2516406
--- /dev/null
+++ b/toontown/src/catalog/CatalogRentalItem.py
@@ -0,0 +1,191 @@
+import CatalogItem
+import time
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from direct.interval.IntervalGlobal import *
+from toontown.toontowngui import TTDialog
+
+class CatalogRentalItem(CatalogItem.CatalogItem):
+ """CatalogRentalItem
+
+ This is an item that goes away after a period of time.
+
+ """
+
+ def makeNewItem(self, typeIndex, duration, cost):
+ self.typeIndex = typeIndex
+ self.duration = duration # duration is in minutes
+ self.cost = cost
+ # this will need to be persistant (db?)
+ CatalogItem.CatalogItem.makeNewItem(self)
+
+ def getDuration(self):
+ return self.duration
+ # TODO: who will check for expired items? CatalogManagerAI?
+
+ def getPurchaseLimit(self):
+ # Returns the maximum number of this particular item an avatar
+ # may purchase. This is either 0, 1, or some larger number; 0
+ # stands for infinity.
+ return 0
+
+ def reachedPurchaseLimit(self, avatar):
+ # Returns true if the item cannot be bought because the avatar
+ # has already bought his limit on this item.
+ if self in avatar.onOrder or self in avatar.mailboxContents or self in avatar.onGiftOrder \
+ or self in avatar.awardMailboxContents or self in avatar.onAwardOrder:
+ return 1
+ return 0
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def getTypeName(self):
+ # Returns the name of the general type of item.
+ return TTLocalizer.RentalTypeName
+
+
+ def getName(self):
+ hours = int(self.duration / 60)
+ if self.typeIndex == ToontownGlobals.RentalCannon:
+ return ("%s %s %s %s" % (hours, TTLocalizer.RentalHours, TTLocalizer.RentalOf , TTLocalizer.RentalCannon))
+ elif self.typeIndex == ToontownGlobals.RentalGameTable:
+ return ("%s %s %s" % (hours, TTLocalizer.RentalHours, TTLocalizer.RentalGameTable))
+ else:
+ return TTLocalizer.RentalTypeName
+
+
+ def recordPurchase(self, avatar, optional):
+ self.notify.debug("rental -- record purchase")
+ #if avatar:
+ # avatar.addMoney(self.beanAmount)
+
+ if avatar:
+ self.notify.debug("rental -- has avater")
+ #zoneId = avatar.zoneId
+ #estateOwnerDoId = simbase.air.estateMgr.zone2owner.get(zoneId)
+ estate = simbase.air.estateMgr.estate.get(avatar.doId)
+ if estate:
+ self.notify.debug("rental -- has estate")
+ estate.rentItem(self.typeIndex, self.duration)
+ else:
+ self.notify.debug("rental -- something not there")
+
+ return ToontownGlobals.P_ItemAvailable
+
+ def getPicture(self, avatar):
+ scale = 1
+ heading = 0
+ pitch = 30
+ roll = 0
+ spin = 1
+ down = -1
+ if self.typeIndex == ToontownGlobals.RentalCannon:
+ model = loader.loadModel("phase_4/models/minigames/toon_cannon")
+ scale = .5
+ heading = 45
+ elif self.typeIndex == ToontownGlobals.RentalGameTable:
+ model = loader.loadModel("phase_6/models/golf/game_table")
+ assert (not self.hasPicture)
+ self.hasPicture = True
+
+ return self.makeFrameModel(model, spin)
+
+ def output(self, store = ~0):
+ return "CatalogRentalItem(%s%s)" % (
+ self.typeIndex,
+ self.formatOptionalData(store))
+
+ def compareTo(self, other):
+ return self.typeIndex - other.typeIndex
+
+ def getHashContents(self):
+ return self.typeIndex
+
+ def getBasePrice(self):
+ if self.typeIndex == ToontownGlobals.RentalCannon:
+ return self.cost
+ elif self.typeIndex == ToontownGlobals.RentalGameTable:
+ return self.cost
+ else:
+ return 50
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogItem.CatalogItem.decodeDatagram(self, di, versionNumber, store)
+ if versionNumber >= 7:
+ # we normally add new fields at the end, but this was already added at the start
+ # and I didn't want to make the situation worst
+ self.cost = di.getUint16()
+ else:
+ self.cost = 1000
+ self.duration = di.getUint16()
+ self.typeIndex = di.getUint16()
+
+ def encodeDatagram(self, dg, store):
+ CatalogItem.CatalogItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.cost)
+ dg.addUint16(self.duration)
+ dg.addUint16(self.typeIndex)
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ return 1 # 1 minute.
+
+ def isRental(self):
+ return 1
+
+ def acceptItem(self, mailbox, index, callback):
+ # Accepts the item from the mailbox. Some items will pop up a
+ # dialog querying the user for more information before
+ # accepting the item; other items will accept it immediately.
+
+ # In either case, the function will return immediately before
+ # the transaction is finished, but the given callback will be
+ # called later with three parameters: the return code (one of
+ # the P_* symbols defined in ToontownGlobals.py), followed by
+ # the item itself, and the supplied index number.
+
+ # The index is the position of this item within the avatar's
+ # mailboxContents list, which is used by the AI to know which
+ # item to remove from the list (and also to doublecheck that
+ # we're accepting the expected item).
+
+ # This method is only called on the client.
+ self.confirmRent = TTDialog.TTGlobalDialog(
+ doneEvent = "confirmRent",
+ message = TTLocalizer.MessageConfirmRent,
+ command = Functor(self.handleRentConfirm, mailbox, index, callback),
+ style = TTDialog.TwoChoice)
+ #self.confirmRent.msg = msg
+ self.confirmRent.show()
+ #self.accept("confirmRent", Functor(self.handleRentConfirm, mailbox, index, callback))
+ #self.__handleRentConfirm)
+ #self.mailbox = mailbox
+ #self.mailIndex = index
+ #self.mailcallback = callback
+
+ def handleRentConfirm(self, mailbox, index, callback, choice):
+ #def handleRentConfirm(self, *args):
+ #print(args)
+ if choice > 0:
+ mailbox.acceptItem(self, index, callback)
+ else:
+ callback(ToontownGlobals.P_UserCancelled, self, index)
+ if self.confirmRent:
+ self.confirmRent.cleanup()
+ self.confirmRent = None
+
+
+def getAllRentalItems():
+ # Returns a list of all valid CatalogRentalItems.
+ list = []
+ # no game tables for now
+ # TODO since all we offer so far is 48 hours of cannons, values pulled for CatalogGenerator
+ # do something else if we have different durations
+ for rentalType in (ToontownGlobals.RentalCannon, ):
+ list.append(CatalogRentalItem(rentalType,2880,1000))
+ return list
diff --git a/toontown/src/catalog/CatalogScreen.py b/toontown/src/catalog/CatalogScreen.py
new file mode 100644
index 0000000..ed9f278
--- /dev/null
+++ b/toontown/src/catalog/CatalogScreen.py
@@ -0,0 +1,1441 @@
+from pandac.PandaModules import *
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from direct.gui.DirectScrolledList import *
+from toontown.toonbase import ToontownGlobals
+from toontown.toontowngui import TTDialog
+import CatalogItem
+import CatalogInvalidItem
+from toontown.toonbase import TTLocalizer
+import CatalogItemPanel
+import CatalogItemTypes
+from direct.actor import Actor
+import random
+from toontown.toon import DistributedToon
+from direct.directnotify import DirectNotifyGlobal
+
+NUM_CATALOG_ROWS = 3
+NUM_CATALOG_COLS = 2
+
+
+# Center of square frames used for positioning gui elements
+CatalogPanelCenters = [[Point3(-0.95, 0, 0.91),
+ Point3(-0.275, 0, 0.91)],
+ [Point3(-0.95, 0, 0.275),
+ Point3(-0.275, 0, 0.275)],
+ [Point3(-0.95, 0, -0.4),
+ Point3(-0.275, 0, -0.4)]]
+
+CatalogPanelColors = {
+ CatalogItemTypes.FURNITURE_ITEM : Vec4(0.733, 0.780, 0.933, 1.000),
+ CatalogItemTypes.CHAT_ITEM : Vec4(0.922, 0.922, 0.753, 1.0),
+ CatalogItemTypes.CLOTHING_ITEM : Vec4(0.918, 0.690, 0.690, 1.000),
+ CatalogItemTypes.EMOTE_ITEM : Vec4(0.922, 0.922, 0.753, 1.0),
+ CatalogItemTypes.WALLPAPER_ITEM : Vec4(0.749, 0.984, 0.608, 1.000),
+ CatalogItemTypes.WINDOW_ITEM : Vec4(0.827, 0.910, 0.659, 1.000),
+ }
+
+class CatalogScreen(DirectFrame):
+ """
+ CatalogScreen
+ This class presents the user interface to an individual's catalog.
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory("CatalogScreen")
+
+ def __init__(self, parent=aspect2d, **kw):
+ guiItems = loader.loadModel('phase_5.5/models/gui/catalog_gui')
+ background = guiItems.find('**/catalog_background')
+ guiButton = loader.loadModel("phase_3/models/gui/quit_button")
+ guiBack = loader.loadModel('phase_5.5/models/gui/package_delivery_panel')
+ optiondefs = (
+ # Define type of DirectGuiWidget
+ ('scale', 0.667, None),
+ ('pos', (0,1,0.025), None),
+ ('phone', None, None),
+ ('doneEvent', None, None),
+ ('image', background, None),
+ ('relief', None, None),
+ )
+ # Merge keyword options with default options
+ self.defineoptions(kw, optiondefs)
+ # Initialize superclasses
+ DirectFrame.__init__(self, parent)
+ # Create items
+ self.friendGiftIndex = 0 #remove this
+ self.friendGiftHandle = None #handle to the friend you are gifting
+ self.frienddoId = None #doId of the friend you are gifting
+ self.receiverName = "Error Nameless Toon"
+ self.friends = {}
+ self.family = {}
+ self.ffList = []
+ 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.giftAvatar = None; #avatar for gifting
+ self.gotAvatar = 0; #flag to know if the gifting avatar has been received
+ self.allowGetDetails = 1
+ self.load(guiItems, guiButton, guiBack)
+ # Call initialization functions
+ self.initialiseoptions(CatalogScreen)
+ self.enableBackorderCatalogButton()
+ self.setMaxPageIndex(self.numNewPages)
+ self.setPageIndex(-1)
+ self.showPageItems()
+ self.hide()
+ self.clarabelleChatNP = None
+ self.clarabelleChatBalloon = None
+ self.gifting = -1
+ self.createdGiftGui = None
+ self.viewing = None
+
+ def show(self):
+ # listen for update requests
+ self.accept("CatalogItemPurchaseRequest", self.__handlePurchaseRequest)
+ self.accept("CatalogItemGiftPurchaseRequest", self.__handleGiftPurchaseRequest)
+ self.accept(localAvatar.uniqueName("moneyChange"), self.__moneyChange)
+ self.accept(localAvatar.uniqueName("bankMoneyChange"), self.__bankMoneyChange)
+ deliveryText = "setDeliverySchedule-%s" % (base.localAvatar.doId)
+ self.accept(deliveryText, self.remoteUpdate)
+
+ # Hide the world since we have a fullscreen interface
+ # this will improve our framerate a bit while in the catalog
+ render.hide()
+ DirectFrame.show(self)
+ def clarabelleGreeting(task):
+ self.setClarabelleChat(TTLocalizer.CatalogGreeting)
+ def clarabelleHelpText1(task):
+ self.setClarabelleChat(TTLocalizer.CatalogHelpText1)
+ taskMgr.doMethodLater(1.0, clarabelleGreeting, "clarabelleGreeting")
+ taskMgr.doMethodLater(12.0, clarabelleHelpText1, "clarabelleHelpText1")
+ if hasattr(self, "giftToggle"):
+ self.giftToggle['state'] = DGG.DISABLED
+ self.giftToggle['text'] = TTLocalizer.CatalogGiftToggleWait
+ base.cr.deliveryManager.sendAck()
+ self.accept("DeliveryManagerAck", self.__handleUDack)
+ taskMgr.doMethodLater(10.0, self.__handleNoAck, "ackTimeOut")
+
+ def hide(self):
+ self.ignore("CatalogItemPurchaseRequest")
+ self.ignore("CatalogItemGiftPurchaseRequest")
+ self.ignore("DeliveryManagerAck")
+ taskMgr.remove("ackTimeOut")
+ self.ignore(localAvatar.uniqueName("moneyChange"))
+ self.ignore(localAvatar.uniqueName("bankMoneyChange"))
+ deliveryText = "setDeliverySchedule-%s" % (base.localAvatar.doId)
+ self.ignore(deliveryText)
+ # Show the world once again
+ render.show()
+ DirectFrame.hide(self)
+ def setNumNewPages(self, numNewPages):
+ self.numNewPages = numNewPages
+ def setNumBackPages(self, numBackPages):
+ self.numBackPages = numBackPages
+ def setNumLoyaltyPages(self, numLoyaltyPages):
+ self.numLoyaltyPages = numLoyaltyPages
+ def setPageIndex(self, index):
+ self.pageIndex = index
+ def setMaxPageIndex(self, numPages):
+ self.maxPageIndex = max(numPages - 1, -1)
+ def enableBackorderCatalogButton(self):
+ self.backCatalogButton['state'] = DGG.NORMAL
+ self.newCatalogButton['state'] = DGG.DISABLED
+ self.loyaltyCatalogButton['state'] = DGG.DISABLED
+ def enableNewCatalogButton(self):
+ self.backCatalogButton['state'] = DGG.DISABLED
+ self.newCatalogButton['state'] = DGG.NORMAL
+ self.loyaltyCatalogButton['state'] = DGG.DISABLED
+ def enableLoyaltyCatalogButton(self):
+ self.backCatalogButton['state'] = DGG.DISABLED
+ self.newCatalogButton['state'] = DGG.DISABLED
+ self.loyaltyCatalogButton['state'] = DGG.NORMAL
+
+ def modeBackorderCatalog(self):
+ self.backCatalogButton['state'] = DGG.DISABLED
+ self.newCatalogButton['state'] = DGG.NORMAL
+ self.loyaltyCatalogButton['state'] = DGG.NORMAL
+ def modeNewCatalog(self):
+ self.backCatalogButton['state'] = DGG.NORMAL
+ self.newCatalogButton['state'] = DGG.DISABLED
+ self.loyaltyCatalogButton['state'] = DGG.NORMAL
+ def modeLoyaltyCatalog(self):
+ self.backCatalogButton['state'] = DGG.NORMAL
+ self.newCatalogButton['state'] = DGG.NORMAL
+ self.loyaltyCatalogButton['state'] = DGG.DISABLED
+
+ def showNewItems(self, index = None):
+ # If you got here, you do not need to see this text
+ taskMgr.remove("clarabelleHelpText1")
+ messenger.send('wakeup')
+ self.viewing = 'New'
+ self.modeNewCatalog()
+ self.setMaxPageIndex(self.numNewPages)
+ if self.numNewPages == 0:
+ self.setPageIndex(-1)
+ elif index is not None:
+ self.setPageIndex(index)
+ else:
+ self.setPageIndex(0)
+ self.showPageItems()
+ def showBackorderItems(self, index = None):
+ # If you got here, you do not need to see this text
+ taskMgr.remove("clarabelleHelpText1")
+ messenger.send('wakeup')
+ self.viewing = 'Backorder'
+ self.modeBackorderCatalog()
+ self.setMaxPageIndex(self.numBackPages)
+ if self.numBackPages == 0:
+ self.setPageIndex(-1)
+ elif index is not None:
+ self.setPageIndex(index)
+ else:
+ self.setPageIndex(0)
+ self.showPageItems()
+ def showLoyaltyItems(self, index = None):
+ # If you got here, you do not need to see this text
+ taskMgr.remove("clarabelleHelpText1")
+ messenger.send('wakeup')
+ self.viewing = 'Loyalty'
+ self.modeLoyaltyCatalog()
+ self.setMaxPageIndex(self.numLoyaltyPages)
+ if self.numLoyaltyPages == 0:
+ self.setPageIndex(-1)
+ elif index is not None:
+ self.setPageIndex(index)
+ else:
+ self.setPageIndex(0)
+ self.showPageItems()
+ def showNextPage(self):
+ # If you got here, you do not need to see this text
+ taskMgr.remove("clarabelleHelpText1")
+ messenger.send('wakeup')
+ self.pageIndex = self.pageIndex + 1
+ if self.viewing == None:
+ self.modeNewCatalog()
+ self.viewing == 'New'
+
+
+ if ((self.viewing == 'New') and
+ (self.pageIndex > self.maxPageIndex) and
+ (self.numBackPages > 0)):
+ self.showBackorderItems()
+ if ((self.viewing == 'New') and
+ (self.pageIndex > self.maxPageIndex) and
+ (self.numLoyaltyPages > 0)):
+ self.showLoyaltyItems()
+ elif ((self.viewing == 'Backorder') and
+ (self.pageIndex > self.maxPageIndex) and
+ (self.numLoyaltyPages > 0)):
+ self.showLoyaltyItems()
+ else:
+ # If viewing backorder catalog, just clamp at last page
+ self.pageIndex = min(self.pageIndex, self.maxPageIndex)
+ self.showPageItems()
+
+ def showBackPage(self):
+ # If you got here, you do not need to see this text
+ taskMgr.remove("clarabelleHelpText1")
+ messenger.send('wakeup')
+ self.pageIndex = self.pageIndex - 1
+ if ((self.viewing == 'Backorder') and
+ (self.pageIndex < 0) and
+ (self.numNewPages > 0)):
+ self.showNewItems(self.numNewPages - 1)
+ elif ((self.viewing == 'Loyalty') and
+ (self.pageIndex < 0) and
+ (self.numBackPages > 0)):
+ self.showBackorderItems(self.numBackPages - 1)
+ elif ((self.viewing == 'Loyalty') and
+ (self.pageIndex < 0) and
+ (self.numNewPages > 0)):
+ self.showNewItems(self.numNewPages - 1)
+ else:
+ self.pageIndex = max(self.pageIndex, -1)
+ self.showPageItems()
+
+ def showPageItems(self):
+ self.hidePages()
+ if self.viewing == None:
+ self.viewing = 'New'
+ if self.pageIndex < 0:
+ self.closeCover()
+ else:
+ # Make sure cover is open
+ if self.pageIndex == 0:
+ self.openCover()
+ # Show appropriate catalog page
+ if self.viewing == 'New':
+ page = self.pageList[self.pageIndex]
+ newOrBackOrLoyalty = 0
+ elif self.viewing == 'Backorder':
+ page = self.backPageList[self.pageIndex]
+ newOrBackOrLoyalty = 1
+ elif self.viewing == 'Loyalty':
+ page = self.loyaltyPageList[self.pageIndex]
+ newOrBackOrLoyalty = 2
+ page.show()
+ for panel in self.panelDict[page.id()]:
+ panel.load()
+ if panel.ival:
+ panel.ival.loop()
+ self.visiblePanels.append(panel)
+ # Now color panels
+ pIndex = 0
+ randGen = random.Random()
+ randGen.seed(
+ base.localAvatar.catalogScheduleCurrentWeek +
+ (self.pageIndex << 8) +
+ (newOrBackOrLoyalty << 16))
+ for i in range(NUM_CATALOG_ROWS):
+ for j in range(NUM_CATALOG_COLS):
+ if pIndex < len(self.visiblePanels):
+ type = self.visiblePanels[pIndex]['item'].getTypeCode()
+ #self.squares[i][j].setColor(CatalogPanelColors[type])
+ self.squares[i][j].setColor(
+ CatalogPanelColors.values()[
+ randGen.randint(0,len(CatalogPanelColors) - 1)])
+ cs = 0.7 + 0.3 * randGen.random()
+ self.squares[i][j].setColorScale(
+ 0.7 + 0.3 * randGen.random(),
+ 0.7 + 0.3 * randGen.random(),
+ 0.7 + 0.3 * randGen.random(),1)
+ else:
+ self.squares[i][j].setColor(
+ CatalogPanelColors[CatalogItemTypes.CHAT_ITEM])
+ self.squares[i][j].clearColorScale()
+ pIndex += 1
+ # get the appropriate text"
+ if self.viewing == 'New':
+ text = TTLocalizer.CatalogNew
+ elif self.viewing == 'Loyalty':
+ text = TTLocalizer.CatalogLoyalty
+ elif self.viewing == 'Backorder':
+ text = TTLocalizer.CatalogBackorder
+ self.pageLabel['text'] = text + (' - %d' % (self.pageIndex + 1))
+ # Adjust next and backorder buttons
+ if self.pageIndex < self.maxPageIndex:
+ self.nextPageButton.show()
+ elif ((self.viewing == 'New') and (self.numBackPages == 0) and (self.numLoyaltyPages == 0)):
+ self.nextPageButton.hide()
+ elif ((self.viewing == 'Backorder') and (self.numLoyaltyPages == 0)):
+ self.nextPageButton.hide()
+ elif (self.viewing == 'Loyalty'):
+ self.nextPageButton.hide()
+
+
+ self.adjustForSound()
+ self.update()
+
+ def adjustForSound(self):
+ """Properly set the state for the snd buttons."""
+ # first lets count the number of emote items in the visible panels
+ numEmoteItems = 0
+ emotePanels = []
+ for visIndex in xrange(len(self.visiblePanels)):
+ panel = self.visiblePanels[visIndex]
+ item = panel['item']
+ catalogType = item.getTypeCode()
+ if catalogType == CatalogItemTypes.EMOTE_ITEM:
+ numEmoteItems += 1
+ emotePanels.append(panel)
+ else:
+ # make sure the buttons don't show
+ panel.soundOnButton.hide()
+ panel.soundOffButton.hide()
+
+ if numEmoteItems == 1:
+ # we have exactly 1 item, turn on the sound
+ emotePanels[0].handleSoundOnButton()
+ elif numEmoteItems > 1:
+ # we have more than 1, turn off all the sounds
+ for panel in emotePanels:
+ panel.handleSoundOffButton()
+
+
+ def hidePages(self):
+ for page in self.pageList:
+ page.hide()
+ for page in self.backPageList:
+ page.hide()
+ for page in self.loyaltyPageList:
+ page.hide()
+ for panel in self.visiblePanels:
+ if panel.ival:
+ panel.ival.finish()
+ self.visiblePanels = []
+ def openCover(self):
+ self.cover.hide()
+ self.hideDummyTabs()
+ self.backPageButton.show()
+ self.pageLabel.show()
+ def closeCover(self):
+ self.cover.show()
+ self.showDummyTabs()
+ self.nextPageButton.show()
+ self.backPageButton.hide()
+ self.pageLabel.hide()
+ self.hidePages()
+ def showDummyTabs(self):
+ # Put in 2nd back catalog button which is enabled when in down posn
+ if self.numNewPages > 0:
+ self.newCatalogButton2.show()
+ if self.numBackPages > 0:
+ self.backCatalogButton2.show()
+ if self.numLoyaltyPages > 0:
+ self.loyaltyCatalogButton2.show()
+ self.newCatalogButton.hide()
+ self.backCatalogButton.hide()
+ self.loyaltyCatalogButton.hide()
+ def hideDummyTabs(self):
+ # Put in 2nd back catalog button which is enabled when in down posn
+ self.newCatalogButton2.hide()
+ self.backCatalogButton2.hide()
+ self.loyaltyCatalogButton2.hide()
+ if self.numNewPages > 0:
+ self.newCatalogButton.show()
+ if self.numBackPages > 0:
+ self.backCatalogButton.show()
+ if self.numLoyaltyPages > 0:
+ self.loyaltyCatalogButton.show()
+ def packPages(self, panelList, pageList, prefix):
+ i = 0
+ j = 0
+ numPages = 0
+ pageName = prefix + '_page%d' % numPages
+ for item in panelList:
+ if (i==0) and (j==0):
+ numPages += 1
+ pageName = prefix + '_page%d' % numPages
+ page = self.base.attachNewNode(pageName)
+ pageList.append(page)
+ item.reparentTo(page)
+ item.setPos(CatalogPanelCenters[i][j])
+ itemList = self.panelDict.get(page.id(), [])
+ itemList.append(item)
+ self.panelDict[page.id()] = itemList
+ j += 1
+ if j == NUM_CATALOG_COLS:
+ j = 0
+ i += 1
+ if i == NUM_CATALOG_ROWS:
+ i = 0
+ return numPages
+
+
+ def load(self, guiItems, guiButton, guiBack):
+ # Initialize variables
+ self.pageIndex = -1
+ self.maxPageIndex = 0
+ self.numNewPages = 0
+ self.numBackPages = 5
+ self.numLoyaltyPages = 0
+ self.viewing = 'New'
+ self.panelList = []
+ self.backPanelList = []
+ self.pageList = []
+ self.backPageList = []
+ self.loyaltyPanelList = []
+ self.loyaltyPageList = []
+ self.panelDict = {}
+ self.visiblePanels = []
+ self.responseDialog = None
+ # Create components
+ # Background, behind catalog items
+ catalogBase = guiItems.find('**/catalog_base')
+ self.base = DirectLabel(
+ self, relief = None, image = catalogBase)
+ # Catalog tabs
+ newDown = guiItems.find('**/new1')
+ newUp = guiItems.find('**/new2')
+ backDown = guiItems.find('**/previous2')
+ backUp = guiItems.find('**/previous1')
+ giftToggleUp = guiItems.find('**/giftButtonUp')
+ giftToggleDown = guiItems.find('**/giftButtonDown')
+ giftFriends = guiItems.find('**/gift_names')
+
+ lift = 0.40
+ smash = 0.80
+
+ self.newCatalogButton = DirectButton(
+ self.base, relief = None,
+ frameSize = (-0.2, 0.25, 0.45, 1.2),
+ image = [newDown, newDown, newDown, newUp],
+ image_scale = (1.0, 1.0,smash),
+ image_pos = (0.0,0.0,lift),
+ pressEffect = 0,
+ command = self.showNewItems,
+ text = TTLocalizer.CatalogNew,
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = (-0.40 -lift, 0.13),
+ text3_pos = (-0.40 -lift, 0.1),
+ text_scale = 0.08,
+ text_fg = (0.353, 0.627, 0.627, 1.000),
+ text2_fg = (0.353, 0.427, 0.427, 1.000),
+ )
+ self.newCatalogButton.hide()
+
+ self.newCatalogButton2 = DirectButton(
+ self.base, relief = None,
+ frameSize = (-0.2, 0.25, 0.45, 1.2),
+ image = newDown,
+ image_scale = (1.0, 1.0,smash),
+ image_pos = (0.0,0.0,lift),
+ pressEffect = 0,
+ command = self.showNewItems,
+ text = TTLocalizer.CatalogNew,# + "2",# + "foo",
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = (-0.40 - lift, 0.13),
+ text_scale = 0.08,
+ text_fg = (0.353, 0.627, 0.627, 1.000),
+ text2_fg = (0.353, 0.427, 0.427, 1.000),
+ )
+ self.newCatalogButton2.hide()
+
+ self.backCatalogButton = DirectButton(
+ self.base, relief = None,
+ frameSize = (-0.2, 0.25, -0.2, 0.40),
+ image = [backDown, backDown, backDown, backUp],
+ image_scale = (1.0, 1.0,smash),
+ image_pos = (0.0,0.0,lift),
+ pressEffect = 0,
+ command = self.showBackorderItems,
+ text = TTLocalizer.CatalogBackorder,
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = (0.30 - lift,.132),
+ text3_pos = (0.30 -lift,.112),
+ text_scale = TTLocalizer.CSbackCatalogButton,
+ text_fg = (0.392, 0.549, 0.627, 1.000),
+ text2_fg = (0.392, 0.349, 0.427, 1.000),
+ )
+ self.backCatalogButton.hide()
+
+ self.backCatalogButton2 = DirectButton(
+ self.base, relief = None,
+ frameSize = (-0.2, 0.25, -0.2, 0.40),
+ image_scale = (1.0, 1.0,smash),
+ image_pos = (0.0,0.0,lift),
+ image = backDown,
+ pressEffect = 0,
+ command = self.showBackorderItems,
+ text = TTLocalizer.CatalogBackorder,# + "2",# + "foo",
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = (0.30 - lift , .132),
+ text_scale = TTLocalizer.CSbackCatalogButton,
+ text_fg = (0.392, 0.549, 0.627, 1.000),
+ text2_fg = (0.392, 0.349, 0.427, 1.000),
+ )
+ self.backCatalogButton2.hide()
+
+ self.loyaltyCatalogButton = DirectButton(
+ self.base, relief = None,
+ frameSize = (-0.2, 0.25, -0.85, -0.3),
+ image = [newDown, newDown, newDown, newUp],
+ image_scale = (1.0, 1.0,smash),
+ image_pos = (0.0,0.0,-1.4 + lift),
+ pressEffect = 0,
+ command = self.showLoyaltyItems,
+ text = TTLocalizer.CatalogLoyalty,
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = (0.95 - lift,.132),
+ text3_pos = (0.95 -lift,.112),
+ text_scale = 0.065,
+ text_fg = (0.353, 0.627, 0.627, 1.000),
+ text2_fg = (0.353, 0.427, 0.427, 1.000),
+ )
+ self.loyaltyCatalogButton.hide()
+
+ self.loyaltyCatalogButton2 = DirectButton(
+ self.base, relief = None,
+ frameSize = (-0.2, 0.25, -0.85, -0.3),
+ image_scale = (1.0, 1.0,smash),
+ image_pos = (0.0,0.0,-1.4 + lift),
+ image = newDown,
+ pressEffect = 0,
+ command = self.showLoyaltyItems,
+ text = TTLocalizer.CatalogLoyalty,# + "2",# + "foo",
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = (0.95 - lift , .132),
+ text_scale = 0.065,
+ text_fg = (0.353, 0.627, 0.627, 1.000),
+ text2_fg = (0.353, 0.427, 0.427, 1.000),
+ )
+ self.loyaltyCatalogButton2.hide()
+
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ #Friends list
+ self.__makeFFlist()
+
+ if len(self.ffList) > 0:
+ self.giftToggle = DirectButton(
+ self.base,
+ relief = None,
+ pressEffect = 0,
+ image = (giftToggleUp,
+ giftToggleDown,
+ giftToggleUp,
+ ),
+ image_scale = (1.0,1,0.7),
+ command = self.__giftToggle,
+ #state = DGG.DISABLED,
+ text = TTLocalizer.CatalogGiftToggleOff,
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = TTLocalizer.CSgiftTogglePos,
+ text_scale = TTLocalizer.CSgiftToggle,
+ text_fg = (0.353, 0.627, 0.627, 1.000),
+ text3_fg = (0.15, 0.3, 0.3, 1.000),
+ text2_fg = (0.353, 0.427, 0.427, 1.000),
+ image_color = Vec4(1.0, 1.0, 0.2, 1.0),
+ image1_color = Vec4(0.9, 0.85, 0.2, 1.0),
+ image2_color = Vec4(0.9, 0.85, 0.2, 1.0),
+ image3_color = Vec4(0.5, 0.45, 0.2, 1.0),
+ )
+ self.giftToggle.setPos(0.0,0,-0.035)
+
+ #says "this gift is for"
+ self.giftLabel = DirectLabel(
+ self.base, relief = None,
+ image = giftFriends,
+ image_scale = (1.15,1,1.14),
+ text = " ",#TTLocalizer.CatalogGiftFor, #HARD
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = (1.2,-0.97),
+ text_scale = 0.07,
+ text_fg = (0.392, 0.549, 0.627, 1.000),
+ sortOrder = 100,
+ textMayChange = 1,
+ )
+ self.giftLabel.setPos(-0.15,0,0.080)
+ self.giftLabel.hide()
+
+ #says the name of the friend the gift is for
+ self.friendLabel = DirectLabel(
+ self.base, relief = None,
+ text = "Friend Name", #HARD
+ text_font = ToontownGlobals.getSignFont(),
+ text_pos = (-0.25,.132),
+ text_scale = 0.068,
+ text_align = TextNode.ALeft,
+ text_fg = (0.992, 0.949, 0.327, 1.000),
+ sortOrder = 100,
+ textMayChange = 1,
+ )
+ self.friendLabel.setPos(0.5,0,-0.42)
+ self.friendLabel.hide()
+
+ gui = loader.loadModel("phase_3.5/models/gui/friendslist_gui")
+
+ self.scrollList = DirectScrolledList(
+ parent = self,
+ relief = None,
+ # inc and dec are DirectButtons
+ incButton_image = (gui.find("**/FndsLst_ScrollUp"),
+ gui.find("**/FndsLst_ScrollDN"),
+ gui.find("**/FndsLst_ScrollUp_Rllvr"),
+ gui.find("**/FndsLst_ScrollUp"),
+ ),
+ incButton_relief = None,
+ incButton_pos = (0.0, 0.0, -0.316),
+ # Make the disabled button darker
+ incButton_image1_color = Vec4(1.0, 0.9, 0.4, 1.0),
+ incButton_image3_color = Vec4(1.0, 1.0, 0.6, 0.5),
+ incButton_scale = (1.0, 1.0, -1.0),
+ decButton_image = (gui.find("**/FndsLst_ScrollUp"),
+ gui.find("**/FndsLst_ScrollDN"),
+ gui.find("**/FndsLst_ScrollUp_Rllvr"),
+ gui.find("**/FndsLst_ScrollUp"),
+ ),
+ decButton_relief = None,
+ decButton_pos = (0.0, 0.0, 0.117),
+ # Make the disabled button darker
+ decButton_image1_color = Vec4(1.0, 1.0, 0.6, 1.0),
+ decButton_image3_color = Vec4(1.0, 1.0, 0.6, 0.6),
+
+ # itemFrame is a DirectFrame
+ itemFrame_pos = (-0.17, 0.0, 0.06),
+ itemFrame_relief = None,
+ # each item is a button with text on it
+ numItemsVisible = 8,
+ items = [],
+ )
+ self.scrollList.setPos(1.2,0,-0.58)
+ self.scrollList.setScale(1.5)
+ self.scrollList.hide()
+
+ # Set up a clipping plane to truncate names that would extend
+ # off the right end of the scrolled list.
+ clipper = PlaneNode('clipper')
+ clipper.setPlane(Plane(Vec3(-1, 0, 0), Point3(0.17, 0, 0)))
+ clipNP = self.scrollList.attachNewNode(clipper)
+ self.scrollList.setClipPlane(clipNP)
+
+ #self.__addFamilyToScrollList()
+ #self.__updateScrollList()
+
+ self.__makeScrollList()
+
+ #self.__setFriendLabelName() #sets the label name
+ #self.__loadFriend()
+
+ friendId = self.ffList[0]
+ self.__chooseFriend(self.ffList[0][0], self.ffList[0][1])
+ self.update()
+
+ self.createdGiftGui = 1;
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ for i in range(4):
+
+ self.newCatalogButton.component('text%d' % i).setR(90)
+ self.newCatalogButton2.component('text%d' % i).setR(90)
+ self.backCatalogButton.component('text%d' % i).setR(90)
+ self.backCatalogButton2.component('text%d' % i).setR(90)
+ self.loyaltyCatalogButton.component('text%d' % i).setR(90)
+ self.loyaltyCatalogButton2.component('text%d' % i).setR(90)
+ # Squares
+ self.squares = [[],[],[],[]]
+ for i in range(NUM_CATALOG_ROWS):
+ for j in range(NUM_CATALOG_COLS):
+ square = guiItems.find('**/square%d%db' % (i+1,j+1))
+ # Use normal state to block rollover of new and prev
+ # catalog buttons
+ label = DirectLabel(self.base, image = square, relief = None,
+ state = 'normal')
+ self.squares[i].append(label)
+ def priceSort(a,b,type):
+ priceA = a.getPrice(type)
+ priceB = b.getPrice(type)
+ return priceB - priceA
+
+ itemList = (base.localAvatar.monthlyCatalog +
+ base.localAvatar.weeklyCatalog)
+ itemList.sort(lambda a,b: priceSort(a,b,CatalogItem.CatalogTypeWeekly))
+ itemList.reverse()
+ for item in itemList:
+ if isinstance(item, CatalogInvalidItem.CatalogInvalidItem):
+ self.notify.warning("skipping catalog invalid item %s" % item)
+ continue
+
+ #check for loyalty program
+ if item.loyaltyRequirement() != 0:
+ self.loyaltyPanelList.append(
+ CatalogItemPanel.CatalogItemPanel(
+ parent = hidden,
+ item=item,
+ type=CatalogItem.CatalogTypeLoyalty,
+ parentCatalogScreen = self,
+ ))
+
+ else:
+ self.panelList.append(
+ CatalogItemPanel.CatalogItemPanel(
+ parent = hidden,
+ item=item,
+ type=CatalogItem.CatalogTypeWeekly,
+ parentCatalogScreen = self,
+ ))
+
+ itemList = base.localAvatar.backCatalog
+ itemList.sort(
+ lambda a,b: priceSort(a,b,CatalogItem.CatalogTypeBackorder))
+ itemList.reverse()
+ for item in itemList:
+ if isinstance(item, CatalogInvalidItem.CatalogInvalidItem):
+ self.notify.warning("skipping catalog invalid item %s" % item)
+ continue
+
+ #check for loyalty program
+ if item.loyaltyRequirement() != 0:
+ self.loyaltyPanelList.append(
+ CatalogItemPanel.CatalogItemPanel(
+ parent = hidden,
+ item=item,
+ type=CatalogItem.CatalogTypeLoyalty,
+ parentCatalogScreen = self,
+ ))
+ else:
+ self.backPanelList.append(
+ CatalogItemPanel.CatalogItemPanel(
+ parent = hidden,
+ item=item,
+ type=CatalogItem.CatalogTypeBackorder,
+ parentCatalogScreen = self,
+ ))
+
+ numPages = self.packPages(self.panelList, self.pageList, 'new')
+ self.setNumNewPages(numPages)
+ numPages = self.packPages(self.backPanelList,self.backPageList,'back')
+ self.setNumBackPages(numPages)
+
+ numPages = self.packPages(self.loyaltyPanelList,self.loyaltyPageList,'loyalty')
+ self.setNumLoyaltyPages(numPages)
+
+ currentWeek = base.localAvatar.catalogScheduleCurrentWeek - 1
+
+ if currentWeek < 57:
+ seriesNumber = currentWeek / ToontownGlobals.CatalogNumWeeksPerSeries + 1
+ weekNumber = currentWeek % ToontownGlobals.CatalogNumWeeksPerSeries + 1
+ # Catalog Series 5 & 6 are short. Need some special math here.
+ elif currentWeek < 65:
+ seriesNumber = 6
+ weekNumber = (currentWeek - 56)
+ # All catalogs after 5 & 6 now need to get bumped up by
+ # one since the last 13 weeks used two series numbers.
+ else:
+ seriesNumber = currentWeek / ToontownGlobals.CatalogNumWeeksPerSeries + 2
+ weekNumber = currentWeek % ToontownGlobals.CatalogNumWeeksPerSeries + 1
+
+ # Cover. We find the items we want out of the gui object and
+ # reparent them to a new node, partly to ensure the ordering
+ # is OK, and partly because we don't necessarily want all the
+ # nodes.
+ geom = NodePath('cover')
+
+ cover = guiItems.find('**/cover')
+
+ # if the catalog has wrapped around, wrap around the images too
+ maxSeries = (ToontownGlobals.CatalogNumWeeks / ToontownGlobals.CatalogNumWeeksPerSeries) + 1
+ coverSeries = ((seriesNumber - 1) % maxSeries) + 1
+
+ coverPicture = cover.find('**/cover_picture%s' % (coverSeries))
+ if not coverPicture.isEmpty():
+ coverPicture.reparentTo(geom)
+
+ bottomSquare = cover.find('**/cover_bottom')
+ topSquare = guiItems.find('**/square12b2')
+
+ if seriesNumber == 1:
+ topSquare.setColor(0.651, 0.404, 0.322, 1.000)
+ bottomSquare.setColor(0.655, 0.522, 0.263, 1.000)
+ else:
+ topSquare.setColor(0.651, 0.404, 0.322, 1.000)
+ bottomSquare.setColor(0.655, 0.522, 0.263, 1.000)
+
+ bottomSquare.reparentTo(geom)
+ topSquare.reparentTo(geom)
+ cover.find('**/clarabelle_text').reparentTo(geom)
+ cover.find('**/blue_circle').reparentTo(geom)
+ cover.find('**/clarabelle').reparentTo(geom)
+ cover.find('**/circle_green').reparentTo(geom)
+ self.cover = DirectLabel(
+ self.base, relief = None, geom = geom)
+ # Cover Labels
+
+ self.catalogNumber = DirectLabel(
+ self.cover,
+ relief = None,
+ scale = 0.2,
+ pos = (-0.22, 0, -0.33),
+ text = "#%d" % weekNumber,
+ text_fg = (0.95, 0.95, 0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getInterfaceFont()
+ )
+ self.catalogSeries = DirectLabel(
+ self.cover,
+ relief = None,
+ scale = (0.22, 1, 0.18),
+ pos = (-0.76, 0, -0.90),
+ text = TTLocalizer.CatalogSeriesLabel % seriesNumber,
+ text_fg = (0.9, 0.9, 0.4, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getInterfaceFont()
+ )
+ self.catalogSeries.setShxz(0.4)
+
+ # Rings, visible when catalog is open
+ self.rings = DirectLabel(
+ self.base, relief = None, geom = guiItems.find('**/rings'))
+ # Frame to hold clarabelle character
+ self.clarabelleFrame = DirectLabel(
+ self, relief = None,
+ image = guiItems.find('**/clarabelle_frame'))
+ # Hangup button
+ hangupGui = guiItems.find('**/hangup')
+ hangupRolloverGui = guiItems.find('**/hangup_rollover')
+ self.hangup = DirectButton(
+ self, relief = None,
+ pos = (1.78, 0, -1.3),
+ image = [hangupGui, hangupRolloverGui,
+ hangupRolloverGui, hangupGui],
+ text = ["", TTLocalizer.CatalogHangUp, TTLocalizer.CatalogHangUp],
+ text_fg = Vec4(1),
+ text_scale = 0.07,
+ text_pos = (0.0,0.14),
+ command = self.hangUp)
+ # Jellybean indicator
+ self.beanBank = DirectLabel(
+ self, relief = None,
+ image = guiItems.find('**/bean_bank'),
+ text = str(base.localAvatar.getMoney() +
+ base.localAvatar.getBankMoney()),
+ text_align = TextNode.ARight,
+ text_scale = 0.11,
+ text_fg = (0.95, 0.95, 0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_pos = (0.75,-0.81),
+ text_font = ToontownGlobals.getSignFont(),
+ )
+ # Page turners
+ nextUp = guiItems.find('**/arrow_up')
+ nextRollover = guiItems.find('**/arrow_Rollover')
+ nextDown = guiItems.find('**/arrow_Down')
+ prevUp = guiItems.find('**/arrowUp')
+ prevDown = guiItems.find('**/arrowDown1')
+ prevRollover = guiItems.find('**/arrowRollover')
+ self.nextPageButton = DirectButton(
+ self, relief = None,
+ pos = (-0.1,0,-0.9),
+ image = [nextUp, nextDown, nextRollover, nextUp],
+ image_color = (.9,.9,.9,1),
+ image2_color = (1,1,1,1),
+ command = self.showNextPage)
+ self.backPageButton = DirectButton(
+ self, relief = None,
+ pos = (-0.1,0,-0.9),
+ image = [prevUp, prevDown, prevRollover, prevUp],
+ image_color = (.9,.9,.9,1),
+ image2_color = (1,1,1,1),
+ command = self.showBackPage)
+ self.backPageButton.hide()
+ self.pageLabel = DirectLabel(
+ self.base, relief = None,
+ pos = (-1.33,0,-0.9),
+ scale = 0.06,
+ text = TTLocalizer.CatalogPagePrefix,
+ text_fg = (0.95, 0.95, 0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getSignFont(),
+ text_align = TextNode.ALeft,
+ )
+ self.loadClarabelle()
+
+ def loadClarabelle(self):
+ # Create a separate reality for clarabelle
+ self.cRender = NodePath('cRender')
+ # It gets its own camera
+ self.cCamera = self.cRender.attachNewNode('cCamera')
+ self.cCamNode = Camera('cCam')
+ self.cLens = PerspectiveLens()
+ self.cLens.setFov(40,40)
+ self.cLens.setNear(0.1)
+ self.cLens.setFar(100.0)
+ self.cCamNode.setLens(self.cLens)
+ self.cCamNode.setScene(self.cRender)
+ self.cCam = self.cCamera.attachNewNode(self.cCamNode)
+
+ self.cDr = base.win.makeDisplayRegion(0.58, 0.82, 0.53, 0.85)
+ self.cDr.setSort(1)
+
+ self.cDr.setClearDepthActive(1)
+ self.cDr.setClearColorActive(1)
+ self.cDr.setClearColor(Vec4(0.3,0.3,0.3,1))
+ self.cDr.setCamera(self.cCam)
+
+ # Add Clarabelle model
+ self.clarabelle = Actor.Actor("phase_5.5/models/char/Clarabelle-zero",
+ { "listen" : "phase_5.5/models/char/Clarabelle-listens" } )
+ self.clarabelle.loop("listen")
+
+ # Force her eyes to render back-to-front, by the simple
+ # expedient of parenting them to the fixed bin in order.
+ self.clarabelle.find('**/eyes').setBin('fixed', 0)
+ self.clarabelle.find('**/pupilL').setBin('fixed', 1)
+ self.clarabelle.find('**/pupilR').setBin('fixed', 1)
+ self.clarabelle.find('**/glassL').setBin('fixed', 2)
+ self.clarabelle.find('**/glassR').setBin('fixed', 2)
+
+ # Get the switchboard too.
+ switchboard = loader.loadModel("phase_5.5/models/estate/switchboard")
+ switchboard.reparentTo(self.clarabelle)
+ switchboard.setPos(0, -2, 0)
+
+ # self.clarabelle = Char.Char()
+ # clarabelleDNA = CharDNA.CharDNA()
+ # clarabelleDNA.newChar('cl')
+ # self.clarabelle.setDNA(clarabelleDNA)
+ # self.clarabelle.hideName()
+ self.clarabelle.reparentTo(self.cRender)
+ self.clarabelle.setPosHprScale(-0.56, 6.43, -3.81,
+ 121.61, 0.00, 0.00,
+ 1.00, 1.00, 1.00)
+ # Match up existing gui frame
+ self.clarabelleFrame.setPosHprScale(-0.00, 0.00, 0.00,
+ 0.00, 0.00, 0.00,
+ 1.00, 1.00, 1.00)
+ def reload(self):
+ for panel in (self.panelList + self.backPanelList + self.loyaltyPanelList):
+ panel.destroy()
+ def priceSort(a,b,type):
+ priceA = a.getPrice(type)
+ priceB = b.getPrice(type)
+ return priceB - priceA
+ # Initialize variables
+ self.pageIndex = -1
+ self.maxPageIndex = 0
+ self.numNewPages = 0
+ self.numBackPages = 5
+ self.numLoyaltyPages = 0
+ self.viewing = 'New'
+ self.panelList = []
+ self.backPanelList = []
+ self.loyaltyList = []
+ self.pageList = []
+ self.backPageList = []
+ self.loyaltyPanelList = []
+ self.loyaltyPageList = []
+ self.panelDict = {}
+ self.visiblePanels = []
+ itemList = (base.localAvatar.monthlyCatalog +
+ base.localAvatar.weeklyCatalog)
+ itemList.sort(lambda a,b: priceSort(a,b,CatalogItem.CatalogTypeWeekly))
+ itemList.reverse()
+ for item in itemList:
+ if item.loyaltyRequirement() != 0:
+ self.loyaltyPanelList.append(
+ CatalogItemPanel.CatalogItemPanel(
+ parent = hidden,
+ item=item,
+ type=CatalogItem.CatalogTypeLoyalty,
+ parentCatalogScreen = self,
+ ))
+
+ else:
+ self.panelList.append(
+ CatalogItemPanel.CatalogItemPanel(
+ parent = hidden,
+ item=item,
+ type=CatalogItem.CatalogTypeWeekly
+ ))
+ itemList = base.localAvatar.backCatalog
+ itemList.sort(
+ lambda a,b: priceSort(a,b,CatalogItem.CatalogTypeBackorder))
+ itemList.reverse()
+ for item in itemList:
+ if item.loyaltyRequirement() != 0:
+ self.loyaltyPanelList.append(
+ CatalogItemPanel.CatalogItemPanel(
+ parent = hidden,
+ item=item,
+ type=CatalogItem.CatalogTypeLoyalty,
+ parentCatalogScreen = self,
+ ))
+
+ else:
+ self.backPanelList.append(
+ CatalogItemPanel.CatalogItemPanel(
+ parent = hidden,
+ item=item,
+ type=CatalogItem.CatalogTypeBackorder
+ ))
+ numPages = self.packPages(self.panelList, self.pageList, 'new')
+ self.setNumNewPages(numPages)
+ numPages = self.packPages(self.backPanelList,self.backPageList,'back')
+ self.setNumBackPages(numPages)
+
+ numPages = self.packPages(self.loyaltyPanelList,self.loyaltyPageList,'loyalty')
+ self.setNumLoyaltyPages(numPages)
+
+ seriesNumber = (base.localAvatar.catalogScheduleCurrentWeek - 1) / ToontownGlobals.CatalogNumWeeksPerSeries + 1
+ self.catalogSeries['text'] = Localizer.CatalogSeriesLabel % seriesNumber
+ weekNumber = (base.localAvatar.catalogScheduleCurrentWeek - 1) % ToontownGlobals.CatalogNumWeeksPerSeries + 1
+ self.catalogNumber['text'] = "#%d" % weekNumber
+ self.enableBackorderCatalogButton()
+ self.setMaxPageIndex(self.numNewPages)
+ self.setPageIndex(-1)
+ self.showPageItems()
+
+ def unload(self):
+ taskMgr.remove("clearClarabelleChat")
+ taskMgr.remove("postGoodbyeHangUp")
+ taskMgr.remove("clarabelleGreeting")
+ taskMgr.remove("clarabelleHelpText1")
+ taskMgr.remove("clarabelleAskAnythingElse")
+ if self.giftAvatar:
+ base.cr.cancelAvatarDetailsRequest(self.giftAvatar)
+ # Make sure to remove Hook
+ self.hide()
+ # remove all graphical elements
+ self.destroy()
+ # Clean up variables
+ del self.base
+ del self.squares
+ for panel in (self.panelList + self.backPanelList + self.loyaltyPanelList):
+ panel.destroy()
+ del self.panelList
+ del self.backPanelList
+ del self.cover
+ del self.rings
+ del self.clarabelleFrame
+ del self.hangup
+ del self.beanBank
+ del self.nextPageButton
+ del self.backPageButton
+ del self.newCatalogButton
+ del self.newCatalogButton2
+ del self.backCatalogButton
+ del self.backCatalogButton2
+ del self.loyaltyCatalogButton
+ del self.loyaltyCatalogButton2
+
+ del self.pageLabel
+ if self.createdGiftGui:
+ del self.giftToggle
+ del self.giftLabel
+ del self.friendLabel
+ del self.scrollList
+ self.unloadClarabelle()
+ # delete dialog (if present)
+ if self.responseDialog:
+ self.responseDialog.cleanup()
+ self.responseDialog = None
+
+ if self.giftAvatar:
+ if hasattr(self.giftAvatar, 'doId'):
+ self.giftAvatar.delete()
+ else:
+ self.giftAvatar = None
+
+ def unloadClarabelle(self):
+ base.win.removeDisplayRegion(self.cDr)
+ del self.cRender
+ del self.cCamera
+ del self.cCamNode
+ del self.cLens
+ del self.cCam
+ del self.cDr
+ self.clarabelle.cleanup()
+ del self.clarabelle
+
+ def hangUp(self):
+ self.setClarabelleChat(random.choice(TTLocalizer.CatalogGoodbyeList))
+ # Flip to the front cover and hide the arrow and tabs
+ # so you can not get out of this mode. We do not want people
+ # purchasing items after hanging up
+ self.setPageIndex(-1)
+ self.showPageItems()
+ self.nextPageButton.hide()
+ self.backPageButton.hide()
+ self.newCatalogButton.hide()
+ self.newCatalogButton2.hide()
+ self.backCatalogButton.hide()
+ self.backCatalogButton2.hide()
+ self.loyaltyCatalogButton.hide()
+ self.loyaltyCatalogButton2.hide()
+ self.hangup.hide()
+
+ # No more helpful text
+ taskMgr.remove("clarabelleGreeting")
+ taskMgr.remove("clarabelleHelpText1")
+ taskMgr.remove("clarabelleAskAnythingElse")
+
+ def postGoodbyeHangUp(task):
+ messenger.send(self['doneEvent'])
+ self.unload()
+ taskMgr.doMethodLater(1.5, postGoodbyeHangUp, "postGoodbyeHangUp")
+
+ def remoteUpdate(self):
+ #print("remoteupdate")
+ #print self.gifting
+ self.update()
+
+ # button handlers
+ def update(self, lock = 0):#, giftActivate = 0):
+ #print("update")
+ if not hasattr(self.giftAvatar, 'doId'):
+ if self.gifting == 1:
+ self.__giftToggle()
+ # Update amount in jellybean bank if the catalog is still open
+ if hasattr(self, "beanBank"):
+ self.beanBank['text'] = str(base.localAvatar.getTotalMoney())
+ # call this when toon's money count changes to update the buy buttons
+ #print lock
+ if lock == 0:
+ for item in (self.panelList + self.backPanelList + self.loyaltyPanelList):
+ if (type(item) != type("")):
+ #item.updateButtons(giftActivate)
+
+ item.updateButtons(self.gifting)
+ #item.updateBuyButton()
+ #item.updateGiftButton(giftActivate)
+
+ def __handlePurchaseRequest(self, item):
+ # ask the user to customize this purchase (if necessary) and
+ # then ask AI to make the purchase.
+ item.requestPurchase(self['phone'], self.__handlePurchaseResponse)
+ # If you are buying something else, she should not say this
+ taskMgr.remove("clarabelleAskAnythingElse")
+
+ def __handleGiftPurchaseRequest(self, item):
+ # ask the user to customize this purchase (if necessary) and
+ # then ask AI to make the purchase.
+ #friendPair = base.localAvatar.friendsList[self.friendGiftIndex]
+ #frienddoId = base.cr.identifyFriend(friendPair[0]).getDoId()
+ item.requestGiftPurchase(self['phone'], self.frienddoId,self.__handleGiftPurchaseResponse)
+ # If you are buying something else, she should not say this
+ taskMgr.remove("clarabelleAskAnythingElse")
+
+ def __handlePurchaseResponse(self, retCode, item):
+ # AI has returned the status of the purchase in retCode
+ if retCode == ToontownGlobals.P_UserCancelled:
+ # No big deal; the user bailed.
+ return
+ self.setClarabelleChat(item.getRequestPurchaseErrorText(retCode),
+ item.getRequestPurchaseErrorTextTimeout())
+ """
+ self.responseDialog = TTDialog.TTDialog(
+ style = TTDialog.Acknowledge,
+ text = item.getRequestPurchaseErrorText(retCode),
+ text_wordwrap = 15,
+ fadeScreen = 1,
+ command = self.__clearDialog,
+ )
+ """
+
+ def __handleGiftPurchaseResponse(self, retCode, item):
+ # AI has returned the status of the purchase in retCode
+ if retCode == ToontownGlobals.P_UserCancelled:
+ # No big deal; the user bailed.
+ return
+ if self.isEmpty() or self.isHidden():
+ # the user hung up the phone before we got the uberdog response
+ return
+ self.setClarabelleChat(item.getRequestGiftPurchaseErrorText(retCode) % (self.receiverName));
+ self.__loadFriend()
+
+ def askAnythingElse(task):
+ self.setClarabelleChat(TTLocalizer.CatalogAnythingElse)
+
+ if retCode >= 0:
+ # success: update catalog screen and buttons
+ # After a while, have Clarabelle ask if there is anything else you would like
+ taskMgr.doMethodLater(8, askAnythingElse, "clarabelleAskAnythingElse")
+
+
+ def __clearDialog(self, event):
+ self.responseDialog.cleanup()
+ self.responseDialog = None
+
+ def setClarabelleChat(self, str, timeout=6):
+ self.clearClarabelleChat()
+ if not self.clarabelleChatBalloon:
+ self.clarabelleChatBalloon = loader.loadModel("phase_3/models/props/chatbox.bam")
+ self.clarabelleChat = ChatBalloon(self.clarabelleChatBalloon.node())
+ chatNode = self.clarabelleChat.generate(
+ str,
+ ToontownGlobals.getInterfaceFont(),
+ 10,
+ Vec4(0,0,0,1),
+ Vec4(1,1,1,1),
+ 0,
+ 0,
+ 0,
+ NodePath(),
+ 0,
+ 0,
+ NodePath(),
+ )
+ self.clarabelleChatNP = self.attachNewNode(chatNode,1000)
+ self.clarabelleChatNP.setScale(0.08)
+ self.clarabelleChatNP.setPos(0.7,0,0.6)
+ if timeout:
+ taskMgr.doMethodLater(timeout, self.clearClarabelleChat, "clearClarabelleChat")
+
+ def clearClarabelleChat(self, task=None):
+ # Clean up old chat
+ taskMgr.remove("clearClarabelleChat")
+ if self.clarabelleChatNP:
+ self.clarabelleChatNP.removeNode()
+ self.clarabelleChatNP = None
+ del self.clarabelleChat
+
+
+ def __moneyChange(self, money):
+ if self.gifting > 0:
+ self.update(1)
+ else:
+ self.update(0)
+ #print ("__moneyChange")
+
+ def __bankMoneyChange(self, bankMoney):
+ if self.gifting > 0:
+ self.update(1)
+ else:
+ self.update(0)
+ #print ("__bankMoneyChange")
+
+ def checkFamily(self, doId):
+ test = 0
+ for familyMember in base.cr.avList:
+ if familyMember.id == doId:
+ test = 1
+ return test
+
+
+ def __makeFFlist(self):
+ for familyMember in base.cr.avList:
+ if familyMember.id != base.localAvatar.doId:
+ newFF = (familyMember.id, familyMember.name, NametagGroup.CCNonPlayer)
+ self.ffList.append(newFF)
+ for friendPair in base.localAvatar.friendsList:
+ friendId, flags = friendPair
+ #print "adding friend"
+ handle = base.cr.identifyFriend(friendId)
+ if handle and not self.checkFamily(friendId):
+ if hasattr(handle, 'getName'):
+ colorCode = NametagGroup.CCSpeedChat
+ if (flags & ToontownGlobals.FriendChat):
+ colorCode = NametagGroup.CCFreeChat
+ newFF = (friendPair[0], handle.getName(), colorCode)
+ self.ffList.append(newFF)
+ else:
+ self.notify.warning("Bad Handle for getName in makeFFlist")
+ hasManager = hasattr(base.cr, "playerFriendsManager")
+ if hasManager:
+ for avatarId in base.cr.playerFriendsManager.getAllOnlinePlayerAvatars():
+ handle = base.cr.playerFriendsManager.getAvHandleFromId(avatarId)
+ playerId = base.cr.playerFriendsManager.findPlayerIdFromAvId(avatarId)
+ playerInfo = base.cr.playerFriendsManager.getFriendInfo(playerId)
+ freeChat = playerInfo.understandableYesNo
+ if handle and not self.checkFamily(avatarId):
+ if hasattr(handle, 'getName'):
+ colorCode = NametagGroup.CCSpeedChat
+ if freeChat:
+ colorCode = NametagGroup.CCFreeChat
+ newFF = (avatarId, handle.getName(), colorCode)
+ self.ffList.append(newFF)
+ else:
+ self.notify.warning("Bad Handle for getName in makeFFlist")
+ #import pdb; pdb.set_trace()
+
+ def __makeScrollList(self):
+ for ff in self.ffList:
+ ffbutton = self.makeFamilyButton(ff[0], ff[1], ff[2])
+ if ffbutton:
+ #print "adding button"
+ self.scrollList.addItem(ffbutton, refresh=0)
+ self.friends[ff] = ffbutton
+ else:
+ pass
+ #print "not adding button"
+ #import pdb; pdb.set_trace()
+ self.scrollList.refresh()
+
+
+
+ def makeFamilyButton(self, familyId, familyName, colorCode):
+
+ #print("Making Family Button")
+ #print familyId
+ # What color should we display the name in? Use the
+ # appropriate nametag color, according to whether we are
+ # "special friends" or not.
+ fg = NametagGlobals.getNameFg(colorCode, PGButton.SInactive)
+
+ #print "made family button"
+
+ return DirectButton(
+ relief = None,
+ text = familyName,
+ text_scale = 0.04,
+ text_align = TextNode.ALeft,
+ text_fg = fg,
+ text1_bg = self.textDownColor,
+ text2_bg = self.textRolloverColor,
+ text3_fg = self.textDisabledColor,
+ textMayChange = 0,
+ command = self.__chooseFriend,
+ extraArgs = [familyId, familyName],
+ )
+
+
+ def __chooseFriend(self, friendId, friendName):
+ """selects a friend for loading"""
+ #messenger.send('wakeup') # I have no idea what this is for
+ #handle = base.cr.identifyFriend(friendId) # the friend handle is throw awayused to get the doId for an avatar
+ #if 0 and handle != None:
+ # friendText = handle.getName()
+ # self.friendLabel['text'] = (TTLocalizer.CatalogGiftTo % (friendText))
+ # self.friendGiftHandle = handle;
+ # self.frienddoId = handle.getDoId()
+ # self.__loadFriend()
+ messenger.send('wakeup')
+ self.frienddoId = friendId
+ self.receiverName = friendName
+ self.friendLabel['text'] = (TTLocalizer.CatalogGiftTo % (self.receiverName))
+ self.__loadFriend()
+
+ def __loadFriend(self):
+
+ #return
+ """Requests a detail avatar from the database"""
+ if self.allowGetDetails == 0:
+ CatalogScreen.notify.warning("smashing requests")
+ if self.frienddoId and self.allowGetDetails:
+ if self.giftAvatar:
+ if hasattr(self.giftAvatar, 'doId'):
+ self.giftAvatar.delete()
+ self.giftAvatar = None
+ self.giftAvatar = DistributedToon.DistributedToon(base.cr) #sets up a dummy avatar
+ self.giftAvatar.doId = self.frienddoId #the doId is required for getAvatarDetails to work
+ # getAvatarDetails puts a DelayDelete on the avatar, and this
+ # is not a real DO, so bypass the 'generated' check
+ self.giftAvatar.forceAllowDelayDelete()
+ base.cr.getAvatarDetails(self.giftAvatar, self.__handleAvatarDetails, "DistributedToon") #request to the database
+ self.gotAvatar = 0 #sets the flag to false so we know we have a database request pending
+ self.allowGetDetails = 0
+ self.scrollList['state'] = DGG.DISABLED
+
+ def __handleAvatarDetails(self, gotData, avatar, dclass):
+
+ """Receives and uses the detail avatar"""
+ #if something goes wrong set teh flag as invlaid
+ if self.giftAvatar.doId != avatar.doId or gotData == 0:
+ CatalogScreen.notify.error("Get Gift Avatar Failed")
+ self.gotAvatar = 0
+ return
+ #otherwise set the flag to valid and update the catalog
+ else:
+ self.gotAvatar = 1
+ self.giftAvatar = avatar
+ #self.giftAvatar = DistributedToon.DistributedToon(base.cr) #error test case 1
+ #self.giftAvatar = None #error test case 2
+ self.scrollList['state'] = DGG.NORMAL
+ self.allowGetDetails = 1
+ self.update()
+
+ def __giftToggle(self):
+ messenger.send('wakeup')
+ if self.gifting == -1:
+ self.gifting = 1
+ self.giftLabel.show()
+ self.friendLabel.show()
+ self.scrollList.show()
+ self.giftToggle['text'] = TTLocalizer.CatalogGiftToggleOn
+ self.__loadFriend()
+ else:
+ self.gifting = -1
+ self.giftLabel.hide()
+ self.friendLabel.hide()
+ self.scrollList.hide()
+ self.giftToggle['text'] = TTLocalizer.CatalogGiftToggleOff
+ self.update()
+
+ def __handleUDack(self, caller = None):
+ taskMgr.remove("ackTimeOut")
+ if hasattr(self, 'giftToggle') and self.giftToggle:
+ self.giftToggle['state'] = DGG.NORMAL
+ self.giftToggle['text'] = TTLocalizer.CatalogGiftToggleOff
+
+ def __handleNoAck(self, caller = None):
+ if hasattr(self, 'giftToggle') and self.giftToggle:
+ self.giftToggle['text'] = TTLocalizer.CatalogGiftToggleNoAck
+
+
+
+
+
+
+
+
+
+
diff --git a/toontown/src/catalog/CatalogSurfaceColors.py b/toontown/src/catalog/CatalogSurfaceColors.py
new file mode 100644
index 0000000..f47af15
--- /dev/null
+++ b/toontown/src/catalog/CatalogSurfaceColors.py
@@ -0,0 +1,103 @@
+
+## COLORS ##
+CT_WHITE = (1.000, 1.000, 1.000, 1.000)
+CT_RED = (1.000, 0.500, 0.500, 1.000)
+CT_BROWN = (0.641, 0.355, 0.270, 1.000)
+CT_CANTELOPE = (0.839, 0.651, 0.549, 1.000)
+CT_TAN = (0.996, 0.695, 0.512, 1.000)
+CT_ORANGE = (0.992, 0.480, 0.168, 1.000)
+CT_CORAL = (0.832, 0.500, 0.297, 1.000)
+CT_PEACH = (1.000, 0.820, 0.700, 1.000)
+CT_BEIGE = (1.000, 0.800, 0.600, 1.000)
+CT_TAN2 = (0.808, 0.678, 0.510, 1.000)
+CT_SIENNA = (0.570, 0.449, 0.164, 1.000)
+CT_YELLOW = (0.996, 0.898, 0.320, 1.000)
+CT_CREAM = (0.996, 0.957, 0.598, 1.000)
+CT_BEIGE2 = (1.000, 1.000, 0.600, 1.000)
+CT_YELLOW2 = (1.000, 1.000, 0.700, 1.000)
+CT_CITRINE = (0.855, 0.934, 0.492, 1.000)
+CT_FOREST_GREEN = (0.500, 0.586, 0.400, 1.000)
+CT_LINE = (0.551, 0.824, 0.324, 1.000)
+CT_PALE_GREEN = (0.789, 1.000, 0.700, 1.000)
+CT_GREEN = (0.305, 0.969, 0.402, 1.000)
+CT_TEAL = (0.600, 1.000, 0.800, 1.000)
+CT_SEA_GREEN = (0.242, 0.742, 0.516, 1.000)
+CT_LIGHT_BLUE = (0.434, 0.906, 0.836, 1.000)
+CT_AQUA = (0.348, 0.820, 0.953, 1.000)
+CT_BLUE = (0.191, 0.563, 0.773, 1.000)
+CT_LIGHT_BLUE2 = (0.875, 0.937, 1.000, 1.000)
+CT_PERIWINKLE = (0.559, 0.590, 0.875, 1.000)
+CT_ROYAL_BLUE = (0.285, 0.328, 0.727, 1.000)
+CT_GREY = (0.700, 0.700, 0.800, 1.000)
+CT_BLUE2 = (0.600, 0.600, 1.000, 1.000)
+CT_SLATE_BLUE = (0.461, 0.379, 0.824, 1.000)
+CT_PURPLE = (0.547, 0.281, 0.750, 1.000)
+CT_LAVENDER = (0.727, 0.473, 0.859, 1.000)
+CT_PINK = (0.898, 0.617, 0.906, 1.000)
+CT_PINK2 = (1.000, 0.600, 1.000, 1.000)
+CT_MAROON = (0.711, 0.234, 0.438, 1.000)
+CT_PEACH2 = (0.969, 0.691, 0.699, 1.000)
+CT_RED2 = (0.863, 0.406, 0.418, 1.000)
+CT_BRIGHT_RED = (0.934, 0.266, 0.281, 1.000)
+
+# Wood colors
+CT_DARK_WOOD = (0.690, 0.741, 0.710, 1.000)
+CT_DARK_WALNUT = (0.549, 0.412, 0.259, 1.000)
+CT_GENERIC_DARK = (0.443, 0.333, 0.176, 1.000)
+CT_PINE = (1.000, 0.812, 0.490, 1.000)
+CT_CHERRY = (0.710, 0.408, 0.267, 1.000)
+CT_BEECH = (0.961, 0.659, 0.400, 1.000)
+
+# Color tables
+
+# To be applied to flat_wallpaper1.
+CTFlatColor = [
+ CT_BEIGE,
+ CT_TEAL,
+ CT_BLUE2,
+ CT_PINK2,
+ CT_BEIGE2,
+ CT_RED,
+ ]
+
+CTValentinesColors = [
+ CT_PINK2,
+ CT_RED,
+ ]
+
+CTUnderwaterColors = [
+ CT_WHITE,
+ CT_TEAL,
+ CT_SEA_GREEN,
+ CT_LIGHT_BLUE,
+ CT_PALE_GREEN,
+ CT_AQUA,
+ CT_CORAL,
+ CT_PEACH,
+ ]
+
+
+# Create darkened versions of the flat colors These are useful for modlings
+# and borders and such
+CTFlatColorDark = []
+tint = 0.75
+for color in CTFlatColor:
+ CTFlatColorDark.append((color[0] * tint,
+ color[1] * tint,
+ color[2] * tint,
+ 1.0))
+
+CTFlatColorAll = CTFlatColor + CTFlatColorDark
+
+# To be applied to grayscale textures.
+CTBasicWoodColorOnWhite = [
+ CT_DARK_WALNUT,
+ CT_GENERIC_DARK,
+ CT_PINE,
+ CT_CHERRY,
+ CT_BEECH,
+ ]
+
+CTWhite = [CT_WHITE,]
+
+
diff --git a/toontown/src/catalog/CatalogSurfaceItem.py b/toontown/src/catalog/CatalogSurfaceItem.py
new file mode 100644
index 0000000..d22476e
--- /dev/null
+++ b/toontown/src/catalog/CatalogSurfaceItem.py
@@ -0,0 +1,51 @@
+import CatalogItem
+import CatalogAtticItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from CatalogSurfaceColors import *
+
+# Surface type. This must be a contiguous series in the range 0-3 to
+# index into DistributedHouse.activeWallpaper.
+STWallpaper = 0
+STMoulding = 1
+STFlooring = 2
+STWainscoting = 3
+NUM_ST_TYPES = 4
+
+class CatalogSurfaceItem(CatalogAtticItem.CatalogAtticItem):
+ """CatalogSurfaceItem
+
+ Parent class for all house surface items (wallpapers, flooring
+ moulding, and wainscotings. These specify the texture/color
+ combination for walls and trim.
+ """
+
+ def makeNewItem(self):
+ CatalogAtticItem.CatalogAtticItem.makeNewItem(self)
+
+ def setPatternIndex(self, patternIndex):
+ self.patternIndex = patternIndex
+
+ def setColorIndex(self, colorIndex):
+ self.colorIndex = colorIndex
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def recordPurchase(self, avatar, optional):
+ # Updates the appropriate field on the avatar to indicate the
+ # purchase (or delivery). This makes the item available to
+ # use by the avatar. This method is only called on the AI side.
+ self.giftTag = None
+ house, retcode = self.getHouseInfo(avatar)
+ if retcode >= 0:
+ house.addWallpaper(self)
+ return retcode
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ return 60 # 1 hour.
+
diff --git a/toontown/src/catalog/CatalogToonStatueItem.py b/toontown/src/catalog/CatalogToonStatueItem.py
new file mode 100644
index 0000000..e68dc1a
--- /dev/null
+++ b/toontown/src/catalog/CatalogToonStatueItem.py
@@ -0,0 +1,93 @@
+import CatalogGardenItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from direct.interval.IntervalGlobal import *
+from toontown.estate import GardenGlobals
+
+class CatalogToonStatueItem(CatalogGardenItem.CatalogGardenItem):
+ """
+ Represents a Toon Statue Garden Item on the Delivery List.
+ CatalogToonStatueItem is derived from CatalogGardenItem for the intention of
+ adding the functionality of customization. The user can cycle through a list
+ of toon statues and choose the statue pose he/she likes.
+
+ NOTE: While creating a CatalogToonStatueItem in CatalogGenerator.py ensure the following:
+ 1) 1st parameter is the poseIndex you want the list to start with
+ 2) Also endPoseIndex parameters while creating CatalogToonStatueItem
+ All poses between the 1st parameter (gardenIndex) and endPoseIndex will be included
+ in the list of poses to choose from.
+ 3) 1st parameter (gardenIndex) should be <= endPoseIndex
+ 4) While adding more toon statue poses, make sure the specialsIndex of the poses
+ are in numeric succession
+ """
+
+ pictureToonStatue = None
+
+ def makeNewItem(self, itemIndex = 105, count = 1, tagCode = 1, endPoseIndex = 108):
+ # The itemIndex has to be less than or equal to the endPoseIndex
+ assert (itemIndex <= endPoseIndex)
+ self.startPoseIndex = itemIndex
+ self.endPoseIndex = endPoseIndex
+ CatalogGardenItem.CatalogGardenItem.makeNewItem(self, itemIndex, count, tagCode)
+
+ def needsCustomize(self):
+ # Returns true if endPoseIndex - startPoseIndex is more 0, this means there are more than 1 choice
+ return ((self.endPoseIndex - self.startPoseIndex) > 0)
+
+ def getPicture(self, avatar):
+ # Handle the ToonStatues separately because we have to load a toon statue of the
+ # local avatar with a predefined pose instead of a predefined static model
+
+ # Don't import this at the top of the file, since this code must run on the AI.
+ from toontown.estate import DistributedToonStatuary
+ toonStatuary = DistributedToonStatuary.DistributedToonStatuary(None)
+ toonStatuary.setupStoneToon(base.localAvatar.style)
+ toonStatuary.poseToonFromSpecialsIndex(self.gardenIndex)
+
+ # Toon with the pedestal looks too small in the catalog, so I'm removing the pedestal.
+ # Bring the toon up to 0 height, because we do a toon.setZ(70) in DistributedToonStatuary.py
+ toonStatuary.toon.setZ(0)
+ model, ival = self.makeFrameModel(toonStatuary.toon, 1)
+
+ self.pictureToonStatue = toonStatuary
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+ return (model, ival)
+
+ def cleanupPicture(self):
+ assert self.pictureToonStatue
+ self.pictureToonStatue.deleteToon()
+ self.pictureToonStatue = None
+ CatalogGardenItem.CatalogGardenItem.cleanupPicture(self)
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogGardenItem.CatalogGardenItem.decodeDatagram(self, di, versionNumber, store)
+ self.startPoseIndex = di.getUint8()
+ self.endPoseIndex = di.getUint8()
+
+ def encodeDatagram(self, dg, store):
+ CatalogGardenItem.CatalogGardenItem.encodeDatagram(self, dg, store)
+ dg.addUint8(self.startPoseIndex)
+ dg.addUint8(self.endPoseIndex)
+
+ def compareTo(self, other):
+ if (self.gardenIndex >= self.startPoseIndex) and (self.gardenIndex <= self.endPoseIndex):
+ return 0
+ return 1
+
+ def getAllToonStatues(self):
+ # This function returns a list of all possible toon statues
+ self.statueList = []
+ for index in range(self.startPoseIndex, self.endPoseIndex + 1):
+ self.statueList.append(CatalogToonStatueItem(index, 1, endPoseIndex = index))
+ return self.statueList
+
+ def deleteAllToonStatues(self):
+ # This function deletes all the toon statues in the list
+ while len(self.statueList):
+ item = self.statueList[0]
+ if item.pictureToonStatue:
+ item.pictureToonStatue.deleteToon()
+ self.statueList.remove(item)
\ No newline at end of file
diff --git a/toontown/src/catalog/CatalogWainscotingItem.py b/toontown/src/catalog/CatalogWainscotingItem.py
new file mode 100644
index 0000000..cda71f8
--- /dev/null
+++ b/toontown/src/catalog/CatalogWainscotingItem.py
@@ -0,0 +1,193 @@
+from CatalogSurfaceItem import *
+
+# Indicies into Wainscoting Textures Dictionary
+WSTTextureName = 0
+WSTColor = 1
+WSTBasePrice = 2
+
+# These index numbers are written to the database. Don't mess with them.
+# Also see TTLocalizer.WainscotingNames.
+WainscotingTypes = {
+ # Plain
+ 1000 : ("phase_3.5/maps/wall_paper_b3.jpg", CTFlatColorDark, 200),
+ # Wood version
+ 1010 : ("phase_5.5/maps/wall_paper_b4_greyscale.jpg",
+ CTBasicWoodColorOnWhite, 200),
+ # Wood version - series 2
+ 1020 : ("phase_5.5/maps/wainscotings_neutral.jpg", CTBasicWoodColorOnWhite, 200),
+ # Painted, valentines
+ 1030 : ("phase_3.5/maps/wall_paper_b3.jpg", CTValentinesColors, 200),
+ # Painted, underwater colors
+ 1040 : ("phase_3.5/maps/wall_paper_b3.jpg", CTUnderwaterColors, 200),
+ }
+
+class CatalogWainscotingItem(CatalogSurfaceItem):
+ """CatalogWainscotingItem
+
+ This represents a texture/color combination for wainscoting.
+
+ """
+
+ def makeNewItem(self, patternIndex, colorIndex):
+ self.patternIndex = patternIndex
+ self.colorIndex = colorIndex
+ CatalogSurfaceItem.makeNewItem(self)
+
+ def getTypeName(self):
+ # e.g. "wallpaper", "wainscoting", etc.
+ return TTLocalizer.SurfaceNames[STWainscoting]
+
+ def getName(self):
+ name = TTLocalizer.WainscotingNames.get(self.patternIndex)
+ if name:
+ return name
+ return self.getTypeName()
+
+ def getSurfaceType(self):
+ # Returns a value reflecting the type of surface this
+ # pattern is intended to be applied to.
+ return STWainscoting
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+ frame = self.makeFrame()
+
+ sample = loader.loadModel('phase_5.5/models/estate/wallpaper_sample')
+ a = sample.find('**/a')
+ b = sample.find('**/b')
+ c = sample.find('**/c')
+
+ # Wainscoting gets applied to the bottom 1/3, with the top
+ # 2/3 hidden.
+ a.hide()
+ b.hide()
+ c.setTexture(self.loadTexture(), 1)
+ c.setColorScale(*self.getColor())
+
+ sample.reparentTo(frame)
+
+## assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (frame, None)
+
+ def output(self, store = ~0):
+ return "CatalogWainscotingItem(%s, %s%s)" % (
+ self.patternIndex, self.colorIndex,
+ self.formatOptionalData(store))
+
+ def getFilename(self):
+ return WainscotingTypes[self.patternIndex][WSTTextureName]
+
+ def compareTo(self, other):
+ if self.patternIndex != other.patternIndex:
+ return self.patternIndex - other.patternIndex
+ return self.colorIndex - other.colorIndex
+
+ def getHashContents(self):
+ return (self.patternIndex, self.colorIndex)
+
+ def getBasePrice(self):
+ return WainscotingTypes[self.patternIndex][WSTBasePrice]
+
+ def loadTexture(self):
+ from pandac.PandaModules import Texture
+ filename = WainscotingTypes[self.patternIndex][WSTTextureName]
+ texture = loader.loadTexture(filename)
+ texture.setMinfilter(Texture.FTLinearMipmapLinear)
+ texture.setMagfilter(Texture.FTLinear)
+ return texture
+
+ def getColor(self):
+ if self.colorIndex == None:
+ # If no color index is set yet, use first color in color list
+ colorIndex = 0
+ else:
+ colorIndex = self.colorIndex
+ colors = WainscotingTypes[self.patternIndex][WSTColor]
+ if colors:
+ if colorIndex < len(colors):
+ return colors[colorIndex]
+ else:
+ print "Warning: colorIndex not in colors. Returning white."
+ return CT_WHITE
+ else:
+ return CT_WHITE
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogAtticItem.CatalogAtticItem.decodeDatagram(self, di, versionNumber, store)
+ if versionNumber < 3:
+ self.patternIndex = di.getUint8()
+ else:
+ self.patternIndex = di.getUint16()
+ self.colorIndex = di.getUint8()
+
+ # The following will generate an exception if
+ # self.patternIndex is invalid. The other fields can take
+ # care of themselves.
+ wtype = WainscotingTypes[self.patternIndex]
+
+ def encodeDatagram(self, dg, store):
+ CatalogAtticItem.CatalogAtticItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.patternIndex)
+ dg.addUint8(self.colorIndex)
+
+def getWainscotings(*indexList):
+ # This function returns a list of CatalogWainscotingItems
+ # The returned items will all need to be customized (i.e
+ # have a color chosen by the user. Until customization,
+ # use a default color index of 0 (if the pattern has a color
+ # list) or CT_WHITE if the pattern has no color list
+ list = []
+ for index in indexList:
+ list.append(CatalogWainscotingItem(index))
+ return list
+
+
+def getAllWainscotings(*indexList):
+ # This function returns a list of all possible
+ # CatalogWainscotingItems (that is, all color variants) for the
+ # indicated type index(es).
+ list = []
+ for index in indexList:
+ colors = WainscotingTypes[index][WSTColor]
+ if colors:
+ for n in range(len(colors)):
+ list.append(CatalogWainscotingItem(index, n))
+ else:
+ list.append(CatalogWainscotingItem(index, 0))
+ return list
+
+
+def getWainscotingRange(fromIndex, toIndex, *otherRanges):
+ # This function returns a list of all possible
+ # CatalogWainscotingItems (that is, all color variants) for the
+ # indicated type index(es).
+
+ # Make sure we got an even number of otherRanges
+ assert(len(otherRanges)%2 == 0)
+
+ list = []
+
+ froms = [fromIndex,]
+ tos = [toIndex,]
+
+ i = 0
+ while i < len(otherRanges):
+ froms.append(otherRanges[i])
+ tos.append(otherRanges[i+1])
+ i += 2
+
+ for patternIndex in WainscotingTypes.keys():
+ for fromIndex, toIndex in zip(froms,tos):
+ if patternIndex >= fromIndex and patternIndex <= toIndex:
+ colors = WainscotingTypes[patternIndex][WSTColor]
+ if colors:
+ for n in range(len(colors)):
+ list.append(CatalogWainscotingItem(patternIndex, n))
+ else:
+ list.append(CatalogWainscotingItem(patternIndex, 0))
+ return list
diff --git a/toontown/src/catalog/CatalogWallpaperItem.py b/toontown/src/catalog/CatalogWallpaperItem.py
new file mode 100644
index 0000000..7b8cb11
--- /dev/null
+++ b/toontown/src/catalog/CatalogWallpaperItem.py
@@ -0,0 +1,519 @@
+from CatalogSurfaceItem import *
+
+## TODO:
+# Get rid of XXX2 wallpaper pattens
+# Initialize borderIndex and borderColorIndex
+
+# Indicies into Wallpaper Textures Dictionary
+WTTextureName = 0
+WTColor = 1
+WTBorderList = 2
+WTBasePrice = 3
+
+BDTextureName = 0
+BDColor = 1
+
+All = (1000,1010,1020,1030,1040,1050,1060,1070)
+
+
+# These index numbers are written to the database. Don't mess with them.
+# Also see TTLocalizer.WallpaperNames.
+# Each is a list of (model, colorlist, border list, price)
+# 0 in border list means no border
+WallpaperTypes = {
+ # Parchment
+ 1000 : ("phase_5.5/maps/flat_wallpaper1.jpg", CTFlatColor, (0,1000,), 180),
+ # Milan
+ 1100 : ("phase_5.5/maps/big_stripes1.jpg", CTWhite, (0,1010,), 180),
+ 1110 : ("phase_5.5/maps/big_stripes2.jpg", CTWhite, (0,1040,), 180),
+ 1120 : ("phase_5.5/maps/big_stripes3.jpg", CTWhite, (0,1030,), 180),
+ 1130 : ("phase_5.5/maps/big_stripes4.jpg", CTWhite, (0,1010,), 180),
+ 1140 : ("phase_5.5/maps/big_stripes5.jpg", CTWhite, (0,1020,), 180),
+ 1150 : ("phase_5.5/maps/big_stripes6.jpg", CTWhite, (0,1020,), 180),
+ # Dover
+ 1200 : ("phase_5.5/maps/stripeB1.jpg", CTWhite, (0,1000,), 180),
+ 1210 : ("phase_5.5/maps/stripeB2.jpg", CTWhite, (0,1000,), 180),
+ 1220 : ("phase_5.5/maps/stripeB3.jpg", CTWhite, (0,1000,), 180),
+ 1230 : ("phase_5.5/maps/stripeB4.jpg", CTWhite, (0,1000,), 180),
+ # stripeB5 ends up in phase3.5 because it is placed on some of the
+ # toon_interior walls.
+ 1240 : ("phase_3.5/maps/stripeB5.jpg", CTWhite, (0,1000,), 180),
+ 1250 : ("phase_5.5/maps/stripeB6.jpg", CTWhite, (0,1000,), 180),
+ 1260 : ("phase_5.5/maps/stripeB7.jpg", CTWhite, (0,1000,), 180),
+ # Victoria
+ 1300 : ("phase_5.5/maps/squiggle1.jpg", CTWhite, (0,), 180),
+ 1310 : ("phase_5.5/maps/squiggle2.jpg", CTWhite, (0,), 180),
+ 1320 : ("phase_5.5/maps/squiggle3.jpg", CTWhite, (0,), 180),
+ 1330 : ("phase_5.5/maps/squiggle4.jpg", CTWhite, (0,), 180),
+ 1340 : ("phase_5.5/maps/squiggle5.jpg", CTWhite, (0,), 180),
+ 1350 : ("phase_5.5/maps/squiggle6.jpg", CTWhite, (0,), 180),
+ # Newport
+ 1400 : ("phase_5.5/maps/stripes_cyan.jpg", CTWhite, (0,1000,), 180),
+ 1410 : ("phase_5.5/maps/stripes_green.jpg", CTWhite, (0,1000,), 180),
+ 1420 : ("phase_5.5/maps/stripes_magenta.jpg", CTWhite, (0,1000,), 180),
+ 1430 : ("phase_5.5/maps/two_stripes1.jpg", CTWhite, (0,1000,), 180),
+ 1440 : ("phase_5.5/maps/two_stripes2.jpg", CTWhite, (0,1000,), 180),
+ 1450 : ("phase_5.5/maps/two_stripes3.jpg", CTWhite, (0,1000,), 180),
+ # Pastoral
+ 1500 : ("phase_5.5/maps/leaves1.jpg", CTWhite, (0,), 180),
+ 1510 : ("phase_5.5/maps/leaves2.jpg", CTWhite, (0,), 180),
+ 1520 : ("phase_5.5/maps/leaves3.jpg", CTWhite, (0,), 180),
+ # Harlequin
+ 1600 : ("phase_5.5/maps/diamonds2_cherries.jpg", CTWhite, (0,1000,), 180),
+ 1610 : ("phase_5.5/maps/diamonds3_cherries.jpg", CTWhite, (0,1000,), 180),
+ 1620 : ("phase_5.5/maps/diamonds3_cherry.jpg", CTWhite, (0,1000,), 180),
+ 1630 : ("phase_5.5/maps/diamonds4_cherries.jpg", CTWhite, (0,1000,), 180),
+ 1640 : ("phase_5.5/maps/diamonds4_cherry.jpg", CTWhite, (0,1000,), 180),
+ 1650 : ("phase_5.5/maps/diamonds5_cherries.jpg", CTWhite, (0,1000,), 180),
+ 1660 : ("phase_5.5/maps/diamonds6_cherry.jpg", CTWhite, (0,1000,), 180),
+ # Moon
+ 1700 : ("phase_5.5/maps/moon1.jpg", CTWhite, (0,), 180),
+ 1710 : ("phase_5.5/maps/moon2.jpg", CTWhite, (0,), 180),
+ 1720 : ("phase_5.5/maps/moon3.jpg", CTWhite, (0,), 180),
+ 1730 : ("phase_5.5/maps/moon4.jpg", CTWhite, (0,), 180),
+ 1740 : ("phase_5.5/maps/moon5.jpg", CTWhite, (0,), 180),
+ 1750 : ("phase_5.5/maps/moon6.jpg", CTWhite, (0,), 180),
+ 1760 : ("phase_5.5/maps/moon7.jpg", CTWhite, (0,), 180),
+ # Stars
+ 1800 : ("phase_5.5/maps/stars1.jpg", CTWhite, (0,), 180),
+ 1810 : ("phase_5.5/maps/stars2.jpg", (CT_BLUE2, CT_PINK2, CT_RED), (0,), 180),
+ 1820 : ("phase_5.5/maps/stars3.jpg", (CT_BLUE2, CT_PINK2, CT_RED, CT_WHITE), (0,), 180),
+ 1830 : ("phase_5.5/maps/stars4.jpg", CTWhite, (0,), 180),
+ 1840 : ("phase_5.5/maps/stars5.jpg", CTWhite, (0,), 180),
+ 1850 : ("phase_5.5/maps/stars6.jpg", CTWhite, (0,), 180),
+ 1860 : ("phase_5.5/maps/stars7.jpg", (CT_BEIGE2, CT_WHITE), (0,), 180),
+ # Flowers
+ 1900 : ("phase_5.5/maps/wall_paper_flower1.jpg", CTWhite, (0,1000), 180),
+ 1910 : ("phase_5.5/maps/wall_paper_flower2.jpg", CTWhite, (0,1000), 180),
+ 1920 : ("phase_5.5/maps/wall_paper_flower3.jpg", CTWhite, (0,1000), 180),
+ 1930 : ("phase_5.5/maps/wall_paper_flower4.jpg", CTWhite, (0,1000), 180),
+ 1940 : ("phase_5.5/maps/wall_paper_flower5.jpg", CTWhite, (0,1000), 180),
+ 1950 : ("phase_5.5/maps/wall_paper_flower6.jpg", CTWhite, (0,1000), 180),
+ # Spring Garden
+ 2000 : ("phase_5.5/maps/flat_wallpaper1.jpg", (CT_BEIGE, CT_BEIGE2, CT_RED), (1050,), 180),
+ 2010 : ("phase_5.5/maps/flat_wallpaper1.jpg", (CT_BLUE2, CT_PINK2), (1060,), 180),
+ 2020 : ("phase_5.5/maps/flat_wallpaper1.jpg", (CT_BEIGE2, CT_BLUE2, CT_PINK2, CT_BEIGE, CT_RED), (1070,), 180),
+ # Formal Garden
+ 2100 : ("phase_5.5/maps/big_stripes1.jpg", CTWhite, (1050,), 180),
+ 2110 : ("phase_5.5/maps/big_stripes2.jpg", CTWhite, (1050,), 180),
+ 2120 : ("phase_5.5/maps/big_stripes3.jpg", CTWhite, (1060,), 180),
+ 2130 : ("phase_5.5/maps/big_stripes3.jpg", CTWhite, (1070,), 180),
+ 2140 : ("phase_5.5/maps/big_stripes6.jpg", CTWhite, (1070,), 180),
+
+ # Race Day
+ 2200 : ("phase_5.5/maps/wall_paper_car.jpg", CTWhite, (0,1000), 180,),
+ 2210 : ("phase_5.5/maps/wall_paper_car_neutral.jpg", CTFlatColor,
+ (0,1000), 180,),
+
+ # Touchdown
+ 2300 : ("phase_5.5/maps/wall_paper_football_neutral.jpg",
+ CTFlatColor, (0,1080), 180,),
+
+ # Cloud 9
+ 2400 : ("phase_5.5/maps/wall_paper_clouds.jpg", CTWhite, (0,1000), 180,),
+
+ # Climbing Vine
+ 2500 : ("phase_5.5/maps/wall_paper_vine_neutral.jpg",
+ CTFlatColorAll, (0,1090), 180,),
+
+ # Springtime
+ 2600 : ("phase_5.5/maps/basket.jpg", CTWhite, (0,1000), 180,),
+ 2610 : ("phase_5.5/maps/basket_neutral.jpg", CTFlatColor, (0, 1000), 180,),
+
+ # Kokeshi
+ 2700 : ("phase_5.5/maps/doll.jpg", CTWhite, (0,1000,1110), 180,),
+ 2710 : ("phase_5.5/maps/doll_neutral.jpg",CTFlatColor,(0,1100,1110),180,),
+
+ # Posies
+ 2800 : ("phase_5.5/maps/littleFlowers.jpg", CTWhite, (0,1000), 180,),
+ 2810 : ("phase_5.5/maps/littleFlowers_neutral.jpg",CTFlatColor,(0,1000),180,),
+
+ # Underwater
+ # Angel Fish
+ 2900 : ("phase_5.5/maps/UWwallPaperAngelFish.jpg", CTWhite, (0,1120,1160), 180,),
+ 2910 : ("phase_5.5/maps/UWwallPaperAngelFishColor.jpg", CTWhite, (0,1120,1160), 180,),
+
+ # keep here temporarily so DB doesn't freak out. These keys were originally offered
+ # on the test server, but it turns out this indexing scheme doesn't work right, so
+ # they weren't offered on live. If we get rid of these indices (2920-2980) we shoul
+ # patch the DB on TEST
+ 2920 : ("phase_5.5/maps/UWwallPaperBubbles.jpg", CTWhite, (0,1120,1160), 180,),
+ 2930 : ("phase_5.5/maps/UWwallPaperBubbles2.jpg", CTWhite, (0,1120,1160), 180,),
+ 2940 : ("phase_5.5/maps/UWwallPaperGreenFish.jpg", CTWhite, (0,1120,1160), 180,),
+ 2950 : ("phase_5.5/maps/UWwallPaperRedFish.jpg", CTWhite, (0,1120,1160), 180,),
+ 2960 : ("phase_5.5/maps/UWwallPaperSea_horse.jpg", CTWhite, (0,1120,1160), 180,),
+ 2970 : ("phase_5.5/maps/UWwallPaperShells.jpg", CTWhite, (0,1140,1150), 180),
+ 2980 : ("phase_5.5/maps/UWwaterFloor1.jpg", (CT_WHITE, CT_PALE_GREEN, CT_LIGHT_BLUE), (0,), 180),
+
+ # Bubbles
+ 3000 : ("phase_5.5/maps/UWwallPaperBubbles.jpg", CTWhite, (0,1120,1160), 180,),
+ 3100 : ("phase_5.5/maps/UWwallPaperBubbles2.jpg", CTWhite, (0,1120,1160), 180,),
+
+ # Fish
+ 3200 : ("phase_5.5/maps/UWwallPaperGreenFish.jpg", CTWhite, (0,1120,1160), 180,),
+ 3300 : ("phase_5.5/maps/UWwallPaperRedFish.jpg", CTWhite, (0,1120,1160), 180,),
+ 3400 : ("phase_5.5/maps/UWwallPaperSea_horse.jpg", CTWhite, (0,1120,1160), 180,),
+
+ # Shells
+ 3500 : ("phase_5.5/maps/UWwallPaperShells.jpg", (CT_WHITE, CT_SEA_GREEN, CT_LIGHT_BLUE), (0,1140,1150), 180),
+
+ # Water
+ 3600 : ("phase_5.5/maps/UWwaterFloor1.jpg", (CT_WHITE, CT_PALE_GREEN, CT_LIGHT_BLUE), (0,), 180),
+
+ # Western
+ 3700 : ("phase_5.5/maps/WesternBootWallpaper1.jpg", CTWhite, (0,1170,1180), 180,),
+ 3800 : ("phase_5.5/maps/WesternCactusWallpaper1.jpg", CTWhite, (0,1170,1180), 180,),
+ 3900 : ("phase_5.5/maps/WesternHatWallpaper1.jpg", CTWhite, (0,1170,1180), 180,),
+
+ # Holiday themed wallpapers
+
+ # Halloween
+ 10100 : ("phase_5.5/maps/cats1.jpg", CTWhite, (0, 10010, 10020), 400),
+ 10200 : ("phase_5.5/maps/bats2.jpg", CTWhite, (0, 10010, 10020), 400),
+
+ # Christmas
+ # Snowflake
+ 11000 : ("phase_5.5/maps/wall_paper_snowflakes.jpg", CTWhite,
+ (0,11000,11010), 400,),
+ # Hollyleaf
+ 11100 : ("phase_5.5/maps/wall_paper_hollyleaf.jpg", CTWhite,
+ (0,11000,11010), 400,),
+ # Snowman
+ 11200 : ("phase_5.5/maps/wall_paper_snowman.jpg", CTWhite,
+ (0,11000,11010), 400,),
+
+ # Valentines
+ # add valentines here
+ #
+ 12000 : ("phase_5.5/maps/VdayWall1.jpg", CTWhite,
+ (0,12000,12010,12020), 400,),
+ #
+ 12100 : ("phase_5.5/maps/VdayWall2.jpg", CTWhite,
+ (0,12000,12010,12020), 400,),
+ #
+ 12200 : ("phase_5.5/maps/VdayWall3.jpg", CTWhite,
+ (0,12000,12010,12020), 400,),
+ #
+ 12300 : ("phase_5.5/maps/VdayWall4.jpg", CTWhite,
+ (0,12000,12010,12020), 400,),
+
+ # St. Patrick's day
+ #
+ 13000 : ("phase_5.5/maps/StPatWallpaper1.jpg", CTWhite, (0,13000), 400),
+ #
+ 13100 : ("phase_5.5/maps/StPatWallpaper2.jpg", CTWhite, (0,13000), 400),
+ #
+ 13200 : ("phase_5.5/maps/StPatWallpaper3.jpg", CTWhite, (0,13000), 400),
+ #
+ 13300 : ("phase_5.5/maps/StPatWallpaper4.jpg", CTWhite, (0,13000), 400),
+ }
+
+WallpaperGroups = {
+ 1100 : (1100, 1110, 1120, 1130, 1140, 1150,),
+ 1200 : (1200, 1210, 1220, 1230, 1240, 1250, 1260,),
+ 1300 : (1300, 1310, 1320, 1330, 1340, 1350,),
+ 1400 : (1400, 1410, 1420, 1430, 1440, 1450,),
+ 1500 : (1500, 1510, 1520,),
+ 1600 : (1600, 1610, 1620, 1630, 1640, 1650, 1660,),
+ 1700 : (1700, 1710, 1720, 1730, 1740, 1750, 1760,),
+ 1800 : (1800, 1810, 1820, 1830, 1840, 1850, 1860,),
+ 1900 : (1900, 1910, 1920, 1930, 1940, 1950,),
+ 2000 : (2000, 2010, 2020,),
+ 2100 : (2100, 2110, 2120, 2130, 2140,),
+ 2200 : (2200, 2210,),
+ 2600 : (2600, 2610,),
+ 2700 : (2700, 2710,),
+ 2800 : (2800, 2810,),
+ 2900 : (2900, 2910,),
+ }
+
+
+# Possible border types
+BorderTypes = {
+ # Index of 0 means no border
+ 1000 : ("phase_5.5/maps/bd_grey_border1.jpg", CTFlatColorDark),
+ 1010 : ("phase_5.5/maps/diamonds_border2.jpg", CTWhite),
+ 1020 : ("phase_5.5/maps/diamonds_border2ch.jpg", CTWhite),
+ 1030 : ("phase_5.5/maps/diamonds_border3ch.jpg", CTWhite),
+ 1040 : ("phase_5.5/maps/diamonds_border4ch.jpg", CTWhite),
+ 1050 : ("phase_5.5/maps/flower_border2.jpg", CTWhite),
+ 1060 : ("phase_5.5/maps/flower_border5.jpg", CTWhite),
+ 1070 : ("phase_5.5/maps/flower_border6.jpg", CTWhite),
+ 1080 : ("phase_5.5/maps/football_border_neutral.jpg", CTFlatColorDark),
+ 1090 : ("phase_5.5/maps/vine_border1.jpg", CTFlatColorDark),
+ 1100 : ("phase_5.5/maps/doll_board.jpg", CTWhite),
+ 1110 : ("phase_5.5/maps/doll_board_neutral.jpg", CTFlatColorDark),
+ # Underwater
+ 1120 : ("phase_5.5/maps/UWwallPaperPlantBorder.jpg", CTWhite),
+ 1130 : ("phase_5.5/maps/UWwallPaperSea_horseBorder.jpg", CTWhite),
+ 1140 : ("phase_5.5/maps/UWwallPaperShellBorder1.jpg", CTWhite),
+ 1150 : ("phase_5.5/maps/UWwallPaperShellBorder2.jpg", CTWhite),
+ 1160 : ("phase_5.5/maps/UWwallPaperWaveBorder.jpg", CTWhite),
+ # Western
+ 1170 : ("phase_5.5/maps/WesternSkullBorder.jpg", CTWhite),
+ 1180 : ("phase_5.5/maps/WesternStarBorder.jpg", CTWhite),
+
+ # Holiday themed borders
+
+ # Halloween
+ 10010 : ("phase_5.5/maps/border_ScarryMoon1.jpg", CTWhite),
+ 10020 : ("phase_5.5/maps/border_candy1.jpg", CTWhite),
+
+ # Christmas
+ 11000 : ("phase_5.5/maps/flakes_border.jpg", CTWhite),
+ 11010 : ("phase_5.5/maps/hollyleaf_border.jpg", CTWhite),
+
+ # Valentines
+ 12000 : ("phase_5.5/maps/Vborder1a.jpg", CTWhite),
+ 12010 : ("phase_5.5/maps/Vborder1b.jpg", CTWhite),
+ 12020 : ("phase_5.5/maps/Vborder2b.jpg", CTWhite),
+
+ # St Patrick's
+ 13000 : ("phase_5.5/maps/StPatBorder1.jpg", CTWhite),
+ }
+
+class CatalogWallpaperItem(CatalogSurfaceItem):
+ """
+ This represents a texture/color combination for walls and trim.
+ It includes wallpaper as well as moulding, wainscoting, and
+ flooring.
+ """
+
+ def makeNewItem(self, patternIndex, colorIndex = None,
+ # Default to no border
+ borderIndex = 0, borderColorIndex = 0):
+ self.patternIndex = patternIndex
+ self.colorIndex = colorIndex
+ # Index of 0 means no border
+ self.borderIndex = borderIndex
+ self.borderColorIndex = borderColorIndex
+ CatalogSurfaceItem.makeNewItem(self)
+
+ def needsCustomize(self):
+ # Returns true if the item still needs to be customized by the
+ # user (e.g. by choosing a color).
+ return (self.colorIndex == None) or (self.borderIndex == None)
+
+ def getTypeName(self):
+ # e.g. "wallpaper", "wainscoting", etc.
+ return TTLocalizer.SurfaceNames[STWallpaper]
+
+ def getName(self):
+ name = TTLocalizer.WallpaperNames.get(self.patternIndex)
+ if name == None:
+ century = self.patternIndex - (self.patternIndex % 100)
+ name = TTLocalizer.WallpaperNames.get(century)
+ if name:
+ return name
+ return self.getTypeName()
+
+ def getSurfaceType(self):
+ # Returns a value reflecting the type of surface this
+ # pattern is intended to be applied to.
+ return STWallpaper
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+ frame = self.makeFrame()
+
+ sample = loader.loadModel('phase_5.5/models/estate/wallpaper_sample')
+ a = sample.find('**/a')
+ b = sample.find('**/b')
+ c = sample.find('**/c')
+
+ # Wallpaper gets applied to the top 2/3, with the border
+ # on the bottom 1/3.
+ a.setTexture(self.loadTexture(), 1)
+ a.setColorScale(*self.getColor())
+ b.setTexture(self.loadTexture(), 1)
+ b.setColorScale(*self.getColor())
+ c.setTexture(self.loadBorderTexture(), 1)
+ c.setColorScale(*self.getBorderColor())
+
+ sample.reparentTo(frame)
+## assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (frame, None)
+
+ def output(self, store = ~0):
+ return "CatalogWallpaperItem(%s, %s, %s, %s%s)" % (
+ self.patternIndex, self.colorIndex,
+ self.borderIndex, self.borderColorIndex,
+ self.formatOptionalData(store))
+
+ def getFilename(self):
+ return WallpaperTypes[self.patternIndex][WTTextureName]
+
+ def compareTo(self, other):
+ if self.patternIndex != other.patternIndex:
+ century = self.patternIndex - (self.patternIndex % 100)
+ otherCentury = other.patternIndex - (other.patternIndex % 100)
+ return century - otherCentury
+ return 0
+
+ def getHashContents(self):
+ century = self.patternIndex - (self.patternIndex % 100)
+ return century
+
+ def getBasePrice(self):
+ return WallpaperTypes[self.patternIndex][WTBasePrice]
+
+ def loadTexture(self):
+ from pandac.PandaModules import Texture
+ filename = WallpaperTypes[self.patternIndex][WTTextureName]
+ texture = loader.loadTexture(filename)
+ texture.setMinfilter(Texture.FTLinearMipmapLinear)
+ texture.setMagfilter(Texture.FTLinear)
+ return texture
+
+ def getColor(self):
+ if self.colorIndex == None:
+ # If no color index is set yet, use first color in color list
+ colorIndex = 0
+ else:
+ colorIndex = self.colorIndex
+ colors = WallpaperTypes[self.patternIndex][WTColor]
+ if colorIndex < len(colors):
+ return colors[colorIndex]
+ else:
+ print "Warning: colorIndex > len(colors). Returning white."
+ return CT_WHITE
+
+ def loadBorderTexture(self):
+ from pandac.PandaModules import Texture
+ if ((self.borderIndex == None) or (self.borderIndex == 0)):
+ # Border hasn't been picked or no border specified
+ return self.loadTexture()
+ borderInfo = BorderTypes[self.borderIndex]
+ filename = borderInfo[BDTextureName]
+ texture = loader.loadTexture(filename)
+ texture.setMinfilter(Texture.FTLinearMipmapLinear)
+ texture.setMagfilter(Texture.FTLinear)
+ return texture
+
+ def getBorderColor(self):
+ if ((self.borderIndex == None) or (self.borderIndex == 0)):
+ return self.getColor()
+ else:
+ colors = BorderTypes[self.borderIndex][BDColor]
+ # Get specified color or return CT_WHITE as default
+ if self.borderColorIndex < len(colors):
+ return colors[self.borderColorIndex]
+ else:
+ return CT_WHITE
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogAtticItem.CatalogAtticItem.decodeDatagram(self, di, versionNumber, store)
+ # Set some default values
+ self.colorIndex = None
+ if (store & CatalogItem.Customization):
+ self.borderIndex = 0
+ else:
+ self.borderIndex = None
+ self.borderColorIndex = 0
+ # Update as needed
+ if versionNumber < 3:
+ self.patternIndex = di.getUint8()
+ self.colorIndex = di.getUint8()
+ elif versionNumber == 3:
+ self.patternIndex = di.getUint16()
+ self.colorIndex = di.getUint8()
+ else:
+ self.patternIndex = di.getUint16()
+ if store & CatalogItem.Customization:
+ self.colorIndex = di.getUint8()
+ self.borderIndex = di.getUint16()
+ self.borderColorIndex = di.getUint8()
+
+ # The following will generate an exception if
+ # self.patternIndex is invalid. The other fields can take
+ # care of themselves.
+ wtype = WallpaperTypes[self.patternIndex]
+
+
+ def encodeDatagram(self, dg, store):
+ CatalogAtticItem.CatalogAtticItem.encodeDatagram(self, dg, store)
+ dg.addUint16(self.patternIndex)
+ if (store & CatalogItem.Customization):
+ dg.addUint8(self.colorIndex)
+ dg.addUint16(self.borderIndex)
+ dg.addUint8(self.borderColorIndex)
+
+def getWallpapers(*typeList):
+ # This function returns a list of CatalogWallpaperItems
+ # The returned items will all need to be customized (i.e
+ # have a color chosen by the user. Until customization,
+ # use a default color index of 0 (if the pattern has a color
+ # list) or CT_WHITE if the pattern has no color list
+ list = []
+ for type in typeList:
+ list.append(CatalogWallpaperItem(type))
+ return list
+
+
+def getAllWallpapers(*typeList):
+ # This function returns a list of all possible
+ # CatalogWallpaperItems (that is, all color variants) for the
+ # indicated type index(es).
+ # If the specified type index is in the group dictionary
+ # get all corresponding patterns from the wallpaperTypes dictionary
+ # This returns an item that has already been customized
+ list = []
+ for type in typeList:
+ # If its a group, return a list of all associated textures,
+ # otherwise, return a simple list of the type itself
+ group = WallpaperGroups.get(type, [type])
+ for index in group:
+ borderKeys = WallpaperTypes[index][WTBorderList]
+ for borderKey in borderKeys:
+ borderData = BorderTypes.get(borderKey)
+ if borderData:
+ numBorderColors = len(borderData[BDColor])
+ else:
+ numBorderColors = 1
+ for borderColorIndex in range(numBorderColors):
+ colors = WallpaperTypes[index][WTColor]
+ for n in range(len(colors)):
+ list.append(CatalogWallpaperItem(
+ index, n, borderKey, borderColorIndex))
+ return list
+
+def getWallpaperRange(fromIndex, toIndex, *otherRanges):
+ # This function returns a list of all possible
+ # CatalogWallpaperItems (that is, all color variants) for the
+ # indicated type index(es).
+
+ # Make sure we got an even number of otherRanges
+ assert(len(otherRanges)%2 == 0)
+
+ list = []
+
+ froms = [fromIndex,]
+ tos = [toIndex,]
+
+ i = 0
+ while i < len(otherRanges):
+ froms.append(otherRanges[i])
+ tos.append(otherRanges[i+1])
+ i += 2
+
+ for patternIndex in WallpaperTypes.keys():
+ for fromIndex, toIndex in zip(froms,tos):
+ if patternIndex >= fromIndex and patternIndex <= toIndex:
+ borderKeys = WallpaperTypes[patternIndex][WTBorderList]
+ for borderKey in borderKeys:
+ borderData = BorderTypes.get(borderKey)
+ if borderData:
+ numBorderColors = len(borderData[BDColor])
+ else:
+ numBorderColors = 1
+ for borderColorIndex in range(numBorderColors):
+ colors = WallpaperTypes[patternIndex][WTColor]
+ for n in range(len(colors)):
+ list.append(CatalogWallpaperItem(
+ patternIndex, n, borderKey, borderColorIndex))
+ return list
diff --git a/toontown/src/catalog/CatalogWindowItem.py b/toontown/src/catalog/CatalogWindowItem.py
new file mode 100644
index 0000000..b31536a
--- /dev/null
+++ b/toontown/src/catalog/CatalogWindowItem.py
@@ -0,0 +1,172 @@
+from pandac.PandaModules import *
+import CatalogAtticItem
+import CatalogItem
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+
+
+WVTModelName = 0
+WVTBasePrice = 1
+WVTSkyName = 2
+
+# These index numbers are written to the database. Don't mess with them.
+# Also see TTLocalizer.WindowViewNames.
+
+# The third entry in the is the name of the backdrop node for a hack Drose
+# and I put in to fix some draw order problems in the render2d drawing
+# of the window views. If not None, we find the node and do some hacky
+# rendering/draworder/binning tricks to get it to draw properly behind
+# alpha'd items in front of it.
+
+WindowViewTypes = {
+ 10 : ("phase_5.5/models/estate/Garden1", 900, None),
+ 20 : ("phase_5.5/models/estate/GardenA", 900, None), # Initial View
+ 30 : ("phase_5.5/models/estate/GardenB", 900, None),
+ 40 : ("phase_5.5/models/estate/cityView", 900, None), # Catalog Series 1
+ 50 : ("phase_5.5/models/estate/westernView", 900, None), # Catalog Series 2
+ 60 : ("phase_5.5/models/estate/underwaterView", 900, None), # Catalog Series 2
+ 70 : ("phase_5.5/models/estate/tropicView", 900, None), # Catalog Series 1
+ 80 : ("phase_5.5/models/estate/spaceView", 900, None), # Catalog Series 2
+ 90 : ("phase_5.5/models/estate/PoolView", 900, None), # Catalog Series 3
+ 100 : ("phase_5.5/models/estate/SnowView", 900, None), # Catalog Series 3
+ 110 : ("phase_5.5/models/estate/FarmView", 900, None), # Catalog Series 3
+ 120 : ("phase_5.5/models/estate/IndianView", 900, None), # Catalog Series 4
+ 130 : ("phase_5.5/models/estate/WesternMainStreetView", 900, None), # Catalog Series 4
+ # Warning! 8-bit index number. 255 max value.
+ }
+
+class CatalogWindowItem(CatalogAtticItem.CatalogAtticItem):
+ """CatalogWindowItem
+
+ # This represents a view to hang outside a window in a house.
+
+ """
+
+ def makeNewItem(self, windowType, placement = None):
+ self.windowType = windowType
+ self.placement = placement
+ CatalogAtticItem.CatalogAtticItem.makeNewItem(self)
+
+ def saveHistory(self):
+ # Returns true if items of this type should be saved in the
+ # back catalog, false otherwise.
+ return 1
+
+ def getTypeName(self):
+ return TTLocalizer.WindowViewTypeName
+
+ def getName(self):
+ return TTLocalizer.WindowViewNames.get(self.windowType)
+
+ def recordPurchase(self, avatar, optional):
+ # Updates the appropriate field on the avatar to indicate the
+ # purchase (or delivery). This makes the item available to
+ # use by the avatar. This method is only called on the AI side.
+ self.giftTag = None
+ house, retcode = self.getHouseInfo(avatar)
+ if retcode >= 0:
+ house.addWindow(self)
+ return retcode
+
+ def getDeliveryTime(self):
+ # Returns the elapsed time in minutes from purchase to
+ # delivery for this particular item.
+ return 4 * 60 # 4 hours.
+
+ def getPicture(self, avatar):
+ # Returns a (DirectWidget, Interval) pair to draw and animate a
+ # little representation of the item, or (None, None) if the
+ # item has no representation. This method is only called on
+ # the client.
+ frame = self.makeFrame()
+ model = self.loadModel()
+
+ # This 3-d model will be drawn in the 2-d scene.
+ model.setDepthTest(1)
+ model.setDepthWrite(1)
+
+ # Set up clipping planes to cut off the parts of the view that
+ # would extend beyond the frame.
+ clipperLeft = PlaneNode('clipper')
+ clipperRight = PlaneNode('clipper')
+ clipperTop = PlaneNode('clipper')
+ clipperBottom = PlaneNode('clipper')
+ clipperLeft.setPlane(Plane(Vec3(1, 0, 0), Point3(-1, 0, 0)))
+ clipperRight.setPlane(Plane(Vec3(-1, 0, 0), Point3(1, 0, 0)))
+ clipperTop.setPlane(Plane(Vec3(0, 0, -1), Point3(0, 0, 1)))
+ clipperBottom.setPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, -1)))
+ model.setClipPlane(frame.attachNewNode(clipperLeft))
+ model.setClipPlane(frame.attachNewNode(clipperRight))
+ model.setClipPlane(frame.attachNewNode(clipperTop))
+ model.setClipPlane(frame.attachNewNode(clipperBottom))
+
+ # Fix draw order of background
+ bgName = WindowViewTypes[self.windowType][WVTSkyName]
+ if bgName:
+ bgNodePath = model.find("**/" + bgName)
+ if not bgNodePath.isEmpty():
+ # Put it at the front of the list to be drawn first
+ bgNodePath.reparentTo(model, -1)
+
+ # Get rid of the window frame that is in the model
+ windowFrame = model.find("**/frame")
+ if not windowFrame.isEmpty():
+ windowFrame.removeNode()
+
+ model.setPos(0,2,0)
+ model.setScale(0.4)
+ model.reparentTo(frame)
+
+ assert (not self.hasPicture)
+ self.hasPicture=True
+
+ return (frame, None)
+
+ def output(self, store = ~0):
+ return "CatalogWindowItem(%s%s)" % (
+ self.windowType,
+ self.formatOptionalData(store))
+
+ def getFilename(self):
+ type = WindowViewTypes[self.windowType]
+ return type[WVTModelName]
+
+ def formatOptionalData(self, store = ~0):
+ # This is used within output() to format optional data
+ # (according to the bits indicated in store).
+ result = CatalogAtticItem.CatalogAtticItem.formatOptionalData(self, store)
+ if (store & CatalogItem.WindowPlacement) and self.placement != None:
+ result += ", placement = %s" % (self.placement)
+ return result
+
+ def compareTo(self, other):
+ return self.windowType - other.windowType
+
+ def getHashContents(self):
+ return self.windowType
+
+ def getBasePrice(self):
+ return WindowViewTypes[self.windowType][WVTBasePrice]
+
+ def loadModel(self):
+ type = WindowViewTypes[self.windowType]
+ model = loader.loadModel(type[WVTModelName])
+
+ return model
+
+ def decodeDatagram(self, di, versionNumber, store):
+ CatalogAtticItem.CatalogAtticItem.decodeDatagram(self, di, versionNumber, store)
+ self.placement = None
+ if store & CatalogItem.WindowPlacement:
+ self.placement = di.getUint8()
+ self.windowType = di.getUint8()
+
+ # The following will generate an exception if
+ # self.windowType is invalid.
+ wvtype = WindowViewTypes[self.windowType]
+
+ def encodeDatagram(self, dg, store):
+ CatalogAtticItem.CatalogAtticItem.encodeDatagram(self, dg, store)
+ if store & CatalogItem.WindowPlacement:
+ dg.addUint8(self.placement)
+ dg.addUint8(self.windowType)
diff --git a/toontown/src/catalog/MailboxScreen.py b/toontown/src/catalog/MailboxScreen.py
new file mode 100644
index 0000000..e53819f
--- /dev/null
+++ b/toontown/src/catalog/MailboxScreen.py
@@ -0,0 +1,797 @@
+from direct.directnotify.DirectNotifyGlobal import *
+from direct.gui.DirectGui import *
+from direct.showbase import DirectObject, PythonUtil
+from pandac.PandaModules import *
+from toontown.parties import PartyGlobals
+from toontown.parties.InviteInfo import InviteInfoBase
+from toontown.parties.PartyGlobals import InviteStatus
+from toontown.parties.SimpleMailBase import SimpleMailBase
+from toontown.toonbase import TTLocalizer, ToontownGlobals
+from toontown.toontowngui import TTDialog
+from toontown.toontowngui.TeaserPanel import TeaserPanel
+from toontown.parties.InviteVisual import InviteVisual
+from toontown.toon import GMUtils
+import CatalogItem
+from direct.showbase.PythonUtil import StackTrace
+
+class MailboxScreen(DirectObject.DirectObject):
+ """
+
+ This class allows the user to accept items one at a time out of
+ his or her mailbox.
+
+ """
+ notify = directNotify.newCategory("MailboxScreen")
+
+ def __init__(self, mailbox, avatar, doneEvent = None):
+ assert( MailboxScreen.notify.debug("__init__") )
+ # This is the DistributedMailbox that is handling our transactions.
+ self.mailbox = mailbox
+ # This is the avatar we're accepting for.
+ self.avatar = avatar
+ self.items = self.getItems()
+ # Send this when we are done so whoever made us can get a callback
+ self.doneEvent = doneEvent
+
+ self.itemIndex = 0
+ self.itemPanel = None
+ self.itemPicture = None
+ self.ival = None
+ self.itemText = None
+ self.giftTag = None
+ self.acceptingIndex = None
+ self.numAtticAccepted = 0
+
+ self.dialogBox = None
+
+ self.load()
+ self.hide()
+
+ def show(self):
+ assert( MailboxScreen.notify.debug("show") )
+ self.frame.show()
+
+ self.__showCurrentItem()
+
+ def hide(self):
+ assert( MailboxScreen.notify.debug("hide") )
+ self.ignore("friendsListChanged")
+ if hasattr(self,'frame'):
+ self.frame.hide()
+ else:
+ self.notify.warning("hide called, but frame is deleted, self.frame deleted in:")
+ if hasattr(self, "frameDelStackTrace"):
+ print self.frameDelStackTrace
+ self.notify.warning("current stackTrace =")
+ print StackTrace()
+ self.notify.warning("crash averted, but root cause unknown")
+ # this will force a crash, hopefully we get the log with it
+ #self.frame.hide()
+
+ def load(self):
+ assert( MailboxScreen.notify.debug("load") )
+ self.accept("setMailboxContents-%s" % (base.localAvatar.doId), self.__refreshItems)
+ self.accept("setAwardMailboxContents-%s" % (base.localAvatar.doId), self.__refreshItems)
+ # load the buttons
+ model = loader.loadModel('phase_5.5/models/gui/package_delivery_panel')
+
+ background = model.find('**/bg')
+ itemBoard = model.find('**/item_board')
+
+ self.frame = DirectFrame(scale = 1.1, relief = DGG.FLAT,
+ frameSize = (-0.5,0.5,-0.45,-0.05),
+ frameColor = (0.737, 0.573, 0.345, 1.000))
+
+ self.background = DirectFrame(self.frame,
+ image = background,
+ image_scale = 0.05,
+ relief = None,
+ pos = (0,1,0),
+ )
+ self.itemBoard = DirectFrame(parent = self.frame,
+ image = itemBoard,
+ image_scale = 0.05,
+ image_color = (.922, 0.922, 0.753, 1),
+ relief = None,
+ pos = (0,1,0),
+ )
+ # shows how many items you have to browse through
+ self.itemCountLabel = DirectLabel(
+ parent = self.frame,
+ relief = None,
+ text = self.__getNumberOfItemsText(),
+ text_wordwrap = 16,
+ pos = (0.0, 0.0, 0.7),
+ scale = 0.09,
+ )
+
+ exitUp = model.find('**/bu_return_rollover')
+ exitDown = model.find('**/bu_return_rollover')
+ exitRollover = model.find('**/bu_return_rollover')
+ exitUp.setP(-90)
+ exitDown.setP(-90)
+ exitRollover.setP(-90)
+ # click this button to discard/decline an item in your mailbox
+ self.DiscardButton = DirectButton(
+ parent = self.frame,
+ relief = None,
+ image = (exitUp, exitDown, exitRollover, exitUp),
+ pos = (-0.01, 1.0, -0.36),
+ scale = 0.048,
+ text = ("", TTLocalizer.MailBoxDiscard,
+ TTLocalizer.MailBoxDiscard, ""),
+ text_scale = 1.0,
+ text_pos = (0, -0.08),
+ textMayChange = 1,
+ command = self.__makeDiscardInterface,
+ )
+
+ gui2 = loader.loadModel("phase_3/models/gui/quit_button")
+ # click this button to stop interacting with the mailbox
+ self.quitButton = DirectButton(
+ parent = self.frame,
+ relief = None,
+ image = (gui2.find("**/QuitBtn_UP"),
+ gui2.find("**/QuitBtn_DN"),
+ gui2.find("**/QuitBtn_RLVR"),
+ ),
+ pos = (0.5, 1.0, -0.42),
+ scale = 0.90,
+ text = TTLocalizer.MailboxExitButton,
+ text_font = ToontownGlobals.getSignFont(),
+ text0_fg = (0.152, 0.750, 0.258, 1),
+ text1_fg = (0.152, 0.750, 0.258, 1),
+ text2_fg = (0.977, 0.816, 0.133, 1),
+ text_scale = 0.045,
+ text_pos = (0, -0.01),
+ command = self.__handleExit,
+ )
+
+ self.gettingText = DirectLabel(
+ parent = self.frame,
+ relief = None,
+ text = '',
+ text_wordwrap = 10,
+ pos = (0.0, 0.0, 0.32),
+ scale = 0.09,
+ )
+ self.gettingText.hide()
+
+ # label describing who the item is from
+ self.giftTagPanel = DirectLabel(
+ parent = self.frame,
+ relief = None,
+ text = 'Gift TAG!!',
+ text_wordwrap = 16,
+ pos = (0.0, 0.0, 0.01),
+ scale = 0.06,
+ )
+ self.giftTagPanel.hide()
+
+ # description of the item. For invites, this might be the body of the
+ # invite.
+ self.itemText = DirectLabel(
+ parent = self.frame,
+ relief = None,
+ text = '',
+ text_wordwrap = 16,
+ pos = (0.0, 0.0, -0.022),
+ scale = 0.07,
+ )
+ self.itemText.hide()
+
+ acceptUp = model.find('**/bu_check_up')
+ acceptDown = model.find('**/bu_check_down')
+ acceptRollover = model.find('**/bu_check_rollover')
+ acceptUp.setP(-90)
+ acceptDown.setP(-90)
+ acceptRollover.setP(-90)
+ # click this button to accept an item
+ self.acceptButton = DirectButton(
+ parent = self.frame,
+ relief = None,
+ image = (acceptUp, acceptDown, acceptRollover, acceptUp),
+ image3_color = (0.8, 0.8, 0.8, 0.6),
+ pos = (-0.01, 1.0, -0.16),
+ scale = 0.048,
+ text = ("", TTLocalizer.MailboxAcceptButton,
+ TTLocalizer.MailboxAcceptButton, ""),
+ text_scale = 1.0,
+ text_pos = (0, -0.09),
+ textMayChange = 1,
+ command = self.__handleAccept,
+ state = DGG.DISABLED,
+ )
+
+ nextUp = model.find("**/bu_next_up")
+ nextUp.setP(-90)
+ nextDown = model.find("**/bu_next_down")
+ nextDown.setP(-90)
+ nextRollover = model.find("**/bu_next_rollover")
+ nextRollover.setP(-90)
+ # click this button to go to the next item in my mailbox
+ self.nextButton = DirectButton(
+ parent = self.frame,
+ relief = None,
+ image = (nextUp, nextDown, nextRollover, nextUp),
+ image3_color = (0.8, 0.8, 0.8, 0.6),
+ pos = (0.31, 1.0, -0.26),
+ scale = 0.05,
+ text = ("", TTLocalizer.MailboxItemNext,
+ TTLocalizer.MailboxItemNext, ""),
+ text_scale = 1,
+ text_pos = (-0.2, 0.3),
+ text_fg = (1,1,1,1),
+ text_shadow = (0,0,0,1),
+ textMayChange = 0,
+ command = self.__nextItem,
+ state = DGG.DISABLED,
+ )
+
+ prevUp = model.find("**/bu_previous_up")
+ prevUp.setP(-90)
+ prevDown = model.find("**/bu_previous_down")
+ prevDown.setP(-90)
+ prevRollover = model.find("**/bu_previous_rollover")
+ prevRollover.setP(-90)
+ # click this button to go to the previous item in my mailbox
+ self.prevButton = DirectButton(
+ parent = self.frame,
+ relief = None,
+ image = (prevUp, prevDown, prevRollover, prevUp),
+ pos = (-0.35, 1, -0.26),
+ scale = 0.05,
+ image3_color = (0.8, 0.8, 0.8, 0.6),
+ text = ("", TTLocalizer.MailboxItemPrev,
+ TTLocalizer.MailboxItemPrev, ""),
+ text_scale = 1,
+ text_pos = (0, 0.3),
+ text_fg = (1,1,1,1),
+ text_shadow = (0,0,0,1),
+ textMayChange = 0,
+ command = self.__prevItem,
+ state = DGG.DISABLED,
+ )
+ self.currentItem=None
+
+ self.partyInviteVisual = InviteVisual(self.frame)
+ self.partyInviteVisual.setScale(0.73)
+ self.partyInviteVisual.setPos(0.0, 0.0, 0.48)
+ self.partyInviteVisual.stash()
+
+ def unload(self):
+ assert( MailboxScreen.notify.debug("unload") )
+ self.__clearCurrentItem()
+ if hasattr(self,"frame"):
+ self.frame.destroy()
+ del self.frame
+ self.frameDelStackTrace = StackTrace()
+ else:
+ self.notify.warning("unload, no self.frame")
+ if hasattr(self, "mailbox"):
+ del self.mailbox
+ else:
+ self.notify.warning("unload, no self.mailbox")
+
+ if self.dialogBox:
+ self.dialogBox.cleanup()
+ self.dialogBox = None
+
+ # Clean up all the items also.
+ for item in self.items:
+ if isinstance(item, CatalogItem.CatalogItem):
+ item.acceptItemCleanup()
+
+ self.ignoreAll()
+
+ # button handlers
+
+ def justExit(self):
+ assert( MailboxScreen.notify.debug("justExit") )
+ #self.__handleExit()
+ self.__acceptExit()
+
+ def __handleExit(self):
+ assert( MailboxScreen.notify.debug("__handleExit") )
+ if self.numAtticAccepted == 0:
+ self.__acceptExit()
+ elif self.numAtticAccepted == 1:
+ self.dialogBox = TTDialog.TTDialog(
+ style = TTDialog.Acknowledge,
+ text = TTLocalizer.CatalogAcceptInAttic,
+ text_wordwrap = 15,
+ command = self.__acceptExit,
+ )
+ self.dialogBox.show()
+ else:
+ self.dialogBox = TTDialog.TTDialog(
+ style = TTDialog.Acknowledge,
+ text = TTLocalizer.CatalogAcceptInAtticP,
+ text_wordwrap = 15,
+ command = self.__acceptExit,
+ )
+ self.dialogBox.show()
+
+ def __acceptExit(self, buttonValue = None):
+ assert( MailboxScreen.notify.debug("__acceptExit") )
+ if hasattr(self, 'frame'):
+ self.hide()
+ self.unload()
+ messenger.send(self.doneEvent)
+
+ def __handleAccept(self):
+ assert( MailboxScreen.notify.debug("__handleAccept") )
+ if self.acceptingIndex != None:
+ # Ignore an extraneous click.
+ return
+ item = self.items[self.itemIndex]
+ isAward = False
+ if isinstance(item, CatalogItem.CatalogItem):
+ isAward = item.isAward()
+ if not base.cr.isPaid() and not (isinstance(item, InviteInfoBase) or isAward):
+ # make sure free players can accept party invites or awards
+ TeaserPanel(pageName='clothing')
+ else:
+ self.acceptingIndex = self.itemIndex
+ self.acceptButton['state'] = DGG.DISABLED
+
+ self.__showCurrentItem()
+
+ item = self.items[self.itemIndex]
+ item.acceptItem(self.mailbox, self.acceptingIndex,
+ self.__acceptItemCallback)
+
+ def __handleDiscard(self, buttonValue = None):
+ assert( MailboxScreen.notify.debug("__handleDiscard") )
+ #print ("Discard Button Value:%s " % (buttonValue))
+
+ if self.acceptingIndex != None:
+ # Ignore an extraneous click.
+ return
+ elif buttonValue == -1:
+ if self.dialogBox:
+ self.dialogBox.cleanup()
+ self.dialogBox = None
+ self.__showCurrentItem()
+
+ else:
+ self.acceptingIndex = self.itemIndex
+ self.acceptButton['state'] = DGG.DISABLED
+
+ self.__showCurrentItem()
+
+ item = self.items[self.itemIndex]
+ item.discardItem(self.mailbox, self.acceptingIndex,
+ self.__discardItemCallback)
+
+ def __discardItemCallback(self, retcode, item, index):
+ assert( MailboxScreen.notify.debug("__discardItemCallback") )
+ if not hasattr(self, "frame"):
+ # The gui might have been closed down already by an
+ # impatient user. If so, we quietly ignore the callback.
+ return
+ if self.dialogBox:
+ self.dialogBox.cleanup()
+ self.dialogBox = None
+ self.acceptingIndex = None
+ self.__updateItems()
+ if isinstance(item, InviteInfoBase):
+ callback = self.__incIndexRemoveDialog
+ self.dialogBox = TTDialog.TTDialog(
+ style = TTDialog.Acknowledge,
+ text = item.getDiscardItemErrorText(retcode),
+ text_wordwrap = 15,
+ command = callback,
+ )
+ self.dialogBox.show()
+ #self.__acceptOk(index)
+
+ def __makeDiscardInterface(self):
+ assert( MailboxScreen.notify.debug("__makeDiscardInterface") )
+ if self.itemIndex >= 0 and self.itemIndex < len(self.items): #check to see if index is within range, should help with button mashing
+ item = self.items[self.itemIndex]
+ if isinstance(item, InviteInfoBase):
+ itemText = (TTLocalizer.MailBoxRejectVerify % (self.getItemName(item)))
+ yesText = TTLocalizer.MailboxReject
+ else:
+ itemText = (TTLocalizer.MailBoxDiscardVerify % (self.getItemName(item)))
+ yesText = TTLocalizer.MailboxDiscard
+ #self.notify.info("Could not take item %s: retcode %s" % (item, retcode))
+ self.dialogBox = TTDialog.TTDialog(
+ style = TTDialog.TwoChoiceCustom, #TwoChoice YesNo
+ text = itemText, #what happens when you try to take chat from the mailbox
+ text_wordwrap = 15,
+ command = self.__handleDiscard,
+ buttonText = [yesText, TTLocalizer.MailboxLeave],
+ )
+ self.dialogBox.show()
+
+ def __acceptItemCallback(self, retcode, item, index):
+ assert( MailboxScreen.notify.debug("__acceptItemCallback") )
+ needtoUpdate = 0
+
+ if self.acceptingIndex == None:
+ needtoUpdate = 1
+ #if the setMailboxContents call won the race back here then
+ #we need to update our current item list
+
+ if not hasattr(self, "frame"):
+ # The gui might have been closed down already by an
+ # impatient user. If so, we quietly ignore the callback.
+ return
+
+ if retcode == ToontownGlobals.P_UserCancelled:
+ print("mailbox screen user canceled")
+ #user canceled probably for a chatitem
+ self.acceptingIndex = None
+ self.__updateItems()
+ #self.__acceptOk(index+1)
+ return
+
+ if self.acceptingIndex != index:
+ self.notify.warning("Got unexpected callback for index %s, expected %s." % (index, self.acceptingIndex))
+ return
+
+ self.acceptingIndex = None
+
+ if retcode < 0:
+ # There was some error with the accept. Pop up an
+ # appropriate dialog.
+ self.notify.info("Could not take item %s: retcode %s" % (item, retcode))
+ self.dialogBox = TTDialog.TTDialog(
+ style = TTDialog.TwoChoiceCustom, #TwoChoice YesNo
+ text = item.getAcceptItemErrorText(retcode), #what happens when you try to take chat from the mailbox
+ text_wordwrap = 15,
+ command = self.__handleDiscard,
+ buttonText = [ TTLocalizer.MailboxDiscard, TTLocalizer.MailboxLeave]
+ )
+ """
+ #buttonText = [TTLocalizer.MailboxOverflowButtonDicard,
+ #TTLocalizer.MailboxOverflowButtonLeave]
+ """
+ #import pdb; pdb.set_trace()
+
+ self.dialogBox.show()
+
+ else:
+ # The item was accepted successfully.
+ if hasattr(item,'storedInAttic') and item.storedInAttic():
+ # It's an attic item; collect these messages to
+ # display just one dialog box at the end.
+ self.numAtticAccepted += 1
+ self.itemIndex += 1
+ #self.__acceptOk(index)
+ if needtoUpdate == 1:
+ self.__updateItems();
+
+ else:
+ # It's some other kind of item; display the accept
+ # dialog immediately.
+ #callback = PythonUtil.Functor(self.__acceptOk, index)
+
+ # TODO force it to just discard the item when appropriate
+ # e.g. invites to cancelled parties
+ if isinstance(item, InviteInfoBase):
+ self.__updateItems();
+ callback = self.__incIndexRemoveDialog
+ # tell player that the party's host got their response
+ self.dialogBox = TTDialog.TTDialog(
+ style = TTDialog.Acknowledge,
+ text = item.getAcceptItemErrorText(retcode),
+ text_wordwrap = 15,
+ command = callback,
+ )
+ self.dialogBox.show()
+
+
+ def __acceptError(self, buttonValue = None):
+ assert( MailboxScreen.notify.debug("__acceptError") )
+ self.dialogBox.cleanup()
+ self.dialogBox = None
+ self.__showCurrentItem()
+
+ def __incIndexRemoveDialog(self, junk = 0):
+ assert( MailboxScreen.notify.debug("__incIndexRemoveDialog") )
+ self.__incIndex()
+ self.dialogBox.cleanup()
+ self.dialogBox = None
+ self.__showCurrentItem()
+
+ def __incIndex(self, junk = 0):
+ assert( MailboxScreen.notify.debug("__incIndex") )
+ self.itemIndex += 1
+
+ def __acceptOk(self, index, buttonValue = None):
+ assert( MailboxScreen.notify.debug("__acceptOk") )
+ self.acceptingIndex = None
+ if self.dialogBox:
+ self.dialogBox.cleanup()
+ self.dialogBox = None
+
+ # We have a new set of items now.
+ self.items = self.getItems()
+ if self.itemIndex > index or self.itemIndex >= len(self.items):
+ print("adjusting item index -1")
+ self.itemIndex -= 1
+
+ if len(self.items) < 1:
+ #print("exiting due to lack of items")
+ # No more items in the mailbox.
+ self.__handleExit()
+ return
+
+ # Show the next item in the mailbox.
+ self.itemCountLabel['text'] = self.__getNumberOfItemsText(),
+ self.__showCurrentItem()
+
+ def __refreshItems(self):
+ assert( MailboxScreen.notify.debug("__refreshItems") )
+ self.acceptingIndex = None
+ self.__updateItems()
+
+ def __updateItems(self):
+ assert( MailboxScreen.notify.debug("__updateItems") )
+ if self.dialogBox:
+ self.dialogBox.cleanup()
+ self.dialogBox = None
+
+ # We have a new set of items now.
+ self.items = self.getItems()
+ if self.itemIndex >= len(self.items):
+ print("adjusting item index -1")
+ self.itemIndex = len(self.items) - 1
+
+ if len(self.items) == 0:
+ print("exiting due to lack of items")
+ # No more items in the mailbox.
+ self.__handleExit()
+ return
+
+ # Show the current item in the mailbox.
+ self.itemCountLabel['text'] = self.__getNumberOfItemsText(),
+ self.__showCurrentItem()
+
+ def __getNumberOfItemsText(self):
+ assert( MailboxScreen.notify.debug("__getNumberOfItemsText") )
+ # Returns the string displayed across the top of the panel:
+ # how many items remain in the mailbox.
+ if len(self.items) == 1:
+ return TTLocalizer.MailboxOneItem
+ else:
+ return TTLocalizer.MailboxNumberOfItems % (len(self.items))
+
+ def __clearCurrentItem(self):
+ assert( MailboxScreen.notify.debug("__clearCurrentItem") )
+ if self.itemPanel:
+ self.itemPanel.destroy()
+ self.itemPanel = None
+ if self.ival:
+ self.ival.finish()
+ self.ival = None
+ if not self.gettingText.isEmpty():
+ self.gettingText.hide()
+ if not self.itemText.isEmpty():
+ self.itemText.hide()
+ if not self.giftTagPanel.isEmpty():
+ self.giftTagPanel.hide()
+ if not self.acceptButton.isEmpty():
+ self.acceptButton['state'] = DGG.DISABLED
+ if(self.currentItem):
+ if isinstance(self.currentItem, CatalogItem.CatalogItem):
+ self.currentItem.cleanupPicture()
+ self.currentItem = None
+
+ def checkFamily(self, doId):
+ assert( MailboxScreen.notify.debug("checkFamily") )
+ for familyMember in base.cr.avList:
+ #print ("Family %s %s" % (familyMember.name, familyMember.id))
+ if familyMember.id == doId:
+ #print("found it")
+ #import pdb; pdb.set_trace()
+ return familyMember
+ return None
+
+ def __showCurrentItem(self):
+ assert( MailboxScreen.notify.debug("__showCurrentItem") )
+ self.__clearCurrentItem()
+ if len(self.items) < 1:
+ # No more items in the mailbox.
+ self.__handleExit()
+ return
+ self.partyInviteVisual.stash()
+ if (self.itemIndex + 1) > len(self.items):
+ self.itemIndex = len(self.items) - 1
+
+ item = self.items[self.itemIndex]
+
+ if self.itemIndex == self.acceptingIndex:
+ # We are currently waiting on the AI to accept this item.
+ self.gettingText['text'] = TTLocalizer.MailboxGettingItem % (self.getItemName(item))
+ self.gettingText.show()
+ return
+
+ # This item is available to be accepted.
+ self.itemText['text'] = self.getItemName(item)
+ assert(self.itemPanel == None and self.ival == None)
+ self.currentItem=item
+ # giftTag is the senders do Id
+ # giftCode is the type of tag to use (added after giftTag or I would have named them differently)
+ if isinstance(item, CatalogItem.CatalogItem):
+ self.acceptButton['text'] = ("", TTLocalizer.MailboxAcceptButton,
+ TTLocalizer.MailboxAcceptButton, "")
+ self.DiscardButton['text'] = ("", TTLocalizer.MailBoxDiscard,
+ TTLocalizer.MailBoxDiscard, "")
+ if item.isAward():
+ self.giftTagPanel['text'] = TTLocalizer.SpecialEventMailboxStrings[item.specialEventId]
+ elif item.giftTag != None:
+ # if it's a gift add the tag
+ nameOfSender = self.getSenderName(item.giftTag)
+
+ if item.giftCode == ToontownGlobals.GIFT_RAT:
+ self.giftTagPanel['text'] = TTLocalizer.CatalogAcceptRATBeans
+ else:
+ self.giftTagPanel['text'] = (TTLocalizer.MailboxGiftTag % (nameOfSender))
+
+ else:
+ self.giftTagPanel['text'] = ""
+ self.itemPanel, self.ival = item.getPicture(base.localAvatar)
+ elif isinstance(item, SimpleMailBase):
+ self.acceptButton['text'] = ("", TTLocalizer.MailboxAcceptButton,
+ TTLocalizer.MailboxAcceptButton, "")
+ self.DiscardButton['text'] = ("", TTLocalizer.MailBoxDiscard,
+ TTLocalizer.MailBoxDiscard, "")
+ senderId = item.senderId
+ nameOfSender = self.getSenderName(senderId)
+ self.giftTagPanel['text'] = (TTLocalizer.MailFromTag % (nameOfSender))
+ self.itemText['text'] = item.body
+ elif isinstance(item, InviteInfoBase):
+ self.acceptButton['text'] = ("", TTLocalizer.MailboxAcceptInvite,
+ TTLocalizer.MailboxAcceptInvite, "")
+ self.DiscardButton['text'] = ("", TTLocalizer.MailBoxRejectInvite,
+ TTLocalizer.MailBoxRejectInvite, "")
+ partyInfo = None
+ for party in self.avatar.partiesInvitedTo:
+ if party.partyId == item.partyId:
+ partyInfo = party
+ break
+ else: # could not find party info
+ MailboxScreen.notify.error("Unable to find party with id %d to match invitation %s"%(item.partyId, item))
+ if self.mailbox:
+ if item.status == PartyGlobals.InviteStatus.NotRead:
+ self.mailbox.sendInviteReadButNotReplied(item.inviteKey)
+ senderId = partyInfo.hostId
+ nameOfSender = self.getSenderName(senderId)
+ self.giftTagPanel['text'] = ""
+ self.itemText['text'] = ""
+ self.partyInviteVisual.updateInvitation(nameOfSender, partyInfo)
+ self.partyInviteVisual.unstash()
+ self.itemPanel = None
+ self.ival = None
+ else:
+ self.acceptButton['text'] = ("", TTLocalizer.MailboxAcceptButton,
+ TTLocalizer.MailboxAcceptButton, "")
+ self.DiscardButton['text'] = ("", TTLocalizer.MailBoxDiscard,
+ TTLocalizer.MailBoxDiscard, "")
+ self.giftTagPanel['text'] = " "
+ self.itemPanel = None
+ self.ival = None
+ self.itemText.show()
+ self.giftTagPanel.show()
+
+ if self.itemPanel and (item.getTypeName() != TTLocalizer.ChatTypeName):
+ # Ensure the item panel is behind any other text.
+ self.itemPanel.reparentTo(self.itemBoard, -1)
+ self.itemPanel.setPos(0, 0, 0.40)
+ self.itemPanel.setScale(0.21)
+ self.itemText['text_wordwrap'] = 16
+ self.itemText.setPos(0.0, 0.0, 0.075)
+ elif isinstance(item, CatalogItem.CatalogItem) and \
+ (item.getTypeName() == TTLocalizer.ChatTypeName):
+ # Ensure the item panel is behind any other text.
+ self.itemPanel.reparentTo(self.itemBoard, -1)
+ self.itemPanel.setPos(0, 0, 0.35)
+ self.itemPanel.setScale(0.21)
+ # Scooch the item text into the chat bubble
+ self.itemText['text_wordwrap'] = 10
+ self.itemText.setPos(0, 0, 0.30)
+ else:
+ # There's no picture for this item. Scooch the item text
+ # up to fill up the space.
+ self.itemText.setPos(0, 0, 0.3)
+
+ if self.ival:
+ self.ival.loop()
+ if self.acceptingIndex == None:
+ self.acceptButton['state'] = DGG.NORMAL
+
+ if self.itemIndex > 0:
+ self.prevButton['state'] = DGG.NORMAL
+ else:
+ self.prevButton['state'] = DGG.DISABLED
+
+ if self.itemIndex + 1 < len(self.items):
+ self.nextButton['state'] = DGG.NORMAL
+ else:
+ self.nextButton['state'] = DGG.DISABLED
+
+
+ def __nextItem(self):
+ assert( MailboxScreen.notify.debug("__nextItem") )
+ messenger.send('wakeup')
+ if self.itemIndex + 1 < len(self.items):
+ self.itemIndex += 1
+ self.__showCurrentItem()
+
+ def __prevItem(self):
+ assert( MailboxScreen.notify.debug("__prevItem") )
+ messenger.send('wakeup')
+ if self.itemIndex > 0:
+ self.itemIndex -= 1
+ self.__showCurrentItem()
+
+ def getItemName(self, item):
+ """Return the name of the item."""
+ assert( MailboxScreen.notify.debug("getItemName") )
+ if isinstance(item, CatalogItem.CatalogItem):
+ return item.getName()
+ elif isinstance(item, str):
+ return TTLocalizer.MailSimpleMail
+ elif isinstance(item, InviteInfoBase):
+ return TTLocalizer.InviteInvitation
+ else:
+ return ''
+
+ def getItems(self):
+ """Return the mailbox items which can be of multiple types."""
+ assert( MailboxScreen.notify.debug("getItems") )
+ result = []
+ result = self.avatar.awardMailboxContents[:]
+ result += self.avatar.mailboxContents[:]
+ if self.avatar.mail:
+ result += self.avatar.mail
+ mailboxInvites = self.avatar.getInvitesToShowInMailbox()
+ if mailboxInvites:
+ result += mailboxInvites
+ return result
+
+
+ def getNumberOfAwardItems(self):
+ """Return the number of award items in self.items."""
+ # since award items go first, the index being sent to ai needs to be adjusted
+ result = 0
+ for item in self.items:
+ if isinstance(item, CatalogItem.CatalogItem) and item.specialEventId > 0:
+ result += 1
+ else:
+ break
+ return result
+
+
+ def getSenderName(self,avId):
+ """Return the name of the toon that matches avId."""
+ assert( MailboxScreen.notify.debug("getSenderName") )
+ sender = base.cr.identifyFriend(avId)
+ nameOfSender = ""
+ if sender:
+ nameOfSender = sender.getName()
+ else:
+ sender = self.checkFamily(avId) # check family
+ if sender:
+ nameOfSender = sender.name # careful a family member returns a PotentialAvatar not a handle
+ elif hasattr(base.cr, "playerFriendsManager"): # check transient toons
+ sender = base.cr.playerFriendsManager.getAvHandleFromId(avId)
+ if sender:
+ nameOfSender = sender.getName()
+
+ if GMUtils.testGMIdentity(nameOfSender):
+ nameOfSender = GMUtils.handleGMName(nameOfSender)
+
+ if not sender:
+ nameOfSender = TTLocalizer.MailboxGiftTagAnonymous
+ if hasattr(base.cr, "playerFriendsManager"): # request the info
+ base.cr.playerFriendsManager.requestAvatarInfo(avId)
+ self.accept('friendsListChanged', self.__showCurrentItem) # accepts this as long as it stays up
+ return nameOfSender
+
+
+
diff --git a/toontown/src/catalog/Sources.pp b/toontown/src/catalog/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/toontown/src/catalog/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/toontown/src/catalog/__init__.py b/toontown/src/catalog/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/char/.cvsignore b/toontown/src/char/.cvsignore
new file mode 100644
index 0000000..985f113
--- /dev/null
+++ b/toontown/src/char/.cvsignore
@@ -0,0 +1,4 @@
+.cvsignore
+Makefile
+pp.dep
+*.pyc
diff --git a/toontown/src/char/Char.py b/toontown/src/char/Char.py
new file mode 100644
index 0000000..8bd6e5c
--- /dev/null
+++ b/toontown/src/char/Char.py
@@ -0,0 +1,762 @@
+"""Char module: contains the Char class"""
+
+from otp.avatar import Avatar
+from pandac.PandaModules import *
+from direct.task import Task
+import random
+from pandac.PandaModules import *
+from direct.directnotify import DirectNotifyGlobal
+
+AnimDict = {
+ "mk": (("walk", "walk", 3),
+ ("run", "run", 3),
+ ("neutral", "wait", 3),
+ ("left-point-start", "left-start", 3.5),
+ ("left-point", "left", 3.5),
+ ("right-point-start", "right-start", 3.5),
+ ("right-point", "right", 3.5),
+ ),
+ "vmk": (("walk", "sneak", 3),
+ ("run", "run", 3),
+ ("neutral", "idle", 3),
+ ("sneak", "sneak",3),
+ ("into_sneak", "into_sneak", 3),
+ ("chat", "run", 3),
+ ("into_idle", "into_idle", 3),
+ ),
+ "wmn":(("walk", "walkHalloween3", 3),
+ ("neutral", "neutral2", 3),
+ ),
+ "mn": (("walk", "walk", 3),
+ ("run", "run", 3),
+ ("neutral", "wait", 3),
+ ("left-point-start", "start-Lpoint", 3.5),
+ ("left-point", "Lpoint", 3.5),
+ ("right-point-start", "start-Rpoint", 3.5),
+ ("right-point", "Rpoint", 3.5),
+ ("up", "up", 4),
+ ("down", "down", 4),
+ ("left", "left", 4),
+ ("right", "right", 4),
+ ),
+ "g": (("walk", "Walk", 6),
+ ("run", "Run", 6),
+ ("neutral", "Wait", 6),
+ ),
+ "sg": (("walk", "walkStrut2", 6),
+ ("neutral", "neutral", 6),
+ ),
+ "d": (("walk", "walk", 6),
+ ("trans", "transition", 6),
+ ("neutral", "neutral", 6),
+ ("trans-back", "transBack", 6),
+ ),
+ "dw": (("wheel", "wheel", 6),
+ ("neutral", "wheel", 6),
+ ),
+ "p": (("walk", "walk", 6),
+ ("sit", "sit", 6),
+ ("neutral", "neutral", 6),
+ ("stand", "stand", 6),
+ ),
+ "wp": (("walk", "walk", 6),
+ ("sit", "sitStart", 6),
+ ("neutral", "sitLoop", 6),
+ ("stand", "sitStop", 6),
+ ),
+ "cl":(),
+ "dd" : (("walk", "walk", 4),
+ ("neutral", "idle", 4),
+ ),
+ "ch" : (("walk", "walk", 6),
+ ("neutral", "idle", 6),
+ ),
+ "da" : (("walk", "walk", 6),
+ ("neutral", "idle", 6),
+ ),
+ }
+
+ModelDict = {
+ "mk": "phase_3/models/char/mickey-",
+ "vmk": "phase_3.5/models/char/tt_a_chr_csc_mickey_vampire_",
+ "mn": "phase_3/models/char/minnie-",
+ "wmn" : "phase_3.5/models/char/tt_a_chr_csc_witchMinnie_",
+ "g" : "phase_6/models/char/TT_G",
+ "sg" : "phase_6/models/char/tt_a_chr_csc_goofyCostume_",
+ "d" : "phase_6/models/char/DL_donald-",
+ "dw": "phase_6/models/char/donald-wheel-",
+ "p" : "phase_6/models/char/pluto-",
+ "wp" : "phase_6/models/char/tt_a_chr_csc_plutoCostume_",
+ "cl": "phase_5.5/models/estate/Clara_pose2-",
+ "dd": "phase_4/models/char/daisyduck_",
+ "ch": "phase_6/models/char/chip_",
+ "da": "phase_6/models/char/dale_",
+ }
+
+
+
+LODModelDict = {
+ "mk": [1200, 800, 400],
+ "vmk": [1200, 800, 400],
+ "wmn": [1200, 800, 400],
+ "mn": [1200, 800, 400],
+ "g" : [1500, 1000, 500],
+ "sg": [1200, 800, 400],
+ "d" : [1000, 500, 250],
+ "dw": [1000],
+ "p" : [1000, 500, 300],
+ "wp" : [1200, 800, 400],
+ "cl": [],
+ "dd" : [1600, 800, 400],
+ "ch" : [1000, 500, 250],
+ "da" : [1000, 500, 250],
+ }
+
+class Char(Avatar.Avatar):
+ """
+ A Char is not a player toon.
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("Char")
+
+ def __init__(self):
+ try:
+ self.Char_initialized
+ except:
+ self.Char_initialized = 1
+ Avatar.Avatar.__init__(self)
+
+ # Characters are generally not pickable. You can't make
+ # friends with Mickey!
+ self.setPickable(0)
+
+ # Chars are generally non-player avatars.
+ self.setPlayerType(NametagGroup.CCNonPlayer)
+
+ self.dialogueArray = []
+ self.chatterArray = [[], [], []]
+
+ def delete(self):
+ try:
+ self.Char_deleted
+ except:
+ self.Char_deleted = 1
+ self.unloadDialogue()
+
+ Avatar.Avatar.delete(self)
+
+ def updateCharDNA(self, newDNA):
+ """updateCharDNA(self, AvatarDNA)
+ update the char's appearance based on new DNA
+ """
+ if (newDNA.name != self.style.name):
+ self.swapCharModel(newDNA)
+
+ def setDNAString(self, dnaString):
+ newDNA = CharDNA.CharDNA()
+ newDNA.makeFromNetString(dnaString)
+ self.setDNA(newDNA)
+
+ def setDNA(self, dna):
+ if self.style:
+ self.updateCharDNA(dna)
+ else:
+ # store the DNA
+ self.style = dna
+
+ self.generateChar()
+
+ # this no longer works in the Avatar init!
+ # I moved it here for lack of a better place
+ # make the drop shadow
+ self.initializeDropShadow()
+ self.initializeNametag3d()
+ self.nametag3d.setBin('fixed', 0)
+
+ # fix Chip and Dales wonky shadow
+ if (self.name == "chip") or (self.name == "dale"):
+ self.find("**/drop-shadow").setScale(0.33)
+
+
+ def setLODs(self):
+ """
+ Get LOD switch distances from dconfig, or use defaults
+ """
+ # set up the LOD node for avatar LOD
+ self.setLODNode()
+
+ # get the switch values
+ levelOneIn = base.config.GetInt("lod1-in", 50)
+ levelOneOut = base.config.GetInt("lod1-out", 1)
+ levelTwoIn = base.config.GetInt("lod2-in", 100)
+ levelTwoOut = base.config.GetInt("lod2-out", 50)
+ levelThreeIn = base.config.GetInt("lod3-in", 280)
+ levelThreeOut = base.config.GetInt("lod3-out", 100)
+
+ # add the LODs
+ self.addLOD(LODModelDict[self.style.name][0], levelOneIn, levelOneOut)
+ self.addLOD(LODModelDict[self.style.name][1], levelTwoIn, levelTwoOut)
+ self.addLOD(LODModelDict[self.style.name][2], levelThreeIn, levelThreeOut)
+
+ def generateChar(self):
+ """
+ Create a non-player character from dna (an array of strings)
+ """
+ dna = self.style
+ self.name = dna.getCharName()
+ self.geoEyes = 0
+ # generate the LOD nodes, if necessary
+ if (len(LODModelDict[dna.name]) > 1):
+ self.setLODs()
+ filePrefix = ModelDict[dna.name]
+ if (self.name == "mickey"):
+ height = 3.0
+ elif (self.name == "vampire_mickey"):
+ height = 3.0
+ elif (self.name == "minnie"):
+ height = 3.0
+ elif (self.name == "witch_minnie"):
+ height = 3.0
+ elif (self.name == "goofy"):
+ height = 4.8
+ elif (self.name == "super_goofy"):
+ height = 4.8
+ elif (self.name == "donald" or self.name == "donald-wheel"):
+ height = 4.5
+ elif (self.name == "daisy"):
+ height = 4.5
+ elif (self.name == "pluto"):
+ height = 3.0
+ elif (self.name == "western_pluto"):
+ height = 4.5
+ elif (self.name == "clarabelle"):
+ height = 3.0
+ elif (self.name == "chip"):
+ height = 2.0
+ elif (self.name == "dale"):
+ height = 2.0
+
+ self.lodStrings = []
+ for lod in LODModelDict[self.style.name]:
+ self.lodStrings.append(str(lod))
+
+ if self.lodStrings:
+ for lodStr in self.lodStrings:
+ if (len(self.lodStrings) > 1):
+ lodName = lodStr
+ else:
+ lodName = "lodRoot"
+ if(self.name == "goofy"):
+ self.loadModel(filePrefix + "-" + lodStr, lodName=lodName)
+ else:
+ self.loadModel(filePrefix + lodStr, lodName=lodName)
+ else:
+ self.loadModel(filePrefix)
+
+ animDict = {}
+
+ animList = AnimDict[self.style.name]
+ for anim in animList:
+ # anim files may be in different phases
+ animFilePrefix = filePrefix[:6] + str(anim[2]) + filePrefix[7:]
+ animDict[anim[0]] = animFilePrefix + anim[1]
+
+ # The animations are loaded when used
+ for lodStr in self.lodStrings:
+ if (len(self.lodStrings) > 1):
+ lodName = lodStr
+ else:
+ lodName = "lodRoot"
+ self.loadAnims(animDict, lodName=lodName)
+
+ self.setHeight(height)
+
+ self.loadDialogue(dna.name)
+
+ # set up the mouse ears for rotation
+ self.ears = []
+ # or self.name == "vampire_mickey"
+ if (self.name == "mickey" or self.name == "vampire_mickey" \
+ or self.name == "minnie"):
+ # Clear the net transforms first, in case we have
+ # merge-lod-bundles on (which would mean this is really
+ # just one bundle).
+ for bundle in self.getPartBundleDict().values():
+ bundle = bundle['modelRoot'].getBundle()
+ earNull = bundle.findChild("sphere3")
+ if not earNull:
+ earNull = bundle.findChild("*sphere3")
+ earNull.clearNetTransforms()
+
+ for bundle in self.getPartBundleDict().values():
+ charNodepath = bundle['modelRoot'].partBundleNP
+ bundle = bundle['modelRoot'].getBundle()
+ earNull = bundle.findChild("sphere3")
+ if not earNull:
+ earNull = bundle.findChild("*sphere3")
+ # import pdb; pdb.set_trace()
+ ears = charNodepath.find("**/sphere3")
+ if ears.isEmpty():
+ ears = charNodepath.find("**/*sphere3")
+ ears.clearEffect(CharacterJointEffect.getClassType())
+ earRoot = charNodepath.attachNewNode("earRoot")
+ earPitch = earRoot.attachNewNode("earPitch")
+ earPitch.setP(40.)
+ ears.reparentTo(earPitch)
+ # put animation channel on ear root
+ earNull.addNetTransform(earRoot.node())
+ ears.clearMat()
+ # bake in the reverse pitch
+ ears.node().setPreserveTransform(ModelNode.PTNone)
+ ears.setP(-40.)
+ ears.flattenMedium()
+ self.ears.append(ears)
+ # now make the ears rotate to the camera at this pitch.
+ ears.setBillboardAxis()
+
+ # set up the blinking eyes
+ self.eyes = None
+ self.lpupil = None
+ self.rpupil = None
+ self.eyesOpen = None
+ self.eyesClosed = None
+
+ if (self.name == "mickey" or self.name == "minnie"):
+ self.eyesOpen = loader.loadTexture("phase_3/maps/eyes1.jpg",
+ "phase_3/maps/eyes1_a.rgb")
+ self.eyesClosed = loader.loadTexture(
+ "phase_3/maps/mickey_eyes_closed.jpg",
+ "phase_3/maps/mickey_eyes_closed_a.rgb")
+ # TODO: other LODs
+ self.eyes = self.find("**/1200/**/eyes")
+ # this fixes a dual-mode transparency problem
+ # that makes the pupils render poorly
+ self.eyes.setBin('transparent', 0)
+ self.lpupil = self.find("**/1200/**/joint_pupilL")
+ self.rpupil = self.find("**/1200/**/joint_pupilR")
+ # make them render correctly
+ for lodName in self.getLODNames():
+ self.drawInFront("joint_pupil?", "eyes*", -3, lodName=lodName)
+ elif (self.name == "witch_minnie" or self.name == "vampire_mickey" \
+ or self.name == "super_goofy" or self.name == "western_pluto"):
+ self.geoEyes = 1
+ self.eyeOpenList = []
+ self.eyeCloseList = []
+
+ if(self.find("**/1200/**/eyesOpen").isEmpty()):
+ self.eyeCloseList.append(self.find("**/eyesClosed"))
+ self.eyeOpenList.append(self.find("**/eyesOpen"))
+ else:
+ self.eyeCloseList.append(self.find("**/1200/**/eyesClosed"))
+ self.eyeOpenList.append(self.find("**/1200/**/eyesOpen"))
+
+ for part in self.eyeOpenList:
+ part.show()
+
+ for part in self.eyeCloseList:
+ part.hide()
+ elif (self.name == "pluto"):
+ self.eyesOpen = loader.loadTexture(
+ "phase_6/maps/plutoEyesOpen.jpg",
+ "phase_6/maps/plutoEyesOpen_a.rgb")
+ self.eyesClosed = loader.loadTexture(
+ "phase_6/maps/plutoEyesClosed.jpg",
+ "phase_6/maps/plutoEyesClosed_a.rgb")
+ # TODO: other LODs
+ self.eyes = self.find("**/1000/**/eyes")
+ self.lpupil = self.find("**/1000/**/joint_pupilL")
+ self.rpupil = self.find("**/1000/**/joint_pupilR")
+ # make them render correctly
+ for lodName in self.getLODNames():
+ self.drawInFront("joint_pupil?", "eyes*", -3, lodName=lodName)
+ elif (self.name == "daisy"):
+ self.geoEyes = 1
+ self.eyeOpenList = []
+ self.eyeCloseList = []
+
+ self.eyeCloseList.append(self.find("**/1600/**/eyesclose"))
+ self.eyeCloseList.append(self.find("**/800/**/eyesclose"))
+
+ self.eyeOpenList.append(self.find("**/1600/**/eyesclose"))
+ self.eyeOpenList.append(self.find("**/800/**/eyesclose"))
+ self.eyeOpenList.append(self.find("**/1600/**/eyespupil"))
+ self.eyeOpenList.append(self.find("**/800/**/eyespupil"))
+ self.eyeOpenList.append(self.find("**/1600/**/eyesopen"))
+ self.eyeOpenList.append(self.find("**/800/**/eyesopen"))
+
+ for part in self.eyeOpenList:
+ part.show()
+
+ for part in self.eyeCloseList:
+ part.hide()
+
+ elif (self.name == "donald-wheel"):
+ # set them up for blinking
+ self.eyes = self.find("**/eyes")
+ self.lpupil = self.find("**/joint_pupilL")
+ self.rpupil = self.find("**/joint_pupilR")
+ # arrange donalds eyes to render properly
+ self.drawInFront("joint_pupil?", "eyes*", -3)
+
+ elif (self.name == "chip") or (self.name == "dale"):
+ self.eyesOpen = loader.loadTexture(
+ "phase_6/maps/dale_eye1.jpg",
+ "phase_6/maps/dale_eye1_a.rgb")
+ self.eyesClosed = loader.loadTexture(
+ "phase_6/maps/chip_dale_eye1_blink.jpg",
+ "phase_6/maps/chip_dale_eye1_blink_a.rgb")
+ self.eyes = self.find("**/eyes")
+ self.lpupil = self.find("**/pupil_left")
+ self.rpupil = self.find("**/pupil_right")
+ # hide the existing blink geom
+ self.find("**/blink").hide()
+
+ # TODO: Clarabelle blinking
+
+ # Bump up the override parameter on the pupil
+ # textures so they won't get overridden when we set
+ # the blink texture.
+ if self.lpupil != None:
+ self.lpupil.adjustAllPriorities(1)
+ self.rpupil.adjustAllPriorities(1)
+
+ if self.eyesOpen:
+ self.eyesOpen.setMinfilter(Texture.FTLinear)
+ self.eyesOpen.setMagfilter(Texture.FTLinear)
+ if self.eyesClosed:
+ self.eyesClosed.setMinfilter(Texture.FTLinear)
+ self.eyesClosed.setMagfilter(Texture.FTLinear)
+
+ # Fix Mickey's screwed up right pupil until the animators redo
+ # Well only fix the highest lod since that is the only one noticeable
+ if (self.name == "mickey"):
+ pupilParent = self.rpupil.getParent()
+ pupilOffsetNode = pupilParent.attachNewNode("pupilOffsetNode")
+ pupilOffsetNode.setPos(0, 0.025, 0)
+ self.rpupil.reparentTo(pupilOffsetNode)
+
+ self.__blinkName = "blink-" + self.name
+
+ #import pdb; pdb.set_trace()
+
+ def swapCharModel(self, charStyle):
+ """swapCharModel(Self, string)
+ Swap out the current char model for the given one
+ """
+ # out with the old
+ for lodStr in self.lodStrings:
+ if (len(self.lodStrings) > 1):
+ lodName = lodStr
+ else:
+ lodName = "lodRoot"
+ self.removePart("modelRoot", lodName=lodName)
+
+ # in with the new
+ self.setStyle(charStyle)
+ self.generateChar()
+
+ def getDialogue(self, type, length):
+ """playDialogue(self, string, int)
+ Play the specified type of dialogue for the specified time
+ """
+ # Choose the appropriate sound effect.
+ sfxIndex = None
+ if (type == "statementA" or type == "statementB"):
+ if (length == 1):
+ sfxIndex = 0
+ elif (length == 2):
+ sfxIndex = 1
+ elif (length >= 3):
+ sfxIndex = 2
+ elif (type == "question"):
+ sfxIndex = 3
+ elif (type == "exclamation"):
+ sfxIndex = 4
+ elif (type == "special"):
+ sfxIndex = 5
+ else:
+ self.notify.error("unrecognized dialogue type: ", type)
+
+ if sfxIndex != None and sfxIndex < len(self.dialogueArray) and \
+ self.dialogueArray[sfxIndex] != None:
+ return self.dialogueArray[sfxIndex]
+ else:
+ return None
+
+ def playDialogue(self, type, length, delay = None):
+ dialogue = self.getDialogue(type, length)
+ base.playSfx(dialogue)
+
+ def getChatterDialogue(self, category, msg):
+ try:
+ return self.chatterArray[category][msg]
+ except IndexError:
+ return None
+
+ def getShadowJoint(self):
+ return self.getGeomNode()
+
+ #def getShadowJoints(self): #*
+ # return [self.getGeomNode()]
+
+ def getNametagJoints(self):
+ """
+ Return the CharacterJoint that animates the nametag, in a list.
+ """
+ # Chars don't animate their nametags.
+ return []
+
+
+ def loadChatterDialogue(self, name, audioIndexArray,
+ loadPath, language):
+ """
+ Load the dialogue audio samples
+ """
+ # load the audio files and store into the dialogue array
+ chatterTypes = ['greetings', 'comments', 'goodbyes']
+ for categoryIndex in range(len(audioIndexArray)):
+ chatterType = chatterTypes[categoryIndex]
+ for fileIndex in audioIndexArray[categoryIndex]:
+ if fileIndex:
+ self.chatterArray[categoryIndex].append(
+ base.loadSfx("%s/CC_%s_chatter_%s%02d.mp3" %
+ (loadPath, name, chatterType, fileIndex))
+ )
+ else:
+ # Just in case you have non contiguous chatter files
+ self.chatterArray[categoryIndex].append(None)
+
+ def loadDialogue(self, char):
+ """
+ Load the dialogue audio samples
+ """
+ if self.dialogueArray:
+ # We've already got a dialogueArray loaded.
+ self.notify.warning("loadDialogue() called twice.")
+
+ self.unloadDialogue()
+
+ language = base.config.GetString("language", "english")
+
+ if (char == "mk"):
+ # load Mickey's dialogue array
+ dialogueFile = base.loadSfx("phase_3/audio/dial/mickey.wav")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ # load Mickey's chatter
+ if language == 'japanese':
+ chatterIndexArray = (
+ # Greetings
+ [1,2],
+ # Comments
+ [1,2,3,4],
+ # Goodbyes
+ [1,2,3,4,5],
+ )
+ self.loadChatterDialogue("mickey", chatterIndexArray,
+ "phase_3/audio/dial", language)
+ elif (char == "vmk"):
+ # load Mickey's dialogue array
+ dialogueFile = base.loadSfx("phase_3/audio/dial/mickey.wav")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ # load Mickey's chatter
+ if language == 'japanese':
+ chatterIndexArray = (
+ # Greetings
+ [1,2],
+ # Comments
+ [1,2,3,4],
+ # Goodbyes
+ [1,2,3,4,5],
+ )
+ self.loadChatterDialogue("mickey", chatterIndexArray,
+ "phase_3/audio/dial", language)
+ elif (char == "mn" or char == "wmn"):
+ # load Minnie's dialogue array
+ dialogueFile = base.loadSfx("phase_3/audio/dial/minnie.wav")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ # load Minnie's chatter
+ if language == 'japanese':
+ chatterIndexArray = (
+ # Greetings
+ [1,2],
+ # Comments
+ [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],
+ # Goodbyes
+ [1,2,3],
+ )
+ self.loadChatterDialogue("minnie", chatterIndexArray,
+ "phase_3/audio/dial", language)
+ elif (char == "dd"):
+ # load Daisy's dialogue array
+ dialogueFile = base.loadSfx("phase_4/audio/dial/daisy.wav")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ # load Diasy's chatter
+ if language == 'japanese':
+ chatterIndexArray = (
+ # Greetings
+ [1,2,3],
+ # Comments
+ [1,2,3,4,5,6,7,8,9,10,11,12],
+ # Goodbyes
+ [1,2,3,4],
+ )
+ self.loadChatterDialogue("daisy", chatterIndexArray,
+ "phase_8/audio/dial", language)
+ elif (char == "g" or char == "sg"):
+ # load Goofy's dialogue array
+ dialogueFile = base.loadSfx("phase_6/audio/dial/goofy.wav")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ # load Goofy's chatter
+ if language == 'japanese':
+ chatterIndexArray = (
+ # Greetings
+ [1,2,3],
+ # Comments
+ [1,2,3,4,5,6,7,8,9,10,11,12],
+ # Goodbyes
+ [1,2,3,4],
+ )
+ self.loadChatterDialogue("goofy", chatterIndexArray,
+ "phase_6/audio/dial", language)
+ elif (char == "d" or char == "dw"):
+ # load Donald's dialogue array
+ dialogueFile = base.loadSfx("phase_6/audio/dial/donald.wav")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ if char == 'd':
+ # load Donalds's chatter
+ if language == 'japanese':
+ chatterIndexArray = (
+ # Greetings
+ [1,2],
+ # Comments
+ [1,2,3,4,5,6,7,8,9,10,11],
+ # Goodbyes
+ [1,2,3,4],
+ )
+ self.loadChatterDialogue("donald", chatterIndexArray,
+ "phase_6/audio/dial", language)
+ elif (char == "p" or char == "wp"):
+ # load Pluto's dialogue array
+ dialogueFile = base.loadSfx("phase_3.5/audio/dial/AV_dog_med.mp3")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ elif (char == "cl"):
+ # TODO: load Clarabelle's dialog array
+ dialogueFile = base.loadSfx("phase_3.5/audio/dial/AV_dog_med.mp3")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ elif (char == "ch"):
+ dialogueFile = base.loadSfx("phase_6/audio/dial/chip.wav")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ elif (char == "da"):
+ dialogueFile = base.loadSfx("phase_6/audio/dial/dale.wav")
+ for i in range(0,6):
+ self.dialogueArray.append(dialogueFile)
+ else:
+ self.notify.error("unknown character %s" % char)
+
+
+
+ def unloadDialogue(self):
+ """
+ Unload the dialogue audio samples
+ """
+ self.dialogueArray = []
+ self.chatterArray = [[], [], []]
+
+
+ ###
+ ### Tasks
+ ###
+
+ def __blinkOpenEyes(self, task):
+ self.openEyes()
+ # Do a double blink every once in a while
+ r = random.random()
+ if r < 0.1:
+ # Short time for a double blink
+ t = 0.2
+ else:
+ # Pick a random time for the next blink
+ # We can just reuse r here instead of computing a new one
+ t = r * 4.0 + 1.0
+ taskMgr.doMethodLater(t, self.__blinkCloseEyes, self.__blinkName)
+ return Task.done
+
+ def __blinkCloseEyes(self, task):
+ self.closeEyes()
+ taskMgr.doMethodLater(0.125, self.__blinkOpenEyes, self.__blinkName)
+ return Task.done
+
+ def openEyes(self):
+ if (self.geoEyes):
+ for part in self.eyeOpenList:
+ part.show()
+ for part in self.eyeCloseList:
+ part.hide()
+ else:
+ if self.eyes:
+ self.eyes.setTexture(self.eyesOpen, 1)
+ self.lpupil.show()
+ self.rpupil.show()
+
+
+ def closeEyes(self):
+ if (self.geoEyes):
+ for part in self.eyeOpenList:
+ part.hide()
+ for part in self.eyeCloseList:
+ part.show()
+ else:
+ if self.eyes:
+ self.eyes.setTexture(self.eyesClosed, 1)
+ self.lpupil.hide()
+ self.rpupil.hide()
+
+
+ def startBlink(self):
+ """
+ Starts the Blink task.
+ """
+ if (self.eyesOpen or self.geoEyes):
+ # remove any old
+ taskMgr.remove(self.__blinkName)
+ # spawn the new task
+ taskMgr.doMethodLater(random.random() * 4 + 1, self.__blinkCloseEyes, self.__blinkName)
+
+ def stopBlink(self):
+ if (self.eyesOpen or self.geoEyes):
+ taskMgr.remove(self.__blinkName)
+ self.openEyes()
+
+## def startEarTask(self):
+## if self.ears:
+## def earTask(task):
+## for ear in self.ears:
+## ear.headsUp(base.camera)
+## return Task.cont
+## taskMgr.add(earTask, self.style.getCharName() + "-earTask")
+
+## def stopEarTask(self):
+## if self.ears:
+## taskMgr.remove(self.style.getCharName() + "-earTask")
+
+ def startEarTask(self):
+ # This used to be an actual task that would rotate the ears to
+ # the camera every frame. But on reflection, this is just
+ # what a billboard does; so now we just make the ears a
+ # billboard (above) and this task doesn't need to exist.
+ pass
+
+ def stopEarTask(self):
+ pass
+
+ def uniqueName(self, idString):
+ return (idString + "-" + str(self.this))
diff --git a/toontown/src/char/CharDNA.py b/toontown/src/char/CharDNA.py
new file mode 100644
index 0000000..c6cd985
--- /dev/null
+++ b/toontown/src/char/CharDNA.py
@@ -0,0 +1,158 @@
+"""AvatarDNA module: contains the methods and definitions for describing
+multipart actors with a simple class"""
+
+import random
+from pandac.PandaModules import *
+from direct.directnotify.DirectNotifyGlobal import *
+import random
+from direct.distributed.PyDatagram import PyDatagram
+from direct.distributed.PyDatagramIterator import PyDatagramIterator
+from otp.avatar import AvatarDNA
+
+notify = directNotify.newCategory("CharDNA")
+
+# char defines
+charTypes = [ "mk", "vmk", "mn", "wmn", "g", "sg", "d", "dw", "p", "wp", "cl", "dd", "ch", "da" ]
+# ...mickey, vampire mickey, minnie, Witch minnie, goofy, donald, donald-wheel, pluto, Clarabelle, Daisy, Chip, Dale
+
+class CharDNA(AvatarDNA.AvatarDNA):
+ """CharDNA class: contains methods for describing avatars with a
+ simple class. The CharDNA class may be converted to lists of strings
+ for network transmission. Also, CharDNA objects can be constructed
+ from lists of strings recieved over the network. Some examples are in
+ order.
+
+ # create a character's dna (defaults to Mickey)
+ dna = AvatarDNA()
+ dna.newChar()
+
+ # create a specific char by passing in an identifier
+ # string (see 'char types' above)
+ dna = AvatarDNA()
+ dna.newChar('mk')
+ # mickey
+
+ """
+ # special methods
+
+ def __init__(self, str=None, type=None, dna=None, r=None, b=None, g=None):
+ """__init__(self, string=None, string=None, string()=None, float=None,
+ float=None, float=None)
+ CharDNA contructor - see class comment for usage
+ """
+ # have they passed in a stringified DNA object?
+ if (str != None):
+ self.makeFromNetString(str)
+ # have they specified what type of DNA?
+ elif (type != None):
+ if (type == 'c'): # Char
+ self.newChar(dna)
+ else:
+ # Invalid type
+ assert(0)
+ else:
+ # mark DNA as undefined
+ self.type = 'u'
+
+ def __str__(self):
+ """__str__(self)
+ Avatar DNA print method
+ """
+ if (self.type == 'c'):
+ return "type = char, name = %s" % self.name
+ else:
+ return "type undefined"
+
+
+ # stringification methods
+ def makeNetString(self):
+ dg = PyDatagram()
+ dg.addFixedString(self.type, 1)
+ if (self.type == 'c'): # Char
+ dg.addFixedString(self.name, 2)
+ elif (self.type == 'u'):
+ notify.error("undefined avatar")
+ else:
+ notify.error("unknown avatar type: ", self.type)
+
+ return dg.getMessage()
+
+ def makeFromNetString(self, string):
+ dg=PyDatagram(string)
+ dgi=PyDatagramIterator(dg)
+ self.type = dgi.getFixedString(1)
+ if (self.type == 'c'): # Char
+ self.name = sgi.getFixedString(2)
+ else:
+ notify.error("unknown avatar type: ", self.type)
+
+ return None
+
+ def __defaultChar(self):
+ """__defaultChar(self)
+ Make a default character dna
+ """
+ self.type = 'c'
+ self.name = charTypes[0]
+
+ def newChar(self, name = None):
+ """newChar(self, string = None)
+ Make the dna for the given character name,
+ or Mickey if not specified
+ """
+ if (name == None):
+ self.__defaultChar()
+ else:
+ self.type = 'c'
+ if (name in charTypes):
+ self.name = name
+ else:
+ notify.error("unknown avatar type: %s" % (name))
+
+ def getType(self):
+ """getType(self)
+ Return which type of actor this dna represents.
+ """
+ if (self.type == 'c'):
+ #char type
+ type = self.getCharName()
+ else:
+ notify.error("Invalid DNA type: ", self.type)
+
+ return type
+
+ # char helper funcs
+ def getCharName(self):
+ """getCharName(self)
+ Return the type of the character as a string
+ """
+ if (self.name == "mk"):
+ return("mickey")
+ elif(self.name=="vmk"):
+ return("vampire_mickey")
+ elif (self.name == "mn"):
+ return("minnie")
+ elif (self.name == "wmn"):
+ return("witch_minnie")
+ elif (self.name == "g"):
+ return("goofy")
+ elif (self.name == "sg"):
+ return("super_goofy")
+ elif (self.name == "d"):
+ return("donald")
+ elif (self.name == "dw"):
+ return("donald-wheel")
+ elif (self.name == "dd"):
+ return("daisy")
+ elif (self.name == "p"):
+ return("pluto")
+ elif( self.name == "wp"):
+ return("western_pluto")
+ elif (self.name == "cl"):
+ return("clarabelle")
+ elif (self.name == "ch"):
+ return("chip")
+ elif (self.name == "da"):
+ return("dale")
+ else:
+ notify.error("unknown char type: ", self.name)
diff --git a/toontown/src/char/DistributedChar.py b/toontown/src/char/DistributedChar.py
new file mode 100644
index 0000000..d11f767
--- /dev/null
+++ b/toontown/src/char/DistributedChar.py
@@ -0,0 +1,54 @@
+"""DistributedChar module: contains the DistributedChar class"""
+
+from otp.avatar import DistributedAvatar
+import Char
+
+class DistributedChar(DistributedAvatar.DistributedAvatar, Char.Char):
+ """DistributedChar class:"""
+
+ def __init__(self, cr):
+ try:
+ self.DistributedChar_initialized
+ except:
+ self.DistributedChar_initialized = 1
+ DistributedAvatar.DistributedAvatar.__init__(self, cr)
+ Char.Char.__init__(self)
+
+ def delete(self):
+ try:
+ self.DistributedChar_deleted
+ except:
+ self.DistributedChar_deleted = 1
+ Char.Char.delete(self)
+ DistributedAvatar.DistributedAvatar.delete(self)
+
+ # We need to force the Char version of these to be called, otherwise
+ # we get the generic Avatar version which is undefined
+ def setDNAString(self, dnaString):
+ Char.Char.setDNAString(self, dnaString)
+
+ def setDNA(self, dna):
+ Char.Char.setDNA(self, dna)
+
+ def playDialogue(self, *args):
+ # Force the right inheritance chain to be called
+ Char.Char.playDialogue(self, *args)
+
+# def setPos(self, x, y, z):
+# from ToonBaseGlobal import *
+# self.reparentTo(render)
+# Char.Char.setPos(self, x, y, z)
+
+# def setHpr(self, h, p, r):
+# from ToonBaseGlobal import *
+# self.reparentTo(render)
+# Char.Char.setHpr(self, h, p, r)
+
+# def setPosHpr(self, x, y, z, h, p, r):
+# from ToonBaseGlobal import *
+# self.reparentTo(render)
+# Char.Char.setPosHpr(self, x, y, z, h, p, r)
+
+ def setHp(self, hp):
+ self.hp = hp
+
diff --git a/toontown/src/char/LocalChar.py b/toontown/src/char/LocalChar.py
new file mode 100644
index 0000000..3c9f98c
--- /dev/null
+++ b/toontown/src/char/LocalChar.py
@@ -0,0 +1,27 @@
+"""LocalChar module: contains the LocalChar class"""
+
+import DistributedChar
+from otp.avatar import LocalAvatar
+from otp.chat import ChatManager
+import Char
+
+class LocalChar(DistributedChar.DistributedChar, LocalAvatar.LocalAvatar):
+ """LocalChar class:"""
+
+ def __init__(self, cr):
+ """
+ Local char constructor
+ """
+ try:
+ self.LocalChar_initialized
+ except:
+ self.LocalChar_initialized = 1
+ DistributedChar.DistributedChar.__init__(self, cr)
+ LocalAvatar.LocalAvatar.__init__(self, cr)
+ # Is this redundant with LocalAvatar: ---> self.chatMgr = ChatManager.ChatManager()
+ self.setNameVisible(0)
+
+ # Init the avatar sounds
+ Char.initializeDialogue()
+
+
diff --git a/toontown/src/char/Sources.pp b/toontown/src/char/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/toontown/src/char/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/toontown/src/char/__init__.py b/toontown/src/char/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/chat/.cvsignore b/toontown/src/chat/.cvsignore
new file mode 100644
index 0000000..985f113
--- /dev/null
+++ b/toontown/src/chat/.cvsignore
@@ -0,0 +1,4 @@
+.cvsignore
+Makefile
+pp.dep
+*.pyc
diff --git a/toontown/src/chat/ResistanceChat.py b/toontown/src/chat/ResistanceChat.py
new file mode 100644
index 0000000..a37b85a
--- /dev/null
+++ b/toontown/src/chat/ResistanceChat.py
@@ -0,0 +1,219 @@
+from direct.interval.IntervalGlobal import *
+from pandac.PandaModules import *
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownBattleGlobals
+from toontown.battle import BattleParticles
+from direct.particles import Particles
+import random
+
+TTBG = ToontownBattleGlobals
+TTL = TTLocalizer
+
+EFFECT_RADIUS = 30
+
+# Do not change the order of these. Only add to the end!
+# Explanation is below.
+RESISTANCE_TOONUP = 0
+RESISTANCE_RESTOCK = 1
+RESISTANCE_MONEY = 2
+
+resistanceMenu = [ RESISTANCE_TOONUP,
+ RESISTANCE_RESTOCK,
+ RESISTANCE_MONEY,
+ ]
+
+# the DB stores values for these based off of the main "resistance
+# type" (above) and the index in to the 'values' array for each.
+# This means that the 'values' array should only be added to, and
+# never reorganized. The 'items' array is what determines which
+# items to allow and in what order they appear in the menu. The
+# 'items' array may be changed willy-nilly :)
+resistanceDict = {
+ RESISTANCE_TOONUP: { 'menuName': TTL.ResistanceToonupMenu,
+ 'itemText': TTL.ResistanceToonupItem,
+ 'chatText': TTL.ResistanceToonupChat,
+ 'values' : [10, 20, 40, 80, -1],
+ 'items' : [0, 1, 2, 3, 4],
+ },
+ RESISTANCE_MONEY: { 'menuName': TTL.ResistanceMoneyMenu,
+ 'itemText': TTL.ResistanceMoneyItem,
+ 'chatText': TTL.ResistanceMoneyChat,
+ 'values' : [100, 200, 350, 600],
+ 'items' : [0, 1, 2, 3],
+ },
+ RESISTANCE_RESTOCK: { 'menuName': TTL.ResistanceRestockMenu,
+ 'itemText': TTL.ResistanceRestockItem,
+ 'chatText': TTL.ResistanceRestockChat,
+ 'values' : [TTBG.HEAL_TRACK,
+ TTBG.TRAP_TRACK,
+ TTBG.LURE_TRACK,
+ TTBG.SOUND_TRACK,
+ TTBG.THROW_TRACK,
+ TTBG.SQUIRT_TRACK,
+ TTBG.DROP_TRACK,
+ -1],
+ 'extra' : [TTL.MovieNPCSOSHeal,
+ TTL.MovieNPCSOSTrap,
+ TTL.MovieNPCSOSLure,
+ TTL.MovieNPCSOSSound,
+ TTL.MovieNPCSOSThrow,
+ TTL.MovieNPCSOSSquirt,
+ TTL.MovieNPCSOSDrop,
+ TTL.MovieNPCSOSAll
+ ],
+ 'items' : [0, 1, 2, 3, 4, 5, 6, 7],
+ },
+ }
+
+def encodeId(menuIndex, itemIndex):
+ textId = menuIndex * 100
+ textId += resistanceDict[menuIndex]['items'][itemIndex]
+ return textId
+
+def decodeId(textId):
+ menuIndex = int(textId/100)
+ itemIndex = textId % 100
+ return (menuIndex, itemIndex)
+
+def validateId(textId):
+ # Returns true if the id is valid, false otherwise.
+ if textId < 0:
+ return 0
+
+ menuIndex, itemIndex = decodeId(textId)
+ if not resistanceDict.has_key(menuIndex):
+ return 0
+
+ if itemIndex >= len(resistanceDict[menuIndex]['values']):
+ return 0
+
+ return 1
+
+def getItems(menuIndex):
+ return resistanceDict[menuIndex]['items']
+
+def getMenuName(textId):
+ menuIndex, itemIndex = decodeId(textId)
+ return resistanceDict[menuIndex]['menuName']
+
+def getItemText(textId):
+ menuIndex, itemIndex = decodeId(textId)
+ value = resistanceDict[menuIndex]['values'][itemIndex]
+ text = resistanceDict[menuIndex]['itemText']
+ if menuIndex is RESISTANCE_TOONUP:
+ if value is -1:
+ value = TTL.ResistanceToonupItemMax
+ elif menuIndex is RESISTANCE_RESTOCK:
+ value = resistanceDict[menuIndex]['extra'][itemIndex]
+
+ return text % str(value)
+
+def getChatText(textId):
+ menuIndex, itemIndex = decodeId(textId)
+ return resistanceDict[menuIndex]['chatText']
+
+def getItemValue(textId):
+ menuIndex, itemIndex = decodeId(textId)
+ return resistanceDict[menuIndex]['values'][itemIndex]
+
+def getRandomId():
+ # Returns a randomly-chosen resistance message id.
+
+ # We weight the distribution evenly between the three broad types,
+ # and within a given type, evenly among the several items
+ # available for that type.
+
+ menuIndex = random.choice(resistanceMenu)
+ itemIndex = random.choice(getItems(menuIndex))
+ return encodeId(menuIndex, itemIndex)
+
+def doEffect(textId, speakingToon, nearbyToons):
+ # Plays the particle effect of the resistance chat out of
+ # speakingToon, and briefly colors all involved toons, so they can
+ # tell what's up.
+
+ menuIndex, itemIndex = decodeId(textId)
+ itemValue = getItemValue(textId)
+
+ if menuIndex == RESISTANCE_TOONUP:
+ effect = BattleParticles.loadParticleFile('resistanceEffectSparkle.ptf')
+ fadeColor = VBase4(1, 0.5, 1, 1)
+
+ elif menuIndex == RESISTANCE_MONEY:
+ effect = BattleParticles.loadParticleFile('resistanceEffectBean.ptf')
+ bean = loader.loadModel('phase_4/models/props/jellybean4.bam')
+ bean = bean.find('**/jellybean')
+
+ # Apply a different-colored bean to each nested particle system.
+ colors = {
+ 'particles-1' : (1, 1, 0, 1),
+ 'particles-2' : (1, 0, 0, 1),
+ 'particles-3' : (0, 1, 0, 1),
+ 'particles-4' : (0, 0, 1, 1),
+ 'particles-5' : (1, 0, 1, 1),
+ }
+ for name, color in colors.items():
+ node = bean.copyTo(NodePath())
+ node.setColorScale(*color)
+
+ p = effect.getParticlesNamed(name)
+ p.renderer.setGeomNode(node.node())
+
+ fadeColor = VBase4(0, 1, 0, 1)
+
+ elif menuIndex == RESISTANCE_RESTOCK:
+ effect = BattleParticles.loadParticleFile('resistanceEffectSprite.ptf')
+ invModel = loader.loadModel("phase_3.5/models/gui/inventory_icons")
+ invModel.setScale(4)
+ invModel.flattenLight()
+
+ # Choose six icons.
+ icons = []
+ if itemValue != -1:
+ for item in range(6):
+ iconName = ToontownBattleGlobals.AvPropsNew[itemValue][item]
+ icons.append(invModel.find('**/%s' % (iconName)))
+ else:
+ # Choose a random six items, one from each of six tracks.
+ tracks = range(7)
+ random.shuffle(tracks)
+ for i in range(6):
+ track = tracks[i]
+ item = random.randint(0, 5)
+ iconName = ToontownBattleGlobals.AvPropsNew[track][item]
+ icons.append(invModel.find('**/%s' % (iconName)))
+
+ # Apply a different icon to each nested particle system.
+ iconDict = {
+ 'particles-1' : icons[0],
+ 'particles-2' : icons[1],
+ 'particles-3' : icons[2],
+ 'particles-4' : icons[3],
+ 'particles-5' : icons[4],
+ 'particles-6' : icons[5],
+ }
+ for name, icon in iconDict.items():
+ p = effect.getParticlesNamed(name)
+ p.renderer.setFromNode(icon)
+
+ fadeColor = VBase4(0, 0, 1, 1)
+
+ else:
+ return
+
+ recolorToons = Parallel()
+ for toonId in nearbyToons:
+ toon = base.cr.doId2do.get(toonId)
+ if toon and not toon.ghostMode:
+ i = Sequence(toon.doToonColorScale(fadeColor, 0.3),
+ toon.doToonColorScale(toon.defaultColorScale, 0.3),
+ Func(toon.restoreDefaultColorScale))
+
+ recolorToons.append(i)
+
+
+ i = Parallel(ParticleInterval(effect, speakingToon, worldRelative = 0, duration = 3, cleanup = True),
+ Sequence(Wait(0.2), recolorToons),
+ autoFinish = 1)
+ i.start()
+
diff --git a/toontown/src/chat/Sources.pp b/toontown/src/chat/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/toontown/src/chat/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/toontown/src/chat/TTChatInputNormal.py b/toontown/src/chat/TTChatInputNormal.py
new file mode 100644
index 0000000..2735056
--- /dev/null
+++ b/toontown/src/chat/TTChatInputNormal.py
@@ -0,0 +1,129 @@
+"""TTChatInputNormal module: contains the TTChatInputNormal class"""
+
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from otp.chat import ChatInputNormal
+from otp.otpbase import OTPLocalizer
+from otp.otpbase import OTPGlobals
+
+class TTChatInputNormal(ChatInputNormal.ChatInputNormal):
+ """TTChatInputNormal class: controls the chat input bubble, and handles
+ chat message construction."""
+
+ # special methods
+ def __init__(self, chatMgr):
+ ChatInputNormal.ChatInputNormal.__init__(self, chatMgr)
+
+ # load the chat balloon
+ gui = loader.loadModel("phase_3.5/models/gui/chat_input_gui")
+
+ self.chatFrame = DirectFrame(
+ parent = aspect2dp,
+ image = gui.find("**/Chat_Bx_FNL"),
+ relief = None,
+ pos = (-1.083, 0, 0.804),
+ state = DGG.NORMAL,
+ sortOrder = DGG.FOREGROUND_SORT_INDEX,
+ )
+
+ self.chatFrame.hide()
+
+ self.chatButton = DirectButton(
+ parent = self.chatFrame,
+ image = (gui.find("**/ChtBx_ChtBtn_UP"),
+ gui.find("**/ChtBx_ChtBtn_DN"),
+ gui.find("**/ChtBx_ChtBtn_RLVR"),
+ ),
+ pos = (0.182, 0, -0.088),
+ relief = None,
+ text = ("",
+ OTPLocalizer.ChatInputNormalSayIt,
+ OTPLocalizer.ChatInputNormalSayIt),
+ text_scale = 0.06,
+ text_fg = Vec4(1,1,1,1),
+ text_shadow = Vec4(0,0,0,1),
+ text_pos = (0,-0.09),
+ textMayChange = 0,
+ command = self.chatButtonPressed,
+ )
+
+ self.cancelButton = DirectButton(
+ parent = self.chatFrame,
+ image = (gui.find("**/CloseBtn_UP"),
+ gui.find("**/CloseBtn_DN"),
+ gui.find("**/CloseBtn_Rllvr"),
+ ),
+ pos = (-0.151, 0, -0.088),
+ relief = None,
+ text = ("",
+ OTPLocalizer.ChatInputNormalCancel,
+ OTPLocalizer.ChatInputNormalCancel),
+ text_scale = 0.06,
+ text_fg = Vec4(1,1,1,1),
+ text_shadow = Vec4(0,0,0,1),
+ text_pos = (0,-0.09),
+ textMayChange = 0,
+ command = self.cancelButtonPressed,
+ )
+
+ self.whisperLabel = DirectLabel(
+ parent = self.chatFrame,
+ pos = (0.02, 0, 0.23),
+ relief = DGG.FLAT,
+ frameColor = (1,1,0.5,1),
+ frameSize = (-0.23, 0.23, -0.07, 0.05),
+ text = OTPLocalizer.ChatInputNormalWhisper,
+ text_scale = 0.04,
+ text_fg = Vec4(0,0,0,1),
+ text_wordwrap = 9.5,
+ textMayChange = 1,
+ )
+ self.whisperLabel.hide()
+
+ self.chatEntry = DirectEntry(
+ parent = self.chatFrame,
+ relief = None,
+ scale = 0.05,
+ pos = (-0.2,0,0.11),
+ entryFont = OTPGlobals.getInterfaceFont(),
+ width = 8.6,
+ numLines = 3,
+ cursorKeys = 0,
+ backgroundFocus = 0, # This will be turned on later by the chatMgr
+ command = self.sendChat,
+ )
+
+ self.chatEntry.bind(DGG.OVERFLOW, self.chatOverflow)
+ self.chatEntry.bind(DGG.TYPE, self.typeCallback)
+
+
+ def delete(self):
+ self.chatEntry.destroy()
+ self.chatButton.destroy()
+ self.cancelButton.destroy()
+ ChatInputNormal.ChatInputNormal.delete(self)
+ loader.unloadModel("phase_3.5/models/gui/chat_input_gui")
+
+ def importExecNamespace(self):
+ ChatInputNormal.ChatInputNormal.importExecNamespace(self)
+
+ exec 'from toontown.toonbase.ToonBaseGlobal import *' in globals(), self.ExecNamespace
+
+ def typeCallback(self, extraArgs):
+ #if hasattr(base, "whiteList"):
+ # if base.whiteList:
+ # return
+ #print("typeCallback")
+
+ if localAvatar.chatMgr.chatInputWhiteList.isActive():
+ return
+ else:
+ messenger.send('enterNormalChat')
+
+ def checkForOverRide(self):
+ return False
+ #ChatInputNormal likes to intercept other direct entries
+ #to much is hard coded to assume this so I'm adding a final stage override - JML
+ if localAvatar.chatMgr.chatInputWhiteList.isActive():
+ return True
+ return False
diff --git a/toontown/src/chat/TTChatInputSpeedChat.py b/toontown/src/chat/TTChatInputSpeedChat.py
new file mode 100644
index 0000000..6ff1f1d
--- /dev/null
+++ b/toontown/src/chat/TTChatInputSpeedChat.py
@@ -0,0 +1,722 @@
+"""TTChatInputSpeedChat module: contains the TTChatInputSpeedChat class"""
+
+"""
+from toontown.chat import TTChatInputSpeedChat;from otp.otpbase import OTPLocalizerEnglish;from otp.otpbase import OTPLocalizer;from otp.speedchat import SCStaticTextTerminal
+reload(TTChatInputSpeedChat);reload(OTPLocalizerEnglish);reload(OTPLocalizer);reload(SCStaticTextTerminal);base.localAvatar.chatMgr.chatInputSpeedChat.createSpeedChat();base.localAvatar.chatMgr.chatInputSpeedChat.handleSpeedChatStyleChange()
+"""
+
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from otp.speedchat.SpeedChatTypes import *
+from toontown.speedchat.TTSpeedChatTypes import *
+from otp.speedchat.SpeedChat import SpeedChat
+from otp.speedchat import SpeedChatGlobals
+from toontown.speedchat import TTSpeedChatGlobals
+from toontown.speedchat import TTSCSingingTerminal
+from toontown.speedchat import TTSCIndexedTerminal
+from direct.showbase import DirectObject
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+import string
+from otp.otpbase import OTPLocalizer
+from otp.otpbase import OTPGlobals
+from toontown.shtiker.OptionsPage import speedChatStyles
+from toontown.toonbase import TTLocalizer
+from toontown.parties.PartyGlobals import ActivityIds, DecorationIds
+from toontown.toonbase import ToontownGlobals
+
+###### see SpeedChat.py for the format of this structure ######
+scStructure = [
+ [OTPLocalizer.SCMenuHello,
+ {100:0},{101:0},{102:0},{103:0},{104:0},{105:0},106,107,108,109],
+
+ [OTPLocalizer.SCMenuBye,
+ # 208: put "I'll be back later" under "I'll be right back"
+ # 209: put "I'll need to go soon" before "I need to go"
+ # NOTE: "I need to go soon" is already a cattlelog phrase, but
+ # it seems like a very useful phrase that everyone should have...
+ {200:0},{201:0},{202:0},203,204,205,206,208,209,207],
+
+ [OTPLocalizer.SCMenuHappy,
+ {300:1},{301:1},{302:1},303,{304:1},305,306,307,308,
+ 309,310,311,{312:1},{313:1},{314:1},315],
+
+ [OTPLocalizer.SCMenuSad,
+ {400:2},{401:2},{402:2},403,404,405,406,407,408,409,410],
+
+ [OTPLocalizer.SCMenuFriendly,
+ [OTPLocalizer.SCMenuFriendlyYou, 600,601,602,603],
+ [OTPLocalizer.SCMenuFriendlyILike, 700,701,702,703,704,705],
+ # Have you been here before? (515) after Are you new here?
+ 500,501,502,503,504,505,506,507,508,509,510,515,511,512,513,514],
+
+ [OTPLocalizer.SCMenuSorry,
+ # Put oops (801) in front of sorry (800) - it looks better. Also add
+ # Sorry I'm busy fishing (811) next to the other "Sorry I'm busy..."
+ # phrases
+ # "Sorry, I'm in a building" and "Sorry, I'm helping a friend" (812/813)
+ # below "Sorry I'm busy..." phrases
+ 801,800,802,803,804,811,814,815,817,812,813,818,805,806,807,816,808,{809:5},810],
+
+ [OTPLocalizer.SCMenuStinky,
+ {900:3},{901:3},{902:3},{903:3},904,{905:3},907],
+
+ [OTPLocalizer.SCMenuPlaces,
+# [OTPLocalizer.SCMenuPlacesLetsGo, 1100,1101,1111,1102,1103,1104,1105,1106,
+# 1107,1108,1109,1110],
+ [OTPLocalizer.SCMenuPlacesPlayground, 1100,1101,1105,1106,1107,1108,1109,1110,1111,1117,1125,1126],
+ [OTPLocalizer.SCMenuPlacesCogs, 1102,1103,1104,1114,1115,1116,1119,1120,1121,1122,1123,1124,1127,1128,1129],
+ [OTPLocalizer.SCMenuPlacesEstate, 1112,1113,1013,1118,1016],
+ [OTPLocalizer.SCMenuParties, 5300, 5301, 5302, 5303],
+ [OTPLocalizer.SCMenuPlacesWait, 1015,1007,1008,1010,1011,1014,1017],
+ 1000,1001,1002,1003,1004,1005,1006,1009,1012],
+
+ [OTPLocalizer.SCMenuToontasks,
+ [TTSCToontaskMenu, OTPLocalizer.SCMenuToontasksMyTasks],
+ [OTPLocalizer.SCMenuToontasksYouShouldChoose, 1300,1301,1302,1303,1304],
+ # This isn't what you need (1208) after This isn't what I'm looking for
+ # I found what you need (1209) after I'm going to look for that
+ [OTPLocalizer.SCMenuToontasksINeedMore, 1206,1210,1211,1212,1207,1213,1214,1215],
+ 1200,1201,1202,1208,1203,1209,1204,1205,],
+
+ [OTPLocalizer.SCMenuBattle,
+ [OTPLocalizer.SCMenuBattleGags, 1500,1501,1502,1503,1504,1505,1506,
+ 1401,1402,1413,],
+ # put Piece of cake! and That was easy! (1407/1408) at the end
+ [OTPLocalizer.SCMenuBattleTaunts, 1403,1406,1520,1521,1522,1523,
+ 1524,1525,1526,1407,1408,],
+ [OTPLocalizer.SCMenuBattleStrategy, 1414,1550,1551,1552,1415,1553,1554,
+ 1555,1556,1557,1558,1559,],
+ # "We can do it" (1416) before "You did it" (1404)
+ 1400,1416,1404,1405,1409,1410,1411,1412,],
+
+ [OTPLocalizer.SCMenuGagShop,
+ 1600,1601,1602,1603,1604,1605,1606],
+
+ {1:17}, # Yes
+ {2:18}, # No
+ 3, # Ok
+ #4 whiteList,
+ ]
+
+if hasattr(base, 'wantPets') and base.wantPets:
+ scPetMenuStructure = [
+ [OTPLocalizer.SCMenuPets,
+ [TTSCPetTrickMenu, OTPLocalizer.SCMenuPetTricks],
+ 21000,21001,21002,21003,21004,21005,21006]
+ ]
+
+cfoMenuStructure = [
+ [OTPLocalizer.SCMenuCFOBattleCranes,
+ 2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110],
+ [OTPLocalizer.SCMenuCFOBattleGoons,
+ 2120,2121,2122,2123,2124,2125,2126],
+ 2130,2131,2132,2133,1410,
+ ]
+
+cjMenuStructure = [
+ 2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,
+ ]
+
+ceoMenuStructure = [
+ 2300,2301,2302,2303,2304,2305,2306,2307,2312,2313,2314,2315,
+ 2308,2309,2310,2311,2316,2317,
+ ]
+
+class TTChatInputSpeedChat(DirectObject.DirectObject):
+ """TTChatInputSpeedChat class: controls the SpeedChat, and generates
+ SpeedChat messages"""
+
+ DefaultSCColorScheme = SCColorScheme()
+
+ # special methods
+ def __init__(self, chatMgr):
+ self.chatMgr = chatMgr
+
+ self.whisperAvatarId = None
+ self.toPlayer = 0
+
+ # create the panel that tells the user that they don't have
+ # access to an emotion yet
+ buttons = loader.loadModel(
+ 'phase_3/models/gui/dialog_box_buttons_gui')
+ okButtonImage = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+ self.emoteNoAccessPanel = DirectFrame(
+ parent = hidden,
+ relief = None,
+ state = 'normal',
+ text = OTPLocalizer.SCEmoteNoAccessMsg,
+ frameSize = (-1,1,-1,1),
+ geom = DGG.getDefaultDialogGeom(),
+ geom_color = OTPGlobals.GlobalDialogColor,
+ geom_scale = (.92, 1, .6),
+ geom_pos = (0,0,-.08),
+ text_scale = .08,
+ )
+ self.okButton = DirectButton(
+ parent = self.emoteNoAccessPanel,
+ image = okButtonImage,
+ relief = None,
+ text = OTPLocalizer.SCEmoteNoAccessOK,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = (0.0, 0.0, -0.2),
+ command = self.handleEmoteNoAccessDone)
+
+ self.insidePartiesMenu = None
+ self.createSpeedChat()
+ self.whiteList = None
+ self.allowWhiteListSpeedChat = base.config.GetBool('white-list-speed-chat', 0)
+ if self.allowWhiteListSpeedChat:
+ self.addWhiteList()
+
+ self.factoryMenu = None
+ self.kartRacingMenu = None
+ self.cogMenu = None
+ self.cfoMenu = None
+ self.cjMenu = None
+ self.ceoMenu = None
+ self.golfMenu = None
+ self.boardingGroupMenu = None
+ self.singingGroupMenu = None
+ self.aprilToonsMenu = None
+ self.carolMenu = None
+ self.victoryPartiesMenu = None
+ self.sillyPhaseOneMenu = None
+ self.sillyPhaseTwoMenu = None
+ self.sillyPhaseThreeMenu = None
+ self.sillyPhaseFourMenu = None
+ self.sillyPhaseFiveMenu = None
+
+ if __debug__:
+ base.speedChat = self.speedChat
+
+ # listen for selection messages
+ def listenForSCEvent(eventBaseName, handler, self=self):
+ eventName = self.speedChat.getEventName(eventBaseName)
+ self.accept(eventName, handler)
+ listenForSCEvent(SpeedChatGlobals.SCTerminalLinkedEmoteEvent,
+ self.handleLinkedEmote)
+ listenForSCEvent(SpeedChatGlobals.SCStaticTextMsgEvent,
+ self.handleStaticTextMsg)
+ listenForSCEvent(SpeedChatGlobals.SCCustomMsgEvent,
+ self.handleCustomMsg)
+ listenForSCEvent(SpeedChatGlobals.SCEmoteMsgEvent,
+ self.handleEmoteMsg)
+ listenForSCEvent(SpeedChatGlobals.SCEmoteNoAccessEvent,
+ self.handleEmoteNoAccess)
+ listenForSCEvent(TTSpeedChatGlobals.TTSCToontaskMsgEvent,
+ self.handleToontaskMsg)
+ listenForSCEvent(TTSpeedChatGlobals.TTSCResistanceMsgEvent,
+ self.handleResistanceMsg)
+ listenForSCEvent(TTSCSingingTerminal.TTSCSingingMsgEvent,
+ self.handleSingingMsg)
+ listenForSCEvent("SpeedChatStyleChange",
+ self.handleSpeedChatStyleChange)
+ listenForSCEvent(TTSCIndexedTerminal.TTSCIndexedMsgEvent,
+ self.handleStaticTextMsg)
+
+ self.fsm = ClassicFSM.ClassicFSM('SpeedChat',
+ [State.State('off',
+ self.enterOff,
+ self.exitOff,
+ ['active']),
+ State.State('active',
+ self.enterActive,
+ self.exitActive,
+ ['off']),
+ ],
+ # Initial state
+ 'off',
+ # Final state
+ 'off',
+ )
+ self.fsm.enterInitialState()
+
+ def delete(self):
+ self.ignoreAll()
+ self.removeWhiteList()
+ self.okButton.destroy()
+ self.emoteNoAccessPanel.destroy()
+ del self.emoteNoAccessPanel
+ self.speedChat.destroy()
+ del self.speedChat
+ del self.fsm
+ del self.chatMgr
+
+ def show(self, whisperAvatarId = None, toPlayer = 0):
+ self.whisperAvatarId = whisperAvatarId
+ self.toPlayer = toPlayer
+ self.fsm.request("active")
+
+ def hide(self):
+ self.fsm.request("off")
+
+ def createSpeedChat(self):
+ # If we have them configed on, add some menus to the top of the
+ # speedchat.
+ structure = []
+ if (launcher and not launcher.isTestServer()) or __dev__:
+ # menu title will be overwritten
+ structure.append([TTSCPromotionalMenu, OTPLocalizer.SCMenuPromotion])
+ structure.append([SCEmoteMenu, OTPLocalizer.SCMenuEmotions])
+ structure.append([SCCustomMenu, OTPLocalizer.SCMenuCustom])
+ structure.append([TTSCResistanceMenu, OTPLocalizer.SCMenuResistance])
+ if hasattr(base, 'wantPets') and base.wantPets:
+ structure += scPetMenuStructure
+ structure += scStructure
+ self.createSpeedChatObject(structure)
+
+ ### ClassicFSM STATE HANDLERS
+ # 'off' state
+ def enterOff(self):
+ pass
+ def exitOff(self):
+ pass
+
+ # 'active' state
+ def enterActive(self):
+ # if they click anywhere else, close up
+ def handleCancel(self=self):
+ self.chatMgr.fsm.request("mainMenu")
+ self.accept('mouse1', handleCancel)
+
+ # listen for any selection event from the SpeedChat menu
+ def selectionMade(self=self):
+ self.chatMgr.fsm.request("mainMenu")
+ self.terminalSelectedEvent = self.speedChat.getEventName(
+ SpeedChatGlobals.SCTerminalSelectedEvent)
+ if base.config.GetBool('want-sc-auto-hide', 1):
+ # We're not hiding the whole speedchat after a single phrase is selected.
+ # Hence, we're not accepting self.terminalSelectedEvent any more.
+ self.accept(self.terminalSelectedEvent, selectionMade)
+
+ # set up the menu
+ self.speedChat.reparentTo(aspect2dp, DGG.FOREGROUND_SORT_INDEX)
+ scZ = .96
+ self.speedChat.setPos(-1.05, 0, scZ)
+ self.speedChat.setWhisperMode(self.whisperAvatarId != None)
+ self.speedChat.enter()
+
+ def exitActive(self):
+ self.ignore('mouse1')
+ self.ignore(self.terminalSelectedEvent)
+
+ self.speedChat.exit()
+ self.speedChat.reparentTo(hidden)
+
+ self.emoteNoAccessPanel.reparentTo(hidden)
+
+ # these handle selection messages coming from the speedchat object
+ def handleLinkedEmote(self, emoteId):
+ # don't do the emote if we're whispering
+ if self.whisperAvatarId is None:
+ lt = base.localAvatar
+ lt.b_setEmoteState(emoteId, animMultiplier=lt.animMultiplier)
+
+ def handleStaticTextMsg(self, textId):
+ if self.whisperAvatarId is None:
+ self.chatMgr.sendSCChatMessage(textId)
+ else:
+ self.chatMgr.sendSCWhisperMessage(textId, self.whisperAvatarId, self.toPlayer)
+ self.toPlayer = 0
+
+ def handleSingingMsg(self, textId):
+ if self.whisperAvatarId is None:
+ self.chatMgr.sendSCSingingChatMessage(textId)
+ else:
+ self.chatMgr.sendSCSingingWhisperMessage(textId)
+ self.toPlayer = 0
+
+ def handleCustomMsg(self, textId):
+ if self.whisperAvatarId is None:
+ self.chatMgr.sendSCCustomChatMessage(textId)
+ else:
+ self.chatMgr.sendSCCustomWhisperMessage(textId,
+ self.whisperAvatarId, self.toPlayer)
+ self.toPlayer = 0
+
+ def handleEmoteMsg(self, emoteId):
+ if self.whisperAvatarId is None:
+ self.chatMgr.sendSCEmoteChatMessage(emoteId)
+ else:
+ self.chatMgr.sendSCEmoteWhisperMessage(emoteId,
+ self.whisperAvatarId, self.toPlayer)
+ self.toPlayer = 0
+
+ def handleEmoteNoAccess(self):
+ if self.whisperAvatarId is None:
+ self.emoteNoAccessPanel.setPos(0,0,0)
+ else:
+ self.emoteNoAccessPanel.setPos(.37,0,0)
+ self.emoteNoAccessPanel.reparentTo(aspect2d)
+
+ def handleEmoteNoAccessDone(self):
+ self.emoteNoAccessPanel.reparentTo(hidden)
+
+ def handleToontaskMsg(self, taskId, toNpcId, toonProgress, msgIndex):
+ if self.whisperAvatarId is None:
+ self.chatMgr.sendSCToontaskChatMessage(
+ taskId, toNpcId, toonProgress, msgIndex)
+ else:
+ self.chatMgr.sendSCToontaskWhisperMessage(
+ taskId, toNpcId, toonProgress, msgIndex, self.whisperAvatarId, self.toPlayer)
+ self.toPlayer = 0
+
+ def handleResistanceMsg(self, textId):
+ self.chatMgr.sendSCResistanceChatMessage(textId)
+
+ def handleSpeedChatStyleChange(self):
+ # get the color scheme information
+ nameKey, arrowColor, rolloverColor, frameColor = \
+ speedChatStyles[base.localAvatar.getSpeedChatStyleIndex()]
+ # create the new color scheme object
+ newSCColorScheme = SCColorScheme(
+ arrowColor=arrowColor,
+ rolloverColor=rolloverColor,
+ frameColor=frameColor,
+ )
+ # set the new color scheme
+ self.speedChat.setColorScheme(newSCColorScheme)
+
+ def createSpeedChatObject(self, structure):
+ if hasattr(self, 'speedChat'):
+ self.speedChat.exit()
+ self.speedChat.destroy()
+ del self.speedChat
+
+ # get a speedchat object
+ self.speedChat = SpeedChat(
+ structure=structure,
+ backgroundModelName='phase_3/models/gui/ChatPanel',
+ guiModelName='phase_3.5/models/gui/speedChatGui')
+ self.speedChat.setScale(TTLocalizer.CISCspeedChat)
+ self.speedChat.setBin('gui-popup',0)
+ self.speedChat.setTopLevelOverlap(TTLocalizer.CISCtopLevelOverlap)
+ self.speedChat.setColorScheme(self.DefaultSCColorScheme)
+ # tell the speedchat to generate the gui elements for the entire
+ # tree, so that the menu won't be chuggy the first time it's used
+ self.speedChat.finalizeAll()
+
+ def addFactoryMenu(self):
+ if self.factoryMenu == None:
+ menu = TTSCFactoryMenu()
+ self.factoryMenu = SCMenuHolder(OTPLocalizer.SCMenuFactory, menu=menu)
+ self.speedChat[2:2] = [self.factoryMenu]
+
+ def removeFactoryMenu(self):
+ if self.factoryMenu:
+ i = self.speedChat.index(self.factoryMenu)
+ del self.speedChat[i]
+ self.factoryMenu.destroy()
+ self.factoryMenu = None
+
+ # methods for creating Kart Racing speed chat menus
+ def addKartRacingMenu(self):
+ if self.kartRacingMenu == None:
+ menu = TTSCKartRacingMenu()
+ self.kartRacingMenu = SCMenuHolder(OTPLocalizer.SCMenuKartRacing, menu=menu)
+ self.speedChat[2:2] = [self.kartRacingMenu]
+
+ def removeKartRacingMenu(self):
+ if self.kartRacingMenu:
+ i = self.speedChat.index(self.kartRacingMenu)
+ del self.speedChat[i]
+ self.kartRacingMenu.destroy()
+ self.kartRacingMenu = None
+
+ def addCogMenu(self, indices):
+ if self.cogMenu == None:
+ menu = TTSCCogMenu(indices)
+ self.cogMenu = SCMenuHolder(OTPLocalizer.SCMenuCog, menu=menu)
+ self.speedChat[2:2] = [self.cogMenu]
+
+ def removeCogMenu(self):
+ if self.cogMenu:
+ i = self.speedChat.index(self.cogMenu)
+ del self.speedChat[i]
+ self.cogMenu.destroy()
+ self.cogMenu = None
+
+ def addCFOMenu(self):
+ if self.cfoMenu == None:
+ menu = SCMenu()
+ menu.rebuildFromStructure(cfoMenuStructure)
+ self.cfoMenu = SCMenuHolder(OTPLocalizer.SCMenuCFOBattle, menu=menu)
+ self.speedChat[2:2] = [self.cfoMenu]
+
+ def removeCFOMenu(self):
+ if self.cfoMenu:
+ i = self.speedChat.index(self.cfoMenu)
+ del self.speedChat[i]
+ self.cfoMenu.destroy()
+ self.cfoMenu = None
+
+ def addCJMenu(self, bonusWeight = -1):
+ """
+ If bonusWeight is -1, don't show the bonus weight option at all
+ """
+ if self.cjMenu == None:
+ menu = SCMenu()
+ myMenuCopy = cjMenuStructure[:]
+ if bonusWeight >= 0:
+ myMenuCopy.append(2211 + bonusWeight)
+ menu.rebuildFromStructure(myMenuCopy)
+ self.cjMenu = SCMenuHolder(OTPLocalizer.SCMenuCJBattle, menu=menu)
+ self.speedChat[2:2] = [self.cjMenu]
+
+ def removeCJMenu(self):
+ if self.cjMenu:
+ i = self.speedChat.index(self.cjMenu)
+ del self.speedChat[i]
+ self.cjMenu.destroy()
+ self.cjMenu = None
+
+ def addCEOMenu(self):
+ if self.ceoMenu == None:
+ menu = SCMenu()
+ menu.rebuildFromStructure(ceoMenuStructure)
+ self.ceoMenu = SCMenuHolder(OTPLocalizer.SCMenuCEOBattle, menu=menu)
+ self.speedChat[2:2] = [self.ceoMenu]
+
+ def removeCEOMenu(self):
+ if self.ceoMenu:
+ i = self.speedChat.index(self.ceoMenu)
+ del self.speedChat[i]
+ self.ceoMenu.destroy()
+ self.ceoMenu = None
+
+
+ def addInsidePartiesMenu(self):
+ """Add the party phrases that can be said inside a party."""
+ def isActivityInParty(activityId):
+ activityList = base.distributedParty.partyInfo.activityList
+ for activity in activityList:
+ if (activity.activityId == activityId):
+ return True
+ return False
+
+ def isDecorInParty(decorId):
+ decorList = base.distributedParty.partyInfo.decors
+ for decor in decorList:
+ if (decor.decorId == decorId):
+ return True
+ return False
+
+ # This is the basic parties speedchat list.
+ insidePartiesMenuStructure = [ 5305, 5306, 5307, 5308, 5309 ]
+
+ if self.insidePartiesMenu == None:
+ menu = SCMenu()
+ # Add elements to the basic list on a case by case basic.
+ if hasattr(base, 'distributedParty') and base.distributedParty:
+ # Add host specific speedchat elements.
+ if (base.distributedParty.partyInfo.hostId == localAvatar.doId):
+ insidePartiesMenuStructure.insert(0, 5304)
+ # Add Jukebox specific speedchat elements.
+ if isActivityInParty(0):
+ insidePartiesMenuStructure.extend([5310, 5311])
+ # Add Cannons specific speedchat elements.
+ if isActivityInParty(1):
+ insidePartiesMenuStructure.append(5312)
+ # Add Trampolines specific speedchat elements.
+ if isActivityInParty(2):
+ insidePartiesMenuStructure.extend([5313, 5314])
+ # Add Catch Game specific speedchat elements.
+ if isActivityInParty(3):
+ insidePartiesMenuStructure.append(5315)
+ # Add Dance Floor specific speedchat elements.
+ if isActivityInParty(4):
+ insidePartiesMenuStructure.extend([5316, 5317])
+ # Add Tug of War specific speedchat elements.
+ if isActivityInParty(5):
+ insidePartiesMenuStructure.append(5318)
+ # Add Fireworks specific speedchat elements.
+ if isActivityInParty(6):
+ insidePartiesMenuStructure.extend([5319, 5320])
+ # Add Decoration specific speedchat elements.
+ if len(base.distributedParty.partyInfo.decors):
+ insidePartiesMenuStructure.append(5321)
+
+ # Add Birthday Cake specific speedchat elements.
+ if isDecorInParty(3):
+ insidePartiesMenuStructure.append(5322)
+
+ menu.rebuildFromStructure(insidePartiesMenuStructure)
+ self.insidePartiesMenu = SCMenuHolder(OTPLocalizer.SCMenuParties, menu=menu)
+ self.speedChat[2:2] = [self.insidePartiesMenu]
+
+ def removeInsidePartiesMenu(self):
+ """Remove party phrases that can be said inside a party."""
+ if self.insidePartiesMenu:
+ i = self.speedChat.index(self.insidePartiesMenu)
+ del self.speedChat[i]
+ self.insidePartiesMenu.destroy()
+ self.insidePartiesMenu = None
+
+ # methods for creating Golf speed chat menus
+ def addGolfMenu(self):
+ if self.golfMenu == None:
+ menu = TTSCGolfMenu()
+ self.golfMenu = SCMenuHolder(OTPLocalizer.SCMenuGolf, menu=menu)
+ self.speedChat[2:2] = [self.golfMenu]
+
+ def removeGolfMenu(self):
+ if self.golfMenu:
+ i = self.speedChat.index(self.golfMenu)
+ del self.speedChat[i]
+ self.golfMenu.destroy()
+ self.golfMenu = None
+
+ # Methods for creating Boarding Group chat menus
+ def addBoardingGroupMenu(self, zoneId):
+ if (self.boardingGroupMenu == None):
+ menu = TTSCBoardingMenu(zoneId)
+ self.boardingGroupMenu = SCMenuHolder(OTPLocalizer.SCMenuBoardingGroup, menu = menu)
+ self.speedChat[2:2] = [self.boardingGroupMenu]
+
+ def removeBoardingGroupMenu(self):
+ if self.boardingGroupMenu:
+ i = self.speedChat.index(self.boardingGroupMenu)
+ del self.speedChat[i]
+ self.boardingGroupMenu.destroy()
+ self.boardingGroupMenu = None
+
+ # Methods for creating Singing chat menus
+ def addSingingGroupMenu(self):
+ if (self.singingGroupMenu == None):
+ menu = TTSCSingingMenu()
+ self.singingGroupMenu = SCMenuHolder(OTPLocalizer.SCMenuSingingGroup, menu = menu)
+ self.speedChat[2:2] = [self.singingGroupMenu]
+
+ def removeSingingMenu(self):
+ if self.singingGroupMenu:
+ i = self.speedChat.index(self.singingGroupMenu)
+ del self.speedChat[i]
+ self.singingGroupMenu.destroy()
+ self.singingGroupMenu = None
+
+ # Methods for creating April toons' chat menus
+ def addAprilToonsMenu(self):
+ if (self.aprilToonsMenu == None):
+ menu = TTSCAprilToonsMenu()
+ self.aprilToonsMenu = SCMenuHolder(OTPLocalizer.SCMenuAprilToons, menu = menu)
+ self.speedChat[3:3] = [self.aprilToonsMenu]
+
+ def removeAprilToonsMenu(self):
+ if self.aprilToonsMenu:
+ i = self.speedChat.index(self.aprilToonsMenu)
+ del self.speedChat[i]
+ self.aprilToonsMenu.destroy()
+ self.aprilToonsMenu = None
+
+ def addSillyPhaseOneMenu(self):
+ if (self.sillyPhaseOneMenu == None):
+ menu = TTSCSillyPhaseOneMenu()
+ self.sillyPhaseOneMenu = SCMenuHolder(OTPLocalizer.SCMenuSillyHoliday, menu = menu)
+ self.speedChat[3:3] = [self.sillyPhaseOneMenu]
+
+ def removeSillyPhaseOneMenu(self):
+ if self.sillyPhaseOneMenu:
+ i = self.speedChat.index(self.sillyPhaseOneMenu)
+ del self.speedChat[i]
+ self.sillyPhaseOneMenu.destroy()
+ self.sillyPhaseOneMenu = None
+
+ def addSillyPhaseTwoMenu(self):
+ if (self.sillyPhaseTwoMenu == None):
+ menu = TTSCSillyPhaseTwoMenu()
+ self.sillyPhaseTwoMenu = SCMenuHolder(OTPLocalizer.SCMenuSillyHoliday, menu = menu)
+ self.speedChat[3:3] = [self.sillyPhaseTwoMenu]
+
+ def removeSillyPhaseTwoMenu(self):
+ if self.sillyPhaseTwoMenu:
+ i = self.speedChat.index(self.sillyPhaseTwoMenu)
+ del self.speedChat[i]
+ self.sillyPhaseTwoMenu.destroy()
+ self.sillyPhaseTwoMenu = None
+
+ def addSillyPhaseThreeMenu(self):
+ if (self.sillyPhaseThreeMenu == None):
+ menu = TTSCSillyPhaseThreeMenu()
+ self.sillyPhaseThreeMenu = SCMenuHolder(OTPLocalizer.SCMenuSillyHoliday, menu = menu)
+ self.speedChat[3:3] = [self.sillyPhaseThreeMenu]
+
+ def removeSillyPhaseThreeMenu(self):
+ if self.sillyPhaseThreeMenu:
+ i = self.speedChat.index(self.sillyPhaseThreeMenu)
+ del self.speedChat[i]
+ self.sillyPhaseThreeMenu.destroy()
+ self.sillyPhaseThreeMenu = None
+
+ def addSillyPhaseFourMenu(self):
+ if (self.sillyPhaseFourMenu == None):
+ menu = TTSCSillyPhaseFourMenu()
+ self.sillyPhaseFourMenu = SCMenuHolder(OTPLocalizer.SCMenuSillyHoliday, menu = menu)
+ self.speedChat[3:3] = [self.sillyPhaseFourMenu]
+
+ def removeSillyPhaseFourMenu(self):
+ if self.sillyPhaseFourMenu:
+ i = self.speedChat.index(self.sillyPhaseFourMenu)
+ del self.speedChat[i]
+ self.sillyPhaseFourMenu.destroy()
+ self.sillyPhaseFourMenu = None
+
+ def addSillyPhaseFiveMenu(self):
+ if (self.sillyPhaseFiveMenu == None):
+ menu = TTSCSillyPhaseFiveMenu()
+ self.sillyPhaseFiveMenu = SCMenuHolder(OTPLocalizer.SCMenuSillyHoliday, menu = menu)
+ self.speedChat[3:3] = [self.sillyPhaseFiveMenu]
+
+ def removeSillyPhaseFiveMenu(self):
+ if self.sillyPhaseFiveMenu:
+ i = self.speedChat.index(self.sillyPhaseFiveMenu)
+ del self.speedChat[i]
+ self.sillyPhaseFiveMenu.destroy()
+ self.sillyPhaseFiveMenu = None
+
+ def addCarolMenu(self):
+ if (self.carolMenu == None):
+ if base.cr.isPaid():
+ menu = TTSCCarolMenu()
+ self.carolMenu = SCMenuHolder(OTPLocalizer.SCMenuCarol, menu = menu)
+ self.speedChat[3:3] = [self.carolMenu]
+
+ def removeCarolMenu(self):
+ if self.carolMenu:
+ i = self.speedChat.index(self.carolMenu)
+ del self.speedChat[i]
+ self.carolMenu.destroy()
+ self.carolMenu = None
+
+ def addVictoryPartiesMenu(self):
+ if self.victoryPartiesMenu == None:
+ menu = TTSCVictoryPartiesMenu()
+ self.victoryPartiesMenu = SCMenuHolder(OTPLocalizer.SCMenuVictoryParties, menu = menu)
+ self.speedChat[3:3] = [self.victoryPartiesMenu]
+
+ def removeVictoryPartiesMenu(self):
+ if self.victoryPartiesMenu:
+ i = self.speedChat.index(self.victoryPartiesMenu)
+ del self.speedChat[i]
+ self.victoryPartiesMenu.destroy()
+ self.victoryPartiesMenu = None
+
+ # methods for creating White List chat menus
+ def addWhiteList(self):
+ if self.whiteList == None:
+ from toontown.chat.TTSCWhiteListTerminal import TTSCWhiteListTerminal
+ self.whiteList = TTSCWhiteListTerminal(4, self)
+ #menu = TTSCWhiteListMenu()
+ #self.whiteListMenu = SCMenuHolder(OTPLocalizer.SCMenuWhiteList, menu=menu)
+ self.speedChat[1:1] = [self.whiteList]
+
+ def removeWhiteList(self):
+ if self.whiteList:
+ i = self.speedChat.index(self.whiteList)
+ del self.speedChat[i]
+ self.whiteList.destroy()
+ self.whiteList = None
diff --git a/toontown/src/chat/TTChatInputWhiteList.py b/toontown/src/chat/TTChatInputWhiteList.py
new file mode 100644
index 0000000..386c4dd
--- /dev/null
+++ b/toontown/src/chat/TTChatInputWhiteList.py
@@ -0,0 +1,358 @@
+#from direct.gui.DirectGui import *
+#from otp.otpbase import OTPGlobals
+from otp.chat.ChatInputWhiteListFrame import ChatInputWhiteListFrame
+from toontown.chat.TTWhiteList import TTWhiteList
+
+from direct.showbase import DirectObject
+from otp.otpbase import OTPGlobals
+import sys
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from otp.otpbase import OTPLocalizer
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals
+
+class TTChatInputWhiteList(ChatInputWhiteListFrame):
+ notify = DirectNotifyGlobal.directNotify.newCategory("TTChatInputWhiteList")
+
+ TFToggleKey = base.config.GetString('true-friend-toggle-key','alt')
+ TFToggleKeyUp = TFToggleKey + '-up'
+
+ def __init__(self, parent = None, **kw):
+ entryOptions = {
+ 'parent' : self,
+ 'relief': DGG.SUNKEN,
+ 'scale': 0.05,
+ #'frameSize' : (-0.2, 25.3, -0.5, 1.2),
+ #'borderWidth' : (0.1, 0.1),
+ 'frameColor' : (0.9, 0.9, 0.85, 0.0),
+ 'pos' : (-0.2,0,0.11),
+ 'entryFont' : OTPGlobals.getInterfaceFont(),
+ 'width': 8.6,
+ 'numLines' : 3,
+ 'cursorKeys' : 0,
+ 'backgroundFocus' : 0,
+ 'suppressKeys' : 0,
+ 'suppressMouse' : 1,
+ 'command' : self.sendChat,
+ 'failedCommand': self.sendFailed,
+ 'focus' : 0,
+ 'text' : '',
+ 'sortOrder' : DGG.FOREGROUND_SORT_INDEX,
+ }
+ ChatInputWhiteListFrame.__init__(self, entryOptions, parent, **kw)
+ #self.initialiseoptions(TTChatInputWhiteList)
+ self.whiteList = TTWhiteList()
+ base.whiteList = self.whiteList
+ base.ttwl = self
+
+
+ self.autoOff = 1
+ self.sendBy = "Data"
+ self.prefilter = 0
+ self.promoteWhiteList = 1
+ self.typeGrabbed = 0
+
+ #self.request("off")
+ self.deactivate()
+
+ gui = loader.loadModel("phase_3.5/models/gui/chat_input_gui")
+
+ self.chatFrame = DirectFrame(
+ parent = self,
+ image = gui.find("**/Chat_Bx_FNL"),
+ relief = None,
+ pos = (0.0, 0, 0.0),
+ state = DGG.NORMAL,
+ #sortOrder = DGG.FOREGROUND_SORT_INDEX,
+ )
+
+ #self.chatFrame.hide()
+
+ self.chatButton = DirectButton(
+ parent = self.chatFrame,
+ image = (gui.find("**/ChtBx_ChtBtn_UP"),
+ gui.find("**/ChtBx_ChtBtn_DN"),
+ gui.find("**/ChtBx_ChtBtn_RLVR"),
+ ),
+ pos = (0.182, 0, -0.088),
+ relief = None,
+ text = ("",
+ OTPLocalizer.ChatInputNormalSayIt,
+ OTPLocalizer.ChatInputNormalSayIt),
+ text_scale = 0.06,
+ text_fg = Vec4(1,1,1,1),
+ text_shadow = Vec4(0,0,0,1),
+ text_pos = (0,-0.09),
+ textMayChange = 0,
+ command = self.chatButtonPressed,
+ )
+
+ self.cancelButton = DirectButton(
+ parent = self.chatFrame,
+ image = (gui.find("**/CloseBtn_UP"),
+ gui.find("**/CloseBtn_DN"),
+ gui.find("**/CloseBtn_Rllvr"),
+ ),
+ pos = (-0.151, 0, -0.088),
+ relief = None,
+ text = ("",
+ OTPLocalizer.ChatInputNormalCancel,
+ OTPLocalizer.ChatInputNormalCancel),
+ text_scale = 0.06,
+ text_fg = Vec4(1,1,1,1),
+ text_shadow = Vec4(0,0,0,1),
+ text_pos = (0,-0.09),
+ textMayChange = 0,
+ command = self.cancelButtonPressed,
+ )
+
+ self.whisperLabel = DirectLabel(
+ parent = self.chatFrame,
+ pos = (0.02, 0, 0.23),
+ relief = DGG.FLAT,
+ frameColor = (1,1,0.5,1),
+ frameSize = (-0.23, 0.23, -0.07, 0.05),
+ text = OTPLocalizer.ChatInputNormalWhisper,
+ text_scale = 0.04,
+ text_fg = Vec4(0,0,0,1),
+ text_wordwrap = 9.5,
+ textMayChange = 1,
+ )
+ #self.whisperLabel.hide()
+ #self.setPos(-0.35, 0.0, 0.7)
+
+ self.chatEntry.bind(DGG.OVERFLOW, self.chatOverflow)
+ self.chatEntry.bind(DGG.TYPE, self.typeCallback)
+ # self.accept("typeEntryGrab", self.handleTypeGrab)
+
+ self.trueFriendChat = 0
+ if base.config.GetBool('whisper-to-nearby-true-friends', 1):
+ self.accept(self.TFToggleKey, self.shiftPressed)
+
+ ## Maintain state of shift key
+ def shiftPressed(self):
+ """
+ Helps maintain the value of the shift key
+ so that if it is pressed while sending chat,
+ then the chat becomes a whisper to true friends
+ in the same zone
+ """
+
+ assert self.notify.debug('shiftPressed %s' % self.desc)
+ self.ignore(self.TFToggleKey)
+ self.trueFriendChat = 1
+ self.accept(self.TFToggleKeyUp, self.shiftReleased)
+
+ def shiftReleased(self):
+ """
+ Helps maintain the value of the shift key
+ so that if it is pressed while sending chat,
+ then the chat becomes a whisper to true friends
+ in the same zone
+ """
+ assert self.notify.debug('shiftReleased')
+ self.ignore(self.TFToggleKeyUp)
+ self.trueFriendChat = 0
+ self.accept(self.TFToggleKey, self.shiftPressed)
+
+ def handleTypeGrab(self):
+ assert self.notify.debug("handleTypeGrab %s" % self.desc)
+ self.ignore("typeEntryGrab")
+ self.accept("typeEntryRelease", self.handleTypeRelease)
+ #self.chatEntry['focus'] = 0
+ self.typeGrabbed = 1
+
+ def handleTypeRelease(self):
+ assert self.notify.debug("handleTypeRelease" % self.desc)
+ self.ignore("typeEntryRelease")
+ self.accept("typeEntryGrab", self.handleTypeGrab)
+ #self.chatEntry['focus'] = 1
+ self.typeGrabbed = 0
+
+ def typeCallback(self, extraArgs):
+ #if hasattr(base, "whiteList"):
+ # if base.whiteList:
+ # return
+ #print("enterNormalChat")
+ if self.typeGrabbed:
+ return
+
+ self.applyFilter(extraArgs)
+ if localAvatar.chatMgr.chatInputWhiteList.isActive():
+ #print("typeCallback return")
+ return
+ else:
+ #print("send")
+ #print self.chatEntry['text']
+ messenger.send("wakeup")
+ messenger.send('enterNormalChat')
+
+ def destroy(self):
+ self.chatEntry.destroy()
+ self.chatFrame.destroy()
+ self.ignoreAll()
+ ChatInputWhiteListFrame.destroy(self)
+
+
+ def delete(self):
+ base.whiteList = None
+ ChatInputWhiteListFrame.delete(self)
+
+ def sendChat(self, text, overflow = False):
+ assert self.notify.debug('sendChat')
+ if self.typeGrabbed:
+ return
+ else:
+ ChatInputWhiteListFrame.sendChat(self, self.chatEntry.get())
+
+ def sendChatByData(self, text):
+ assert self.notify.debug('sendChatByData desc=%s tfChat=%s' % (self.desc,self.trueFriendChat))
+ if self.trueFriendChat:
+ for friendId, flags in base.localAvatar.friendsList:
+ if flags & ToontownGlobals.FriendChat:
+ self.sendWhisperByFriend(friendId, text)
+ elif not self.receiverId:
+ base.talkAssistant.sendOpenTalk(text)
+ elif self.receiverId and not self.toPlayer:
+ base.talkAssistant.sendWhisperTalk(text, self.receiverId)
+ elif self.receiverId and self.toPlayer:
+ base.talkAssistant.sendAccountTalk(text, self.receiverId)
+
+ def sendWhisperByFriend(self, avatarId, text):
+ """
+ Check whether it is appropriate to send a message to the true
+ friend avatarId and then send it.
+ """
+ online = 0
+ if base.cr.doId2do.has_key(avatarId):
+ # The avatar is online, and in fact, nearby.
+ online = 1
+
+ # Uncomment the following section of code if it is required to
+ # send whispers to all true friends regardless of zone
+## elif base.cr.isFriend(avatarId):
+## # The avatar is a friend of ours. Is she online?
+## online = base.cr.isFriendOnline(avatarId)
+##
+## hasManager = hasattr(base.cr, "playerFriendsManager")
+## if hasManager:
+## if base.cr.playerFriendsManager.askAvatarOnline(avatarId):
+## online = 1
+
+ # Do we have chat permission with the other avatar?
+ avatarUnderstandable = 0
+ av = None
+ if avatarId:
+ av = base.cr.identifyAvatar(avatarId)
+ if av != None:
+ avatarUnderstandable = av.isUnderstandable()
+
+ # To do: find out how to access chat manager from here
+ # normalButtonObscured, scButtonObscured = self.isObscured()
+
+ if avatarUnderstandable and online:
+ # and not normalButtonObscured:
+ base.talkAssistant.sendWhisperTalk(text, avatarId)
+
+ def chatButtonPressed(self):
+ print("chatButtonPressed")
+ if self.okayToSubmit:
+ #self.chatEntry.commandFunc(None)
+ self.sendChat(self.chatEntry.get())
+ else:
+ #self.chatEntry.failedCommandFunc(None)
+ self.sendFailed(self.chatEntry.get())
+
+
+ def cancelButtonPressed(self):
+ self.requestMode("Off")
+
+ localAvatar.chatMgr.fsm.request("mainMenu")
+
+ def enterAllChat(self):
+ #print("enterAllChat")
+ ChatInputWhiteListFrame.enterAllChat(self)
+ self.whisperLabel.hide()
+
+
+ def exitAllChat(self):
+ #print("exitAllChat")
+ ChatInputWhiteListFrame.exitAllChat(self)
+
+ def enterPlayerWhisper(self):
+ ChatInputWhiteListFrame.enterPlayerWhisper(self)
+ #self.whisperLabel.show()
+ self.labelWhisper()
+
+ def exitPlayerWhisper(self):
+ ChatInputWhiteListFrame.exitPlayerWhisper(self)
+ self.whisperLabel.hide()
+
+ def enterAvatarWhisper(self):
+ #print("enterAvatarWhisper")
+ ChatInputWhiteListFrame.enterAvatarWhisper(self)
+ #self.whisperLabel.show()
+ self.labelWhisper()
+
+ def exitAvatarWhisper(self):
+ #print("exitAvatarWhisper")
+ ChatInputWhiteListFrame.exitAvatarWhisper(self)
+ self.whisperLabel.hide()
+
+ def labelWhisper(self):
+ if self.receiverId:
+ self.whisperName = base.talkAssistant.findName(self.receiverId, self.toPlayer)
+ self.whisperLabel["text"] = (OTPLocalizer.ChatInputWhisperLabel %
+ (self.whisperName))
+ self.whisperLabel.show()
+ else:
+ self.whisperLabel.hide()
+
+ def applyFilter(self,keyArgs,strict=False):
+ text = self.chatEntry.get(plain=True)
+
+ if len(text) > 0 and text[0] in ['~','>']:
+ self.okayToSubmit = True
+ else:
+ words = text.split(" ")
+ newwords = []
+ assert self.notify.debug("%s" % words)
+
+ self.okayToSubmit = True
+
+ # If we are true friends then we should italacize bad text
+ flag = 0
+ for friendId, flags in base.localAvatar.friendsList:
+ if flags & ToontownGlobals.FriendChat:
+ flag = 1
+
+ for word in words:
+ if word == "" or self.whiteList.isWord(word) or (not base.cr.whiteListChatEnabled):
+ newwords.append(word)
+ else:
+ if self.checkBeforeSend:
+ self.okayToSubmit = False
+ else:
+ self.okayToSubmit = True
+ if flag:
+ newwords.append("\1WLDisplay\1" + word + "\2")
+ else:
+ newwords.append("\1WLEnter\1" + word + "\2")
+
+ if not strict:
+ lastword = words[-1]
+
+ if lastword == "" or self.whiteList.isPrefix(lastword) or (not base.cr.whiteListChatEnabled):
+ newwords[-1] = lastword
+ else:
+ if flag:
+ newwords[-1] = "\1WLDisplay\1" + lastword + "\2"
+ else:
+ newwords[-1] = "\1WLEnter\1" + lastword + "\2"
+
+ newtext = " ".join(newwords)
+ self.chatEntry.set(newtext)
+
+ self.chatEntry.guiItem.setAcceptEnabled(self.okayToSubmit)
+
diff --git a/toontown/src/chat/TTSCWhiteListTerminal.py b/toontown/src/chat/TTSCWhiteListTerminal.py
new file mode 100644
index 0000000..3b110aa
--- /dev/null
+++ b/toontown/src/chat/TTSCWhiteListTerminal.py
@@ -0,0 +1,40 @@
+"""SCStaticTextTerminal.py: contains the SCStaticTextTerminal class"""
+
+from otp.speedchat.SCTerminal import SCTerminal
+from otp.otpbase.OTPLocalizer import SpeedChatStaticText
+
+# args: textId
+SCStaticTextMsgEvent = 'SCStaticTextMsg'
+
+def decodeSCStaticTextMsg(textId):
+ return SpeedChatStaticText.get(textId, None)
+
+class TTSCWhiteListTerminal(SCTerminal):
+ """ SCStaticTextTerminal represents a terminal SpeedChat entry that
+ contains a piece of static (never-changing/constant) text.
+
+ When selected, generates a 'SCStaticTextMsg' event, with arguments:
+ - textId (16-bit; use as index into OTPLocalizer.SpeedChatStaticText)
+ """
+ def __init__(self, textId, parentMenu = None):
+ SCTerminal.__init__(self)
+ self.parentClass = parentMenu
+
+ self.textId = textId
+ self.text = SpeedChatStaticText[self.textId]
+
+ print ("SpeedText %s %s" % (self.textId, self.text))
+
+ def handleSelect(self):
+ SCTerminal.handleSelect(self)
+ #messenger.send(self.getEventName(SCStaticTextMsgEvent), [self.textId])
+ #print ("Message Sent %s %s" % (self.getEventName(SCStaticTextMsgEvent), [self.textId]))
+ #base.whiteList.activate()
+ if not self.parentClass.whisperAvatarId:
+ base.localAvatar.chatMgr.fsm.request("whiteListOpenChat")
+ elif self.parentClass.toPlayer:
+ base.localAvatar.chatMgr.fsm.request("whiteListPlayerChat", [self.parentClass.whisperAvatarId])
+ else:
+ base.localAvatar.chatMgr.fsm.request("whiteListAvatarChat", [self.parentClass.whisperAvatarId])
+
+
diff --git a/toontown/src/chat/TTTalkAssistant.py b/toontown/src/chat/TTTalkAssistant.py
new file mode 100644
index 0000000..3b1f950
--- /dev/null
+++ b/toontown/src/chat/TTTalkAssistant.py
@@ -0,0 +1,74 @@
+import string
+import sys
+from direct.showbase import DirectObject
+from otp.otpbase import OTPLocalizer
+from toontown.toonbase import TTLocalizer
+from direct.directnotify import DirectNotifyGlobal
+from otp.otpbase import OTPGlobals
+from otp.speedchat import SCDecoders
+from pandac.PandaModules import *
+from otp.chat.ChatGlobals import *
+from otp.chat.TalkGlobals import *
+from otp.speedchat import SpeedChatGlobals
+from otp.chat.TalkMessage import TalkMessage
+from otp.chat.TalkAssistant import TalkAssistant
+from toontown.speedchat import TTSCDecoders
+import time
+
+
+
+class TTTalkAssistant(TalkAssistant):
+ """
+ contains methods for turning chat inputs
+ into onscreen thought/word balloons
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory("TTTalkAssistant")
+
+ def __init__(self):
+ TalkAssistant.__init__(self)
+
+#SETUP AND CLEANUP
+
+ def clearHistory(self):
+ TalkAssistant.clearHistory(self)
+
+#TOONTOWN SPECFIC SPEEDCHAT
+
+ def sendPlayerWhisperToonTaskSpeedChat(self, taskId, toNpcId, toonProgress, msgIndex, receiverId):
+ error = None
+
+ base.cr.speedchatRelay.sendSpeedchatToonTask(receiverId, taskId, toNpcId, toonProgress, msgIndex)
+ #message = SCDecoders.decodeSCCustomMsg(messageIndex)
+ message = TTSCDecoders.decodeTTSCToontaskMsg(
+ taskId, toNpcId, toonProgress, msgIndex)
+
+ if self.logWhispers:
+ receiverName = self.findName(receiverId, 1)
+
+ newMessage = TalkMessage(self.countMessage(), #messageNumber
+ self.stampTime(), #timeStamp
+ message, #message Body
+ localAvatar.doId, #senderAvatarId
+ localAvatar.getName(), #senderAvatarName
+ localAvatar.DISLid, #senderAccountId
+ localAvatar.DISLname, #senderAccountName
+ None, #receiverAvatarId
+ None, #receiverAvatarName
+ receiverId, #receiverAccountId
+ receiverName, #receiverAccountName
+ TALK_ACCOUNT, #talkType
+ None) #extraInfo
+
+ self.historyComplete.append(newMessage)
+ self.addToHistoryDoId(newMessage, localAvatar.doId)
+ self.addToHistoryDISLId(newMessage, base.cr.accountDetailRecord.playerAccountId)
+ messenger.send("NewOpenMessage", [newMessage])
+ return error
+
+ def sendToonTaskSpeedChat(self, taskId, toNpcId, toonProgress, msgIndex):
+ error = None
+ # Open Avatar speed chat is sent through the avatar
+ messenger.send(SCChatEvent)
+ messenger.send("chatUpdateSCToontask", [taskId, toNpcId, toonProgress, msgIndex])
+ #base.localAvatar.b_setSCToontask(taskId, toNpcId, toonProgress, msgIndex)
+ return error
diff --git a/toontown/src/chat/TTWhiteList.py b/toontown/src/chat/TTWhiteList.py
new file mode 100644
index 0000000..049b6b3
--- /dev/null
+++ b/toontown/src/chat/TTWhiteList.py
@@ -0,0 +1,31 @@
+import os
+from pandac.PandaModules import *
+from direct.showbase import AppRunnerGlobal
+from otp.chat.WhiteList import WhiteList
+from toontown.toonbase import TTLocalizer
+
+class TTWhiteList(WhiteList):
+ def __init__(self):
+ vfs = VirtualFileSystem.getGlobalPtr()
+ filename = Filename('twhitelist.dat')
+ searchPath = DSearchPath()
+ if AppRunnerGlobal.appRunner:
+ # In the web-publish runtime, it will always be here:
+ searchPath.appendDirectory(Filename.expandFrom('$TT_3_ROOT/phase_3/etc'))
+ else:
+ # In other environments, including the dev environment, look here:
+ searchPath.appendDirectory(Filename('.'))
+ searchPath.appendDirectory(Filename('etc'))
+ searchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('$TOONTOWN/src/chat')))
+ searchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('toontown/src/chat')))
+ searchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('toontown/chat')))
+ found = vfs.resolveFilename(filename,searchPath)
+ if not found:
+ print "Couldn't find whitelist data file!"
+
+ data = vfs.readFile(filename, 1)
+
+ lines = data.split("\n")
+
+ WhiteList.__init__(self,lines)
+ self.defaultWord = TTLocalizer.ChatGarblerDefault[0]
diff --git a/toontown/src/chat/ToonChatGarbler.py b/toontown/src/chat/ToonChatGarbler.py
new file mode 100644
index 0000000..9791da6
--- /dev/null
+++ b/toontown/src/chat/ToonChatGarbler.py
@@ -0,0 +1,81 @@
+"""ChatGarbler module: conatins the ChatGarbler class"""
+
+import string
+import random
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPLocalizer
+from otp.chat import ChatGarbler
+
+class ToonChatGarbler(ChatGarbler.ChatGarbler):
+ """ToonChatGarbler class: contains methods to convert chat messages
+ to animal sounds"""
+
+ animalSounds = {
+ "dog" : TTLocalizer.ChatGarblerDog,
+ "cat" : TTLocalizer.ChatGarblerCat,
+ "mouse" : TTLocalizer.ChatGarblerMouse,
+ "horse" : TTLocalizer.ChatGarblerHorse,
+ "rabbit" : TTLocalizer.ChatGarblerRabbit,
+ "duck" : TTLocalizer.ChatGarblerDuck,
+ "monkey" : TTLocalizer.ChatGarblerMonkey,
+ "bear" : TTLocalizer.ChatGarblerBear,
+ "pig" : TTLocalizer.ChatGarblerPig,
+ "default" : OTPLocalizer.ChatGarblerDefault,
+ }
+
+
+ def garble(self, toon, message):
+ """garble(self, Avatar, string)
+ Replace a chat message with a series of animal sounds
+ based on the toon's animal type
+ Algorithm completely disregards original message to
+ prohibit any sort of meaningful communication
+ """
+ newMessage = ""
+
+ animalType = toon.getStyle().getType()
+
+ if (ToonChatGarbler.animalSounds.has_key(animalType)):
+ wordlist = ToonChatGarbler.animalSounds[animalType]
+ else:
+ wordlist = ToonChatGarbler.animalSounds["default"]
+
+ numWords = random.randint(1, 7)
+
+ for i in range(1, numWords+1):
+ wordIndex = random.randint(0, len(wordlist)-1)
+ newMessage = newMessage + wordlist[wordIndex]
+ if (i < numWords):
+ newMessage = newMessage + " "
+
+ return newMessage
+
+ def garbleSingle(self, toon, message):
+ """garble(self, Avatar, string)
+ Replace a chat message with a series of animal sounds
+ based on the toon's animal type
+ Algorithm completely disregards original message to
+ prohibit any sort of meaningful communication
+ """
+ newMessage = ""
+
+ animalType = toon.getStyle().getType()
+
+ if (ToonChatGarbler.animalSounds.has_key(animalType)):
+ wordlist = ToonChatGarbler.animalSounds[animalType]
+ else:
+ wordlist = ToonChatGarbler.animalSounds["default"]
+
+ numWords = 1
+
+ for i in range(1, numWords+1):
+ wordIndex = random.randint(0, len(wordlist)-1)
+ newMessage = newMessage + wordlist[wordIndex]
+ if (i < numWords):
+ newMessage = newMessage + " "
+
+ return newMessage
+
+
+
+
diff --git a/toontown/src/chat/ToontownChatManager.py b/toontown/src/chat/ToontownChatManager.py
new file mode 100644
index 0000000..2f47264
--- /dev/null
+++ b/toontown/src/chat/ToontownChatManager.py
@@ -0,0 +1,1205 @@
+"""ChatManager module: contains the ChatManager class"""
+
+import sys
+from direct.showbase import DirectObject
+from direct.showbase.PythonUtil import traceFunctionCall
+from otp.otpbase import OTPGlobals
+from otp.otpbase import OTPLocalizer
+from toontown.toonbase import TTLocalizer
+from toontown.toontowngui import TeaserPanel
+#import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from otp.chat import ChatManager
+from TTChatInputSpeedChat import TTChatInputSpeedChat
+from TTChatInputNormal import TTChatInputNormal
+from TTChatInputWhiteList import TTChatInputWhiteList
+#from toontown.launcher import QuickLauncher
+
+# hack class to simulate radio buttons (prevent radio button from clearing
+# itself when clicked twice)
+class HackedDirectRadioButton(DirectCheckButton):
+ def __init__(self, parent = None, **kw):
+ optiondefs = (
+ )
+ # Merge keyword options with default options
+ self.defineoptions(kw, optiondefs)
+ # Initialize superclasses
+ DirectCheckButton.__init__(self, parent)
+ # Call option initialization functions
+ self.initialiseoptions(HackedDirectRadioButton)
+ def commandFunc(self, event):
+ # if our indicator is 'on', set it to 'off' before calling
+ # DirectCheckButton.commandFunc, which will set it back to 'on'
+ if self['indicatorValue']:
+ self['indicatorValue'] = 0
+ DirectCheckButton.commandFunc(self, event)
+
+class ToontownChatManager(ChatManager.ChatManager):
+ """
+ contains methods for turning chat inputs
+ into onscreen thought/word balloons"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("ToontownChatManager")
+
+ # special methods
+ def __init__(self, cr, localAvatar):
+
+ gui = loader.loadModel("phase_3.5/models/gui/chat_input_gui")
+
+ self.normalButton = DirectButton(
+ image = (gui.find("**/ChtBx_ChtBtn_UP"),
+ gui.find("**/ChtBx_ChtBtn_DN"),
+ gui.find("**/ChtBx_ChtBtn_RLVR"),
+ ),
+ pos = (-1.2647, 0, 0.928),
+ scale = 1.179,
+ relief = None,
+ image_color = Vec4(1,1,1,1),
+ text = ("", OTPLocalizer.ChatManagerChat, OTPLocalizer.ChatManagerChat),
+ text_align = TextNode.ALeft,
+ text_scale = TTLocalizer.CMnormalButton,
+ text_fg = Vec4(1,1,1,1),
+ text_shadow = Vec4(0,0,0,1),
+ text_pos = (-0.0525, -0.09),
+ textMayChange = 0,
+ sortOrder = DGG.FOREGROUND_SORT_INDEX,
+ command = self.__normalButtonPressed,
+ )
+ self.normalButton.hide()
+
+ self.openScSfx = loader.loadSfx(
+ 'phase_3.5/audio/sfx/GUI_quicktalker.mp3')
+ # Make it a bit more subtle
+ self.openScSfx.setVolume(0.6)
+
+ self.scButton = DirectButton(
+ image = (gui.find("**/ChtBx_ChtBtn_UP"),
+ gui.find("**/ChtBx_ChtBtn_DN"),
+ gui.find("**/ChtBx_ChtBtn_RLVR"),
+ ),
+ pos = TTLocalizer.CMscButtonPos,
+ scale = 1.179,
+ relief = None,
+ image_color = Vec4(0.75,1,0.6,1),
+ #image_color = Vec4(1.00,0.75,0.50,1),
+ text = ("",
+ OTPLocalizer.GlobalSpeedChatName,
+ OTPLocalizer.GlobalSpeedChatName),
+ text_scale = TTLocalizer.CMscButton,
+ text_fg = Vec4(1,1,1,1),
+ text_shadow = Vec4(0,0,0,1),
+ text_pos = (0,-0.09),
+ textMayChange = 0,
+ sortOrder = DGG.FOREGROUND_SORT_INDEX,
+ command = self.__scButtonPressed,
+ clickSound = self.openScSfx,
+ )
+ self.scButton.hide()
+
+ self.whisperFrame = DirectFrame(
+ parent = aspect2dp,
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_scale = (0.45, 0.45, 0.45),
+ image_color = OTPGlobals.GlobalDialogColor,
+ pos = (-0.4, 0, 0.754),
+ text = OTPLocalizer.ChatManagerWhisperTo,
+ text_wordwrap = 7.0,
+ text_scale = TTLocalizer.CMwhisperFrame,
+ text_fg = Vec4(0,0,0,1),
+ text_pos = (0,0.14),
+ textMayChange = 1,
+ sortOrder = DGG.FOREGROUND_SORT_INDEX,
+ )
+ self.whisperFrame.hide()
+
+ self.whisperButton = DirectButton(
+ parent = self.whisperFrame,
+ image = (gui.find("**/ChtBx_ChtBtn_UP"),
+ gui.find("**/ChtBx_ChtBtn_DN"),
+ gui.find("**/ChtBx_ChtBtn_RLVR"),
+ ),
+ pos = (-0.125, 0, -0.1),
+ scale = 1.179,
+ relief = None,
+ image_color = Vec4(1,1,1,1),
+ text = ("",
+ OTPLocalizer.ChatManagerChat,
+ OTPLocalizer.ChatManagerChat,
+ ""),
+ # Make the disabled button darker
+ image3_color = Vec4(0.6, 0.6, 0.6, 0.6),
+ text_scale = TTLocalizer.CMwhisperButton,
+ text_fg = (0,0,0,1),
+ text_pos = (0,-0.09),
+ textMayChange = 0,
+ command = self.__whisperButtonPressed,
+ )
+
+ self.whisperScButton = DirectButton(
+ parent = self.whisperFrame,
+ image = (gui.find("**/ChtBx_ChtBtn_UP"),
+ gui.find("**/ChtBx_ChtBtn_DN"),
+ gui.find("**/ChtBx_ChtBtn_RLVR"),
+ ),
+ pos = (0.0, 0, -0.1),
+ scale = 1.179,
+ relief = None,
+ image_color = Vec4(0.75,1,0.6,1),
+ text = ("",
+ OTPLocalizer.GlobalSpeedChatName,
+ OTPLocalizer.GlobalSpeedChatName,
+ ""),
+ # Make the disabled button darker
+ image3_color = Vec4(0.6, 0.6, 0.6, 0.6),
+ text_scale = TTLocalizer.CMwhisperButton,
+ text_fg = (0,0,0,1),
+ text_pos = (0,-0.09),
+ textMayChange = 0,
+ command = self.__whisperScButtonPressed,
+ )
+
+ self.whisperCancelButton = DirectButton(
+ parent = self.whisperFrame,
+ image = (gui.find("**/CloseBtn_UP"),
+ gui.find("**/CloseBtn_DN"),
+ gui.find("**/CloseBtn_Rllvr"),
+ ),
+ pos = (0.125, 0, -0.1),
+ scale = 1.179,
+ relief = None,
+ text = ("",
+ OTPLocalizer.ChatManagerCancel,
+ OTPLocalizer.ChatManagerCancel),
+ text_scale = 0.05,
+ text_fg = (0,0,0,1),
+ text_pos = (0,-0.09),
+ textMayChange = 0,
+ command = self.__whisperCancelPressed,
+ )
+
+ gui.removeNode()
+
+ ChatManager.ChatManager.__init__(self, cr, localAvatar)
+ self.defaultToWhiteList = base.config.GetBool('white-list-is-default', 1)
+ self.chatInputSpeedChat = TTChatInputSpeedChat(self)
+
+ self.normalPos = Vec3(-1.083, 0, 0.804)
+ self.whisperPos = Vec3(0.0, 0, 0.71)
+ self.speedChatPlusPos = Vec3(-0.35, 0, 0.71)
+
+ if self.defaultToWhiteList:
+ self.chatInputNormal = TTChatInputWhiteList()
+ self.chatInputNormal.setPos(self.normalPos)
+ self.chatInputNormal.desc = "chatInputNormal"
+ else:
+ self.chatInputNormal = TTChatInputNormal(self)
+ self.chatInputWhiteList = TTChatInputWhiteList()
+ self.chatInputWhiteList.setPos(self.speedChatPlusPos)
+ self.chatInputWhiteList.desc = "chatInputWhiteList"
+
+ def delete(self):
+ ChatManager.ChatManager.delete(self)
+
+ loader.unloadModel("phase_3.5/models/gui/chat_input_gui")
+ self.normalButton.destroy()
+ del self.normalButton
+ self.scButton.destroy()
+ del self.scButton
+ del self.openScSfx
+ self.whisperFrame.destroy()
+ del self.whisperFrame
+ self.whisperButton.destroy()
+ del self.whisperButton
+ self.whisperScButton.destroy()
+ del self.whisperScButton
+ self.whisperCancelButton.destroy()
+ del self.whisperCancelButton
+ self.chatInputWhiteList.destroy()
+ del self.chatInputWhiteList
+
+
+ def sendSCResistanceChatMessage(self, textId):
+ """
+ Send resistance speedchat message update
+ """
+ assert self.debugFunction()
+ messenger.send("chatUpdateSCResistance", [textId])
+ self.announceSCChat()
+
+ def sendSCSingingChatMessage(self, textId):
+ """
+ Send singing speedchat message update.
+ """
+ assert self.debugFunction()
+ messenger.send("chatUpdateSCSinging", [textId])
+ self.announceSCChat()
+
+ def sendSCSingingWhisperMessage(self, textId):
+ """
+ Send singing speedchat whisper message update.
+ """
+ pass
+
+ def sendSCToontaskChatMessage(self,
+ taskId, toNpcId, toonProgress, msgIndex):
+ """
+ Send speedchat message update
+ """
+ assert self.debugFunction()
+## base.talkAssistant.sendToonTaskSpeedChat(taskId, toNpcId, toonProgress, msgIndex)
+ messenger.send("chatUpdateSCToontask", [taskId, toNpcId, toonProgress, msgIndex])
+ self.announceSCChat()
+
+ def sendSCToontaskWhisperMessage(self,
+ taskId, toNpcId, toonProgress, msgIndex,
+ whisperAvatarId, toPlayer):
+ """
+ Send speedchat message update
+ """
+ assert self.debugFunction()
+ if toPlayer:
+ base.talkAssistant.sendPlayerWhisperToonTaskSpeedChat(taskId, toNpcId, toonProgress, msgIndex, whisperAvatarId)
+ elif 0:
+ base.talkAssistant.sendAvatarWhisperToonTaskSpeedChat(taskId, toNpcId, toonProgress, msgIndex, whisperAvatarId)
+ else:
+ messenger.send("whisperUpdateSCToontask",
+ [taskId, toNpcId, toonProgress, msgIndex,
+
+ whisperAvatarId])
+
+ def enterOpenChatWarning(self):
+ assert self.debugFunction()
+ # Pop up a dialog indicating the user doesn't have chat
+ # permission or any secret friends, so no one will understand
+ # what he has to say anyway.
+ if self.openChatWarning == None:
+ buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui')
+ buttonImage = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+ self.openChatWarning = \
+ DirectFrame(parent = aspect2dp,
+ pos = (0.0, 0.1, 0.2),
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = OTPGlobals.GlobalDialogColor,
+ image_scale = (1.2, 1.0, 1.4),
+ text = OTPLocalizer.OpenChatWarning,
+ text_wordwrap = 19,
+ text_scale = TTLocalizer.CMopenChatWarning,
+ text_pos = (0.0, 0.575),
+ textMayChange = 0,
+ )
+ DirectButton(self.openChatWarning,
+ image = buttonImage,
+ relief = None,
+ text = OTPLocalizer.OpenChatWarningOK,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = (0.0, 0.0, -0.55),
+ command = self.__handleOpenChatWarningOK)
+ buttons.removeNode()
+ self.openChatWarning.show()
+
+ # The speedchat button is visible in this mode, but not the
+ # normal chat button.
+ normObs, scObs = self.isObscured()
+ if (not scObs):
+ self.scButton.show()
+ if (not normObs):
+ self.normalButton.show()
+
+
+ def enterMainMenu(self):
+ self.chatInputNormal.setPos(self.normalPos)
+ if self.chatInputWhiteList.isActive():
+ self.notify.debug('enterMainMenu calling checkObscured')
+ ChatManager.ChatManager.checkObscurred(self)
+ else:
+ ChatManager.ChatManager.enterMainMenu(self)
+
+ def exitOpenChatWarning(self):
+ assert self.debugFunction()
+ self.openChatWarning.hide()
+ self.scButton.hide()
+
+ def enterUnpaidChatWarning(self):
+ assert self.debugFunction()
+ self.forceHidePayButton = False
+ # This is handling three cases:
+ # 1)parent password not set
+ # 2)UK Chat elligible and paid
+ # Pop up a dialog indicating the user can't chat and show the appropriate
+ # dialog box
+ if base.cr.productName in ['DisneyOnline-UK', 'JP', 'DE', 'BR', 'FR']:
+ #print "### paid - uk user need to enable chat!!!"
+ directFrameText = OTPLocalizer.PaidParentPasswordUKWarning
+ payButtonText = OTPLocalizer.PaidParentPasswordUKWarningSet
+ directButtonText = OTPLocalizer.PaidParentPasswordUKWarningContinue
+ else:
+ #print "### paid - no parent password!!!"
+ directFrameText = OTPLocalizer.PaidNoParentPasswordWarning
+ payButtonText = OTPLocalizer.PaidNoParentPasswordWarningSet
+ directButtonText = OTPLocalizer.PaidNoParentPasswordWarningContinue
+
+ # unpaid player can now set their parent password, but the web code
+ # can't currently handle it for an activex client
+ if not 'QuickLauncher' in str(base.cr.launcher.__class__) and not base.cr.isPaid():
+ # if not isinstance(base.cr.launcher, QuickLauncher.QuickLauncher) and not base.cr.isPaid():
+ # we are not using the vista launcher, and we are not paid
+ directFrameText = OTPLocalizer.UnpaidNoParentPasswordWarning
+ self.forceHidePayButton = True
+
+ if self.unpaidChatWarning == None:
+ guiButton = loader.loadModel("phase_3/models/gui/quit_button")
+
+ buttonImage = (guiButton.find("**/QuitBtn_UP"),
+ guiButton.find("**/QuitBtn_DN"),
+ guiButton.find("**/QuitBtn_RLVR"))
+ self.unpaidChatWarning = \
+ DirectFrame(parent = aspect2dp,
+ pos = (0.0, 0.1, 0.4),
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = OTPGlobals.GlobalDialogColor,
+ image_scale = (1.2, 1.0, 0.8),
+ text = directFrameText,
+ text_wordwrap = TTLocalizer.CMunpaidChatWarningwordwrap,
+ text_scale = TTLocalizer.CMunpaidChatWarning,
+ text_pos = (0.0, TTLocalizer.CMunpaidChatWarning_text_z),
+ textMayChange = 0,
+ )
+ self.payButton = DirectButton(self.unpaidChatWarning,
+ image = buttonImage,
+ relief = None,
+ text = payButtonText,
+ image_scale = (1.75, 1, 1.15),
+ text_scale = TTLocalizer.CMpayButton,
+ text_pos = (0,-0.02),
+ textMayChange = 0,
+ pos = (0.0, 0.0, TTLocalizer.CMpayButton_pos_z),
+ command = self.__handleUnpaidChatWarningPay)
+ DirectButton(self.unpaidChatWarning,
+ image = buttonImage,
+ relief = None,
+ text = directButtonText,
+ textMayChange = 0,
+ image_scale = (1.75, 1, 1.15),
+ text_scale = 0.06,
+ text_pos = (0,-0.02),
+ pos = (0.0, 0.0, TTLocalizer.CMNoPasswordContinue_z),
+ command = self.__handleUnpaidChatWarningContinue)
+ guiButton.removeNode()
+
+ # Do not show the pay button if they are doing something special (tutorial, battle, elevator, etc.)
+ if base.localAvatar.cantLeaveGame or self.forceHidePayButton:
+ self.payButton.hide()
+ else:
+ self.payButton.show()
+
+ if not base.cr.productName in ['ES', 'JP', 'DE', 'BR', 'FR']:
+ self.unpaidChatWarning.show()
+ else:
+ # INTL need to show UnpaidChatWarning panel
+ # entering 'stopped' mode will stop the user's motion
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.fsm.request("stopped")
+
+ # make a teaser panel
+ self.teaser = TeaserPanel.TeaserPanel('secretChat',
+ self.__handleUnpaidChatWarningDone)
+
+ # Do not show the pay button if they are in tutorial.
+ # Let them finish the tutorial first or they will have to do it again.
+ if base.localAvatar.inTutorial:
+ self.teaser.hidePay()
+
+ # The speedchat button is visible in this mode, but not the
+ # normal chat button.
+ normObs, scObs = self.isObscured()
+ if (not scObs):
+ self.scButton.show()
+ if (not normObs):
+ self.normalButton.show()
+
+ def exitUnpaidChatWarning(self):
+ assert self.debugFunction()
+ if self.unpaidChatWarning:
+ self.unpaidChatWarning.hide()
+ self.scButton.hide()
+
+ def enterNoSecretChatAtAll(self):
+ assert self.debugFunction()
+ # Pop up a dialog indicating the user hasn't activated secret
+ # chat yet, and that he/she must quit the game and go to the
+ # web page in order to activate it.
+ if self.noSecretChatAtAll == None:
+ buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui')
+ okButtonImage = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+ self.noSecretChatAtAll = DirectFrame(
+ parent = aspect2dp,
+ pos = (0.0, 0.1, 0.2),
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = OTPGlobals.GlobalDialogColor,
+ image_scale = (1.4, 1.0, 1.1),
+ text = OTPLocalizer.NoSecretChatAtAll,
+ text_wordwrap = 20,
+ textMayChange = 0,
+ text_scale = 0.06,
+ text_pos = (0, 0.3),
+ )
+
+ DirectLabel(parent = self.noSecretChatAtAll,
+ relief = None,
+ pos = (0, 0, 0.4),
+ text = OTPLocalizer.NoSecretChatAtAllTitle,
+ textMayChange = 0,
+ text_scale = 0.08)
+ DirectButton(self.noSecretChatAtAll,
+ image = okButtonImage,
+ relief = None,
+ text = OTPLocalizer.NoSecretChatAtAllOK,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = (0.0, 0.0, -0.4),
+ command = self.__handleNoSecretChatAtAllOK)
+ buttons.removeNode()
+
+ self.noSecretChatAtAll.show()
+
+ def exitNoSecretChatAtAll(self):
+ assert self.debugFunction()
+ self.noSecretChatAtAll.hide()
+
+
+ def enterNoSecretChatWarning(self, passwordOnly=0):
+ # If passwordOnly is true, just prompt for user password again before entering
+ # the secret friends options page, otherwise act as if secret friends are disabled.
+ assert self.debugFunction()
+
+ # pick the right text for this context
+ if not passwordOnly:
+ warningText = OTPLocalizer.NoSecretChatWarning
+ else:
+ warningText = OTPLocalizer.ChangeSecretFriendsOptionsWarning
+
+ # Pop up a dialog indicating the user hasn't activated secret
+ # chat yet, and offering to activate it.
+ if self.noSecretChatWarning == None:
+ buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui')
+ nameBalloon = loader.loadModel("phase_3/models/props/chatbox_input")
+ okButtonImage = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+ cancelButtonImage = (buttons.find('**/CloseBtn_UP'),
+ buttons.find('**/CloseBtn_DN'),
+ buttons.find('**/CloseBtn_Rllvr'))
+
+ # The Castillian (and all foreign) version relies on a seperate parent
+ # password system. Omit the the password entry field and cancel button.
+
+ if base.cr.productName != "Terra-DMC":
+ okPos = (-0.22, 0.0, -0.35)
+ textPos = (0, 0.25)
+ okCommand = self.__handleNoSecretChatWarningOK
+ else:
+ self.passwordEntry = None
+ okPos = (0, 0, -0.35)
+ textPos = (0, 0.125)
+ okCommand = self.__handleNoSecretChatWarningCancel
+
+ # make the common gui elements
+ self.noSecretChatWarning = \
+ DirectFrame(parent = aspect2dp,
+ pos = (0.0, 0.1, 0.2),
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = OTPGlobals.GlobalDialogColor,
+ image_scale = (1.4, 1.0, 1.0),
+ text = warningText,
+ text_wordwrap = 20,
+ text_scale = 0.055,
+ text_pos = textPos,
+ textMayChange = 1,
+ )
+
+ DirectButton(self.noSecretChatWarning,
+ image = okButtonImage,
+ relief = None,
+ text = OTPLocalizer.NoSecretChatWarningOK,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = okPos,
+ command = okCommand)
+
+ DirectLabel(parent = self.noSecretChatWarning,
+ relief = None,
+ pos = (0, 0, 0.35),
+ text = OTPLocalizer.NoSecretChatWarningTitle,
+ textMayChange = 0,
+ text_scale = 0.08)
+
+ # if not foreign, make the domestic only password entry elements
+ if base.cr.productName != "Terra-DMC":
+ self.passwordLabel = DirectLabel(
+ parent = self.noSecretChatWarning,
+ relief = None,
+ pos = (-0.07, 0.0, -0.2),
+ text = OTPLocalizer.ParentPassword,
+ text_scale = 0.06,
+ text_align = TextNode.ARight,
+ textMayChange = 0,
+ )
+
+ self.passwordEntry = DirectEntry(
+ parent = self.noSecretChatWarning,
+ relief = None,
+ image = nameBalloon,
+ image1_color = (0.8, 0.8, 0.8, 1.0),
+ scale = 0.064,
+ pos = (0.0, 0.0, -0.2),
+ width = OTPGlobals.maxLoginWidth,
+ numLines = 1,
+ focus = 1,
+ cursorKeys = 1,
+ obscured = 1,
+ command = self.__handleNoSecretChatWarningOK,
+ )
+
+ DirectButton(self.noSecretChatWarning,
+ image = cancelButtonImage,
+ relief = None,
+ text = OTPLocalizer.NoSecretChatWarningCancel,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 1,
+ pos = (0.2, 0.0, -0.35),
+ command = self.__handleNoSecretChatWarningCancel)
+
+
+ buttons.removeNode()
+ nameBalloon.removeNode()
+ else:
+ self.noSecretChatWarning['text'] = warningText
+ if self.passwordEntry:
+ self.passwordEntry['focus'] = 1
+ self.passwordEntry.enterText('')
+
+ self.noSecretChatWarning.show()
+
+ def exitNoSecretChatWarning(self):
+ assert self.debugFunction()
+ self.noSecretChatWarning.hide()
+
+
+ def enterActivateChat(self):
+ assert self.debugFunction()
+ # The parent password has been entered, so now provide the
+ # option to enable secret friends.
+
+ if self.activateChatGui == None:
+ # setup ok/cancel button art
+ guiButton = loader.loadModel("phase_3/models/gui/quit_button")
+ buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui')
+ okButtonImage = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+ cancelButtonImage = (buttons.find('**/CloseBtn_UP'),
+ buttons.find('**/CloseBtn_DN'),
+ buttons.find('**/CloseBtn_Rllvr'))
+ moreButtonImage = (guiButton.find("**/QuitBtn_UP"),
+ guiButton.find("**/QuitBtn_DN"),
+ guiButton.find("**/QuitBtn_RLVR"))
+ # setup radio button art
+ nameShopGui = loader.loadModel("phase_3/models/gui/nameshop_gui")
+ circle = nameShopGui.find("**/namePanelCircle")
+
+ # need frame for radio button setup
+ self.activateChatGui = DirectFrame(parent = aspect2dp,
+ pos = (0.0, 0.1, 0.2),
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = OTPGlobals.GlobalDialogColor,
+ image_scale = (1.8, 1.0, 1.6),
+ text = OTPLocalizer.ActivateChat,
+ text_align = TextNode.ALeft,
+ text_wordwrap = 33,
+ text_scale = TTLocalizer.CMactivateChat,
+ text_pos = (-0.82, 0.58),
+ textMayChange = 0,
+ )
+
+ # Make a copy of the circle so we can move it up a bit.
+ innerCircle = circle.copyTo(hidden)
+ innerCircle.setPos(0, 0, 0.2)
+
+ # Also copy the circle to the frame itself, as a white
+ # circular background behind the radio button circles (and a
+ # black ring behind that).
+ # check box 1
+ self.c1b = circle.copyTo(self.activateChatGui, -1)
+ self.c1b.setColor(0, 0, 0, 1)
+ self.c1b.setPos(-0.8, 0, 0.29)
+ self.c1b.setScale(0.4)
+ c1f = circle.copyTo(self.c1b)
+ c1f.setColor(1, 1, 1, 1)
+ c1f.setScale(0.8)
+ # check box 2
+ self.c2b = circle.copyTo(self.activateChatGui, -2)
+ self.c2b.setColor(0, 0, 0, 1)
+ self.c2b.setPos(-0.8, 0, 0.14)
+ self.c2b.setScale(0.4)
+ c2f = circle.copyTo(self.c2b)
+ c2f.setColor(1, 1, 1, 1)
+ c2f.setScale(0.8)
+ # check box 3
+ self.c3b = circle.copyTo(self.activateChatGui, -2)
+ self.c3b.setColor(0, 0, 0, 1)
+ self.c3b.setPos(-0.8, 0, -0.01)
+ self.c3b.setScale(0.4)
+ c3f = circle.copyTo(self.c3b)
+ c3f.setColor(1, 1, 1, 1)
+ c3f.setScale(0.8)
+
+ # add some title text
+ DirectLabel(self.activateChatGui,
+ relief = None,
+ text = OTPLocalizer.ActivateChatTitle,
+ text_align = TextNode.ACenter,
+ text_scale = 0.07,
+ text_pos = (0, 0.7),
+ textMayChange = 0,
+ )
+ if (base.cr.productName != "JP"):
+ # In the Japanese version, we do not want the more info button
+ # so we will simply not create it
+ DirectButton(self.activateChatGui,
+ image = moreButtonImage,
+ image_scale = (1.25, 1.0, 1.0),
+ relief = None,
+ text = OTPLocalizer.ActivateChatMoreInfo,
+ text_scale = 0.06,
+ text_pos = (0,-0.02),
+ textMayChange = 0,
+ pos = (0.0, 0.0, -0.7),
+ command = self.__handleActivateChatMoreInfo)
+ # no secret friends
+ self.dcb1 = HackedDirectRadioButton(
+ parent = self.activateChatGui,
+ relief = None,
+ scale = 0.1,
+ boxImage = innerCircle,
+ boxImageScale = 2.5,
+ boxImageColor = VBase4(0, 0.25, 0.5, 1),
+ boxRelief = None,
+ pos = (-0.745, 0, 0.297),
+ command = self.__updateCheckBoxen,
+ extraArgs = [1])
+ # restricted secret friends
+ self.dcb2 = HackedDirectRadioButton(
+ parent = self.activateChatGui,
+ relief = None,
+ scale = 0.1,
+ boxImage = innerCircle,
+ boxImageScale = 2.5,
+ boxImageColor = VBase4(0, 0.25, 0.5, 1),
+ boxRelief = None,
+ pos = (-0.745, 0, 0.147),
+ command = self.__updateCheckBoxen,
+ extraArgs = [2])
+ # open secret friends
+ self.dcb3 = HackedDirectRadioButton(
+ parent = self.activateChatGui,
+ relief = None,
+ scale = 0.1,
+ boxImage = innerCircle,
+ boxImageScale = 2.5,
+ boxImageColor = VBase4(0, 0.25, 0.5, 1),
+ boxRelief = None,
+ pos = (-0.745, 0, -0.003),
+ command = self.__updateCheckBoxen,
+ extraArgs = [3])
+ DirectButton(self.activateChatGui,
+ image = okButtonImage,
+ relief = None,
+ text = OTPLocalizer.ActivateChatYes,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = (-0.35, 0.0, -0.27),
+ command = self.__handleActivateChatYes)
+ DirectButton(self.activateChatGui,
+ image = cancelButtonImage,
+ relief = None,
+ text = OTPLocalizer.ActivateChatNo,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = (0.35, 0.0, -0.27),
+ command = self.__handleActivateChatNo)
+ guiButton.removeNode()
+ buttons.removeNode()
+ nameShopGui.removeNode()
+ innerCircle.removeNode()
+ # put the check buttons into a start state
+ self.__initializeCheckBoxen()
+ self.activateChatGui.show()
+
+ def __initializeCheckBoxen(self):
+ # update the check box gui based on current chat state
+ if base.cr.secretChatAllowed and not base.cr.secretChatNeedsParentPassword:
+ # enabled
+ self.dcb1['indicatorValue'] = 0
+ self.dcb2['indicatorValue'] = 0
+ self.dcb3['indicatorValue'] = 1
+ elif base.cr.secretChatAllowed and base.cr.secretChatNeedsParentPassword:
+ # restricted
+ self.dcb1['indicatorValue'] = 0
+ self.dcb2['indicatorValue'] = 1
+ self.dcb3['indicatorValue'] = 0
+ else:
+ # disabled
+ self.dcb1['indicatorValue'] = 1
+ self.dcb2['indicatorValue'] = 0
+ self.dcb3['indicatorValue'] = 0
+
+ def __updateCheckBoxen(self, value, checkBox):
+ # if we have a value...
+ if value == 0:
+ return
+ # update the other check boxes so only one is selected at a given time
+ if checkBox == 1:
+ # disabled
+ self.dcb2['indicatorValue'] = 0
+ self.dcb3['indicatorValue'] = 0
+ elif checkBox == 2:
+ # restricted
+ self.dcb1['indicatorValue'] = 0
+ self.dcb3['indicatorValue'] = 0
+ else:
+ # enabled
+ self.dcb1['indicatorValue'] = 0
+ self.dcb2['indicatorValue'] = 0
+
+ def exitActivateChat(self):
+ assert self.debugFunction()
+ self.activateChatGui.hide()
+
+ def enterSecretChatActivated(self, mode=2):
+ assert self.debugFunction()
+ # Feedback that secret chat mode been changed.
+
+ # pick the appropriate text based on the secret friends mode activated
+ if mode == 0:
+ modeText = OTPLocalizer.SecretChatDeactivated
+ elif mode == 1:
+ modeText = OTPLocalizer.RestrictedSecretChatActivated
+ else:
+ modeText = OTPLocalizer.SecretChatActivated
+
+ if self.secretChatActivated == None:
+ guiButton = loader.loadModel("phase_3/models/gui/quit_button")
+ optionsButtonImage = (guiButton.find("**/QuitBtn_UP"),
+ guiButton.find("**/QuitBtn_DN"),
+ guiButton.find("**/QuitBtn_RLVR"))
+ buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui')
+ buttonImage = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+ self.secretChatActivated = \
+ DirectFrame(parent = aspect2dp,
+ pos = (0.0, 0.1, 0.4),
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = OTPGlobals.GlobalDialogColor,
+ image_scale = (1.0, 1.0, 0.8),
+ text = modeText,
+ text_align = TextNode.ACenter,
+ text_wordwrap = 14,
+ text_scale = TTLocalizer.CMchatActivated,
+ text_pos = (0, 0.25),
+ )
+ # ok button
+ DirectButton(self.secretChatActivated,
+ image = buttonImage,
+ relief = None,
+ text = OTPLocalizer.SecretChatActivatedOK,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = (0.0, 0.0, -0.1),
+ command = self.__handleSecretChatActivatedOK)
+ # change options button
+ """DirectButton(self.secretChatActivated,
+ image = optionsButtonImage,
+ image_scale = (1.75, 1.0, 1.0),
+ relief = None,
+ text = OTPLocalizer.SecretChatActivatedChange,
+ text_scale = 0.06,
+ text_pos = (0,-0.02),
+ textMayChange = 0,
+ pos = (0.0, 0.0, -0.3),
+ command = self.__handleSecretChatActivatedChangeOptions)"""
+ buttons.removeNode()
+ guiButton.removeNode()
+ else:
+ # dialog exists, just set the text
+ self.secretChatActivated['text'] = modeText
+ self.secretChatActivated.show()
+
+ def exitSecretChatActivated(self):
+ assert self.debugFunction()
+ self.secretChatActivated.hide()
+
+ def enterProblemActivatingChat(self):
+ assert self.debugFunction()
+ # Some rare problem activating secret chat.
+
+ if self.problemActivatingChat == None:
+ buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui')
+ buttonImage = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+ self.problemActivatingChat = \
+ DirectFrame(parent = aspect2dp,
+ pos = (0.0, 0.1, 0.4),
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = OTPGlobals.GlobalDialogColor,
+ image_scale = (1.2, 1.0, 0.9),
+ text = "",
+ text_align = TextNode.ALeft,
+ text_wordwrap = 18,
+ text_scale = 0.06,
+ text_pos = (-0.50, 0.28),
+ textMayChange = 1,
+ )
+ DirectButton(self.problemActivatingChat,
+ image = buttonImage,
+ relief = None,
+ text = OTPLocalizer.ProblemActivatingChatOK,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = (0.0, 0.0, -0.28),
+ command = self.__handleProblemActivatingChatOK)
+ buttons.removeNode()
+ self.problemActivatingChat.show()
+
+ def exitProblemActivatingChat(self):
+ assert self.debugFunction()
+ self.problemActivatingChat.hide()
+
+ def __normalButtonPressed(self):
+ """
+ The "normal button" is the button in the upper left of the screen
+ that is normally used to do free chat.
+ """
+ assert self.debugFunction()
+ messenger.send('wakeup')
+ if base.cr.productName in ["DisneyOnline-US", "ES"]:
+ if base.cr.whiteListChatEnabled:
+ self.fsm.request("normalChat")
+ elif not base.cr.isParentPasswordSet():
+ self.paidNoParentPassword = 1
+ self.fsm.request("unpaidChatWarning")
+ elif not base.cr.allowSecretChat():
+ self.fsm.request("noSecretChatAtAllAndNoWhitelist")
+ elif not base.localAvatar.canChat():
+ self.fsm.request("openChatWarning")
+ else:
+ self.fsm.request("normalChat")
+ elif (base.cr.productName == 'Terra-DMC'):
+ if not base.cr.allowSecretChat():
+ self.fsm.request("noSecretChatWarning")
+ elif not base.localAvatar.canChat():
+ self.fsm.request("openChatWarning")
+ else:
+ self.fsm.request("normalChat")
+ # TODO: finalize Disney Japan chat policy
+ elif base.cr.productName in ['DisneyOnline-UK', 'DisneyOnline-AP', 'JP', 'BR', 'FR']:
+ if base.cr.whiteListChatEnabled:
+ self.fsm.request("normalChat")
+ elif not base.cr.isParentPasswordSet():
+ self.paidNoParentPassword = 1
+ self.fsm.request("unpaidChatWarning")
+ elif not base.cr.allowSecretChat():
+ self.paidNoParentPassword = 1
+ self.fsm.request("unpaidChatWarning")
+ elif not base.localAvatar.canChat():
+ self.fsm.request("openChatWarning")
+ else:
+ self.fsm.request("normalChat")
+ else:
+ print ("ChatManager: productName: %s not recognized" % (base.cr.productName))
+
+ def __scButtonPressed(self):
+ assert self.debugFunction()
+ messenger.send('wakeup')
+ if (self.fsm.getCurrentState().getName() == "speedChat"):
+ self.fsm.request("mainMenu")
+ else:
+ self.fsm.request("speedChat")
+
+ def __whisperButtonPressed(self, avatarName, avatarId, playerId):
+ assert self.debugFunction()
+ messenger.send('wakeup')
+ playerInfo = None
+ if playerId:
+ playerInfo = base.cr.playerFriendsManager.getFriendInfo(playerId)
+
+ if playerInfo:
+ if playerInfo.understandableYesNo:
+ self.fsm.request("whisperChatPlayer", [avatarName, playerId])
+ return
+ if avatarId:
+ self.fsm.request("whisperChat", [avatarName, avatarId])
+
+ def enterNormalChat(self):
+ result = ChatManager.ChatManager.enterNormalChat(self)
+ # if result is None, something went wrong, fallback to main menu
+ if result == None:
+ self.notify.warning('something went wrong in enterNormalChat, falling back to main menu')
+ self.fsm.request('mainMenu')
+
+
+ def enterWhisperChatPlayer(self, avatarName, playerId):
+ result = ChatManager.ChatManager.enterWhisperChatPlayer(self, avatarName, playerId)
+ self.chatInputNormal.setPos(self.whisperPos)
+ # if result is None, something went wrong, fallback to main menu
+ if result == None:
+ self.notify.warning('something went wrong in enterWhisperChatPlayer, falling back to main menu')
+ self.fsm.request('mainMenu')
+
+ def enterWhisperChat(self, avatarName, avatarId):
+ result = ChatManager.ChatManager.enterWhisperChat(self, avatarName, avatarId)
+ self.chatInputNormal.setPos(self.whisperPos)
+ if result == None:
+ self.notify.warning('something went wrong in enterWhisperChat, falling back to main menu')
+ self.fsm.request('mainMenu')
+
+ def enterNoSecretChatAtAllAndNoWhitelist(self):
+ assert self.debugFunction()
+ # Pop up a dialog indicating the user hasn't activated secret
+ # chat yet, and that he/she must quit the game and go to the
+ # web page in order to activate it.
+ if self.noSecretChatAtAllAndNoWhitelist == None:
+ buttons = loader.loadModel('phase_3/models/gui/dialog_box_buttons_gui')
+ okButtonImage = (buttons.find('**/ChtBx_OKBtn_UP'),
+ buttons.find('**/ChtBx_OKBtn_DN'),
+ buttons.find('**/ChtBx_OKBtn_Rllvr'))
+ self.noSecretChatAtAllAndNoWhitelist = DirectFrame(
+ parent = aspect2dp,
+ pos = (0.0, 0.1, 0.2),
+ relief = None,
+ image = DGG.getDefaultDialogGeom(),
+ image_color = OTPGlobals.GlobalDialogColor,
+ image_scale = (1.4, 1.0, 1.48),
+ text = OTPLocalizer.NoSecretChatAtAllAndNoWhitelist,
+ text_wordwrap = 20,
+ textMayChange = 0,
+ text_scale = 0.06,
+ text_pos = (0, 0.5),
+ )
+
+ DirectLabel(parent = self.noSecretChatAtAllAndNoWhitelist,
+ relief = None,
+ pos = (0, 0, 0.6),
+ text = OTPLocalizer.NoSecretChatAtAllAndNoWhitelistTitle,
+ textMayChange = 0,
+ text_scale = 0.08)
+ DirectButton(self.noSecretChatAtAllAndNoWhitelist,
+ image = okButtonImage,
+ relief = None,
+ text = OTPLocalizer.NoSecretChatAtAllOK,
+ text_scale = 0.05,
+ text_pos = (0.0, -0.1),
+ textMayChange = 0,
+ pos = (0.0, 0.0, -0.6),
+ command = self.__handleNoSecretChatAtAllOK)
+ buttons.removeNode()
+
+ self.noSecretChatAtAllAndNoWhitelist.show()
+
+ def exitNoSecretChatAtAllAndNoWhitelist(self):
+ assert self.debugFunction()
+ self.noSecretChatAtAllAndNoWhitelist.hide()
+
+ def enterTrueFriendTeaserPanel(self):
+ self.previousStateBeforeTeaser = None
+ place = base.cr.playGame.getPlace()
+ if place:
+ self.previousStateBeforeTeaser = place.fsm.getCurrentState().getName()
+ place.fsm.request('stopped')
+ self.teaser = TeaserPanel.TeaserPanel(pageName='secretChat',
+ doneFunc=self.handleOkTeaser)
+ pass
+
+ def exitTrueFriendTeaserPanel(self):
+ self.teaser.destroy()
+ place = base.cr.playGame.getPlace()
+ if place:
+ if self.previousStateBeforeTeaser:
+ place.fsm.request(self.previousStateBeforeTeaser, force=1)
+ else:
+ place.fsm.request('walk')
+ pass
+
+ def handleOkTeaser(self):
+ self.fsm.request("mainMenu")
+
+ def __whisperScButtonPressed(self, avatarName, avatarId, playerId):
+ assert self.debugFunction()
+ messenger.send('wakeup')
+ #print("__whisperScButtonPressed %s %s" % (avatarId, playerId))
+ hasManager = hasattr(base.cr, "playerFriendsManager")
+ transientFriend = 0
+ if hasManager:
+ transientFriend = base.cr.playerFriendsManager.askTransientFriend(avatarId)
+ if transientFriend:
+ playerId = base.cr.playerFriendsManager.findPlayerIdFromAvId(avatarId)
+
+ if avatarId and not transientFriend:
+ if (self.fsm.getCurrentState().getName() == "whisperSpeedChat"):
+ self.fsm.request("whisper", [avatarName, avatarId, playerId])
+ else:
+ self.fsm.request("whisperSpeedChat", [avatarId])
+
+ elif playerId:
+ if (self.fsm.getCurrentState().getName() == "whisperSpeedChatPlayer"):
+ self.fsm.request("whisper", [avatarName, avatarId, playerId])
+ else:
+ self.fsm.request("whisperSpeedChatPlayer", [playerId])
+
+ def __whisperCancelPressed(self):
+ assert self.debugFunction()
+ self.fsm.request("mainMenu")
+
+ def __handleOpenChatWarningOK(self):
+ assert self.debugFunction()
+ self.fsm.request("mainMenu")
+
+ def __handleUnpaidChatWarningDone(self):
+ assert self.debugFunction()
+ # no, we don't have a book. But this code conveniently decides whether to walk or swim
+ place = base.cr.playGame.getPlace()
+ if place:
+ place.handleBookClose()
+ self.fsm.request("mainMenu")
+
+ def __handleUnpaidChatWarningContinue(self):
+ assert self.debugFunction()
+ self.fsm.request("mainMenu")
+
+ def __handleUnpaidChatWarningPay(self):
+ assert self.debugFunction()
+ if base.cr.isWebPlayToken():
+ self.fsm.request("leaveToPayDialog")
+ else:
+ # We should never get here
+ self.fsm.request("mainMenu")
+
+ def __handleNoSecretChatAtAllOK(self):
+ assert self.debugFunction()
+ self.fsm.request("mainMenu")
+
+ def __handleNoSecretChatWarningOK(self, *args):
+ assert self.debugFunction()
+ password = self.passwordEntry.get()
+ tt = base.cr.loginInterface
+ okflag, message = tt.authenticateParentPassword(
+ base.cr.userName, base.cr.password, password)
+ if okflag:
+ self.fsm.request("activateChat")
+ elif message:
+ # Error connecting.
+ self.fsm.request("problemActivatingChat")
+ self.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % (message)
+ else:
+ # Wrong password.
+ self.noSecretChatWarning['text'] = OTPLocalizer.NoSecretChatWarningWrongPassword
+ self.passwordEntry['focus'] = 1
+ self.passwordEntry.enterText('')
+
+ def __handleNoSecretChatWarningCancel(self):
+ assert self.debugFunction()
+ self.fsm.request("mainMenu")
+
+ def __handleActivateChatYes(self):
+ assert self.debugFunction()
+ password = self.passwordEntry.get()
+ tt = base.cr.loginInterface
+
+ if self.dcb1['indicatorValue']:
+ base.cr.secretChatAllowed = 0
+ mode = 0
+ elif self.dcb2['indicatorValue']:
+ base.cr.secretChatAllowed = 1
+ base.cr.secretChatNeedsParentPassword = 1
+ mode = 1
+ else:
+ base.cr.secretChatAllowed = 1
+ base.cr.secretChatNeedsParentPassword = 0
+ mode = 2
+
+ okflag, message = tt.enableSecretFriends(
+ base.cr.userName, base.cr.password, password)
+ if okflag:
+ # Tell the server we now have chat enabled.
+ tt.resendPlayToken()
+ self.fsm.request("secretChatActivated", [mode])
+ else:
+ # Error connecting.
+ if message == None:
+ # Wrong password! This shouldn't be possible because
+ # we just validated this password. But what the heck.
+ message = "Parent Password was invalid."
+ self.fsm.request("problemActivatingChat")
+ self.problemActivatingChat['text'] = OTPLocalizer.ProblemActivatingChat % (message)
+
+ def __handleActivateChatMoreInfo(self):
+ assert self.debugFunction()
+ self.fsm.request("chatMoreInfo")
+
+ def __handleActivateChatNo(self):
+ assert self.debugFunction()
+ self.fsm.request("mainMenu")
+
+ def __handleSecretChatActivatedOK(self):
+ assert self.debugFunction()
+ self.fsm.request("mainMenu")
+
+ def __handleSecretChatActivatedChangeOptions(self):
+ assert self.debugFunction()
+ self.fsm.request("activateChat")
+
+ def __handleProblemActivatingChatOK(self):
+ assert self.debugFunction()
+ self.fsm.request("mainMenu")
+
+ if __debug__:
+ def debugFunction(self):
+ """for debugging"""
+ self.debugPrint(traceFunctionCall(sys._getframe(1)))
+ return 1
+
+ def debugPrint(self, message):
+ """for debugging"""
+ return self.notify.debug("%s %s"%(self.fsm.getCurrentState().getName(), message))
+
+ def messageSent(self):
+ pass
+
+ def deactivateChat(self):
+ pass
+
diff --git a/toontown/src/chat/__init__.py b/toontown/src/chat/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/chat/twhitelist.dat b/toontown/src/chat/twhitelist.dat
new file mode 100644
index 0000000..8f53607
--- /dev/null
+++ b/toontown/src/chat/twhitelist.dat
@@ -0,0 +1,25717 @@
+!
+%
+%s
+&
+'
+'boss
+'course
+'ello
+(
+)
+*scared
+,
+-
+-_-
+-_-:
+-licious
+.
+...
+0_0
+0_o
+:
+:'(
+:(
+:)
+:*
+:-(
+:-)
+:-o
+:_
+:d
+:o
+:o&
+:o)
+:p
+;
+;)
+;-)
+=
+=)
+=-)
+=o)
+?
+@_@
+[
+]
+_
+a
+a-hem
+aa
+aacres
+aah
+aardvark
+aardvark's
+aardvarks
+aarg
+aargghh
+aargh
+aarrgghh
+aarrgghhh
+aarrm
+aarrr
+aarrrgg
+aarrrgh
+aarrrr
+aarrrrggg
+aarrrrr
+aarrrrrr
+aarrrrrrr
+aarrrrrrrrr
+aarrrrrrrrrghhhhh
+aay
+abacus
+abaft
+abalone-shell
+abandon
+abandoned
+abandoner
+abandoning
+abandons
+abassa
+abbot
+abbrev
+abbreviate
+abbreviated
+abbreviation
+abbreviations
+abe
+abeam
+aberrant
+abhor
+abhors
+abide
+abigail
+abilities
+ability
+ability's
+abira
+able
+able-bodied
+abler
+ablest
+abnormal
+aboard
+abode
+abominable
+abound
+abounds
+about
+above
+abracadabra
+abraham
+abrasive
+abrupt
+abruptly
+absence
+absence's
+absences
+absent
+absent-minded
+absented
+absenting
+absently
+absents
+absolute
+absolutely
+absolutes
+absolution
+absorb
+absorbs
+absurd
+absurdly
+abu
+abu's
+abundant
+academic
+academics
+academies
+academy
+academy's
+acc
+accelerate
+accelerated
+accelerates
+acceleration
+accelerator
+accelerators
+accent
+accented
+accents
+accentuate
+accentuates
+accept
+acceptable
+acceptance
+accepted
+accepter
+accepter's
+accepters
+accepting
+acceptive
+accepts
+access
+accessed
+accesses
+accessibility
+accessing
+accessories
+accessorize
+accessory
+accident
+accident's
+accidental
+accidentally
+accidently
+accidents
+accolade
+accompanies
+accompany
+accompanying
+accomplice
+accomplish
+accomplished
+accomplishes
+accomplishing
+accomplishment
+accord
+according
+accordingly
+accordion
+accordions
+accountable
+accountant
+accounted
+accounting
+accountings
+accounts
+accrue
+acct
+acct's
+accts
+accumulate
+accumulated
+accumulating
+accumulator
+accumulators
+accuracy
+accurate
+accurately
+accursed
+accusation
+accusations
+accuse
+accused
+accuser
+accusers
+accuses
+accusing
+accustomed
+ace
+ace's
+aced
+aces
+ach
+ache
+ached
+aches
+achieve
+achieved
+achievement
+achievement's
+achievements
+achiever
+achievers
+achieves
+achieving
+aching
+achoo
+achy
+acknowledge
+acknowledged
+acknowledgement
+acme
+acorn
+acorns
+acoustic
+acoustics
+acquaintance
+acquaintances
+acquainted
+acquiesce
+acquire
+acquired
+acquires
+acquiring
+acquit
+acre
+acres
+acrobat
+acronym
+acronyms
+across
+acsot
+act
+act's
+acted
+acting
+action
+action's
+action-figure
+actions
+activate
+activated
+activates
+activating
+activation
+active
+actively
+activies
+activist
+activities
+activity
+activity's
+actor
+actor's
+actors
+actress
+actress's
+actresses
+acts
+actual
+actually
+actuals
+acuda
+acupuncture
+ad
+ad's
+adam
+adamant
+adapt
+adapted
+adapter
+adaptor
+adaptors
+add
+added
+adder
+adders
+adding
+addition
+addition's
+additional
+additionally
+additions
+addle
+addled
+addressed
+addresses
+addressing
+adds
+adella
+adept
+adequate
+adequately
+adieu
+adios
+adjective
+adjoined
+adjoining
+adjourn
+adjourned
+adjudicator
+adjust
+adjusted
+adjuster
+adjuster's
+adjusters
+adjusting
+adjustive
+adjustment
+adjustment's
+adjustments
+adjusts
+admin
+administrative
+administrator
+administrators
+admirable
+admirably
+admiral
+admiral's
+admirals
+admiralty
+admiration
+admire
+admired
+admirer
+admirer's
+admirers
+admires
+admiring
+admission
+admissions
+admit
+admits
+admittance
+admitted
+admittedly
+admitting
+ado
+adobe
+adopt
+adopted
+adopting
+adopts
+adorable
+adoration
+adore
+adored
+adores
+adoria
+adoring
+adrenalin
+adrenaline
+adrienne
+adrienne's
+adrift
+ads
+adults
+adv
+advance
+advanced
+advancer
+advancers
+advances
+advancing
+advantage
+advantaged
+advantages
+advantaging
+advent
+adventure
+adventured
+adventureland
+adventureland's
+adventurer
+adventurer's
+adventurers
+adventures
+adventuring
+adventurous
+adversary
+adverse
+advert
+advertise
+advertised
+advertisement
+advertisements
+advertising
+adverts
+advice
+advices
+advisable
+advise
+advised
+adviser
+advisers
+advises
+advising
+advisor
+advocacy
+advocate
+adz
+adz's
+aerobic
+aerobics
+aerodynamic
+aesthetically
+afar
+affect
+affected
+affecter
+affecting
+affection
+affectionate
+affections
+affective
+affects
+affiliate
+affiliation
+affirmation
+affirmative
+affixed
+afflict
+afflicted
+affliction
+afford
+affordable
+afforded
+affording
+affords
+afire
+afk
+afloat
+afloatin
+afloats
+afn
+afoot
+afore
+afoul
+afraid
+aft
+afta
+after
+afterburner
+afterburners
+afterlife
+afternoon
+afternoons
+aftershave
+afterthought
+afterward
+afterwards
+again
+against
+agatha
+aged
+ageless
+agencies
+agency
+agenda
+agent
+agent's
+agentive
+agents
+ages
+aggravate
+aggravated
+aggravates
+aggravating
+aggravation
+aggregation
+aggression
+aggressions
+aggressive
+aggressively
+aggressiveness
+aggrieved
+aggro
+agile
+agility
+aging
+agitated
+aglow
+ago
+agony
+agora
+agoraphobic
+agrabah
+agrabah's
+agree
+agreeable
+agreed
+agreeing
+agreement
+agreement's
+agreements
+agreer
+agreers
+agrees
+agro
+aground
+ah
+aha
+ahab's
+ahead
+ahem
+ahh
+ahhh
+ahhhhh
+ahhhhhh
+ahhhhhhh
+ahhhhhhhh
+ahhhhhhhhh
+ahhhhhhhhhh
+ahhhhhhhhhhh
+ahhhhhhhhhhhh
+ahhhhhhhhhhhhh
+ahhhhhhhhhhhhhh
+ahhhhhhhhhhhhhhhh
+ahhhhhhhhhhhhhhhhh
+ahhhhhhhhhhhhhhhhhh
+ahhhhhhhhhhhhhhhhhhh
+ahhhhhhhhhhhhhhhhhhhhh
+ahhhhhhhhhhhhhhhhhhhhhhhhhhh
+ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+ahoy
+ahs
+ai
+aid
+aide
+aided
+aiden
+aiden's
+aiding
+ail
+ailed
+aileen
+ailing
+ailment
+ailments
+ails
+aim
+aimed
+aimer
+aimers
+aiming
+aimless
+aimlessly
+aims
+ain't
+aint
+air
+airbags
+airborne
+aircraft
+aircrew
+aired
+airer
+airers
+airhead
+airhead's
+airheads
+airing
+airings
+airlock
+airplane
+airplane's
+airplanes
+airport
+airport's
+airports
+airs
+airship
+airships
+airwaves
+aisle
+aj
+aj's
+aka
+akaboshi
+akin
+akon
+al
+al's
+ala
+alabaster
+aladdin
+aladdin's
+alameda
+alameda's
+alamedas
+alan
+alan's
+alana
+alana's
+alarm
+alarmed
+alarming
+alarms
+alas
+alb
+alba
+albatross
+albeit
+albert
+alberto
+albino
+album
+albums
+alchemic
+alcove
+aldous
+aldrin
+aldrin's
+ale
+alehouse
+alert
+alerted
+alerting
+alerts
+ales
+alex
+alex's
+alexa
+alexander
+alexia
+alfa
+alfredo
+algebra
+algorithm
+ali
+alias
+aliases
+alibi
+alibis
+alice
+alice's
+alien
+alien's
+alienated
+aliens
+alight
+align
+aligned
+alignment
+alike
+alina
+alive
+alkies
+alky
+all
+all-4-fun
+all-in
+all-new
+all-out
+all-small
+all-star
+all-stars
+allaince
+allegiance
+allegory
+allergic
+allergies
+allergy
+alley
+alleys
+allianc
+alliance
+alliances
+allied
+allies
+alligator
+alligator's
+alligators
+alligators'
+allin
+allocate
+allocated
+allocation
+allot
+allover
+allow
+allowable
+allowance
+allowance's
+allowanced
+allowances
+allowancing
+allowed
+allowing
+allows
+alls
+allsorts
+alludes
+allure
+ally
+ally's
+alma
+almighty
+almond
+almost
+alodia
+aloe
+aloft
+aloha
+alone
+along
+alongside
+alot
+aloud
+alpha
+alphabet
+alphabetical
+alphabetically
+alphas
+alpine
+alps
+already
+alright
+also
+alt
+altar
+altar-ego
+alter
+alterations
+altercations
+altered
+altering
+alternate
+alternately
+alternates
+alternating
+alternative
+alternatively
+alternatives
+alternator
+alters
+althea
+although
+altitude
+alto
+altogether
+altos
+altruistic
+alts
+alumni
+always
+aly
+aly's
+alyson
+alyson's
+am
+amanda
+amaryllis
+amass
+amassing
+amateur
+amaze
+amazed
+amazes
+amazing
+amazingly
+amazon
+amazon's
+amazons
+ambassador
+ambassador's
+ambassadors
+amber
+ambidextrous
+ambiguous
+ambition
+ambition's
+ambitions
+ambitious
+ambitiously
+ambrosia
+ambulance
+ambulances
+ambush
+ambushed
+ambushes
+ambushing
+amelia
+amen
+amenable
+amend
+amending
+amends
+amenities
+american
+amethyst
+amidships
+amidst
+amigas
+amigo
+amigos
+amine
+amiss
+amnesia
+among
+amongst
+amore
+amos
+amount
+amounted
+amounter
+amounters
+amounting
+amounts
+amp
+amphitrite
+ample
+amputated
+amry
+ams
+amt
+amts
+amuck
+amulets
+amuse
+amused
+amusement
+amusement's
+amusements
+amuses
+amusing
+amy
+an
+an'
+anachronistic
+analog
+analytical
+analyze
+analyzing
+anarchy
+anatomy
+ancestors
+anchor
+anchorage
+anchored
+anchoring
+anchors
+anchovies
+ancient
+anciently
+ancients
+and
+andaba
+andago
+andaire
+andama
+andila
+andira
+andoso
+andrea
+andrea's
+andrew
+andrina
+andrina's
+andro
+andros
+andumal
+anegola
+anegoso
+anemic
+anemones
+anent
+anew
+angaba
+angama
+angassa
+ange
+angel
+angel's
+angelfish
+angelfood
+angels
+anger
+angered
+angering
+angers
+angle
+angled
+angler
+angler's
+anglers
+angles
+angling
+angrier
+angries
+angriest
+angrily
+angry
+angst
+angus
+animal
+animal's
+animal-talent
+animal-talents
+animally
+animals
+animate
+animated
+animates
+animatings
+animation
+animations
+animator
+animator's
+animators
+anime
+anita
+ankle
+anklet
+anklets
+ankoku
+ann
+ann's
+anna
+anna's
+anne
+anne's
+anneliese
+anneliese's
+annie
+annie's
+annihilate
+annihilated
+annihilation
+anniversary
+annos
+announce
+announced
+announcement
+announcements
+announcer
+announcer's
+announcers
+announces
+announcing
+annoy
+annoyance
+annoyance's
+annoyances
+annoyed
+annoyer
+annoyers
+annoying
+annoyingly
+annoys
+annual
+annually
+annuals
+annul
+anomaly
+anon
+anonymity
+another
+another's
+anselmo
+answer
+answered
+answerer
+answerers
+answering
+answers
+ant
+ant's
+antacid
+antagonist
+antagonize
+antagonized
+antagonizing
+antama
+antarctic
+antassa
+ante
+antelope
+antelope's
+antelopes
+antenna
+antes
+anthem
+anther
+anthers
+anthill
+anthill's
+anthills
+anthony
+anthropology
+anti-gravity
+antiano
+antibacterial
+antibiotic
+antibiotics
+antibodies
+antic
+anticipate
+anticipated
+anticipating
+anticipation
+anticipatively
+anticlimactic
+antics
+antigue
+antik
+antima
+antios
+antique
+antiques
+antiros
+antis
+antisocial
+antivirus
+anton
+anton's
+ants
+antsy
+antumal
+anuberos
+anubi
+anubos
+anvil
+anvils
+anxieties
+anxiety
+anxious
+anxiously
+any
+anybodies
+anybody
+anybody's
+anyhow
+anymore
+anyone
+anyone's
+anyones
+anyplace
+anything
+anything's
+anythings
+anytime
+anywas
+anyway
+anyways
+anywhere
+anywheres
+aoba
+aobasar
+aoboshi
+aoi
+aoogah
+aoogahs
+aoteoroa
+apart
+apartment
+apartments
+apathetic
+ape
+ape's
+apes
+apex
+apiece
+aplenty
+apocalyptyca
+apologies
+apologize
+apologized
+apologizes
+apologizing
+apology
+apology's
+apostles
+apostrophe
+apostrophe's
+apostrophes
+app
+appalled
+apparel
+apparent
+apparently
+appeal
+appealed
+appealer
+appealers
+appealing
+appeals
+appear
+appearance
+appearances
+appeared
+appearer
+appearers
+appearing
+appears
+appease
+appeasing
+append
+appendices
+appendix
+appetite
+appetites
+appetizer
+appetizers
+appetizing
+applaud
+applauded
+applauder
+applauding
+applauds
+applause
+apple
+apples
+applesauce
+appliances
+applicable
+applicants
+application
+application's
+applications
+applied
+applier
+appliers
+applies
+apply
+applying
+appoint
+appointed
+appointer
+appointers
+appointing
+appointive
+appointment
+appointments
+appoints
+appose
+apposed
+appreciate
+appreciated
+appreciates
+appreciation
+appreciative
+apprehension
+apprehensive
+apprentice
+approach
+approached
+approacher
+approachers
+approaches
+approaching
+appropriate
+appropriated
+appropriately
+appropriates
+appropriatest
+appropriating
+appropriation
+appropriations
+appropriative
+approval
+approve
+approved
+approver
+approver's
+approvers
+approves
+approving
+approx
+approximate
+approximately
+apps
+appt
+apr
+apricot
+april
+april's
+apron
+apt
+aptly
+aqua
+aquablue
+aquarium
+aquariums
+aquatic
+aquatta
+arabian
+arbitrage
+arbitrarily
+arbitrary
+arbor
+arc
+arc's
+arcade
+arcades
+arcadia
+arcane
+arch
+archaeology
+archaic
+archer
+archer's
+archers
+arches
+archibald
+architect
+architecture
+archway
+archways
+arctic
+arctic's
+are
+area
+area's
+areas
+aren't
+arena
+arenas
+arenberg
+arenberg's
+arent
+arf
+arfur
+arg
+argg
+arggest
+arggg
+argggg
+arggggg
+arggggge
+argggggg
+argggggggggggg
+argggggggh
+argggghhh
+argggh
+arggghhh
+arggh
+argghh
+argghhh
+argghhhh
+argh
+arghgh
+arghghghggh
+arghh
+arghhh
+arghhhh
+arghhhhh
+arghhhhhhhhhhhhhhhhhh
+argon
+argue
+argued
+arguer
+arguer's
+arguers
+argues
+arguing
+argument
+argument's
+arguments
+argust
+aria
+arianna
+ariel
+ariel's
+aright
+aril
+arising
+arista
+aristocat
+aristocat's
+aristocats
+aristocratic
+ark
+arks
+arm
+arm's
+armada
+armadas
+armadillo
+armadillo's
+armadillos
+armchair
+armchair's
+armchairs
+armed
+armer
+armers
+armies
+arming
+armlets
+armoire
+armor
+armory
+armpit
+arms
+armstrong
+army
+army's
+aroma
+aromatic
+around
+arr
+arrack
+arraignment
+arrange
+arranged
+arrangement
+arrangement's
+arrangements
+arranger
+arrangers
+arranges
+arranging
+arrant
+array
+arrest
+arrested
+arresting
+arrests
+arrgg
+arrggg
+arrgggg
+arrggghhh
+arrgghh
+arrgghhh
+arrgh
+arrghh
+arrghhh
+arrghhhh
+arrghhhhhhh
+arrgonauts
+arrival
+arrivals
+arrive
+arrived
+arrivederci
+arriver
+arrives
+arriving
+arrogant
+arrow
+arrowed
+arrowing
+arrows
+arrr
+arrrr
+arrrrgh
+arsis
+art
+art's
+art-talent
+arte
+artezza
+arthritis
+artichoke
+article
+article's
+articled
+articles
+articling
+articulate
+artie
+artifact
+artifacts
+artificial
+artificially
+artillerymen
+artist
+artist's
+artiste
+artistic
+artists
+arts
+artwork
+arty
+aruba
+arwin
+arwin's
+as
+asap
+asarion
+ascended
+ascending
+ascent
+ashame
+ashamed
+ashes
+ashley
+ashley's
+ashore
+ashtray
+ashy
+aside
+asides
+ask
+asked
+asker
+askers
+asking
+asks
+aslan
+aslan's
+aslans
+asleep
+asp
+asparagus
+aspect
+aspect's
+aspects
+aspen
+asphalt
+aspiration
+aspirations
+aspire
+aspirin
+aspiring
+asps
+assemble
+assembled
+assembler
+assemblers
+assembles
+assembling
+assembly
+assert
+assertive
+assessment
+asset
+asset's
+assets
+assign
+assigned
+assigning
+assignment
+assignments
+assigns
+assist
+assistance
+assistant
+assistants
+assisted
+assisting
+assistive
+assn
+assoc
+associate
+associated
+associates
+associating
+association
+association's
+associations
+associative
+assorted
+assortment
+asst
+assume
+assumed
+assumer
+assumes
+assuming
+assumption
+assumption's
+assumptions
+assurance
+assure
+assured
+assuredly
+assures
+aster
+asterisks
+asterius
+astern
+asteroid
+asteroid's
+asteroids
+asthma
+astonish
+astonished
+astonishes
+astonishing
+astounded
+astounds
+astray
+astro
+astro's
+astro-barrier
+astronaut
+astronaut's
+astronauts
+astronomy
+astroturf
+asylum
+at
+ate
+atheist
+athlete
+athletes
+athletic
+athletics
+atlantic
+atlantis
+atlantyans
+atlas
+atm
+atm's
+atmosphere
+atmosphere's
+atmosphered
+atmospheres
+atms
+atom
+atom's
+atomettes
+atomic
+atoms
+atone
+atonement
+atop
+atrocious
+atrocities
+atrocity
+attach
+attached
+attacher
+attachers
+attaches
+attaching
+attachment
+attachments
+attack
+attackable
+attacked
+attacker
+attacker's
+attackers
+attacking
+attacks
+attainable
+attained
+attempt
+attempted
+attempter
+attempters
+attempting
+attempts
+attend
+attendance
+attendant
+attended
+attender
+attenders
+attending
+attends
+attention
+attention's
+attentions
+attentive
+attentively
+attest
+attic
+attic's
+attics
+attina
+attire
+attitude
+attitude's
+attitudes
+attn
+attorney
+attorney's
+attorneys
+attract
+attractant
+attracted
+attracting
+attraction
+attractions
+attractive
+attractively
+attracts
+attribute
+attributes
+attune
+attuned
+attunement
+attunements
+attunes
+attuning
+atty
+auburn
+auction
+audience
+audience's
+audiences
+audio
+audit
+audition
+auditioned
+audits
+auf
+aug
+aught
+augmenter
+august
+auguste
+aula
+aunt
+auntie
+auntie's
+aunties
+aunts
+aunty
+aura
+aurora
+aurora's
+aurorium
+aurors
+aurours
+auspicious
+auspiciously
+australia
+australia's
+auth
+authenticity
+author
+author's
+authored
+authoring
+authoritative
+authorities
+authority
+authority's
+authorization
+authorize
+authorized
+authors
+autism
+autistic
+auto
+auto's
+auto-reel
+autocratic
+autograph
+autographed
+autographs
+automated
+automatic
+automatically
+automatics
+automobile
+automobile's
+automobiles
+autopia
+autopia's
+autopilot
+autos
+autumn
+autumn's
+autumns
+aux
+av
+avail
+availability
+available
+avalanche
+avarice
+avaricia
+avast
+avatar
+avatar's
+avatars
+avater
+avec
+avenge
+avenged
+avenger
+avenger's
+avengers
+avenging
+avenue
+aver
+average
+averaged
+averagely
+averages
+averaging
+aversion
+averted
+aviation
+avid
+avis
+avocados
+avoid
+avoidance
+avoided
+avoider
+avoiders
+avoiding
+avoids
+aw
+await
+awaiting
+awaits
+awake
+awaked
+awaken
+awakening
+awakes
+awaking
+award
+award-winning
+awarded
+awarder
+awarders
+awarding
+awards
+aware
+awareness
+away
+awayme
+awe
+awed
+aweigh
+awesome
+awesomely
+awesomeness
+awesomers
+awesomus
+awestruck
+awful
+awfully
+awhile
+awkward
+awkwardly
+awkwardness
+awl
+awn
+awoke
+awry
+aww
+axed
+axel
+axis
+axisd
+axle
+axles
+ay
+aye
+ayes
+azamaros
+azamaru
+azapi
+azeko
+azenor
+azewana
+aztec
+aztec's
+aztecs
+b-day
+b-sharp
+b4
+babble
+babbles
+babbling
+babied
+babies
+baboon
+baby's
+babyface
+babyish
+babysitter
+babysitters
+babysitting
+baccaneer
+bacchus
+bacchus's
+bachelor
+bachelors
+back
+back-to-school
+back-up
+backbiters
+backbone
+backbones
+backcrash
+backdrop
+backed
+backer
+backers
+backfall
+backfire
+backfired
+backfires
+backflip
+backflips
+backginty
+background
+background's
+backgrounds
+backing
+backpack
+backpack's
+backpacking
+backpacks
+backpedaling
+backpedals
+backs
+backslash
+backspace
+backspaces
+backspacing
+backstabbed
+backstabbers
+backstabbing
+backstreet
+backstroke
+backtrack
+backup
+backups
+backward
+backwardly
+backwardness
+backwards
+backwater
+backwaters
+backwoods
+backyard
+backyard's
+backyards
+bacon
+bacon's
+bacons
+bacteria
+bad
+baddest
+baddie
+baddies
+baddy
+bade
+badge
+badger
+badger's
+badgered
+badgering
+badgers
+badges
+badlands
+badly
+badness
+badr
+baffle
+baffled
+bafflement
+bag
+bag's
+bag-it
+bagel
+bagel's
+bagels
+baggage
+bagged
+bagger
+baggie
+bagging
+bagheera
+bagheera's
+bagpipe
+bags
+bags'
+bah
+baha
+bahaha
+bahama
+bahamas
+bahano
+bahh
+bahhh
+bahia
+bahira
+bail
+bailed
+bailey
+bailey's
+baileys
+bailing
+bails
+bain
+bait
+baiter
+baiters
+baits
+bajillion
+baker
+baker's
+bakers
+bakery
+bakuraiya
+balance
+balanced
+balancer
+balancers
+balances
+balancing
+balas
+balboa
+balconies
+balcony
+bald
+balding
+baldness
+baldy
+bale
+baled
+bales
+baling
+balios
+balk
+ball
+ballad
+ballast
+ballasts
+ballerina
+ballet
+ballgame
+ballistae
+ballistic
+balloon
+balloons
+ballroom
+ballrooms
+ballsy
+balmy
+baloney
+baloo
+baloo's
+balsa
+balthasar
+bam
+bambadee
+bambi
+bambi's
+bamboo
+ban
+banana
+bananabounce
+bananabouncer
+bananapop
+bananas
+band
+band's
+bandage
+bandages
+bandaid
+bandaids
+bandana
+banded
+banding
+bandit
+banditos
+bandits
+bands
+bandwagon
+bandwidth
+bane
+banes
+bangle
+bangs
+banish
+banished
+banishes
+banishing
+banjo
+bank
+bank's
+banked
+banker
+banker's
+bankers
+banking
+bankroll
+bankrupt
+bankrupted
+banks
+banned
+banner
+banners
+banning
+banquet
+banquets
+bans
+banshee
+banter
+banzai
+baptized
+bar
+bar's
+baraba
+barago
+barama
+barano
+barb
+barbara
+barbarian
+barbarian's
+barbarians
+barbaric
+barbary
+barbecue
+barbecued
+barbecuing
+barbed
+barbeque
+barbequed
+barber
+barbers
+barbershop
+barbosa's
+barbossa
+barbossa's
+barbossas
+barbs
+bard
+barded
+barding
+bards
+bared
+barefoot
+barefooted
+barefootraiders
+barely
+bares
+barette
+barf
+barfed
+bargain
+bargain's
+bargained
+bargainer
+bargainin'
+bargaining
+bargains
+barge
+barge's
+barged
+barges
+barila
+baritone
+baritones
+bark
+barkeep
+barkeeper
+barker
+barker's
+barkin
+barking
+barks
+barky
+barley
+barmaid
+barman
+barmen
+barn
+barn's
+barnacle
+barnacle's
+barnacles
+barney
+barns
+barnyard
+barnyard's
+barnyards
+baron
+baron's
+barons
+barrack
+barracks
+barracuda
+barracudas
+barrage
+barrages
+barras
+barred
+barrel
+barrel's
+barreled
+barrels
+barren
+barrens
+barrette
+barrettes
+barricader
+barricading
+barrier
+barrier's
+barriers
+barron
+barron's
+barrons
+barrow
+barrows
+barry
+barry's
+bars
+barsinister
+barstool
+bart
+barten
+bartend
+bartender
+bartender's
+bartenders
+bartending
+barter
+bartholomew
+bartolor
+bartolosa
+bartor
+barts
+barumal
+base
+baseball
+baseball's
+baseballs
+based
+baseline
+baseline's
+baselines
+basely
+basement
+basement's
+basements
+baser
+bases
+basest
+bash
+bashed
+bashes
+bashful
+bashful's
+bashing
+basic
+basically
+basics
+basil
+basil's
+basils
+basin
+basin's
+basing
+basins
+basis
+bask
+basket
+basket's
+basketball
+basketball's
+basketballs
+baskets
+basks
+bass
+baste
+basted
+bastien
+bastion
+bat
+bat's
+batcave
+batcave's
+batcaves
+bateau
+bateaus
+bateaux
+batgirl
+bath
+bath-drawing
+bathe
+bathed
+bather
+bathers
+bathes
+bathing
+bathrobes
+bathroom
+bathroom's
+bathrooms
+bathroon
+baths
+bathtub
+bathtubs
+bathwater
+bating
+batman
+baton
+baton's
+batons
+bats
+battaba
+battacare
+battada
+battago
+battagua
+battaire
+battalions
+battama
+battano
+battassa
+batted
+batten
+battens
+batter
+battered
+batteries
+battering
+battermo
+batters
+battery
+battevos
+batting
+battira
+battle
+battled
+battledore
+battlefield
+battlefront
+battlegrounds
+battlements
+battleon
+battler
+battlers
+battles
+battleship
+battling
+battola
+batty
+batwing
+batwings
+baubles
+baud
+bavarian
+bawd
+bawl
+baxter
+bay
+bayard
+bayberry
+baying
+baylor
+bayou
+bayous
+bays
+bazaar
+bazaars
+bazillion
+bazookas
+bbhq
+bbl
+bc
+bcnu
+be
+be-awesome
+be-yoink
+beachcombers
+beachead
+beached
+beaches
+beachfront
+beachhead
+beaching
+beachplum
+beachside
+beacon
+bead
+beaded
+beads
+beagle
+beak
+beaker
+beaks
+beam
+beam's
+beamed
+beamer
+beamers
+beaming
+beams
+bean
+bean's
+beanie
+beanies
+beans
+beanstalks
+bear
+bear's
+beard
+beard's
+bearded
+beardless
+beardmonsters
+beards
+beardy
+bearer
+bearers
+bearing
+bearings
+bearish
+bears
+beast
+beast's
+beastie
+beasties
+beastings
+beastly
+beasts
+beatable
+beau
+beaucoup
+beauteous
+beauticians
+beauties
+beautiful
+beautifully
+beautifulness
+beauty
+beauty's
+beawesome
+became
+because
+beck
+beck's
+beckets
+beckett
+beckett's
+beckoned
+beckons
+become
+becomes
+becoming
+bed
+bed-sized
+bedazzle
+bedazzled
+bedazzler
+bedazzles
+bedclothes
+bedding
+bedhog
+bedknobs
+bedo's
+bedroll
+bedroom
+bedrooms
+beds
+bedspread
+bedspreads
+bee
+bee's
+beef
+beefcake
+beefed
+beefs
+beefy
+beehive
+beehive's
+beehives
+beeline
+been
+beep
+beeped
+beepers
+beeping
+beeps
+bees
+beeswax
+beet
+beetle
+beetle's
+beetles
+beets
+beever
+before
+beforehand
+befriend
+befriended
+befriending
+befriends
+befuddle
+befuddled
+beg
+began
+begat
+begets
+beggar
+beggars
+begged
+begging
+begin
+beginner
+beginner's
+beginners
+beginning
+beginning's
+beginnings
+begins
+begonia
+begotten
+begs
+begun
+behalf
+behave
+behaved
+behaver
+behaves
+behaving
+behavior
+behavioral
+behaviors
+behemoth
+behemoths
+behind
+behind-the-scenes
+behinds
+behold
+beholden
+beholding
+behoove
+behop
+behr
+beige
+bein'
+being
+being's
+beings
+bejeweled
+bela
+belated
+belay
+belch
+belief
+belief's
+beliefs
+believable
+believe
+believed
+believer
+believers
+believes
+believing
+belittle
+belittles
+bell
+bell's
+bella
+bella's
+bellas
+belle
+belle's
+belles
+belles'
+bellflower
+bellhop
+belli
+bellied
+bellies
+bellow
+bellows
+bells
+belly
+bellyache
+belong
+belonged
+belonging
+belongings
+belongs
+beloved
+beloveds
+below
+belowdeck
+belt
+belts
+ben
+bench
+benches
+benchmark
+benchwarmers
+bend
+bending
+bends
+beneath
+benedek
+benedek's
+beneficial
+benefit
+benefited
+benefiting
+benefits
+benjamin
+benjy
+benne
+benny
+benny's
+bent
+bequeaths
+bequermo
+bequila
+beret
+berets
+berg
+beriths27th
+bernadette
+bernard
+bernie
+berried
+berries
+berry
+berserk
+bert
+bert's
+berth
+bertha
+berthed
+berthing
+berths
+beruna
+beseech
+beside
+besides
+bess
+bess'
+bess's
+bess-statue
+bessie
+best
+bested
+bester
+besting
+bests
+bet
+bet's
+beta
+beta's
+betas
+betcha
+beth
+betray
+betrayal
+betrayed
+bets
+betsy
+better
+bettered
+bettering
+betterment
+betters
+bettie
+betting
+betty
+between
+betwixt
+bev
+bevel
+beverage
+beverages
+beware
+bewilder
+bewildered
+bewildering
+bewitch
+bewitched
+bewitches
+bewitching
+beyblade
+beyond
+bezerk
+bff
+bfs
+bi-lingual
+bias
+biased
+bib
+bibbidi
+bible
+biblical
+bicep
+biceps
+bickering
+bicuspid
+bicycle
+bicycles
+bid
+bidder
+bidding
+biddlesmore
+bide
+bids
+bifocals
+big
+big-screen
+bigger
+biggest
+biggie
+biggies
+biggles
+biggleton
+bight
+bike
+bike's
+biked
+biker
+bikers
+bikes
+biking
+bikini
+bile
+bilge
+bilgepump
+bilgerats
+bilges
+bilging
+bilingual
+bill
+bill's
+billed
+biller
+billers
+billiards
+billing
+billings
+billington
+billion
+billionaire
+billions
+billionth
+billow
+billows
+billowy
+bills
+billy
+billybob
+bim
+bimbim
+bimos
+bin
+binary
+bind
+binding
+bing
+bingham
+bingo
+bingo's
+bingos
+binky
+binnacle
+binoculars
+bins
+bio
+biog
+biology
+bionic
+bionicle
+biopsychology
+bios
+bip
+birch-bark
+bird
+bird's
+birdbrain
+birddog
+birder
+birdhouse
+birdie
+birdies
+birdman
+birds
+birdseed
+birth
+birthdates
+birthday
+birthdays
+birthed
+birthmark
+birthmarks
+birthplace
+birthstone
+biscotti
+biscuit
+biscuits
+bishop
+bishops
+bison
+bisque
+bisquit
+bistro
+bit's
+biter
+biters
+bites
+biting
+bits
+bitsy
+bitten
+bitter
+bitty
+biz
+bizarre
+bla
+blabbing
+black-eyed
+blackbeard
+blackbeard's
+blackbeards
+blackbeared
+blackbelt
+blackberries
+blackberry
+blackbird
+blackboard
+blackboards
+blackdeath
+blacked
+blackened
+blackest
+blackguard
+blackguards
+blackhaerts
+blackhawk
+blackheads
+blackheart
+blackheart's
+blackhearts
+blacking
+blackish
+blackjack
+blackjacks
+blackness
+blackout
+blackouts
+blackrage
+blackrose
+blacks
+blacksail
+blacksmith
+blacksmith's
+blacksmithing
+blacksmiths
+blackthorn
+blackwatch
+blackwater
+bladder
+bladders
+blade's
+bladebreakerr
+blademasters
+blades
+bladeskulls
+bladestorm
+blaggards
+blah
+blair
+blake
+blakeley
+blakeley's
+blame
+blamed
+blamer
+blamers
+blames
+blaming
+blanada
+blanago
+blanca
+blanca's
+blanche
+bland
+blank
+blanked
+blanket
+blankets
+blanking
+blankly
+blanks
+blanos
+blaring
+blast
+blast'em
+blasted
+blaster
+blasters
+blastin'
+blasting
+blasts
+blasty
+blat
+bldg
+bldgs
+bleached
+bleak
+bleary
+bled
+bleep
+bleeped
+bleeper
+bleepin
+bleeping
+bleeps
+blend
+blended
+blender
+blends
+blenny
+bless
+blessed
+blesses
+blessing
+blessings
+bleu
+blew
+bligh
+blight
+blighters
+blimey
+blimp
+blind
+blinded
+blinder
+blinders
+blindfold
+blinding
+blindly
+blindness
+blinds
+blindsided
+bling
+bling-bling
+blingbling
+blinged
+blinging
+blings
+blink
+blinked
+blinker
+blinkers
+blinking
+blinks
+blinky
+blip
+blipping
+bliss
+blissfully
+blister
+blistering
+blisters
+blitz
+blizzard
+blizzards
+bloat
+bloated
+bloats
+blob
+blobby
+blobs
+bloc
+block
+block's
+blockade
+blockader
+blockades
+blockading
+blockbuster
+blocked
+blocker
+blockers
+blocking
+blockout
+blocks
+blocky
+bloke
+blokes
+blond
+blonde
+blonde's
+blondes
+blondie
+blonds
+bloodbrothers
+bloodhound
+bloodhounds
+bloodless
+bloodshot
+bloodsucker
+bloodsuckers
+bloodthrushers
+bloom
+bloomers
+blooming
+blooms
+bloop
+bloopers
+blossom
+blossoms
+blossum
+blot
+blots
+blouse
+blowfish
+blu-ray
+blubber
+blubbering
+bludgeon
+bludgeoning
+blue
+blue's
+bluebeards
+bluebell
+blueberries
+blueberry
+bluebird
+bluebirds
+blueblood
+bluefishes
+bluegrass
+bluejay
+blueprints
+blues
+bluff
+bluffed
+bluffer
+bluffers
+bluffing
+bluffs
+blunder
+blundering
+bluntly
+blur
+blurb
+blurbs
+blurred
+blurry
+blurs
+blurting
+blush
+blushed
+blushes
+blushing
+blustery
+blut
+blynken
+bo
+boa
+boar
+board
+board's
+boarded
+boarder
+boarders
+boarding
+boards
+boardwalk
+boardwalks
+boarhound
+boars
+boas
+boast
+boastful
+boasting
+boat
+boat's
+boated
+boater
+boaters
+boathouse
+boating
+boatload
+boatloads
+boats
+boatswain
+boatswain's
+boatswains
+boatyard
+bob
+bobbed
+bobber
+bobbidi
+bobble
+bobbleheads
+bobby
+bobby's
+bobbys
+boberts
+bobsled
+bobsleded
+bobsleding
+bobsleds
+bobsleigh
+bobsleighes
+bock
+bodacious
+bode
+bodeguita
+bodice
+bodices
+bodied
+bodies
+bodily
+body
+body's
+bodyguard
+bodyguards
+boffo
+bog
+bogart
+bogey
+bogger
+boggle
+boggles
+boggy
+bogie
+bogs
+bogus
+boil
+boiled
+boiler
+boiling
+boils
+bokugeki
+bokuji
+bokuzama
+bold
+bolder
+boldest
+boldly
+bole
+bolivia
+bollard
+bologna
+bolt
+bolt's
+bolted
+bolton
+bolts
+boma
+boma-boma
+bombard
+bombarding
+bombardment
+bombe
+bombed
+bomber
+bombers
+bombing
+bombs
+bombshell
+bon
+bonaam
+bonanza
+bonbons
+bond
+bonded
+bonding
+bonds
+bondsman
+bonehead
+boneheads
+boneless
+boneyard
+boneyards
+bonfire
+bonfires
+bongo
+bonita
+bonita's
+bonito
+bonjour
+bonkers
+bonkl
+bonnet
+bonnets
+bonney
+bonnie
+bonny
+bono
+bonsai
+bonsoir
+bonus
+bonuses
+bony
+bonzo
+boo
+boo's
+boo-yaa
+boobyprizes
+booed
+booger
+boogers
+boogey
+boogie
+boogie-woogie
+boogied
+boogies
+boohoo
+book
+book's
+bookball
+bookcase
+bookcases
+booked
+bookie
+booking
+bookings
+bookkeeping
+booklet
+bookmark
+bookmarked
+books
+bookshelf
+bookshelves
+bookstore
+bookworm
+boom
+boomcrash
+boomed
+boomer
+boomerang
+boomers
+booming
+booms
+boon
+boonies
+boooo
+booooh
+boooom
+booooo
+booooom
+booooomin
+boooooo
+boooooooooooooooooooo
+boooooooooooooooooooooooooommm
+boor
+boos
+boost
+boosted
+booster
+boosters
+boosting
+boosts
+boot
+boot'n'ears
+booted
+booth
+booth's
+booths
+booties
+booting
+bootleggers
+boots
+bootstrap
+bootstraps
+bootsy
+booyah
+bop
+bopper
+bord
+border
+border's
+bordered
+borderer
+bordering
+borderings
+borderline
+borders
+bore
+borealis
+bored
+boredom
+borer
+bores
+boring
+boris
+bork
+born
+borrow
+borrowed
+borrower
+borrowers
+borrowing
+borrowings
+borrows
+bos
+boss
+boss'
+bossbot
+bossbots
+bossed
+bosses
+bossily
+bossing
+bossy
+bossyboots
+bot
+bot's
+botched
+both
+bother
+bothered
+bothering
+bothers
+bothersome
+bots
+bottle
+bottle's
+bottled
+bottleneck
+bottler
+bottlers
+bottles
+bottling
+bottom
+bottomed
+bottomer
+bottoming
+bottomless
+bottoms
+bough
+boughs
+bought
+boulder
+boulders
+boulevard
+boulevard's
+boulevards
+bounce
+bounced
+bouncer
+bouncers
+bounces
+bouncing
+bouncy
+bound
+boundaries
+boundary
+boundary's
+bounding
+bounds
+bounteous
+bounties
+bountiful
+bounty
+bountyhunter
+bourbon
+bourse
+bout
+boutique
+bouts
+bovel
+bovine
+bow
+bow's
+bowdash
+bowed
+bowers
+bowie
+bowing
+bowl
+bowl's
+bowlegged
+bowler
+bowling
+bowls
+bowman
+bows
+box
+boxcar
+boxed
+boxer
+boxes
+boxfish
+boxing
+boy
+boy's
+boycott
+boycotting
+boyfriend
+boyfriend's
+boyfriends
+boyish
+boys
+boysenberry
+bozo
+bozo's
+bozos
+brace
+bracelet
+bracelets
+braces
+bracket
+bracketing
+brad
+brad's
+bradley
+brag
+braggart
+braggarts
+bragged
+bragger
+braggers
+bragging
+brags
+braid
+braided
+braiding
+braids
+brail
+brain
+brain's
+brained
+braining
+brainless
+brains
+brainstorm
+brainwash
+brainwashed
+brainy
+brake
+brakes
+braking
+bran
+branch
+branched
+branches
+branching
+branchings
+brand
+brandishing
+brandon
+brandon's
+brands
+brandy
+brandy's
+brash
+brass
+brat
+brats
+bratty
+brave
+braved
+bravely
+braver
+bravery
+braves
+bravest
+braving
+bravo
+bravo's
+brawl
+brawny
+bray
+brazen
+brazil
+brb
+breach
+breached
+bread
+bread's
+bread-buttering
+breadcrumbs
+breaded
+breading
+breads
+breadstick
+breadstick's
+breadth
+break
+breakable
+breakdown
+breaker
+breakers
+breakfast
+breakfasted
+breakfaster
+breakfasters
+breakfasting
+breakfasts
+breaking
+breakout
+breaks
+breakup
+breasted
+breath
+breathe
+breathed
+breather
+breathers
+breathes
+breathing
+breathless
+breaths
+breathtaking
+bred
+breech
+breeches
+breeze
+breeze's
+breezed
+breezes
+breezest
+breezing
+breezy
+brenda
+brenda's
+brethern
+brethren
+brevrend
+brew
+brewed
+brewer
+brewers
+brewing
+brews
+brian
+briar
+briars
+briarstone
+briarstones
+bribe
+bribed
+bribery
+bribes
+bribing
+brick
+bricked
+bricker
+bricking
+bricks
+bridal
+bride
+bride's
+brides
+bridesmaid
+bridge
+bridge's
+bridged
+bridges
+bridget
+bridging
+brie
+brief
+briefed
+briefer
+briefest
+briefing
+briefings
+briefly
+briefs
+brig
+brig's
+brigad
+brigade
+brigadeers
+brigades
+brigadier
+brigadiers
+brigand
+brigands
+brigantine
+brigantines
+bright
+brighten
+brightens
+brighter
+brightest
+brighting
+brightly
+brightness
+brights
+brigs
+brilliance
+brilliant
+brilliantly
+brim
+brimming
+brimstone
+brine
+briney
+bring
+bringer
+bringers
+bringing
+brings
+brining
+brink
+brinks
+briny
+brio
+briquettes
+brisk
+brisket
+britches
+british
+bro
+bro's
+broached
+broad
+broadband
+broadcast
+broadcasts
+broaden
+broadens
+broader
+broadest
+broadly
+broads
+broadside
+broadside's
+broadsided
+broadsides
+broadsword
+broadway
+broccoli
+brochure
+brogan
+brogans
+broil
+broiled
+broke
+broken
+brokenly
+broker
+brokers
+broking
+bronco
+broncos
+bronze
+bronzed
+bronzy
+brood
+brook
+brook's
+brooks
+broom
+broom's
+brooms
+broomstick
+broomstick's
+broomsticks
+bros
+broth
+brother
+brother's
+brotherhood
+brotherhood's
+brotherhoods
+brothering
+brotherly
+brothers
+brothers'
+broths
+brought
+brouhaha
+brow
+brown
+browncoats
+browner
+brownie
+brownies
+browning
+brownish
+browns
+brows
+browse
+browsed
+browser
+browsers
+browsing
+brrr
+brrrgh
+bruce
+bruce's
+bruckheimer
+bruin
+bruise
+bruised
+bruiser
+bruises
+bruising
+brulee
+brume
+brunch
+brunette
+brunettes
+brunt
+brush
+brushed
+brusher
+brushes
+brushing
+brushoff
+brussels
+brute
+brutish
+btb
+btl
+btw
+bubba
+bubble
+bubble's
+bubbled
+bubblegum
+bubbles
+bubbling
+bubbloon
+bubbly
+bubo
+bubs
+buc's
+bucaneer
+bucanneers
+buccaneer
+buccaneer's
+buccaneers
+bucco
+buckaroo
+buckaroo's
+buckaroos
+bucker
+bucket
+bucket's
+bucketed
+bucketing
+buckets
+buckeye
+buckeyes
+buckle
+buckle's
+buckled
+buckler
+buckles
+bucko
+bucko's
+buckos
+bucks
+buckshot
+buckskin
+buckwheat
+bucs
+bud
+bud's
+budd
+budd's
+buddies
+buddy
+buddy's
+budge
+budged
+budget
+budgeted
+budgeter
+budgeters
+budgeting
+budgets
+budgie
+budging
+buds
+bueno
+buff
+buffalo
+buffalo's
+buffalos
+buffed
+buffer
+buffet
+buffet's
+buffets
+buffoon
+buffoons
+buffs
+bug
+bug's
+bugalicious
+bugariffic
+bugbear
+bugbears
+bugged
+buggered
+buggers
+buggier
+buggies
+buggiest
+bugging
+buggy
+bugle
+bugles
+bugs
+bugsit
+bugtastic
+buh
+build
+builded
+builder
+builder's
+builders
+building
+building's
+buildings
+builds
+buildup
+buillion
+built
+bulb
+bulb's
+bulbs
+bulge
+bulging
+bulgy
+bulk
+bulkhead
+bulky
+bull
+bull's
+bulldog
+bulldogs
+bulldozer
+bulletin
+bullfighters
+bullfighting
+bullied
+bullies
+bullion
+bullpen
+bulls
+bullseye
+bully
+bully's
+bullying
+bulwark
+bulwark's
+bulwarks
+bumble
+bumbler
+bumbler's
+bumblers
+bumblesoup
+bumm
+bummed
+bummer
+bummers
+bumming
+bumms
+bumpkin
+bumpy
+bun
+bunch
+bunched
+bunches
+bunching
+bunchy
+bund
+bundle
+bundled
+bundler
+bundles
+bundling
+bunions
+bunk
+bunked
+bunker
+bunking
+bunks
+bunnies
+bunny
+buns
+bunsen
+bunting
+bunyan
+buoy
+buoys
+bur
+burden
+burdened
+bureau
+bureaus
+burg
+burger
+burger's
+burgers
+burghers
+burglar
+burgundy
+burial
+buried
+burier
+buries
+burly
+burn
+burned
+burner
+burners
+burning
+burnings
+burnout
+burns
+burnt
+burp
+burped
+burping
+burps
+burr
+burred
+burrito
+burrito's
+burritos
+burrning
+burro
+burrow
+burrowing
+burry
+burst
+bursted
+burster
+bursting
+bursts
+burton
+bus
+bus'
+buses
+bushed
+bushel
+bushel's
+bushels
+bushes
+bushido
+bushy
+busier
+busies
+busiest
+business
+business's
+businesses
+busing
+buss
+bussed
+bussing
+busters
+bustle
+busy
+busybody
+busywork
+but
+butler
+butler's
+butlers
+butobasu
+butte
+butted
+butter
+butterball
+butterballs
+butterbean
+butterbeans
+buttercup
+buttercups
+buttered
+butterfingers
+butterflies
+butterfly
+butterfly's
+butterfly-herders
+buttering
+buttermilk
+butternut
+butterscotch
+buttery
+button
+button's
+buttoned
+buttoner
+buttoning
+buttons
+buy
+buyable
+buyer
+buyer's
+buyers
+buying
+buys
+buzz's
+buzz-worthy
+buzzard
+buzzards
+buzzer
+buzzer's
+buzzers
+buzzes
+buzzsaw
+bwah
+bwahaha
+bwahahah
+bwahahaha
+bwahahahah
+bwahahahaha
+bwahahahahaha
+bwahahha
+bwhahahaha
+by
+bye
+byes
+bylaws
+bypass
+bypassed
+bypassing
+bystander
+bystanders
+byte
+bytes
+c'mon
+c-flat
+c.a.r.t.e.r.
+c.f.o.
+c.f.o.s
+ca'me
+cab
+caballeros
+cabana
+cabbage
+cabbages
+caber
+cabeza
+cabin
+cabin's
+cabinboy
+cabinet
+cabinet's
+cabinets
+cabins
+cable
+cables
+cabob
+caboose
+cabs
+caca
+cache
+caches
+cackle
+cacti
+cactus
+cactus'
+cad
+caddies
+caddy
+caddy's
+cades
+cadet
+cadet's
+cadets
+cadets'
+cadillac
+cads
+caesar
+caf
+cafac
+cafe
+cafe's
+cafes
+cafeteria
+cafeteria's
+cafeterias
+caffeine
+caf
+cage
+cage's
+caged
+cages
+cahoots
+caicaba
+caicada
+caicama
+caicaux
+caicos
+caicumal
+caio
+cake
+cake's
+caked
+cakes
+cakewalk
+cal
+cal's
+calamari
+calamity
+calculate
+calculated
+calculating
+calculations
+calculator
+calculators
+calendar
+calendars
+calf
+caliber
+calibrate
+calico
+calico's
+california
+calked
+call
+calle
+callecutter's
+called
+caller
+callers
+calligraphy
+calling
+calls
+calluses
+calm
+calmed
+calmer
+calmest
+calming
+calmly
+calms
+calories
+calves
+calypso
+calzone
+calzones
+camaada
+camaago
+camanes
+camareau
+camaros
+camaten
+camcorder
+came
+camellia
+camels
+cameo
+cami
+camilla
+camillia
+camillo
+cammy
+camouflage
+camouflages
+camp
+camp's
+campaign
+camped
+camper
+campers
+campfire
+campfires
+campground
+campgrounds
+camping
+camps
+campus
+can
+can's
+can't
+canal
+canals
+canard
+canary
+canary's
+cancel
+canceled
+canceling
+cancelled
+cancelling
+cancels
+cancer
+cancerous
+cancers
+candelabra
+candelabras
+candid
+candidate
+candidate's
+candidates
+candied
+candies
+candle
+candlelight
+candles
+candles'
+candlestick
+candlestick's
+candlesticks
+candlesticks'
+candy
+candy's
+cane
+cane's
+caner
+canes
+cangrejos
+canine
+canister
+canisters
+canker
+cannas
+canned
+cannelloni
+canner
+canners
+cannery
+canning
+cannon
+cannon's
+cannonade
+cannonball
+cannonball's
+cannonballs
+cannoned
+cannoneer's
+cannoneering
+cannoneers
+cannoning
+cannonry
+cannons
+cannot
+canny
+canoe
+canoe's
+canoed
+canoeing
+canoes
+canoes'
+canoing
+canon
+canon's
+canonballs
+cans
+canst
+cant
+cant's
+cantaloupe
+canteen
+canteens
+canter
+canticle
+cantina
+canto
+canton
+cants
+canvas
+canvasses
+canyon
+canyons
+cap
+cap'n
+cap's
+capabilities
+capability
+capable
+capacities
+capacity
+cape
+cape's
+caped
+capellago
+capelligos
+caper
+capers
+capes
+capisce
+capisci
+capisco
+capital
+capital's
+capitalize
+capitalizes
+capitally
+capitals
+capitano
+capitol
+capitols
+caplock
+caplocks
+capn
+capo
+capos
+capped
+capping
+capri
+caps
+capsize
+capsized
+capstan
+captain
+captain's
+captained
+captaining
+captains
+captains'
+captainscolors
+captainship
+captian
+captin
+caption
+captioning
+captivating
+captive
+captives
+capture
+captured
+capturer
+capturers
+captures
+capturing
+caput
+capybaras
+car
+car's
+carafe
+caramel
+caramelized
+caramels
+carapace
+caravan
+caravans
+carbon
+carbonated
+carcass
+card
+card's
+carda
+cardboard
+carded
+carder
+cardinal
+cardinalfish
+cardinals
+carding
+cardio
+cardiologist
+cardj
+cardk
+cardq
+cards
+care
+cared
+careening
+career
+career's
+careered
+careering
+careers
+carefree
+careful
+carefully
+careless
+carer
+carers
+cares
+caretaker
+caretaker's
+caretakers
+carey
+cargo
+cargo's
+cargoes
+cargos
+carib
+caribbean
+caring
+carl
+carla
+carlos
+carlos's
+carmine
+carnation
+carnations
+carnevore
+carnival
+carnival's
+carnivals
+carol
+carol's
+caroling
+carols
+carousel
+carousel's
+carousels
+carpal
+carpe
+carped
+carpel
+carpenter
+carpenters
+carper
+carpet
+carpet's
+carpeted
+carpeting
+carpets
+carpool
+carrebean
+carrera
+carrera's
+carreras
+carriage
+carribean
+carried
+carrier
+carriers
+carries
+carrion
+carrions
+carrot
+carrot's
+carrots
+carrou
+carrousel
+carrousel's
+carrousels
+carry
+carry's
+carrying
+cars
+carsick
+cart
+cart's
+carte
+carted
+carter
+carter's
+carters
+carting
+carton
+carton's
+cartons
+cartoon
+cartoon's
+cartoonists
+cartoons
+cartridges
+cartrip
+carts
+carve
+carved
+carven
+carver
+carver's
+carvers
+carving
+carving's
+carvings
+casa
+cascade
+cascades
+case
+cased
+cases
+cash
+cash's
+cashbot
+cashbot's
+cashbots
+cashed
+cashemerus-appearus
+casher
+cashers
+cashes
+cashew
+cashews
+cashier
+cashier's
+cashiers
+cashing
+cashmere
+casing
+casings
+caspian
+caspian's
+caspians
+cassandra
+casserole
+cast
+cast's
+castanet
+castanets
+castaway
+castaway's
+castaways
+caste
+casted
+caster
+casters
+casting
+castings
+castle
+castle's
+castled
+castles
+castling
+casts
+casual
+casually
+casualties
+cat
+cat's
+catacomb
+catacombs
+catalog
+catalog's
+catalogs
+catalogue
+catalyst
+catapult
+cataract
+cataracts
+catastrophe
+catastrophic
+catatonic
+catch
+catcher
+catcher's
+catchers
+catches
+catchin'
+catching
+catchy
+categories
+category
+category's
+cater
+catered
+catering
+caterpillar
+caterpillar's
+caterpillar-herdering
+caterpillar-shearing
+caterpillars
+caters
+catfight
+catfights
+catfish
+catherine
+catholic
+cathrine
+catnip
+cats
+catsup
+cattle
+cattlelog
+catty
+caught
+cauldron
+cauldron's
+cauldrons
+cauliflower
+cause
+caused
+causer
+causes
+causing
+caution
+cautioned
+cautioner
+cautioners
+cautioning
+cautionings
+cautions
+cautious
+cautiously
+cava
+cavalier
+cavaliers
+cavalry
+cavalry's
+cave
+cave's
+caveat
+caved
+caveman
+cavemen
+caver
+cavern
+cavern's
+caverns
+caves
+caviar
+caviar's
+caving
+cavort
+cavy
+caws
+cay
+cbhq
+cd
+cd's
+cds
+cease
+ceased
+ceasefire
+ceases
+ceasing
+cecile
+cedar
+cee
+ceiling
+ceiling's
+ceilings
+celeb
+celeb's
+celebrate
+celebrated
+celebrates
+celebrating
+celebration
+celebration-setup
+celebrationen
+celebrations
+celebrities
+celebrity
+celebs
+celery
+cell
+cellar
+cellars
+cellmate
+cellos
+cells
+cellular
+cellulite
+celtic
+cement
+cements
+cemetery
+censor
+censored
+censoring
+censors
+censorship
+cent
+centaur
+centaur's
+centaurs
+center
+center's
+centered
+centering
+centerpiece
+centers
+centimeter
+central
+centrally
+centrals
+centre
+cents
+centuries
+centurion
+centurion's
+centurions
+century
+century's
+ceo
+ceramic
+cerdo
+cereal
+cereal's
+cereals
+ceremonies
+ceremony
+ceremony's
+cert
+certain
+certainly
+certificate
+certificate's
+certificated
+certificates
+certificating
+certification
+certifications
+certified
+cerulean
+cesar
+cesar's
+cezanne
+cfo
+cfo's
+cfos
+cg
+chad
+chad's
+chads
+chafed
+chafes
+chain
+chain's
+chained
+chaining
+chains
+chair
+chair's
+chaired
+chairing
+chairlift
+chairs
+chaise
+chalet
+chalk
+challenge
+challenge's
+challenged
+challenger
+challenger's
+challengers
+challenges
+challenging
+chamber
+chamber's
+chambered
+chamberer
+chamberers
+chambering
+chamberpot
+chambers
+chameleon
+chameleons
+champ
+champ's
+champagne
+champion
+champion's
+champions
+championship
+champs
+chan
+chance
+chanced
+chances
+chancing
+chancy
+chandelier
+chandler
+chanel
+change
+change-o
+changeable
+changed
+changer
+changers
+changes
+changin'
+changing
+channel
+channel's
+channeled
+channels
+chant
+chant's
+chanted
+chanting
+chants
+chanukah
+chaos
+chaotic
+chap
+chapeau
+chapel
+chappy
+chaps
+chapsitck
+chapter
+chapter's
+chaptered
+chaptering
+chapters
+char
+character
+character's
+charactered
+charactering
+characteristics
+characters
+charade
+charade's
+charades
+charcoal
+chard
+chare
+chares
+charge
+charged
+charger
+charger's
+chargers
+charges
+charging
+chariot
+charisma
+charismatic
+charitable
+charity
+charles
+charley
+charlie
+charlie's
+charlotte
+charlottes
+charm
+charm's
+charmed
+charmer
+charmers
+charming
+charming's
+charmingly
+charms
+charred
+chars
+chart
+chart's
+charted
+charter
+chartered
+charters
+charting
+chartings
+chartreuse
+charts
+chase
+chase's
+chased
+chaser
+chasers
+chases
+chasing
+chasse
+chassed
+chastise
+chastised
+chastity
+chat
+chat's
+chateau
+chats
+chatted
+chatter
+chatter's
+chattering
+chatters
+chatting
+chatty
+chauffer
+chauffeur
+cheap
+cheapen
+cheapens
+cheaper
+cheapest
+cheaply
+cheapo
+cheapskate
+cheapskates
+cheat
+cheat's
+cheated
+cheater
+cheater's
+cheaters
+cheating
+cheats
+check
+check's
+checkbook
+checkbox
+checked
+checker
+checker's
+checkerboard
+checkered
+checkers
+checking
+checklist
+checkmark
+checkout
+checkpoint
+checks
+checkup
+cheddar
+cheek
+cheeks
+cheeky
+cheep
+cheer
+cheer's
+cheered
+cheerer
+cheerers
+cheerful
+cheerier
+cheering
+cheerio
+cheerios
+cheerleader
+cheerleaders
+cheerleading
+cheerly
+cheers
+cheery
+cheese
+cheese's
+cheeseburger
+cheeseburger's
+cheeseburgers
+cheesecake
+cheesed
+cheeses
+cheesey
+cheesing
+cheesy
+cheetah
+cheetah's
+cheetahs
+chef
+chef's
+chefs
+chelsea
+chelsea's
+chelseas
+chemical
+chemicals
+cherish
+cherishes
+chernabog
+chernabog's
+cherries
+cherrywood
+cherubfish
+cheshire
+cheshire's
+chess
+chestnut
+chestnut-shell
+chetagua
+chetermo
+chetik
+chetros
+chettia
+chetuan
+chevalle
+chewed
+chewing
+cheyenne
+cheyenne's
+cheyennes
+chez
+chic
+chick
+chick's
+chickadee
+chickadees
+chicken
+chicken's
+chickened
+chickenhearted
+chickening
+chickens
+chicks
+chief
+chief's
+chiefly
+chiefs
+chiffon
+chihuahua
+child
+child's
+childcare
+childhood
+childish
+childlike
+children
+children's
+chili
+chili's
+chill
+chill's
+chilled
+chillin
+chillin'
+chilling
+chills
+chilly
+chim
+chime
+chime's
+chimes
+chiming
+chimnes
+chimney
+chimneys
+chimp
+chin
+chin's
+china
+chinchilla
+chinchilla's
+chinchillas
+chine
+ching
+chino
+chins
+chip
+chip's
+chipmunk
+chipmunk's
+chipmunks
+chipotle
+chipped
+chipper
+chipping
+chips
+chiropractic
+chiropractor
+chirp
+chirping
+chirpy
+chisel
+chit-chat
+chivalrous
+chivalry
+chloe
+choc
+chock
+chocolate
+chocolate's
+chocolates
+choice
+choicely
+choicer
+choices
+choicest
+choir
+choirs
+choking
+chomp
+chomping
+chomugon
+choo
+choo-choo
+choo-choo's
+choo-choos
+chook
+choose
+chooser
+choosers
+chooses
+choosey
+choosing
+choosy
+chop
+chopin
+chopped
+chopper
+choppers
+choppier
+choppiness
+chopping
+choppy
+chops
+chopsticks
+choral
+chord
+chords
+chore
+choreographed
+chores
+chortle
+chortled
+chortles
+chortling
+chorus
+chose
+chosen
+chow
+chowder
+chris
+christina
+christina's
+christinas
+christmas
+christmastime
+christopher
+chrome
+chrome's
+chromed
+chromes
+chronicle
+chronicled
+chronicles
+chronicling
+chuck
+chuck's
+chucked
+chucking
+chuckle
+chuckle's
+chuckled
+chuckles
+chuckling
+chucks
+chucky
+chuff
+chug
+chugged
+chugging
+chugyo
+chum
+chum's
+chummed
+chums
+chunk
+chunked
+chunking
+chunks
+chunky
+church
+churches
+churn
+churned
+churning
+churro
+churro's
+churros
+chute
+chutes
+cia
+ciao
+cid
+cider
+cienfuegos
+cigar
+cimson
+cinammon
+cinch
+cinda
+cinda's
+cinder
+cinder's
+cinderbones
+cinderella
+cinderella's
+cinderellas
+cinders
+cindy
+cine
+cinema
+cinema's
+cinemas
+cinematic
+cinematics
+cineplex
+cinerama
+cinnamon
+cinnamon's
+cinnamons
+cir
+circ
+circle
+circle's
+circled
+circler
+circlers
+circles
+circling
+circuit
+circuit's
+circuited
+circuiting
+circuits
+circular
+circular's
+circularly
+circulars
+circumnavigate
+circumstance
+circumstances
+circumvent
+circus
+circus's
+circuses
+citadel
+cite
+cites
+cities
+citified
+citing
+citizen
+citizen's
+citizenly
+citizens
+citn
+citrine
+citrus
+civics
+civil
+civilians
+civility
+civilization
+civilizations
+civilized
+cj
+clack
+clad
+claim
+claim's
+claimed
+claimer
+claiming
+claims
+claire
+clam
+clam's
+clamed
+clammed
+clams
+clan
+clang
+clangs
+clank
+clans
+clap
+clapped
+clapping
+claps
+clara
+clarabelle
+clarabelle's
+clarified
+clarify
+clarifying
+clarion
+clark
+clash
+clashes
+clashing
+class
+class's
+classed
+classer
+classes
+classic
+classic's
+classical
+classics
+classiest
+classifications
+classified
+classify
+classing
+classmate
+classmate's
+classmates
+classy
+claus
+clause
+clauses
+clavier
+claw
+claw's
+clawed
+clawing
+claws
+clay
+clayton
+clean
+cleaned
+cleaner
+cleaner's
+cleaners
+cleanest
+cleaning
+cleanliness
+cleanly
+cleanout
+cleans
+cleanse
+cleansing
+cleanup
+clear
+clearance
+cleared
+clearer
+clearest
+clearing
+clearings
+clearly
+clears
+cleat's
+cleats
+cleave
+cleaved
+cleaver
+cleaves
+cleff
+clementine
+cleric
+clerics
+clerk
+clerk's
+clerks
+clever
+clew
+click
+click-and-drag
+clickable
+clickables
+clicked
+clicker
+clickers
+clicking
+clicks
+client
+client's
+clients
+cliff
+cliff's
+cliffs
+climactic
+climate
+climate's
+climates
+climb
+climbed
+climber
+climbers
+climbing
+climbs
+clime
+cling
+clinger
+clinging
+clings
+clingy
+clinic
+clinics
+clink
+clinker
+clip
+clipboard
+clipped
+clipper
+clippers
+clipping
+clips
+clique
+cloak
+cloaking
+clobber
+clobbered
+clock
+clocked
+clocker
+clockers
+clocking
+clockings
+clocks
+clockwise
+clockwork
+clockworks
+clod
+clodley
+clods
+clog
+clogged
+clogging
+clogs
+clomping
+clone
+clone's
+cloned
+clones
+cloning
+clonk
+clopping
+close
+close-up
+closed
+closely
+closeness
+closer
+closers
+closes
+closest
+closet
+closets
+closing
+closings
+closure
+cloth
+clothe
+clothed
+clothes
+clothesline
+clothespins
+clothing
+cloths
+clots
+cloture
+cloud
+cloud's
+clouded
+clouding
+clouds
+cloudy
+clout
+clove
+clover
+clover's
+cloverleaf
+cloverleaf's
+cloverleafs
+clovers
+cloves
+clovi
+clovinia
+clovis
+clown
+clowns
+clu
+club
+club's
+club33
+clubbing
+clubhouse
+clubpenguin
+clubs
+clucked
+clucking
+clue
+clue's
+clued
+clueing
+clueless
+clues
+clump
+clumped
+clumsies
+clumsies'
+clumsily
+clumsy
+clunk
+clunker
+clunkers
+clunky
+cluster
+cluster's
+clustered
+clusters
+clutch
+clutches
+clutching
+clutter
+cluttered
+clydesdale
+co
+co-starred
+coach
+coach's
+coache's
+coached
+coacher
+coaches
+coaching
+coal
+coal's
+coaled
+coaler
+coalfire
+coaling
+coalmine
+coals
+coarse
+coast
+coastal
+coasted
+coaster
+coaster's
+coasters
+coasting
+coastline
+coasts
+coat
+coat's
+coated
+coater
+coatings
+coats
+coax
+coaxes
+cobalt
+cobber
+cobble
+cobbler
+cobbles
+cobblestone
+cobra
+cobra's
+cobras
+cobweb
+cobwebs
+coca
+coco
+coco's
+cocoa
+coconut
+coconut's
+coconuts
+cod
+coda
+codas
+coddles
+code
+code's
+codec
+coded
+coder
+coders
+codes
+codex
+codfish
+coding
+codings
+cods
+cody
+cody's
+coed
+coerce
+coffee
+coffee's
+coffees
+coffer
+coffers
+cog
+cog's
+cog-tastrophe
+cogbuck's
+cogbucks
+cognation
+cogs
+coherently
+cohesive
+cohort
+coiffure
+coiled
+coin
+coin's
+coinage
+coincide
+coincidence
+coincidences
+coined
+coiner
+coining
+coins
+cola
+colada
+colada's
+coladas
+cold
+cold's
+colder
+coldest
+coldly
+colds
+cole
+cole's
+coleman
+coleman's
+colemans
+colestra
+colette
+colette's
+colettes
+colin
+colin's
+coliseum
+coliseum's
+coliseums
+collaborate
+collaboration
+collage
+collapse
+collapsed
+collapsing
+collar
+collard
+collars
+collateral
+collaterals
+colleagues
+collect
+collect's
+collectable
+collectables
+collected
+collectible
+collectibles
+collecting
+collection
+collection's
+collections
+collective
+collector
+collectors
+collects
+colleen
+colleens
+college
+colleting
+collette
+collette's
+collettes
+collide
+collie
+collier
+collision
+collision's
+collisions
+colm
+cologne
+colonel
+colonial
+colonials
+colonies
+colonized
+colony
+color
+color's
+colorblind
+colored
+colorfast
+colorful
+coloring
+colorless
+colors
+colossal
+colossus
+colour
+colour's
+colours
+cols
+colt
+coltello
+coltellos
+colts
+columbia
+columbia's
+columbus
+column
+columns
+coma
+comatose
+comb
+comb's
+combat
+combat's
+combatants
+combater
+combats
+combination
+combination's
+combinations
+combine
+combined
+combiner
+combiners
+combines
+combing
+combining
+combo
+combo's
+combos
+combs
+combustible
+combustion
+come
+comeback
+comebacks
+comedian
+comedians
+comedies
+comedown
+comedy
+comely
+comer
+comers
+comes
+comet
+comfort
+comfortable
+comfortably
+comforted
+comforter
+comforters
+comforting
+comforts
+comfy
+comic
+comic's
+comic-con
+comical
+comics
+coming
+comings
+comma
+command
+command's
+commandant
+commanded
+commandeer
+commandeered
+commandeering
+commander
+commander's
+commanders
+commanding
+commando
+commands
+commence
+commencer
+commences
+commencing
+commend
+commendations
+comment
+comment's
+commentary
+commented
+commenter
+commenting
+comments
+commerce
+commercial
+commercially
+commercials
+commissar
+commission
+commission's
+commissioned
+commissioner
+commissioners
+commissioning
+commissions
+commit
+commitment
+commitment's
+commitments
+commits
+committed
+committee
+committee's
+committees
+committing
+commodore
+commodore's
+commodores
+common
+commoner
+commoner's
+commoners
+commonest
+commonly
+commons
+commotion
+communal
+communicate
+communicated
+communicates
+communicating
+communication
+communications
+communicative
+communist
+communities
+community
+community's
+commute
+comp
+comp's
+compact
+companies
+companion
+companions
+companionships
+company
+company's
+companying
+comparable
+comparatively
+compare
+compared
+comparer
+compares
+comparing
+comparison
+comparison's
+comparisons
+compartments
+compass
+compassed
+compasses
+compassing
+compassion
+compassionate
+compatibility
+compatible
+compel
+compelled
+compensate
+compensates
+compensating
+compensation
+compete
+competed
+competence
+competences
+competent
+competes
+competing
+competition
+competition's
+competitions
+competitive
+complain
+complained
+complainer
+complainer's
+complainers
+complaining
+complains
+complaint
+complaint's
+complaints
+complement
+complete
+completed
+completely
+completer
+completes
+completing
+completion
+completion's
+completions
+completive
+complex
+complexes
+complexly
+complicate
+complicated
+complicates
+complicating
+complication
+complications
+complied
+compliment
+compliment's
+complimentary
+complimented
+complimenting
+compliments
+comply
+component
+component's
+components
+compos
+compose
+composed
+composer
+composer's
+composers
+composes
+composing
+compost
+composure
+compound
+compounded
+compounds
+comprehend
+compress
+compressed
+compresses
+compression
+compressions
+compromise
+compromising
+comps
+compulsive
+compulsively
+compute
+computer
+computer's
+computers
+computes
+computing
+comrade
+comrades
+con
+con's
+conceal
+concealment
+concede
+conceited
+conceivably
+conceived
+concentrate
+concentrated
+concentrates
+concentrating
+concentration
+concentrations
+concentrative
+concept
+concept's
+concepts
+concern
+concern's
+concerned
+concernedly
+concerner
+concerners
+concerning
+concerns
+concert
+concertina
+concerto
+concerts
+concession
+conch
+conches
+conclude
+concluded
+concludes
+concluding
+conclusion
+conclusion's
+conclusions
+concoction
+concord
+concourse
+concrete
+concreted
+concretely
+concretes
+concreting
+concretion
+concur
+concussed
+concussion
+concussions
+condense
+condescending
+condition
+conditioned
+conditioner
+conditioners
+conditioning
+conditions
+condo
+condolence
+condolences
+condone
+condoning
+condor
+condorman
+conduct
+conducted
+conducting
+conductive
+conducts
+conduit
+conduits
+cone
+cones
+conf
+conference
+conference's
+conferences
+conferencing
+confess
+confessing
+confession
+confetti
+confidant
+confide
+confidence
+confidences
+confident
+confidential
+confidentially
+confidently
+configuration
+configure
+configured
+configuring
+confine
+confined
+confinement
+confines
+confirm
+confirmation
+confirmed
+confirming
+confirms
+confiscate
+confiscated
+conflict
+conflicted
+conflicting
+conflictive
+conflicts
+conform
+conformed
+conformist
+conformists
+confounded
+confront
+confrontation
+confronted
+confuse
+confused
+confuser
+confusers
+confuses
+confusing
+confusion
+confusions
+conga
+conga's
+congas
+congeniality
+congested
+congrats
+congratulate
+congratulated
+congratulates
+congratulating
+congratulation
+congratulations
+congratulatory
+congregate
+congregates
+congregation
+congrejos
+congress
+congress's
+congressed
+congresses
+congressing
+conjunction
+conjure
+conman
+connect
+connected
+connecter
+connecters
+connecting
+connection
+connection's
+connections
+connective
+connectivity
+connectors
+connects
+conned
+connie
+conqestadors
+conquer
+conquered
+conquerer
+conquerer's
+conquerers
+conquering
+conqueror
+conquerors
+conquers
+conquest
+conquests
+conquistador
+conquistadors
+conrad
+conrad's
+conrads
+cons
+conscience
+conscious
+consciousness
+conscript
+consensus
+consent
+consequence
+consequence's
+consequences
+consequently
+conservation
+conservative
+conservative's
+conservatively
+conservatives
+conservatory
+conserve
+consider
+considerably
+considerate
+consideration
+considerations
+considered
+considerer
+considerer's
+considerers
+considering
+considers
+consign
+consist
+consisted
+consistent
+consistently
+consisting
+consists
+consolation
+console
+console's
+consoles
+consolidation
+consonant
+consonants
+conspicuous
+conspiracy
+conspiring
+constable
+constance
+constant
+constantly
+constants
+constipation
+constitution
+constrain
+constrict
+construct
+construction
+construction's
+constructions
+constructive
+consul
+consult
+consultation
+consulted
+consulting
+consumable
+consumables
+consume
+consumed
+consumer
+consumer's
+consumers
+consumes
+consuming
+consumption
+cont
+contact
+contacted
+contacting
+contacts
+contagious
+contain
+contained
+container
+container's
+containers
+containing
+contains
+contaminated
+contemplate
+contemplates
+contemplating
+contemplative
+contemporary
+contempt
+contend
+contender
+content
+content's
+contented
+contenting
+contention
+contently
+contents
+contest
+contest's
+contestant
+contests
+context
+context's
+contexts
+continent
+continents
+contingent
+continual
+continually
+continuation
+continue
+continued
+continuer
+continues
+continuing
+continuous
+continuously
+contra
+contraband
+contract
+contract's
+contracted
+contracting
+contractions
+contractive
+contractor
+contracts
+contradict
+contradiction
+contradicts
+contraption
+contraption's
+contraptions
+contrary
+contrast
+contribute
+contributed
+contributer
+contributer's
+contributers
+contributes
+contributing
+contribution
+contributions
+contributive
+control
+control's
+controled
+controling
+controlled
+controller
+controllers
+controlling
+controls
+controversy
+convection
+convene
+convenience
+convenient
+conveniently
+convention
+conventional
+conventions
+converge
+converging
+conversation
+conversation's
+conversations
+converse
+conversed
+conversely
+conversing
+conversion
+conversion's
+conversions
+convert
+converted
+converter
+convertible
+converting
+converts
+convey
+convict
+conviction
+convicts
+convince
+convinced
+convincer
+convincing
+convoy
+convoys
+coo
+cook
+cook's
+cookbook
+cookbook's
+cookbooks
+cooked
+cooker
+cooker's
+cookers
+cookie
+cookie's
+cookies
+cookin'
+cooking
+cookouts
+cooks
+cool
+cool-errific
+cool-palooza
+coolcats
+cooldown
+cooled
+cooler
+cooler's
+coolers
+coolest
+cooling
+cooling's
+coolings
+coolio
+coolly
+coolness
+cools
+coop
+cooper
+cooperate
+cooperates
+cooperating
+cooperation
+cooperative
+coordinate
+coordinated
+coordinates
+coordination
+cooties
+cop
+cope
+copes
+copie's
+copied
+copier
+copier's
+copiers
+copies
+coping
+copper
+copper's
+coppered
+coppering
+coppers
+coppertop
+copping
+cops
+copter
+copters
+copy
+copy's
+copycat
+copying
+copyright
+copyrighted
+cora
+cora's
+coral
+coral's
+corals
+corazon
+corbin
+corbin's
+cord
+cord's
+corded
+corder
+cordial
+cordially
+cording
+cordless
+cords
+core
+coriander
+corianders
+cork
+corks
+corkscrew
+corkscrew's
+corkscrews
+corky
+corm
+corn
+corn's
+cornball
+cornball's
+cornballs
+cornbread
+corned
+cornelius
+cornelius'
+corner
+corner's
+cornered
+cornering
+corners
+cornfields
+cornflower
+cornflowers
+corns
+corny
+coronium
+corporal
+corporate
+corporation
+corporations
+corps
+corral
+corral's
+corrals
+correct
+corrected
+correcting
+correction
+corrective
+correctly
+correctness
+corrects
+correlation
+corresponding
+corridor
+corrupt
+corrupted
+corrupting
+corruption
+corsair
+corsaire
+corsairs
+corseers
+corset
+corsets
+cortada
+cortagua
+cortassa
+cortaux
+cortevos
+cortilles
+cortola
+cortona
+cortos
+cortreau
+corvette
+corvette's
+corvettes
+cory
+cory's
+corys
+cosmic
+cosmicly
+cost
+cost's
+costed
+costello
+costello's
+costing
+costive
+costly
+costs
+costume
+costume's
+costumer
+costumers
+costumes
+cot
+cote
+cotes
+cotillion
+cotillion's
+cotillions
+cots
+cottage's
+cottager
+cottagers
+cottages
+cotton
+cotton's
+cottons
+cottontail
+couch
+couches
+couches'
+cough
+coughed
+cougher
+coughes
+coughes'
+coughing
+coughs
+could
+could've
+coulda
+couldest
+couldn't
+coulter
+council
+council's
+councils
+counsel
+counseling
+counselor
+counselor's
+counselors
+count
+count's
+countdown
+countdown's
+countdowns
+counted
+counter
+counteract
+counteracts
+counterattack
+countered
+countermeasures
+counterpart
+counterparts
+counters
+counterstrike
+countess
+counting
+countless
+countries
+countries'
+country
+countryside
+counts
+county
+county's
+coup
+coupe
+couple
+couple's
+coupled
+coupler
+couplers
+couples
+coupling
+couplings
+coupon
+courage
+courageous
+courant
+courier
+courier's
+couriers
+course
+course's
+coursed
+courser
+courses
+coursework
+coursing
+court
+court's
+courted
+courteously
+courter
+courters
+courtesies
+courtesy
+courthouse
+courting
+courtly
+courts
+courtside
+courtyard
+courtyard's
+courtyards
+couscous
+cousin
+cousin's
+cousins
+couture
+cove
+cove's
+coven
+covenant
+cover
+cover's
+coverage
+coverage's
+coverages
+covered
+covering
+coverings
+covers
+covert
+coves
+covet
+covets
+cow
+cow's
+coward
+coward's
+cowardice
+cowardly
+cowards
+cowbos
+cowboy
+cowboy's
+cowboys
+cowed
+cower
+cowering
+cowers
+cowfish
+cowgirl
+cowing
+coworker
+coworkers
+cows
+coy
+coyote
+coyotes
+coz
+cozana
+cozigos
+cozila
+cozilles
+cozreau
+cozros
+cozuan
+cozy
+cp
+cpip
+cps
+crab
+crab's
+crabapple
+crabean
+crabmeat
+crabster
+crack's
+cracked
+cracked-uptick
+crackers
+crackin'
+cracking
+crackle
+crackle's
+crackles
+crackly
+crackpot
+cracks
+cradle
+cradle's
+cradles
+craft
+crafted
+crafter
+craftiness
+crafting
+crafts
+craftsmanship
+crafty
+crag
+craggy
+crags
+cram
+crammed
+cramming
+cramp
+cramped
+cramping
+cramps
+crams
+cranberries
+cranberry
+crane
+crane's
+cranes
+craning
+cranium
+cranium's
+craniums
+cranky
+cranny
+crash
+crashed
+crasher
+crasher's
+crashers
+crashes
+crashing
+crass
+crate
+crate's
+crated
+crater
+crater's
+craters
+crates
+crating
+crave
+craved
+craven
+craven's
+cravens
+craver
+cravers
+craves
+craving
+cravings
+craw
+crawfish
+crawford
+crawford's
+crawfords
+crawl
+crawled
+crawlers
+crawlies
+crawlin'
+crawling
+crawls
+crawly
+craws
+craze
+crazed
+crazier
+crazies
+craziest
+craziness
+crazy
+crazycorner
+crazycorner's
+crazycorners
+crazyquilt
+crazyquilt's
+crazyquilts
+crazzlepops
+creak
+creaked
+creaking
+creaks
+cream
+creamed
+creamer
+creamery
+creaming
+creams
+creamy
+crease
+creasy
+create
+created
+creates
+creating
+creation
+creation's
+creations
+creative
+creatively
+creativity
+creator
+creator's
+creators
+creature
+creature's
+creaturely
+creatures
+cred
+credit
+credit's
+credited
+crediting
+creditor
+credits
+credo
+creed
+creek
+creek's
+creeked
+creeking
+creeks
+creep
+creep's
+creeper
+creeper's
+creepers
+creeping
+creeps
+creepy
+cremate
+cremated
+crept
+crescent
+crest
+crest's
+crested
+crests
+cretin
+cretins
+crew
+crew's
+crewe
+crewed
+crewing
+crewman
+crewmate
+crewmates
+crewmember
+crewmember's
+crewmembers
+crewmen
+crewperson
+crews
+crews'
+crewship
+crib
+crib's
+cribs
+crick
+cricket
+cricket's
+cricket-whistling
+crickets
+cried
+crier
+criers
+cries
+crime
+crime-fighting
+crimes
+criminal
+crimonson
+crimson
+crimson's
+crimsonm
+crimsons
+cringe
+cringes
+cringing
+cripes
+cripple
+crippled
+cripples
+crippling
+crises
+crisis
+crisp
+crisps
+crispy
+cristo
+cristo's
+criteria
+criterias
+critic
+critic's
+critical
+critically
+criticism
+criticism's
+criticisms
+criticize
+criticized
+criticizing
+critics
+critter
+critter's
+critters
+croak
+croaked
+croaking
+croatia
+croatian
+croc
+croc's
+crochet
+crock
+crocked
+crocket
+crockett
+crockett's
+crocketts
+crockpot
+crockpot's
+crockpots
+crocks
+crocodile
+crocodile's
+crocodiles
+crocodilian
+crocs
+crocus
+crom
+cronies
+crook
+crooked
+cropped
+cropping
+crops
+croquet
+cross
+cross-eyed
+crossbar
+crossbones
+crossbow
+crossed
+crosser
+crossers
+crosses
+crossfire
+crosshair
+crosshairs
+crossing
+crossings
+crossly
+crossover
+crossroads
+crosstrees
+crosswalk
+crossway
+crossword
+crosswords
+crouch
+crouched
+croup
+croupier
+croutons
+crow
+crow's
+crowbar
+crowd
+crowded
+crowder
+crowding
+crowds
+crowed
+crowing
+crown
+crown's
+crown-repair
+crowned
+crowner
+crowning
+crowns
+crows
+cruces
+crucial
+crud
+cruddier
+cruddy
+crude
+cruder
+cruds
+cruel
+crueler
+cruelest
+cruella
+cruella's
+cruelly
+cruelty
+cruise
+cruise's
+cruised
+cruiser
+cruisers
+cruises
+cruisin'
+cruising
+crumb
+crumble
+crumbled
+crumbles
+crumbly
+crumbs
+crumby
+crummy
+crunch
+crunche's
+crunched
+cruncher
+cruncher's
+crunchers
+crunches
+crunchmouth
+crunchy
+crusade
+crusader
+crusaders
+cruse
+cruses
+crush
+crush's
+crushed
+crusher
+crusher's
+crushers
+crushes
+crushing
+crust
+crustaceans
+crustatious
+crusted
+crustless
+crusty
+crutch
+crutches
+crux
+cruz
+cruzada
+cruzaire
+cruzigos
+cruzilles
+cruzman
+cruzola
+cruzos
+cruzuan
+cruzumal
+cry
+crying
+crypt
+crypt's
+cryptic
+crypts
+crystal
+crystal's
+crystals
+ctf
+ctrl
+cub
+cuba
+cuban
+cubby
+cubby's
+cubbyhole
+cubbyhole's
+cubbyholes
+cubbys
+cube
+cube's
+cubes
+cubic
+cubicle
+cubicle's
+cubicles
+cubkyle's
+cuckoo
+cuckoo's
+cuckoos
+cud
+cuddle
+cuddled
+cuddles
+cuddling
+cuddly
+cuds
+cue
+cues
+cuff
+cuffed
+cufflinks
+cuffs
+culinary
+cull
+culling
+cully
+culpa
+culprit
+cult
+cultivate
+cultural
+culturally
+culture
+culture's
+cultured
+cultures
+culturing
+cumbersome
+cumulative
+cunning
+cup
+cup's
+cupboard
+cupboard's
+cupboards
+cupcake
+cupcake's
+cupcakes
+cups
+cur
+curb
+curb's
+curbs
+curd
+curdle
+cure
+cure's
+cured
+curer
+cures
+curing
+curio
+curio's
+curios
+curiosity
+curious
+curiouser
+curiousest
+curiously
+curl
+curl's
+curled
+curling
+curlly
+curls
+curly
+curly-maned
+currant
+currants
+currency
+current
+current's
+currently
+currents
+curriculum
+curry
+curry's
+curs
+curse
+curse's
+cursed
+curser
+cursers
+curses
+cursing
+cursive
+cursor
+curst
+curt
+curtain
+curtain's
+curtained
+curtaining
+curtains
+curtis
+curtsey
+curtseys
+curtsies
+curtsy
+curve
+curve's
+curved
+curves
+curvey
+curving
+curvy
+cushion
+cushion's
+cushioned
+cushioning
+cushions
+cushy
+cuss
+cussed
+cusses
+cussing
+custard
+custody
+custom
+customary
+customer
+customer's
+customers
+customizable
+customization
+customize
+customized
+customizer
+customizes
+customizing
+customs
+cut's
+cut-throat
+cutbacks
+cute
+cuteness
+cuter
+cutest
+cutesy
+cutie
+cutie's
+cuties
+cutla
+cutlass
+cutlass'
+cutlass's
+cutlasses
+cutler
+cutler's
+cutoff
+cutout
+cuts
+cutscene
+cutter
+cutters
+cutthroart
+cutthroat
+cutthroat's
+cutthroats
+cutts
+cuz
+cya
+cyan
+cyberspace
+cycle
+cycled
+cycles
+cycling
+cyclone
+cyclones
+cynthia
+cynthia's
+cypress
+cyrus
+cyrus'
+d&b
+d'dogs
+d'etable
+d*concert
+d-concert
+d-name
+d-points
+da
+da's
+dab
+dabbled
+daccor
+dace
+dacja
+dad
+dad's
+dada
+daddies
+daddy
+daddy's
+daddy-long-legs
+daffodil
+daffodilly
+daffodils
+daffy
+daft
+dahlia
+daichi
+daigunder
+daigyo
+dailies
+daily
+dainty
+dairy
+dais
+daisies
+daisy
+daisy's
+dajin
+dale
+dale's
+dales
+dalma
+dalma's
+dalmatian
+dalmatian's
+dalmatians
+damage
+damage's
+damaged
+damager
+damagers
+damages
+damaging
+dame's
+dames
+damp
+damper
+damps
+damsel
+damsel's
+damselfish
+damsels
+dan
+dan's
+dana
+danaphant
+danapix
+danawa
+dance
+dance's
+dance-along
+danced
+dancer
+dancer's
+dancers
+dancers'
+dances
+dancin'
+dancing
+dandelion
+dandelion-fluff
+dandelions
+dander
+dandruff
+dandy
+danforth
+danforth's
+danforths
+dang
+danged
+danger
+danger's
+dangerous
+dangerously
+dangers
+dangle
+daniel
+daniel's
+danield
+daniels
+dans
+dante
+dap
+daphne
+dapple
+darby
+dare
+dare's
+dared
+daredevil
+daredevils
+darer
+darer's
+darers
+dares
+daring
+daring-do
+daringly
+dark
+dark's
+dark-blade
+dark-sail
+dark-water
+dark-wind
+darkage
+darkblade
+darkblood
+darken
+darkened
+darkening
+darkens
+darker
+darkest
+darkiron
+darkly
+darkmasters
+darkmos
+darkness
+darkraptors
+darks
+darkshadow
+darkskulls
+darkstalkers
+darkstealers
+darkwaters
+darkwind
+darkwing
+darling
+darn
+darned
+darns
+darrell
+darrell's
+darrells
+darryl
+dart
+dart's
+darted
+darts
+darucho
+darutake
+darutori
+das
+dash
+dashboard
+dashboard's
+dashboards
+dashe's
+dasher
+dashes
+dashing
+dastardly
+data
+database
+date
+dated
+dateline
+dater
+dates
+dating
+daughter
+daughter's
+daughters
+daunting
+dauntless
+dave
+dave's
+davenport
+davenport's
+davenports
+daves
+davey
+davey's
+david
+davis
+davy
+davy's
+dawdling
+dawgs
+dawn
+dawn's
+dawned
+dawning
+dawns
+daxisd
+day
+day's
+daybreak
+daycare
+daydream
+daydreamer
+daydreamer's
+daydreamers
+daydreaming
+daydreams
+daylight
+daylights
+days
+daytime
+daze
+dazed
+dazy
+dazzle
+dazzling
+db
+dbl
+dc
+dca
+dcom
+ddl
+ddock
+ddream
+ddreamland
+deacon
+deactivate
+deactivated
+deadeye
+deadeyes
+deadhead
+deadheading
+deadline
+deadlines
+deadliness
+deadlok
+deads
+deadwood
+deaf
+deafening
+deal
+deal's
+dealer
+dealer's
+dealers
+dealership
+dealing
+dealing's
+dealings
+deals
+dealt
+dealthy
+dean
+dean's
+deans
+dear
+dear's
+dearer
+dearest
+dearheart
+dearly
+dears
+dearth
+debark
+debatable
+debate
+debate's
+debated
+debater
+debaters
+debates
+debating
+debbie
+debbie's
+debbies
+debilitating
+debit
+debonair
+debris
+debs
+debt
+debt's
+debts
+debug
+debugged
+debugging
+debut
+debut's
+debuted
+debuts
+decade
+decades
+decaf
+decal
+decals
+decamps
+decapitate
+decathlon
+decathlon's
+decathlons
+decay
+deceased
+deceiving
+december
+december's
+decembers
+decemeber
+decency
+decent
+decently
+deception
+deceptive
+decide
+decided
+decidedly
+decider
+decides
+deciding
+decimate
+decimated
+decimating
+decipher
+deciphering
+decision
+decision's
+decisions
+decked
+decker
+deckhand
+deckhands
+decking
+deckings
+declaration
+declaration's
+declarations
+declare
+declared
+declarer
+declarer's
+declarers
+declares
+declaring
+decline
+declined
+declines
+declining
+deco
+decode
+decompress
+decompressing
+decor
+decorate
+decorated
+decorates
+decorating
+decoration
+decoration's
+decorations
+decorator
+decorator's
+decorators
+decoy
+decrease
+decreased
+decreases
+decreasing
+dedans
+dedicate
+dedicated
+dedicating
+dedication
+deduct
+deduction
+deductive
+deducts
+deed
+deeds
+deejay
+deem
+deemed
+deems
+deep
+deeper
+deepest
+deeply
+deeps
+deepwater
+deer
+deer's
+deers
+deez
+def
+default
+defaulted
+defaulting
+defaults
+defeat
+defeated
+defeateds
+defeater
+defeater's
+defeaters
+defeating
+defeats
+defect
+defect's
+defected
+defecting
+defective
+defector
+defects
+defend
+defended
+defender
+defender's
+defenders
+defending
+defends
+defense
+defenseless
+defenses
+defensive
+defensively
+defer
+deff
+defiant
+defies
+define
+defined
+definer
+definer's
+definers
+defines
+defining
+definite
+definitely
+definition
+definition's
+definitions
+definitive
+deflate
+defog
+defogging
+deform
+deformed
+defrag
+defragged
+defragging
+defrags
+defrost
+deft
+defy
+defying
+deg
+degenerate
+degenerative
+deglitched
+degraded
+degree
+degree's
+degreed
+degrees
+dehydrated
+dehydration
+deity
+deja
+dejectedly
+dejon
+dekay
+del
+delas
+delay
+delayed
+delaying
+delays
+dele
+deles
+delete
+deleted
+deletes
+deleting
+deletion
+deli
+deliberated
+deliberately
+deliberating
+delicacy
+delicate
+delicately
+delicates
+delicioso
+delicious
+delight
+delighted
+delightful
+delinquent
+delirious
+deliriously
+delis
+deliver
+deliver's
+delivered
+deliverer
+deliverers
+deliveries
+delivering
+delivers
+delivery
+dell
+dell's
+della
+dells
+delta
+deluded
+delusional
+delusions
+deluxe
+delve
+delver
+delves
+demand
+demanded
+demander
+demanding
+demands
+demeanor
+demented
+dementor
+dementor's
+dementors
+demise
+democratic
+demolition
+demolitions
+demon
+demons
+demonstrate
+demonstrated
+demonstrates
+demonstrating
+demonstration
+demonstrations
+demonstrative
+demotion
+demure
+demures
+den
+den's
+dendama
+denden
+denial
+denied
+denier
+deniers
+deniers'
+denies
+denim
+denim's
+denims
+dennis
+dennison
+denomination
+denominational
+denote
+denpachi
+dens
+dens'
+dense
+dent
+dental
+dented
+dentin
+dentinal
+denting
+dentist
+dentist's
+dentistry
+dentists
+dents
+dentures
+deny
+denying
+deodorant
+depart
+departed
+departing
+department
+department's
+departments
+departs
+departure
+departures
+depend
+dependable
+dependant
+depended
+dependent
+depending
+depends
+depleted
+deploy
+deployed
+deploying
+deport
+deporting
+deposed
+deposer
+deposit
+deposited
+depositing
+deposits
+depot
+depot's
+depots
+depp
+depp's
+depreciated
+depress
+depressed
+depressing
+depression
+deprivation
+deprive
+deprived
+depriving
+dept
+depth
+depths
+deputy
+derail
+derange
+deranged
+derby
+deregulate
+derek
+derek's
+dereks
+derezzed
+derive
+deriving
+dernier
+derogatory
+derrick
+derriere
+des
+desc
+descended
+descending
+descent
+descent's
+descents
+descm
+descp
+descpl
+descpn
+describe
+described
+describer
+describer's
+describers
+describes
+describing
+descript
+description
+description's
+descriptions
+descriptive
+descs
+descsl
+descsn
+deseago
+deseano
+desecration
+desegua
+deselect
+desensitization
+deseona
+deseos
+desereau
+deseros
+desert
+deserted
+deserter
+deserter's
+deserters
+deserting
+deserts
+deserts'
+deserve
+deserved
+deserves
+deserving
+design
+design's
+designate
+designated
+designation
+designed
+designer
+designer's
+designers
+designing
+designs
+desirable
+desire
+desired
+desirer
+desires
+desiring
+desk
+desk's
+desks
+desktop
+desktops
+desolate
+desolation
+desolations
+despair
+desperate
+desperately
+despicable
+despise
+despite
+despited
+despoiler
+dessert
+dessert's
+desserts
+destination
+destinations
+destined
+destinie's
+destinies
+destiny
+destiny's
+destinys
+destoryers
+destroy
+destroyable
+destroye
+destroyed
+destroyer
+destroyer's
+destroyers
+destroying
+destroys
+destruct
+destruction
+destructive
+detachment
+detail
+detailed
+detailer
+detailer's
+detailers
+detailing
+details
+detained
+detect
+detected
+detecting
+detection
+detective
+detective's
+detectives
+detector
+detector's
+detectors
+detects
+detention
+deter
+deteriorating
+determination
+determination's
+determinations
+determine
+determined
+determiner
+determiner's
+determiners
+determines
+determining
+detest
+detonate
+detonates
+detonation
+detour
+detour's
+detouring
+detours
+deuce
+deuces
+deutsche
+dev
+devastated
+devastating
+devastatingly
+develop
+developed
+developer
+developer's
+developers
+developing
+development
+development's
+developments
+develops
+deviant
+device
+device's
+devices
+devil
+devil's
+deviled
+devilish
+devilishly
+devils
+devils'
+devious
+devise
+devised
+devises
+devoid
+devoir
+devote
+devoted
+devotion
+devour
+devoured
+devours
+dew
+dewdrop
+dewdrops
+dews
+dewy
+dexterity
+dg
+dgamer
+dgarden
+dhow
+diabetes
+diabetic
+diabolical
+diagnosed
+diagnosis
+diagonal
+diagonally
+diagonals
+dial
+dialect
+dialing
+dialog
+dialogue
+dialup
+dialysis
+diamante
+diamond
+diamond's
+diamonds
+diana
+diane
+diaper
+diaper's
+diapered
+diapers
+diaries
+diary
+dibs
+dice
+dice'
+diced
+dicer
+dices
+dicey
+dicing
+dickens
+dictate
+dictates
+diction
+dictionaries
+dictionary
+dictionary's
+did
+didn't
+didnt
+didst
+didymus
+die
+died
+diego
+diehard
+dieing
+diem
+dies
+diesel
+diet
+dietary
+diets
+dif
+diff
+differ
+differed
+difference
+difference's
+differenced
+differences
+differencing
+different
+differential
+differentiate
+differently
+differing
+differs
+difficult
+difficulties
+difficultly
+difficulty
+difficulty's
+diffusers
+diffy
+dig
+digest
+digg
+diggable
+digger
+diggers
+digging
+digging's
+diggings
+diggity
+digimon
+digit
+digital
+dignity
+digress
+digs
+dilemma
+dill's
+dillinger
+dillinger's
+dilly
+dilute
+diluted
+dim
+dime
+dimension
+dimensions
+diminished
+diminishing
+diminutive
+dimm
+dimmed
+dimond
+dimple
+dimples
+dims
+dimwit
+dimwits
+dimwitted
+din
+dinah
+dine
+dine-in
+dined
+diner
+diner's
+diners
+dines
+dinette
+ding
+dinged
+dinghies
+dinghy
+dinghy's
+dinghys
+dinging
+dingo
+dings
+dingy
+dining
+dink
+dinks
+dinky
+dinner
+dinner's
+dinners
+dinnertime
+dino
+dino's
+dinos
+dinosaur
+dinosaur's
+dinosaurs
+dinothunder
+dins
+dint
+dip
+diplomat
+diplomatic
+diplomats
+dipped
+dipper
+dipping
+dippy
+dips
+dir
+dire
+direct
+directed
+directing
+direction
+direction's
+directional
+directions
+directive
+directives
+directly
+director
+director's
+directors
+directory
+directs
+direr
+direst
+dirge
+dirk
+dirks
+dirt
+dirty
+dis
+disability
+disable
+disabled
+disabler
+disables
+disabling
+disadvantage
+disadvantaged
+disadvantages
+disaffected
+disagree
+disagreed
+disagreement
+disagreements
+disagrees
+disappear
+disappearance
+disappeared
+disappearing
+disappears
+disappoint
+disappointed
+disappointing
+disappointment
+disappoints
+disapprove
+disapproved
+disapprover
+disapproves
+disapproving
+disarm
+disarray
+disaster
+disasters
+disastrous
+disavow
+disband
+disbanded
+disbanding
+disbands
+disbelief
+disc
+discarded
+discernible
+discharge
+discharged
+discharger
+disciples
+disciplinary
+discipline
+disciplined
+discipliner
+disciplines
+disciplining
+disclaimer
+disco
+discoed
+discoing
+disconcerting
+disconnect
+disconnected
+disconnecting
+disconnection
+disconnections
+disconnects
+discontinued
+discos
+discount
+discount's
+discounted
+discounter
+discounter's
+discounters
+discounting
+discounts
+discourage
+discouraged
+discourages
+discouraging
+discover
+discovered
+discoverer
+discoverer's
+discoverers
+discoveries
+discovering
+discovers
+discovery
+discovery's
+discrepancies
+discrepancy
+discretion
+discriminate
+discrimination
+discs
+discus
+discuss
+discussed
+discusser
+discusser's
+discusses
+discussing
+discussion
+discussion's
+discussions
+disdain
+disease
+diseased
+diseases
+diseasing
+disembark
+disembarked
+disembarking
+disembodied
+disenfranchised
+disengage
+disengaged
+disengages
+disengaging
+disgrace
+disgraced
+disgraces
+disguise
+disguise's
+disguised
+disguises
+disgust
+disgust's
+disgusted
+disgusting
+disgustingly
+disgusts
+dish
+disheartened
+dished
+dishes
+dishes'
+dishing
+dishonest
+dishonorable
+dishwasher
+disillusioned
+disinclined
+disintegrated
+disinterest
+disinterested
+disjoin
+disjoined
+disk
+disk's
+disks
+disky
+dislike
+disliked
+dislikes
+disliking
+disloyal
+dismal
+dismantle
+dismantled
+dismay
+dismiss
+dismissal
+dismissed
+dismisser
+dismissers
+dismisses
+dismissing
+dismissive
+disney
+disney's
+disney.com
+disneyana
+disneyana's
+disneyfairies.com
+disneyfairies.com/pixiehollow
+disneyland
+disneyland's
+disneymania
+disneyworld
+disneyworld's
+disobedience
+disobey
+disobeyed
+disobeying
+disorder
+disorders
+disorganized
+disorienting
+disown
+disowned
+dispassionately
+dispatch
+dispatched
+dispatching
+dispense
+dispenser
+dispensers
+displaced
+displaces
+displas
+display
+displayed
+displayer
+displaying
+displays
+displeased
+displeases
+displeasure
+disposal
+dispose
+dispute
+disqualification
+disregard
+disregarding
+disrespect
+disrespectful
+disrespecting
+disrespects
+disrupt
+disrupted
+disrupting
+disruptive
+disrupts
+dissect
+dissension
+dissent
+dissenter
+dissention
+dissolved
+dist
+distance
+distanced
+distances
+distancing
+distant
+distantly
+distemper
+distill
+distinct
+distinction
+distinctions
+distinguish
+distinguished
+distorted
+distortion
+distortions
+distract
+distracted
+distracting
+distraction
+distractions
+distractive
+distracts
+distraught
+distress
+distressed
+distressing
+distribute
+distributed
+distributer
+distributer's
+distributers
+distributes
+distributing
+distribution
+distributions
+distributive
+district
+district's
+districts
+disturb
+disturbance
+disturbances
+disturbed
+disturber
+disturber's
+disturbers
+disturbing
+disturbingly
+disturbs
+ditch
+ditched
+ditcher
+ditchers
+ditches
+ditching
+ditsy
+dittany
+ditties
+ditto
+ditty
+ditz
+ditzy
+diva
+diva's
+divas
+dive
+dived
+diver
+diver's
+divers
+diverse
+diversion
+divert
+diverted
+diverts
+dives
+divest
+divide
+divided
+divider
+divider's
+dividers
+divides
+dividing
+divine
+divinely
+diving
+divinity
+division
+division's
+divisions
+divorce
+divorced
+divorcing
+divulge
+divvied
+divvying
+diwali
+dizzied
+dizzier
+dizziest
+dizziness
+dizzy
+dizzying
+dizzyly
+dj
+dlp
+dlr
+dluffy
+dname
+do
+do-re-mi
+dobra
+doc
+doc's
+docile
+dociousaliexpiisticfragilcalirupus
+dock
+dock's
+docked
+docker
+docker's
+dockers
+dockhand
+docking
+docks
+docksplinter
+dockworker
+dockworkers
+docs
+doctor
+doctor's
+doctored
+doctoring
+doctors
+document
+document's
+documentary
+documented
+documenter
+documenters
+documenting
+documents
+dodge
+dodgeball
+dodgeball's
+dodgeballs
+dodged
+dodgem
+dodger
+dodgers
+dodges
+dodging
+dodgy
+dodo
+doe
+doer
+does
+doesdoesnt
+doesn't
+doesnt
+doest
+dog
+dog's
+doge
+dogfish
+dogged
+doggerel
+doggie
+doggies
+doggone
+doggy
+doghouse
+doghouse's
+doghouses
+dogs
+dogwood
+doh
+doids
+doilies
+doin
+doin'
+doing
+doings
+dojo
+dojo's
+dojos
+dole
+doll
+doll's
+dollar
+dollar's
+dollars
+dolled
+dollhouse
+dollhouse's
+dollhouses
+dollies
+dolls
+dolly
+dolman
+dolor
+dolores
+dolph
+dolphin
+dolphin's
+dolphins
+dolt
+dolts
+doma-boma
+domain
+domed
+domestic
+domesticated
+dominant
+dominion
+domino
+domino's
+dominoes
+dominos
+don
+don't
+donald
+donald's
+donalds
+donate
+donated
+donates
+donating
+donation
+donations
+done
+dongiga
+dongor
+dongora
+donkey
+donkeys
+donned
+donnon
+dont
+donut
+donut's
+donuts
+doodad
+doodad's
+doodads
+doodle
+doodle's
+doodlebops
+doodlebug
+doodlebugs
+doodles
+doodles'
+doohickey
+dooly
+doom
+doombringers
+doomed
+dooming
+doompirates
+doomraiders
+dooms
+doonan
+door
+door's
+doorbell
+doorknob
+doorknob's
+doorknobs
+doorman
+doorman's
+doormans
+doors
+doorway
+doorway's
+doorways
+dopey
+dopey's
+doppler's
+dorado
+doris
+doris'
+dorm
+dorm's
+dormant
+dormouse
+dorms
+dory
+dory's
+dos
+dose
+dost
+dot
+doth
+doting
+dots
+dotted
+dotty
+double
+double-click
+double-decker
+doubled
+doubledown
+doubler
+doublers
+doubles
+doubling
+doubloon
+doubloons
+doubly
+doubt
+doubted
+doubter
+doubter's
+doubters
+doubtful
+doubting
+doubts
+doug
+doug's
+dougal
+dough
+doughnut
+doughnuts
+dougs
+douse
+douses
+dove
+dove's
+dover
+doves
+dowdy
+dower
+down
+down-home
+downed
+downer
+downer's
+downers
+downfall
+downfalls
+downgrade
+downgraded
+downgrades
+downhill
+downing
+download
+downloaded
+downloading
+downloads
+downrange
+downright
+downs
+downside
+downsize
+downsized
+downsizer
+downsizers
+downstairs
+downtime
+downtown
+downward
+downwards
+downy
+dowry
+doze
+dozed
+dozen
+dozens
+dozer
+dozes
+dozin'
+dozing
+dr
+dr.
+drab
+drabs
+draco's
+draconis
+dracos
+dracula
+dracyla's
+draft
+drafted
+drafting
+drafts
+drag
+dragged
+dragger
+dragging
+dragion
+dragon
+dragon's
+dragonblood
+dragonfly
+dragons
+dragonslayers
+dragoon
+drags
+dragstrip
+drain
+drainage
+drained
+drainer
+draining
+drains
+drak
+drake
+drakes
+drakken
+drakken's
+dram
+drama
+dramamine
+dramas
+dramatic
+dramatically
+drams
+drank
+drapes
+draping
+drapmeister
+drastic
+drastically
+drat
+drats
+dratted
+draught
+draughts
+draw
+drawback
+drawbacks
+drawbridge
+drawbridges
+drawer
+drawers
+drawing
+drawings
+drawl
+drawly
+drawn
+drawnly
+draws
+dray
+drays
+dread
+dread's
+dreaded
+dreadful
+dreadfully
+dreading
+dreadlock
+dreadlocks
+dreadnaught
+dreadnaughts
+dreadnought
+dreadnoughts
+dreads
+dream
+dreamboat
+dreamed
+dreamer
+dreamer's
+dreamers
+dreaming
+dreamland
+dreamlike
+dreams
+dreamscape
+dreamscapes
+dreamt
+dreamy
+dreary
+dredd
+dreg
+dregs
+dreidel
+dreidel's
+dreidels
+drench
+drenched
+drenches
+dress
+dress'
+dress-making
+dressed
+dresser
+dressers
+dresses
+dresses'
+dressing
+dressings
+drew
+drib
+dribble
+dribbles
+dribbling
+dried
+drier
+drier's
+driers
+dries
+driest
+drift
+drifted
+drifter
+drifter's
+drifters
+drifting
+drifts
+driftwood
+driftwoods
+drifty
+drill
+drilled
+drilling
+drills
+drink
+drink's
+drinkable
+drinker
+drinker's
+drinkers
+drinking
+drinks
+drip
+dripping
+drips
+drivable
+drive
+drive-thru
+driven
+driver
+driver's
+drivers
+drives
+driveway
+drivin'
+driving
+drizzle
+drizzles
+droid
+drone
+droned
+droning
+drool
+drooled
+drooling
+drools
+droop
+drooped
+drooping
+droops
+droopy
+drop
+drop's
+dropdown
+dropout
+droppable
+dropped
+dropper
+droppers
+dropping
+droppings
+drops
+drought
+droughts
+drove
+droves
+drown
+drowned
+drowning
+drowns
+drowsy
+druid
+drum
+drum's
+drummer
+drummer's
+drummers
+drumming
+drums
+dry
+dryad
+dryad's
+dryads
+drydock
+dryer
+drying
+dryly
+drywall
+ds
+du
+dual
+dually
+duals
+dub
+dubious
+dubloon
+dubs
+ducat
+duce
+duchamps
+duchess
+duck
+duck's
+ducked
+duckies
+ducking
+ducks
+ducktales
+ducky
+duct
+ducts
+dud
+dude
+dude's
+dudes
+dudley
+dudley's
+duds
+due
+duel
+dueled
+dueling
+duels
+dues
+duet
+dug
+dugout
+duh
+duke
+duke's
+dukes
+dulcie
+dulcie's
+dull
+dulled
+duller
+dulling
+dulls
+duly
+dumbfounded
+dumbness
+dumbo
+dumbo's
+dummies
+dummy's
+dump
+dumped
+dumping
+dumpling
+dumpling's
+dumplings
+dumps
+dumpster
+dumpy
+dun
+dunce
+dundee
+dune
+dunes
+dung
+dungeon
+dungeon's
+dungeons
+dunk
+dunked
+dunking
+dunks
+dunno
+duns
+duo
+duo's
+duos
+dup
+dupe
+duped
+duper
+dupes
+duplicate
+duplicated
+duplicates
+durability
+durable
+duranies
+duration
+durations
+during
+dusk
+duskfall
+dusky
+dust
+dusted
+duster
+duster's
+dusters
+dusting
+dusts
+dusty
+dutch
+dutchman
+duties
+duty
+duty's
+dvd
+dvd's
+dvds
+dwarf
+dwarfs
+dwarves
+dwayne
+dwayne's
+dwaynes
+dweeb
+dweebs
+dwell
+dwellers
+dwelling
+dwells
+dwight
+dwindle
+dwindling
+dxd
+dxd3
+dxdart
+dxdef
+dxdome
+dxdream
+dye
+dyed
+dyeing
+dyeing-talent
+dyes
+dying
+dylan
+dylan's
+dylans
+dynamic
+dynamite
+dynamo
+dynamo's
+dynamos
+dynasty
+dysfunctional
+dyslectic
+dyslexia
+dyslexic
+dcor
+dj
+e.z.
+eac
+each
+eager
+eagerly
+eagle
+eagle's
+eagles
+ear
+ear's
+earache
+earaches
+eared
+earful
+earing
+earl
+earl's
+earlier
+earliest
+earls
+early
+early-morning
+earn
+earnable
+earned
+earner
+earner's
+earners
+earnest
+earning
+earning's
+earnings
+earns
+earplug
+earplugs
+earring
+earrings
+ears
+earshot
+earth
+earth's
+earthed
+earthen
+earthling
+earthlings
+earthly
+earthquake
+earthy
+earwax
+ease
+easel
+easel's
+easels
+eases
+easier
+easiest
+easily
+easing
+east
+east's
+easter
+easter's
+eastern
+easterner
+easterners
+easting
+easton
+easts
+easy
+eat
+eaten
+eater
+eaters
+eating
+eats
+eau
+eave
+eavesdropped
+eavesdroppers
+ebay
+ebay's
+ebony
+eccentric
+echo
+echoes
+eclectic
+eclipse
+eco
+eco-logic
+economically
+economics
+economy
+ed
+ed's
+eddie
+eddie's
+eddies
+eddy
+edgar
+edge
+edge's
+edge-of-your-seat
+edged
+edger
+edges
+edgest
+edgewise
+edging
+edgy
+edible
+edit
+edit's
+edited
+editing
+edition
+edition's
+editions
+editor
+editor's
+editors
+edits
+edmund
+edmund's
+edmunds
+eds
+educate
+educated
+educating
+education
+education's
+educational
+educationally
+educations
+edward
+eek
+eeky
+eepr
+eepy
+eerie
+eerily
+eewee
+eewee's
+eeyore
+eeyore's
+effect
+effect's
+effected
+effecting
+effective
+effectively
+effectiveness
+effectives
+effects
+efficiency
+efficient
+effort
+effort's
+effortless
+efforts
+egad
+egan
+egg
+egg's
+egg-cellent
+eggcellent
+egged
+egging
+eggnog
+eggplant
+eggroll
+eggs
+eggshells
+eggventure
+ego
+ego's
+egocentric
+egomaniac
+egos
+egotistical
+eh
+ehem
+ehre
+eider
+eileen
+einherjar
+einstein
+einstein's
+einsteins
+eitc
+either
+eject
+ejected
+ejecting
+ejects
+ekes
+el
+elaborate
+eland
+elastic
+elbow
+elbowed
+elbows
+elda
+elda's
+elder
+elderberry
+elderly
+elders
+eldest
+elect
+elected
+electing
+election
+election's
+elections
+elective
+elective's
+electives
+electra
+electric
+electric's
+electrical
+electricities
+electricity
+electrics
+electrified
+electrifying
+electro
+electrocuted
+electron
+elects
+elegant
+elegantly
+elegies
+element
+element's
+elemental
+elementals
+elements
+elephant
+elephant's
+elephants
+elevate
+elevated
+elevator
+elevator's
+elevators
+elf
+elf's
+elif
+eligible
+eliminate
+eliminated
+elimination
+eliminator
+elite
+elites
+elitism
+elixa
+elixa's
+elixir
+elixirs
+eliza
+elizabeth
+elizabeth's
+elk
+ell
+ella
+ella's
+ellie
+ellie's
+ello'
+ells
+elm
+elma
+elms
+elo
+eloise
+eloise's
+elope
+elopuba
+eloquent
+eloquently
+elozar
+else
+else's
+elsewhere
+elsie
+elude
+eludes
+eluding
+elusive
+elva
+elves
+elvis
+elvis's
+em
+embarcadero
+embark
+embarking
+embarks
+embarrass
+embarrassed
+embarrasses
+embarrassing
+embassy
+embed
+embedded
+ember
+embers
+emblem
+emblems
+embrace
+embraced
+embraces
+embracing
+emerald
+emeralds
+emerge
+emergencies
+emergency
+emerges
+emile
+emile's
+emily
+emily's
+emit
+emitting
+emma
+emote
+emoted
+emotes
+emoticon
+emoticon's
+emoticons
+emotion
+emotion's
+emotional
+emotions
+emoto-scope
+empathize
+emperor
+emperor's
+emphasis
+emphasize
+emphasized
+empire
+empire's
+empires
+employed
+employee
+employees
+employers
+employment
+employs
+empoison
+emporium
+emporium's
+emporiums
+empress
+empresses
+emptied
+emptier
+empties
+emptiest
+emptiness
+empty
+emptying
+empyrean
+emrald
+enable
+enabled
+enabler
+enabler's
+enablers
+enables
+enabling
+encampment
+enchant
+enchanted
+enchanter
+enchanter's
+enchanting
+enchantmen
+enchantment
+enchantmet
+enchants
+enchiladas
+encircle
+encircling
+enclosed
+encoder
+encom
+encore
+encore's
+encores
+encounter
+encourage
+encouraged
+encouragement
+encourager
+encourages
+encouraging
+encrusted
+encryption
+encyclopedia
+end
+endearing
+endeavor
+endeavors
+endeavour
+endeavours
+ended
+ender
+enders
+ending
+endings
+endive
+endive's
+endives
+endless
+endlessly
+ends
+endurance
+endure
+enemies
+enemy
+enemy's
+energetic
+energies
+energize
+energized
+energizer
+energy
+enflame
+enforce
+enforcement
+enforcing
+eng
+engagement
+engagements
+engenio
+engine
+engine's
+engined
+engineer
+engineer's
+engineered
+engineering
+engineers
+engines
+engining
+english
+engrave
+engraved
+engraves
+engrossed
+enigma
+enigmatic
+enjos
+enjoy
+enjoyable
+enjoyed
+enjoying
+enjoyment
+enjoys
+enlighten
+enlightening
+enlightenment
+enlist
+enlisted
+enlisting
+enough
+enquire
+enraged
+enraging
+enriching
+enrique
+enroll
+enrolled
+enrolling
+ensemble
+ensembles
+ensign
+ensnare
+ensue
+ensues
+ensure
+ensured
+ensures
+ensuring
+entail
+entails
+entendre
+entendres
+enter
+entered
+enterer
+entering
+enterprise
+enterprisers
+enterprises
+enters
+entertain
+entertained
+entertainer
+entertainers
+entertaining
+entertainment
+entertainment's
+entertainments
+entertains
+enthused
+enthusiasm
+enthusiastic
+entire
+entirely
+entirety
+entitled
+entourage
+entrain
+entrance
+entrance's
+entranced
+entrances
+entrancing
+entries
+entropic
+entry
+entry's
+entryway
+envelope
+envelope's
+enveloped
+enveloper
+envelopes
+enveloping
+envied
+envious
+environ
+environment
+environment's
+environmental
+environmentally
+environments
+envision
+envoy
+envy
+enzyme
+enzymes
+eon
+eons
+epcot
+epcot's
+epic
+epics
+epilepsy
+epiphany
+episode
+episodes
+equal
+equaling
+equalizer
+equally
+equals
+equation
+equations
+equilibrium
+equip
+equipage
+equipment
+equipments
+equipped
+equips
+equivalent
+era
+eradicate
+eradication
+eradicators
+erase
+erased
+eraser
+erasers
+erases
+erasing
+erasmus
+ere
+erge
+ergo
+ergonomic
+eric
+err
+errand
+errands
+errant
+erratic
+erratically
+erring
+error
+error's
+errors
+errs
+errup
+esc
+escalate
+escalated
+escalates
+escalator
+escapade
+escapades
+escape
+escaped
+escaper
+escaper's
+escapers
+escapes
+escaping
+escorted
+esmeralda
+esmeralda's
+esmerelda
+especial
+especially
+esplanade
+espn
+espn's
+espresso
+esquada
+esquago
+esquira
+esquire
+esqujillo
+esquoso
+esqutia
+essay
+essayer
+essays
+essence
+essences
+essential
+essentially
+essentials
+establish
+established
+establisher
+establisher's
+establishers
+establishes
+establishing
+establishment
+establishment's
+establishments
+estate
+estates
+esteem
+esteemed
+estenicks
+estimate
+estimated
+estimates
+estimating
+estimation
+estimations
+estimative
+etc
+eternal
+eternally
+eternity
+eternus
+ethan
+ethel
+ether
+ethereal
+ethics
+ethnic
+etiquette
+etude
+eugene
+euphoric
+eureka
+euro
+euros
+eustabia
+eustaros
+evacuate
+evacuated
+evacuation
+evade
+evaded
+evades
+evading
+eval
+evan's
+evans
+evaporate
+evaporated
+evasion
+evasive
+eve
+even
+evened
+evener
+evening
+evening's
+evenings
+evenly
+evenness
+evens
+event
+event's
+eventful
+events
+eventual
+eventually
+ever
+everest
+everest's
+everfruit
+evergreen
+evergreens
+everlasting
+everlife
+everlife's
+evertree
+every
+everybody
+everybody's
+everyday
+everyman
+everyone
+everyone's
+everyones
+everything
+everything's
+everywhere
+eves
+evict
+evicted
+eviction
+evidence
+evidenced
+evidences
+evidencing
+evident
+evidently
+evil
+evildance
+evilest
+evilly
+evilness
+evils
+evolution
+ewan
+ewan's
+eww
+ewww
+ewwww
+ewwwww
+ewwwwww
+ewwwwwww
+ewwwwwwww
+ewwwwwwwww
+ex
+exacerbate
+exact
+exacted
+exacter
+exacter's
+exacters
+exacting
+exactly
+exacts
+exaggerate
+exaggerated
+exaggeration
+exam
+examine
+examined
+examiner
+examiner's
+examiners
+examines
+examining
+example
+example's
+exampled
+examples
+exampling
+exams
+excavate
+excavation
+exceed
+exceeded
+exceedingly
+exceeds
+excel
+excellence
+excellences
+excellent
+excellently
+excels
+except
+excepted
+excepting
+exception
+exceptionally
+exceptions
+exceptive
+excepts
+excess
+excesses
+excessive
+exchange
+exchanged
+exchanger
+exchanger's
+exchangers
+exchanges
+exchanging
+excitable
+excite-o-meter
+excited
+excitedly
+excitement
+exciter
+exciter's
+exciters
+excites
+exciting
+exclaim
+exclaims
+exclamation
+exclamations
+exclude
+excluded
+excludes
+excluding
+exclusive
+exclusively
+excommunicate
+excruciating
+excursion
+excuse
+excused
+excuser
+excuser's
+excusers
+excuses
+excusing
+exe
+exec
+executive
+executor
+exempt
+exercise
+exercised
+exerciser
+exerciser's
+exercisers
+exercises
+exercising
+exert
+exhales
+exhaust
+exhausted
+exhausting
+exhibit
+exhibition
+exhibition's
+exhibitioner
+exhibitioner's
+exhibitions
+exhibitor
+exhilarating
+exile
+exile's
+exiled
+exiles
+exist
+existed
+existence
+existences
+existing
+exists
+exit
+exited
+exiting
+exits
+exodus
+exorcising
+exotic
+expand
+expanded
+expanding
+expands
+expansion
+expansions
+expect
+expectation
+expectation's
+expectations
+expected
+expecting
+expects
+expedition
+expedition's
+expeditions
+expel
+expelled
+expend
+expenditures
+expends
+expense
+expensed
+expenses
+expensing
+expensive
+expensively
+experience
+experienced
+experiences
+experiencing
+experiment
+experimental
+experimented
+experimenter
+experimenter's
+experimenters
+experimenting
+experiments
+expert
+expert's
+expertise
+expertly
+experts
+expiration
+expire
+expired
+expires
+explain
+explained
+explainer
+explainer's
+explainers
+explaining
+explains
+explanation
+explanation's
+explanations
+explanatory
+explode
+exploded
+exploder
+exploder's
+exploders
+explodes
+exploding
+exploration
+exploration's
+explorations
+explore
+explored
+explorer
+explorer's
+explorers
+explores
+exploring
+explosion
+explosion's
+explosions
+expo
+expo's
+exponential
+exponentially
+export
+exporter
+exports
+expose
+exposed
+exposing
+exposition
+exposure
+express
+expressed
+expresser
+expresser's
+expresses
+expressing
+expression
+expression's
+expressions
+expressive
+expressly
+expunge
+expunged
+ext
+extend
+extended
+extender
+extending
+extends
+extension
+extensive
+extent
+extent's
+extents
+exterior
+external
+externals
+extinct
+extinction
+extinguish
+extinguisher
+extinguishers
+extra
+extract
+extracting
+extraordinarily
+extraordinary
+extras
+extravagant
+extravaganza
+extream
+extreme
+extremed
+extremely
+extremer
+extremes
+extremest
+extricate
+exuberant
+exubia
+exuma
+exumbris
+eye
+eye's
+eyeball
+eyeballing
+eyeballs
+eyebrow
+eyebrows
+eyed
+eyeglass
+eyeglasses
+eyeing
+eyelash
+eyelashes
+eyeless
+eyelids
+eyes
+eyesight
+eyestrain
+eying
+ezra
+ezra's
+f-untangles
+fab
+faber
+fabiola
+fable
+fabric
+fabrics
+fabulous
+facade
+face
+face's
+faced
+faceing
+faceless
+faceoff
+facepalm
+facepalms
+facer
+faces
+facets
+facialhair
+facile
+facilitate
+facilities
+facility
+facin'
+facing
+facings
+fact
+fact's
+factio
+faction
+factious
+factor
+factor's
+factored
+factories
+factoring
+factorings
+factors
+factory
+facts
+factual
+faculties
+faculty
+fad
+faddy
+fade
+faded
+fader
+faders
+fades
+fading
+fads
+faffy
+fail
+failed
+failing
+failings
+fails
+failure
+failure's
+failures
+faint
+fainted
+fainter
+fainter's
+fainters
+faintest
+fainting
+faintly
+faints
+fair
+fair's
+fairbanks
+faire
+faire's
+faired
+fairer
+fairest
+fairies
+fairies'
+fairing
+fairly
+fairness
+fairs
+fairy
+fairy's
+fairycake
+fairytale
+fairytales
+fait
+faith
+faithful
+faithless
+fajitas
+faked
+faker
+faking
+falchion
+falco
+falcon
+falcons
+fall
+fallback
+fallbrook
+fallen
+faller
+falling
+fallout
+fallover
+fallow
+fallowing
+fallows
+falls
+false
+falsely
+falser
+falsest
+falsified
+fame
+famed
+famers
+fames
+familiar
+familiarize
+familiarly
+familiars
+families
+family
+family's
+famine
+faming
+famished
+famous
+famously
+fan
+fan's
+fanatic
+fanatical
+fanboy
+fancied
+fancier
+fancier's
+fanciers
+fancies
+fanciest
+fancy
+fancying
+fandom
+fane
+fanfare
+fanfiction
+fang's
+fangled
+fangs
+fanned
+fans
+fantabulous
+fantasia
+fantasmic
+fantastic
+fantasticly
+fantastico
+fantasy
+fantasy's
+fantasyland
+fantasyland's
+faq
+faqs
+far
+far-fetched
+faraway
+farce
+fare
+fared
+farer
+fares
+farewell
+farewells
+faring
+farm
+farm's
+farmed
+farmer
+farmer's
+farmers
+farming
+farmland
+farms
+farther
+farthest
+fascinate
+fascinated
+fascinates
+fascinating
+fascination
+fascists
+fashion
+fashion's
+fashionable
+fashionably
+fashioned
+fashioner
+fashioner's
+fashioners
+fashioning
+fashions
+fast
+fast-flying
+fast-pass
+fasted
+fasten
+fastens
+faster
+fasters
+fastest
+fasting
+fastpass
+fasts
+fatale
+fatales
+fatality
+fate
+fate's
+fated
+fateful
+fates
+father
+father's
+fathers
+fathom
+fatigue
+fating
+fatten
+fattening
+faucet
+fault
+faulted
+faulting
+faults
+faulty
+fauna
+fauna's
+faunas
+fauns
+fausto
+fauxhawk
+fave
+favor
+favorable
+favored
+favoring
+favorite
+favorites
+favors
+fawn
+fawn's
+fax
+faxing
+faye
+faze
+fear
+fear's
+feared
+fearer
+fearful
+fearfully
+fearhawk
+fearing
+fearles
+fearless
+fearlessly
+fearlessness
+fears
+fearsome
+feasible
+feast
+feast's
+feasted
+feaster
+feasting
+feasts
+feat
+feather
+feather's
+feather.
+feathered
+featherer
+featherer's
+featherers
+feathering
+feathers
+feathery
+feature
+featured
+features
+featurette
+featurettes
+featuring
+feb
+february
+feckless
+fed
+federal
+federation
+fedex
+feds
+feeble
+feed
+feedback
+feeder
+feeder's
+feeders
+feeding
+feedings
+feeds
+feel
+feelers
+feelin
+feelin'
+feeling
+feelings
+feels
+fees
+feet
+feints
+feisty
+felicia
+felicitation
+felicity
+felicity's
+feline
+felipe
+felix
+fell
+fella
+felled
+feller
+fellers
+felling
+felloe
+fellow
+fellows
+fellowship
+fells
+felt
+fem
+female
+females
+feminine
+femme
+femmes
+fen
+fence
+fenced
+fencer
+fencer's
+fencers
+fences
+fencing
+fend
+fender
+fenders
+fending
+feng
+feral
+ferdie
+fern
+fern's
+fern-frond
+fernando
+ferns
+ferrera
+ferrera's
+ferret
+ferret's
+ferrets
+ferris
+fertilizer
+fess
+fesses
+fest
+festering
+festival
+festival's
+festivals
+festive
+festively
+festivities
+fests
+feta
+fetch
+fetcher
+fetches
+fetching
+fetes
+fetter
+fetters
+feud
+feuding
+fever
+fever's
+fevered
+fevering
+feverish
+fevers
+few
+fewer
+fewest
+fews
+fi
+fiasco
+fib
+fibbed
+fibber
+fibbing
+fiber
+fiberglass
+fibre
+fickle
+fiction
+fiction's
+fictional
+fictions
+fid
+fid's
+fiddle
+fiddled
+fiddlehead
+fiddler's
+fiddles
+fiddlestick
+fiddling
+fide
+fidelity
+fidgety
+fids
+fie
+fief
+field
+field's
+fielded
+fielder
+fielder's
+fielders
+fielding
+fieldpiece
+fields
+fiend
+fiends
+fierce
+fiercely
+fiercer
+fiercest
+fiery
+fifi
+fifi's
+fig
+fig's
+fig-chocolate
+figaro
+figaro's
+figaros
+fight
+fight's
+fightable
+fighter
+fighter's
+fighters
+fighting
+fights
+figment
+figment's
+figments
+figs
+figurations
+figurative
+figure
+figure's
+figured
+figurehead
+figureheads
+figurer
+figurer's
+figurers
+figures
+figurine
+figurine's
+figurines
+figuring
+figurings
+file
+file's
+filed
+filename
+fileplanet
+filer
+filers
+files
+filial
+filibuster
+filing
+filings
+fill
+fill's
+filled
+filler
+fillers
+fillies
+filling
+fillings
+fillmore
+fills
+filly
+filmed
+filming
+filmmaking
+films
+filter
+filtered
+filtering
+filters
+fin
+fin's
+finagled
+final
+finale
+finale's
+finales
+finalist
+finalize
+finalized
+finally
+finals
+finance
+finances
+financial
+financially
+financing
+finch
+find
+finder
+finder's
+finders
+findin
+findin'
+finding
+finding's
+findings
+finds
+fine
+fined
+finely
+finer
+fines
+finesse
+finest
+finfish
+fingerboards
+fingernails
+fingertips
+finicky
+fining
+finis
+finish
+finished
+finisher
+finishers
+finishes
+finishing
+finishings
+fink
+finks
+finn
+finn's
+fins
+fira
+fira's
+fire
+fire's
+fire-sail
+firebrand
+firebrands
+firecrackers
+fired
+firefighter
+fireflies
+firehawks
+firehydrant
+firehydrants
+fireman
+fireman's
+firemans
+firemen
+firemen's
+fireplace
+fireplaces
+firepots
+firepower
+fireproof
+firer
+firers
+fires
+firetoon
+firewall
+firewalls
+fireweed
+firework
+firework's
+fireworks
+firey
+firing
+firings
+firms
+firmware
+first
+firsthand
+firstly
+firsts
+fiscal
+fish
+fish's
+fished
+fisher
+fisherman
+fisherman's
+fishermans
+fishers
+fishertoon
+fishertoons
+fishery
+fishes
+fisheyes
+fishier
+fishies
+fishin
+fishin'
+fishing
+fishtailed
+fishy
+fist
+fistful
+fists
+fit
+fitly
+fitness
+fits
+fitte
+fitted
+fittest
+fitting
+fitz
+fitzpatrick
+fix
+fixable
+fixated
+fixe
+fixed
+fixer
+fixer's
+fixers
+fixes
+fixin
+fixin'
+fixin's
+fixing
+fixings
+fixit
+fixture
+fizpatrick
+fizzle
+fizzled
+fizzles
+fizzling
+fizzy
+flack
+flag
+flag's
+flaged
+flagged
+flagger
+flaggers
+flagging
+flaggy
+flaging
+flagon
+flagons
+flagpole
+flagpole's
+flagpoles
+flags
+flagship
+flagships
+flagships'
+flail
+flailin
+flailing
+flails
+flair
+flak
+flakcannon
+flake
+flaked
+flakes
+flakey
+flaky
+flam
+flamboyant
+flame
+flame's
+flamed
+flamefish
+flameless
+flames
+flamethrower
+flaming
+flamingo
+flamingo's
+flamingos
+flammable
+flammables
+flank
+flanked
+flanking
+flannel
+flap
+flapjack
+flapjacks
+flappin
+flappin'
+flapping
+flappy
+flare
+flare's
+flared
+flares
+flashback
+flashbacks
+flashed
+flasher
+flashers
+flashing
+flashium
+flashlight
+flashy
+flask
+flat
+flatbed
+flatfish
+flatly
+flats
+flatten
+flattened
+flattener
+flattening
+flattens
+flatter
+flattered
+flatterer
+flattering
+flattery
+flattop
+flatts
+flaunt
+flava
+flavio
+flavio's
+flavor
+flavor's
+flavored
+flavorful
+flavoring
+flavors
+flavour
+flaw
+flawed
+flawless
+flaws
+flax
+flay
+flayin
+flaying
+flea
+flea's
+fleabag
+fleas
+fleck
+fled
+fledge
+fledged
+flee
+fleece
+fleeces
+fleed
+fleein
+fleeing
+fleem
+fleemco
+fleer
+fleet
+fleet's
+fleeting
+fleets
+fleshwound
+fletching
+fleur
+flew
+flex
+flexible
+flick
+flicker
+flickered
+flickering
+flickers
+flicking
+flicks
+flied
+flier
+fliers
+flies
+flight
+flightless
+flights
+flighty
+flim
+flimsy
+flinches
+fling
+flinging
+flint
+flint's
+flintlock
+flintlocke
+flintlocks
+flints
+flinty
+flinty's
+flip
+flipbook
+flipbook's
+flipbooks
+fliped
+flipped
+flipper
+flippers
+flippin
+flippin'
+flipping
+flippy
+flips
+flipside
+flit
+flits
+flitter
+flix
+flo
+flo's
+float
+floatation
+floated
+floater
+floater's
+floaters
+floating
+floats
+floe
+flood
+flooded
+flooder
+flooding
+floods
+floor
+floorboard
+floored
+floorer
+flooring
+floorings
+floorplan
+floors
+flop
+flopped
+flopping
+flops
+flora
+flora's
+floras
+florence
+florian
+florian's
+florist
+floss
+flossing
+flotation
+flotsam
+flotsam's
+flotsams
+flounder
+flounder's
+flounders
+flour
+flourish
+flourishes
+flours
+flout
+flouting
+flow
+flowchart
+flowed
+flower
+flower's
+flowered
+flowerer
+flowering
+flowers
+flowery
+flowing
+flown
+flows
+floyd
+flu
+flub
+flubber
+flue
+fluent
+fluently
+fluffy
+fluffy's
+flugle
+fluid
+fluids
+fluke
+fluky
+flump
+flung
+flunk
+flunked
+flunkies
+flunking
+flunky
+flurries
+flurry
+flustered
+flute
+flutes
+flutter
+flutterby
+flutterby's
+fluttering
+flutters
+fluttery
+fly
+fly's
+fly-away
+flyby
+flycatcher
+flycatchers
+flyer
+flyers
+flyinator
+flying
+flyleaf
+flyleaves
+flynn
+flynn's
+flys
+flyswatter
+flytrap
+flytraps
+foam
+foaming
+fobs
+focus
+focused
+focuser
+focuses
+focusing
+fodder
+fodders
+foe
+foes
+fog
+fog's
+foggiest
+foggy
+foghorn
+foghorns
+fogs
+foil
+foiled
+fold
+folded
+folder
+folder's
+folders
+folding
+foldings
+folds
+foley
+foliage
+folio
+folio's
+folk
+folk's
+folks
+follow
+followed
+follower
+followers
+following
+followings
+follows
+folly
+fond
+fonder
+fondness
+fondue
+fons
+font
+fonts
+food
+food's
+foodnetwork
+foods
+foodservice
+fool
+fool's
+fooled
+fooler
+fooler's
+foolers
+foolery
+foolhardiness
+foolhardy
+fooling
+foolings
+foolish
+foolishly
+foolishness
+foolproof
+fools
+foot
+foot-high
+football
+football's
+footballed
+footballer
+footballer's
+footballers
+footballs
+foote
+footed
+footer
+footer's
+footers
+foothills
+footie
+footing
+footings
+footprints
+foots
+footsies
+footsteps
+footsy
+footwork
+footy
+for
+forage
+forager
+foraging
+forbid
+forbidden
+forbids
+force
+force's
+force-field
+forced
+forcer
+forces
+forcing
+ford
+ford's
+fordoing
+fords
+fore
+forearm
+forecast
+forecastle
+forecasts
+foredeck
+forego
+forehead
+foreign
+foreigner
+foreman
+foremast
+foresail
+foresee
+foreshadow
+forest
+forest's
+forested
+forester
+foresters
+forests
+forever
+forewarn
+forewarned
+forfeit
+forfeited
+forgave
+forge
+forged
+forger
+forget
+forget-me-not
+forgetful
+forgetive
+forgets
+forgettin
+forgettin'
+forgetting
+forging
+forgive
+forgiven
+forgiveness
+forgiver
+forgives
+forgiving
+forgo
+forgot
+forgotten
+fork
+fork's
+forks
+form
+forma
+formal
+formalities
+formality
+formally
+formals
+format
+formation
+formations
+formatted
+formatting
+formed
+former
+formerly
+formers
+formidable
+forming
+forms
+formula
+forsake
+forsaken
+forsworn
+fort
+fort's
+forte
+forted
+forth
+forthcoming
+forthwith
+fortification
+forting
+fortitude
+fortnight
+fortress
+fortresses
+forts
+fortuitous
+fortunate
+fortunately
+fortunates
+fortune
+fortune's
+fortuned
+fortunes
+fortuneteller
+fortuneteller's
+fortuning
+forty
+forum
+forums
+forward
+forwarded
+forwarder
+forwarders
+forwarding
+forwardly
+forwards
+fossil
+fossils
+foster
+fought
+foughted
+foughter
+foughters
+foughting
+foughts
+foul
+fouling
+fouls
+found
+foundation
+foundation's
+foundations
+founded
+founder
+founder's
+founders
+founding
+foundlings
+foundry
+founds
+fount
+fountain
+fountain's
+fountains
+fousto's
+fowl
+fowler
+fox
+foxed
+foxes
+foxglove
+foxtail
+foxtails
+fozzie
+fps
+fps's
+fraction
+fractions
+fractured
+fracturing
+fragaba
+fragermo
+fraggue
+fragile
+fragility
+fragilles
+fragmented
+fragnoe
+fragoso
+fragrance
+fraguilla
+fraid
+frail
+frailago
+frailano
+frame
+framed
+framer
+framer's
+framerate
+framers
+frames
+framework
+framing
+fran
+franc
+francais
+francaise
+france
+frances
+francesca
+franchise
+francis
+francisco
+francisco's
+franciscos
+francois
+frank
+frank's
+frankfurters
+frankie
+frankie's
+frankies
+frankly
+franks
+franky's
+franny
+franny's
+frannys
+frantic
+frantically
+franticly
+franz
+frap
+frappe
+fraps
+fraser
+frat
+fraternal
+fraternities
+fraternity
+fraternize
+frau
+fray
+frayed
+freakier
+freakish
+freakishly
+freaks
+freaky
+freckles
+fred
+fred's
+freddie
+freddie's
+freddy
+freddy's
+fredrica
+free
+free2play
+freebooter
+freebooters
+freed
+freedom
+freedom's
+freedoms
+freefall
+freeing
+freelance
+freelancers
+freeload
+freeloader
+freeloaders
+freeloading
+freely
+freeman
+freemason
+freemasons
+freeness
+freer
+frees
+freest
+freestyle
+freeware
+freeway
+freeze
+freeze's
+freezer
+freezer's
+freezers
+freezes
+freezing
+freida
+freight
+freighter
+freights
+frenzy
+frequency
+frequent
+frequented
+frequenter
+frequenter's
+frequenters
+frequenting
+frequently
+frequents
+fresh
+freshasa
+freshen
+freshener
+freshens
+fresher
+freshers
+freshest
+freshly
+freshman
+freshmen
+freshness
+fret
+fret's
+fretless
+fretting
+freud
+frication
+friday
+friday's
+fridays
+fridge
+fried
+friend
+friend's
+friendlier
+friendly
+friends
+friends'
+friendship
+friendship's
+friendships
+frier
+fries
+friezeframe
+frigate
+frigate's
+frigates
+fright
+frighten
+frightened
+frightening
+frightens
+frightful
+frights
+frigs
+frill
+frills
+frilly
+fringe
+fringes
+frisbee
+frisbees
+frit
+frites
+fritos
+frits
+fritter
+fritters
+fritz
+frivolity
+frizz
+frizzle
+frizzles
+frizzy
+fro
+frock
+frocks
+froe
+frog
+frog's
+frogg
+frogs
+frolicking
+from
+frond
+front
+front's
+fronted
+frontier
+frontierland
+frontierland's
+fronting
+fronts
+frontwards
+froot
+frost
+frostbite
+frostbites
+frosted
+frosting
+frosts
+frosty
+frosty's
+froth
+frown
+frowned
+frowning
+froze
+frozen
+frozenly
+frozenness
+frugal
+fruit
+fruitful
+fruitless
+fruitloop
+fruitloops
+fruits
+fruity
+frump
+frustrate
+frustrated
+frustrates
+frustratin
+frustrating
+frustration
+frustrations
+fry
+fry's
+fryer
+fryin
+frying
+fubuki
+fuchsia
+fuego
+fuegos
+fuel
+fueling
+fuels
+fufalla
+fufallas
+fugitive
+fugitives
+fugitve
+fugue
+fulfill
+fulfilled
+fulfilling
+full
+full-length
+full-on
+full-trailer
+fuller
+fuller's
+fullest
+fullly
+fullscreen
+fulltime
+fully
+fulvina
+fumble
+fumbled
+fumbles
+fumbling
+fume
+fumed
+fumes
+fumigate
+fuming
+fun
+fun-filled
+fun-loving
+fun-palooza
+funcovers
+function
+function's
+functional
+functioned
+functioning
+functions
+fund
+fundamental
+fundamentally
+funded
+funder
+funders
+funding
+fundraiser
+fundraising
+funds
+funeral
+funerals
+funfest
+fungi
+fungus
+funhouse
+funkiest
+funky
+funland
+funload
+funn-ee
+funnel
+funnier
+funnies
+funniest
+funnin'
+funning
+funny
+funs
+funscape
+funstuff
+funtime
+funzone
+furball
+furious
+furiously
+furnace
+furnish
+furnished
+furnisher
+furnisher's
+furnishers
+furnishes
+furnishing
+furnishing's
+furnishings
+furniture
+furrowing
+furrows
+furter
+further
+furthered
+furtherer
+furtherest
+furthering
+furthers
+furthest
+fury
+furys
+fuse
+fused
+fuses
+fusil
+fusion
+fuss
+fussed
+fussing
+fussy
+futile
+future
+future's
+futures
+futuristic
+futz
+fuzz
+fuzzy
+fyi
+g'bye
+g'day
+g'luck
+g'night
+g'nite
+g'way
+g2g
+g2get
+g2go
+g2store
+g2take
+gab
+gabber
+gabbing
+gabble
+gabby
+gabriella
+gabriella's
+gabs
+gad
+gadget
+gadget's
+gadgets
+gaelins
+gaff
+gaffing
+gaffs
+gag
+gaga
+gage
+gaggle
+gagong
+gags
+gain
+gained
+gainer
+gainers
+gaining
+gainings
+gainly
+gains
+gainst
+gaits
+gal
+gal's
+gala
+galaacare
+galaana
+galactic
+galagris
+galagua
+galaigos
+galaira
+galajeres
+galanoe
+galaros
+galaxies
+galaxy
+galaxy's
+gale
+galeon
+gall
+gall's
+gallant
+gallants
+gallbladder
+galleon
+galleons
+galleria
+galleries
+gallery
+galley
+galleys
+gallions
+gallium
+gallon
+gallons
+galloon
+galloons
+galloping
+gallow
+gallow's
+gallows
+galls
+galoot
+galore
+galoshes
+gals
+gambit
+game
+game's
+game-face
+gamecards
+gamecrashes
+gamecube
+gamed
+gameface
+gamekeeper's
+gamekeepers
+gamely
+gamemaster
+gamemasters
+gameplan
+gameplay
+gamer
+gamerfriend
+gamergirl
+gamermaniac
+gamers
+gamertag
+gamerz
+games
+gamesboy
+gameserver
+gamesite
+gamestop
+gamestyle
+gametap
+gamin
+gaming
+gamma
+gamming
+gander
+gandolf
+gang
+gange
+gangley
+gangly
+gangplanks
+gank
+ganked
+gankers
+ganking
+gantu
+gap
+gaps
+garage
+garage's
+garaged
+garages
+garaging
+garbage
+garbahj
+garcia
+garcon
+garden
+garden's
+garden-talent
+gardened
+gardener
+gardener's
+gardeners
+gardenia
+gardening
+gardens
+gardien
+gargantuan
+garget
+gargoyle
+gargoyle's
+gargoyles
+garibay-immobilitay
+garland
+garlic
+garment
+garner
+garners
+garnet
+garrett
+garrett's
+garrison
+garry
+garry's
+garter
+garters
+gary
+gary's
+gasket
+gasoline
+gasp
+gasped
+gasps
+gaston
+gaston's
+gastons
+gate
+gate's
+gatecrash
+gatecrashers
+gated
+gatekeeper
+gates
+gateway
+gateways
+gather
+gathered
+gatherer
+gatherer's
+gatherers
+gatherin
+gatherin'
+gathering
+gatherings
+gathers
+gating
+gatling
+gator
+gator's
+gators
+gauche
+gauge
+gaunt
+gauntlet
+gauze
+gave
+gavel
+gawk
+gawrsh
+gaze
+gazebo
+gazelle
+gazillion
+gazing
+gba
+gear
+geared
+gearing
+gears
+gee
+geek
+geek's
+geeks
+geez
+geezer
+geezers
+geffory's
+gejigage
+gejigen
+gejio
+gekikro
+gekikuri
+gekimugon
+gelatin
+gelberus
+geld
+gem
+gem's
+gems
+gemstone
+gemstone's
+gemstones
+gen
+gender
+genders
+general
+general's
+generalize
+generally
+generals
+generate
+generated
+generates
+generating
+generation
+generational
+generations
+generative
+generator's
+generators
+generic
+genericly
+generous
+generously
+genes
+genetic
+genetics
+genial
+genie
+genie's
+genies
+genius
+geniuses
+genre
+genres
+gens
+genshi
+gent
+gent's
+gentle
+gentlefolk
+gentleman
+gentleman's
+gentlemanlike
+gentlemanly
+gentlemen
+gentlemen's
+gently
+gentry
+gentry's
+gents
+genuine
+genuinely
+genus
+geo
+geoffrey
+geography
+geology
+geometry
+george
+george's
+georges
+geos
+gepetto
+gepetto's
+gepettos
+geranium
+gerard
+gerbil
+germ
+german
+germs
+germy
+gertrude
+gesture
+gestures
+gesturing
+get
+get'cha
+get's
+get-cha
+getaway
+getaways
+gets
+gettable
+getter
+getting
+geyser
+geysers
+gf
+gg
+gguuiilldd
+ghastly
+ghede
+ghost
+ghost's
+ghostbusters
+ghosted
+ghostly
+ghosts
+ghostwriter
+ghosty
+ghoul
+ghouls
+ghoxt
+gi-normous
+giant
+giant's
+giants
+gibber
+gibberish
+gibbet
+gibbons
+gibbous
+gibbs
+gibe
+gibes
+gibing
+giddy
+gif
+gift
+gift's
+gifted
+gifts
+giftshop
+giftwrapped
+gig
+gigantic
+giggle
+giggled
+giggles
+gigglin
+giggling
+giggly
+gigi
+gigi's
+gigis
+gigs
+gila
+giladoga
+gilbert
+gilded
+gill
+gilled
+gills
+gimme
+gimmie
+ginger
+gingerbread
+ginkgo
+ginny
+ginty
+giorna
+giraff-o-dil
+giraffe
+giraffe's
+giraffes
+girdle
+girl
+girl's
+girl-next-door
+girlfriend
+girls
+girls'
+gist
+giuld
+giulia
+giulia's
+giulias
+giulio
+giulio's
+giulios
+give
+given
+giver
+giver's
+givers
+gives
+giveth
+giving
+gizmo
+gizmos
+gizzard
+gizzards
+gl
+glace
+glacia
+glad
+glade
+gladiator
+gladly
+gladness
+glam
+glamorous
+glance
+glanced
+glances
+glancing
+glare
+glared
+glares
+glaring
+glass
+glass's
+glassed
+glasses
+glasses'
+glasswater
+glaze
+glazed
+glazing
+gleam
+gleaming
+glee
+glen
+glenstorm
+glider
+glimmer
+glimmering
+glimpse
+glint
+glints
+glitch
+glittering
+glittery
+glitzy
+gloat
+gloating
+glob
+global
+globals
+globe
+globe's
+globes
+glogg
+gloom
+gloomy
+gloria
+glorified
+gloriosa
+glorious
+gloriously
+glory
+gloss
+glossy
+glove
+gloved
+glover
+glover's
+glovers
+gloves
+glovey
+gloving
+glow
+glowed
+glower
+glowie
+glowies
+glowing
+glows
+glowworm
+glowworms
+glozelle
+glozelle's
+glozelles
+glubglub
+glucose
+glue
+glued
+glues
+gluey
+glug
+glugging
+gluing
+glum
+glumness
+gluten
+glutton
+gm
+gm's
+gman
+gmta
+gnarly
+gnasher
+gnat
+gnats
+gnawing
+gnaws
+gnight
+gnite
+gnome
+gnomes
+gnu
+gnus
+go
+go-getter
+go2g
+goal
+goal's
+goalie
+goals
+goat
+goat's
+goatee
+goats
+gob
+goblet
+goblin
+goblins
+gobs
+goby
+goddaughter
+goddess
+goddesses
+godfather
+gods
+godson
+goes
+goggle
+goggles
+goin
+goin'
+going
+goings
+gokazoa
+gold
+golden
+goldenly
+goldenrod
+goldenseal
+goldfarmers
+goldfarming
+golding
+goldmine
+golds
+golem
+golf
+golfed
+golfing
+golfs
+goliath
+goliaths
+golly
+gona
+gone
+goner
+goners
+gong
+gonna
+gonzalo
+gonzo
+goob
+goober
+goobers
+good
+good-day
+good-hearted
+goodbye
+goodbye's
+goodbyes
+goodfellas
+goodie
+goodies
+goodly
+goodness
+goodnight
+goodnights
+goodprice
+goods
+goodwill
+goof
+goofball
+goofballs
+goofed
+goofing
+goofy
+goofy's
+goofywrench
+goon
+goon's
+goonies
+goons
+goonsquad
+goop
+goose
+gooseberry
+goosebumps
+gooseburger
+goosed
+gooses
+goosing
+gopher
+gopher's
+gophers
+gordo
+gordo's
+gordon
+gorge
+gorge's
+gorgeous
+gorges
+gorgon
+gorgong
+gorilla
+gorilla's
+gorillas
+gorp
+gosh
+goslin
+gospel
+gospels
+gossip
+gossiping
+gossips
+got
+got'm
+gotcha
+gotes
+goth
+gothic
+gotta
+gotten
+gouge
+gouged
+gound
+gourd
+gourds
+gourmet
+gourmets
+gourtmet's
+gout
+gov
+gov'na
+gov'ner
+gov'ners
+govern
+governator
+governess
+government
+government's
+governmental
+governments
+governor
+governor's
+governors
+governs
+goway
+gown
+gowns
+grab
+grabbed
+grabber
+grabbin'
+grabbing
+grabble
+grabbles
+grabbling
+grabeel
+grabing
+grabs
+grace
+grace's
+graced
+graceful
+gracefully
+graces
+graces'
+gracias
+gracie
+gracie's
+gracing
+gracious
+graciously
+grad
+grade
+graded
+grader
+graders
+grades
+gradient
+grading
+grads
+gradual
+gradually
+graduate
+graduated
+graduation
+grafts
+graham
+grahams
+grail
+grain
+grains
+grammar
+grammars
+grammas
+grammatical
+grammy
+grammys
+grampa
+grampy
+grams
+grand
+grandbabies
+grandbaby
+grandchildren
+granddaughter
+grander
+grandest
+grandfather
+grandfather's
+grandfathers
+grandiose
+grandkid
+grandkids
+grandly
+grandma
+grandma's
+grandmas
+grandmother
+grandmothers
+grandpa
+grandpappy
+grandparent
+grandparents
+grandpas
+grandpop
+grands
+grandson
+grannies
+granny
+granola
+grant
+grant's
+granted
+granter
+granting
+grants
+grapefruits
+grapeshot
+graphic
+graphics
+graphics..
+graphs
+grapple
+grappled
+grappler
+grapplers
+grapples
+grappling
+grasps
+grass
+grass-weaving
+grasses
+grasshopper
+grasshopper's
+grasshoppers
+grassy
+grate
+grated
+grateful
+gratefully
+gratefulness
+grater
+graters
+grates
+gratifying
+gratin
+gratis
+gratitude
+grats
+gratz
+gravel
+gravely
+graven
+gravepeople
+graveryard
+graves
+graves'
+gravest
+graveyard
+graveyards
+gravitate
+gravity
+gravy
+gray
+grayed
+grayish
+grays
+graze
+grazed
+grazing
+grease
+greased
+greaser
+greases
+greasier
+greasy
+great
+greaten
+greater
+greatest
+greatly
+greatness
+greats
+greave
+greed
+greediest
+greedy
+greek
+green
+greener
+greenery
+greenethumb
+greenhorns
+greenhouse
+greenie
+greening
+greenish
+greenly
+greer
+greet
+greeted
+greeter
+greeter's
+greeting
+greetings
+greets
+greg
+gregoire
+gregoire's
+gregoires
+gremlin
+gremlins
+grew
+grey
+greybeard
+greyhound
+greyhounds
+grid
+griddle
+grief
+grief's
+griefs
+grieve
+grieved
+griever
+griever's
+grievers
+grieves
+grieving
+grievous
+griffin
+grilda
+grilden
+grildragos
+grill
+grilled
+grilling
+grills
+grim
+grimsditch
+grimy
+grin
+grinch
+grinded
+grinder
+grinders
+grining
+grinned
+grinning
+grins
+grintley
+grip
+gripe
+griper
+gripes
+griping
+gripper
+gripping
+grips
+grisly
+gristly
+grit
+grits
+grizzle
+grizzly
+grizzly's
+groceries
+grocery
+grocery's
+grog
+grog's
+grogginess
+groggy
+groggybeards
+grogs
+grommet
+gronos
+groom
+groove
+grooved
+groover
+grooves
+grooving
+groovy
+gross
+grossed
+grosser
+grosses
+grossest
+grossing
+grossly
+grossness
+grotesque
+grotesquely
+grotto
+grouch
+grouchy
+ground
+ground-up
+grounded
+grounder
+grounder's
+grounders
+groundhog
+grounding
+grounds
+groundskeeper
+group
+grouped
+grouper
+groupie
+groupies
+grouping
+groupleader
+groups
+grousing
+grouting
+grove
+groves
+grow
+grower
+growin
+growin'
+growing
+growl
+growl-licous
+growling
+growls
+grown
+grown-up
+grownups
+grows
+growth
+grr
+grrr
+grrrrrrrl
+grub
+grubb
+grubbing
+grubby
+grubs
+grudge
+grudger
+grudges
+gruel
+grueling
+gruesome
+gruff
+gruffly
+grumble
+grumbles
+grumbling
+grummet
+grumpy
+grumpy's
+grundy
+grunge
+grungy
+grunt
+grunt's
+gruntbusters
+grunter
+grunting
+grunts
+grunts'
+gryphons
+gtg
+guacamole
+guano
+guarantee
+guaranteed
+guarantees
+guarantees'
+guard
+guard's
+guarded
+guarder
+guardian
+guardians
+guarding
+guards
+guards'
+guardsman
+guardsmen
+guess
+guessed
+guesser
+guessers
+guesses
+guessin
+guessin'
+guessing
+guesstimate
+guest
+guest's
+guestbook
+guested
+guesting
+guests
+guff
+guffaw
+gui
+guide
+guide's
+guided
+guideline
+guidelines
+guidemap
+guidemap's
+guider
+guides
+guiding
+guil
+guila
+guild
+guild1
+guild14
+guilded
+guilders
+guildhall
+guildhouse
+guildie
+guildies
+guilding
+guildleader
+guildless
+guildmasta
+guildmaster
+guildmaster's
+guildmasters
+guildmate
+guildmates
+guildmateys
+guildmeister
+guildmember
+guildmembers
+guildname
+guildpirates
+guilds
+guildship
+guildships
+guildsmen
+guildtag
+guildtalk
+guildwars
+guildwise
+guildy
+guildy's
+guildys
+guile
+guilld
+guilt
+guilty
+guinea
+guines
+guise
+guised
+guitar
+guitar's
+guitarist
+guitars
+gulf
+gulf's
+gulfs
+gull
+gullet
+gullible
+gulls
+gullwings
+gully
+gulp
+gulped
+gulps
+gum
+gum's
+gumball
+gumballs
+gumbo
+gumby
+gummer
+gummy
+gumption
+gums
+gunk
+gunman
+gunmasters
+gunmates
+gunn
+gunnies
+gunny
+gunny's
+guns
+guns'
+gunship
+gunshow
+gunsights
+gunskill
+gunskills
+gunsmith
+gunsmith's
+gunsmithes
+gunsmithing
+gunsmiths
+gunwale
+guppy
+gurth
+guru
+gus
+gus's
+gush
+gusher
+gushing
+gushy
+gussied
+gust
+gusteau
+gusteau's
+gusto
+gusts
+gusty
+gut
+guts
+gutsy
+gutted
+gutter
+gutterat
+gutters
+gutting
+guy
+guy's
+guyago
+guyona
+guyoso
+guyros
+guys
+guytia
+gwa
+gwarsh
+gwinn
+gwinn's
+gym
+gym's
+gymnasium
+gymnastic
+gymnastics
+gyms
+gypsies
+gypsy
+gypsy's
+gyro
+gyro's
+gyros
+h-pos
+h.g.
+h2o
+ha
+habbo
+habbo's
+habbos
+habit
+habit's
+habitat
+habitat's
+habitats
+habited
+habits
+habitual
+hacky
+had
+hade
+hades
+hades'
+hadn't
+haft
+hah
+haha
+hail
+hailed
+hailing
+hails
+hair
+hair's
+hairball
+hairballs
+hairband
+hairbrush
+hairbrushes
+hairclip
+haircut
+haircut's
+haircuts
+hairdo
+hairdresser
+hairdryer
+haired
+hairs
+hairspray
+hairstyle
+hairstyle's
+hairstyles
+hairy
+hakaba
+hake
+hakuna
+hal
+halau
+hale
+hales
+haley
+haley's
+haleys
+half
+half-o-dil
+halftime
+halfway
+halfwits
+hali
+halibut
+haling
+hall
+hall's
+hallelujah
+haller
+hallmark
+hallo
+halloo
+hallow
+halloween
+halloween's
+hallows
+halls
+hallway
+hallways
+halo
+halos
+halt
+halting
+halva
+halve
+halves
+ham
+hambrrrgers
+hamburger
+hamburgers
+hamburglar
+hamilton
+hamlet
+hammer
+hammerhead
+hammerhead's
+hammerheads
+hammering
+hammers
+hammock
+hammocks
+hammy
+hams
+hamster
+hamster's
+hamsterball
+hamsters
+hand
+hand's
+handbag
+handbags
+handball
+handcraft
+handed
+handedly
+handel
+hander
+handers
+handful
+handheld
+handicapped
+handier
+handing
+handkerchief
+handkerchiefs
+handle
+handlebar
+handled
+handler
+handler's
+handlers
+handles
+handling
+handout
+handpicked
+handrails
+hands
+handshake
+handshakes
+handsome
+handsomely
+handwriting
+handy
+hanebakuon
+hanegaku
+haneoto
+hang-loose
+hangnail
+hangout
+hank
+hanker
+hankering
+hankie
+hankie's
+hankies
+hanks
+hanky
+hanna
+hannah
+hannah's
+hannahs
+hans
+hans'
+hanukkah
+hap
+hap'n
+hapacha
+hapaxion
+hapazoa
+happen
+happened
+happening
+happenings
+happens
+happier
+happiest
+happily
+happiness
+happy
+happy-go-lucky
+haps
+hara
+harassed
+harassment
+harbinger
+harbingers
+harbor
+harbor's
+harbored
+harbormaster
+harbormasters
+harbors
+hard
+hardball
+hardcode
+hardest
+hardies
+hardly
+hardness
+hardships
+hardware
+hardwire
+hardwood
+hardy
+hare
+hared
+hares
+hark
+harlequin
+harlets
+harm
+harm's
+harmed
+harmful
+harming
+harmless
+harmonicas
+harmonies
+harmonious
+harmony
+harms
+harness
+harp
+harper
+harper's
+harping
+harpoon
+harpooning
+harpoons
+harps
+harr
+harried
+harriet
+harrow
+harrows
+harsh
+harsher
+harshly
+hart
+harumi
+harumite
+harumitey
+harv
+harv's
+harvest
+harvested
+harvesting
+harvests
+harvs
+has
+hashanah
+hasher
+hasn't
+hasnt
+hassaba
+hassagua
+hassano
+hassigos
+hassilles
+hassle
+hassled
+hassling
+hassros
+hast
+haste
+hasten
+hastily
+hasty
+hat's
+hatch
+hatched
+hatches
+hatchet
+hatchets
+hath
+hating
+hatred
+hats
+hatter
+haul
+hauled
+hauling
+hauls
+haunt
+haunt's
+haunted
+haunter
+haunting
+haunting's
+hauntings
+haunts
+haut
+havarti
+have
+haven
+haven's
+haven't
+havendish
+havendish's
+havens
+havent
+haver
+havers
+haves
+havin'
+having
+havoc
+haw
+hawk
+hawk's
+hawkeyes
+hawkling
+hawks
+hawkster
+hawkweed
+haws
+hawthorne
+haxby
+hay
+haydn
+hayes
+haymaker
+hayseed
+haystack
+haystacks
+haywire
+hazard
+hazardous
+hazel
+hazelnut
+hazes
+hazy
+hazzard
+he
+he'd
+he'll
+he's
+head
+headache
+headaches
+headband
+headboards
+headbutt
+headdress
+headed
+header
+headgear
+headhunter
+headhunters
+heading
+heading's
+headings
+headless
+headlight
+headlights
+headlock
+headpiece
+headpieces
+headquarter
+headquarters
+heads
+headset
+headsets
+headshot
+headstone
+headstone's
+headstones
+headstrong
+headway
+heal
+healed
+healer
+healers
+healing
+healings
+heals
+health
+health's
+healthier
+healthiest
+healthly
+healthy
+heap
+heaps
+hear
+heard
+hearer
+hearers
+hearest
+hearing
+hearings
+hears
+hearsay
+heart
+heart's
+heart-shaped
+heartache
+heartbeat
+heartbreak
+heartbreakers
+heartbreaking
+heartbreaks
+heartbroken
+hearted
+heartedly
+hearten
+heartens
+heartfelt
+hearth
+hearth's
+hearties
+heartily
+heartiness
+heartless
+hearts
+heartstea
+heartthrob
+heartwrecker
+hearty
+hearty's
+heartys
+heat
+heat-get
+heated
+heater
+heater's
+heaters
+heath
+heathen
+heathens
+heather
+heating
+heats
+heave
+heaven
+heaven's
+heavenly
+heavens
+heaver
+heavier
+heavies
+heaviest
+heavily
+heavy
+heavyset
+heavyweight
+heckle
+hectic
+hector
+hector's
+hectors
+hedge
+hedgehog
+hedgehog's
+hedgehogs
+hedley
+hedly
+hedy
+hee
+heed
+heeded
+heel
+heeled
+heeler
+heeling
+heels
+heffalump
+heffalump's
+heffalumps
+heft
+hefts
+hefty
+heh
+hehe
+heidi
+height
+heights
+heikyung
+heinous
+heirloom
+heirs
+heist
+heisting
+heists
+held
+helen
+helicopter
+helicopters
+helios
+hello
+hellos
+helm
+helm's
+helmed
+helmet
+helmets
+helming
+helms
+helmsman
+helmsmen
+helmswoman
+help
+helpdesk
+helped
+helper
+helper's
+helpers
+helpful
+helpfully
+helping
+helpings
+helpless
+helps
+hem
+hemi
+hemisphere
+hempen
+hems
+hen
+hen's
+hence
+henchmen
+hendry
+henhouse
+henry
+henrys
+hens
+her
+her's
+herbert
+herbie
+herbie's
+herbies
+herbs
+hercules
+hercules'
+herd
+herded
+herder
+herders
+herding
+herds
+here
+here's
+hereby
+herein
+heres
+heresy
+heretic
+hereunto
+heritage
+hermit
+hermit's
+hermits
+hernia
+hero
+hero's
+heroes
+heroic
+heroism
+heron
+herons
+heros
+herring
+herrings
+hers
+herself
+hershey
+hersheys
+hes
+hesitant
+hesitate
+hesitating
+hesitation
+hest
+hewent
+hey
+heya
+heywood
+hi
+hibernate
+hibernating
+hibernation
+hibiscus
+hic
+hiccup
+hiccups
+hick
+hickory
+hickorytip
+hicks
+hid
+hidden
+hide
+hide-and-seek
+hideaway
+hideaways
+hided
+hideous
+hideously
+hideout
+hideouts
+hides
+hiding
+hierarchy
+higglyball
+higglytown
+high
+high-energy
+high-flying
+high-impact
+high-octane
+high-powered
+high-seas
+high-speed
+high-strung
+high-voltage
+higher
+highest
+highest-rated
+highjack
+highlander
+highlands
+highlight
+highlighted
+highlighting
+highlights
+highly
+highness
+highs
+highsea
+highseas
+hightail
+hightailed
+highway
+hihi
+hiiii-yaaaaa
+hike
+hiker
+hiking
+hilarious
+hilarity
+hill
+hill's
+hilled
+hiller
+hilling
+hills
+hillside
+hilltop
+hilly
+hilt
+him
+hims
+himself
+himuro
+hindered
+hindering
+hinds
+hindsight
+hinges
+hint
+hinted
+hinter
+hinterseas
+hinting
+hints
+hip
+hip-hop
+hip-hoppin'
+hippie
+hippies
+hippies'
+hippo
+hippo's
+hippos
+hippy
+hipster
+hire
+hired
+hirer
+hires
+hiring
+his
+hissed
+hisses
+hissing
+hissy
+histoire
+historian
+historical
+historically
+histories
+history
+history's
+hit's
+hitch
+hitched
+hitchhike
+hitchhiked
+hitchhiker
+hitchhikers
+hitchhiking
+hitching
+hither
+hits
+hitter
+hitters
+hitting
+hive
+hive's
+hives
+hiya
+hkdl
+hmm
+hmmm
+hms
+hoard
+hoarder
+hoarding
+hoards
+hoax
+hob
+hobbies
+hobbit
+hobbits
+hobbles
+hobbling
+hobby
+hoboken
+hock
+hocked
+hockey
+hocks
+hocus
+hocus-focus
+hodgepodge
+hofflord
+hog
+hogg
+hogge
+hogged
+hogging
+hogglestock
+hogs
+hogwash
+hoi
+hoist
+hoisting
+hola
+hold
+hold'em
+hold's
+holdem
+holden
+holder
+holders
+holdin'
+holding
+holdings
+holdout
+holds
+holey
+holidas
+holiday
+holiday's
+holidayer
+holidays
+holl
+holler
+hollered
+hollering
+hollow
+hollow's
+hollowed-out
+hollows
+holly
+holly's
+hollywood
+hollywoods
+holms
+holo
+hologram
+holograms
+holt
+holy
+homage
+hombre
+hombres
+home
+home's
+home-grown
+home-made
+homebound
+homeboy
+homecoming
+homed
+homedog
+homegirl
+homeland
+homeless
+homely
+homemade
+homeopathic
+homepage
+homeport
+homer
+homer's
+homers
+homes
+homespun
+homestead
+hometown
+homework
+homeworker
+homeworker's
+homeworkers
+homey
+homeys
+homier
+homies
+homing
+hon
+honcho
+honda
+hone
+honed
+hones
+honest
+honestly
+honesty
+honey
+honeybunch
+honeycomb
+honeydew
+honeys
+honeysuckle
+honeysuckles
+honing
+honk
+honor
+honorable
+honorary
+honored
+honoring
+honors
+hood
+hood's
+hoods
+hoof
+hook
+hook's
+hooked
+hooks
+hooligan
+hooligans
+hoop
+hoops
+hoopster
+hoopsters
+hooray
+hoot
+hootenanny
+hooting
+hoots
+hop
+hope
+hoped
+hopeful
+hopeful's
+hopefully
+hoper
+hopes
+hoping
+hopped
+hopper
+hopping
+hoppy
+hops
+hopscotch
+horatio
+horatio's
+horatios
+horde
+hordes
+horison
+horizon
+horizon's
+horizons
+horizontal
+horn
+hornbow
+hornet
+hornet's
+hornets
+horns
+hornswoggle
+horoscope
+horrendous
+horrible
+horribly
+horrid
+horridness
+horrific
+horrified
+horrifying
+horror
+horror's
+horrors
+horse
+horse's
+horseman
+horsepower
+horses
+horseshoe
+horseshoe's
+horseshoes
+horseshow
+horsey
+horsing
+hose
+hosed
+hoses
+hosiery
+hospital
+hospitality
+hospitalize
+hospitals
+host
+host's
+hosted
+hostile
+hostiles
+hostility
+hosting
+hosts
+hot
+hot-tempered
+hotel
+hotel's
+hotels
+hothead
+hotkey
+hotkeys
+hotshot
+hotspot
+hotspots
+hotter
+hottest
+hound
+hounded
+hounding
+hounds
+hour's
+hourglass
+hourly
+house
+house's
+housebroken
+housecleaning
+housed
+houseful
+household
+housekeeper
+housemates
+houseplant
+houser
+houses
+housewife
+housewives
+housework
+housing
+housings
+hovel
+hover
+hovercraft
+hovered
+hovering
+hovers
+how
+how'd
+how're
+how's
+how've
+how-to
+how-to-video
+how2
+howard
+howdy
+however
+howl
+howled
+howling
+hows
+hp
+hp's
+hq
+hqofficerf
+hqofficerm
+hr
+hr.
+hrage
+hsm
+hsm-2's
+hsm2
+hsm3
+hub
+hub's
+hubbies
+hubby
+hubby's
+hubs
+huddle
+huddled
+hudgen's
+hudgens
+hudson
+hudson's
+hudsons
+hue
+hug
+hug's
+huge
+hugely
+huger
+hugers
+hugest
+hugged
+hugh
+hugo
+hugs
+huh
+hula
+hula's
+hulas
+hulk
+hulking
+hull
+hull's
+hullabaloo
+hulled
+hullo
+hulls
+human
+human's
+humane
+humanities
+humanity
+humankind
+humans
+humble
+humbled
+humbly
+humbuckers
+humbug
+humdinger
+humid
+humidia
+humidia's
+humiliating
+humility
+hummingbird
+hummingbird's
+hummingbirds
+hummus
+humor
+humor's
+humored
+humoring
+humorous
+humors
+hums
+hunch
+hunchback
+hunger
+hungering
+hungrier
+hungriest
+hungry
+hunny
+hunt
+hunter
+hunter's
+hunters
+hunting
+huntress
+hunts
+huntsclan
+huntsgirl
+huntsman
+huntsman's
+hurdle
+hurl
+hurled
+hurls
+hurrah
+hurray
+hurricane
+hurricanes
+hurried
+hurrier
+hurries
+hurry
+hurrying
+hurt
+hurter
+hurtful
+hurting
+hurts
+husband
+husband's
+husbands
+hush
+hushes
+husk
+huskers
+huskies
+husky
+hustle
+hustling
+hut
+hutch
+huts
+huzza
+huzzah
+hw
+hxdq
+hyacinth
+hybrid
+hydra
+hydrant
+hydrants
+hydrated
+hydro-hopper
+hydrofoil
+hyena
+hyena's
+hyenas
+hygiene
+hymn
+hymns
+hyoga
+hype
+hyped
+hyper
+hyper-drive
+hyperactive
+hyperforce
+hypersensitive
+hyperspace
+hypno
+hypno-goggles
+hypnotic
+hypnotized
+hypocrite
+hypothetical
+hypothetically
+hysterical
+hysterically
+i
+i'd
+i'll
+i'm
+i've
+i.m.
+i.w.g.
+iago
+iago's
+iagos
+iam
+iambic
+ibuprofen
+ice
+ice's
+iceberg
+icebergs
+iceburg
+icecold
+icecream
+iced
+iceman
+icerage
+ices
+iceshark
+icestockings
+ichabod
+ichabod's
+ichabods
+icicle
+icicles
+icing
+icing's
+icings
+icky
+icon
+icons
+icy
+id
+ida
+idea
+idea's
+ideal
+ideally
+ideals
+ideas
+identical
+identify
+identifying
+identity
+ides
+idk
+idol
+idol's
+idolizing
+idols
+ids
+if
+ifalla
+iger
+igloo
+igloos
+ignatius
+ignatius'
+ignite
+ignorance
+ignorant
+ignore
+ignored
+ignorer
+ignores
+ignoring
+igo
+igo's
+iguana
+ii
+iid
+ik
+ile
+ill
+illegal
+illegally
+illiterate
+illness
+illnesses
+ills
+illuminati
+illumination
+illusion
+illusive
+illustrate
+illustrated
+illustrates
+illustrating
+illustration
+illustrations
+illustrative
+illustrious
+im
+image
+imaged
+images
+imaginable
+imaginary
+imagination
+imagination's
+imaginations
+imaginative
+imaginatively
+imagine
+imagined
+imagineer
+imagineers
+imaginer
+imagines
+imaging
+imagining
+imaginings
+imam
+imbedded
+imho
+imitate
+imitated
+imitating
+imitation
+immature
+immediate
+immediately
+immense
+immensely
+immigrant
+immigrate
+immigrated
+imminent
+immortal
+immune
+immunity
+imo
+imp
+impact
+impacted
+impacter
+impacting
+impactive
+impacts
+impaired
+impartial
+impatient
+impending
+imperal
+imperfect
+imperial
+impersonal
+impersonate
+impersonating
+impersonation
+impervious
+implement
+implemented
+implementing
+implication
+implications
+implicit
+implied
+implies
+imploringly
+imply
+implying
+impolite
+import
+importance
+important
+importantly
+imported
+importer
+importer's
+importers
+importing
+imports
+impose
+imposed
+imposer
+imposes
+imposing
+impossibility
+impossible
+impossibles
+impossibly
+imposter
+impound
+impounded
+impractical
+impress
+impressed
+impresses
+impressing
+impression
+impression's
+impressions
+impressive
+imprison
+imprisoned
+imprisonment
+improbably
+impromptu
+improper
+improprieties
+improve
+improved
+improvement
+improvements
+improver
+improves
+improving
+improvise
+imps
+impudent
+impulse
+impulsive
+impunity
+in
+inability
+inaccuracies
+inaccurate
+inaction
+inactive
+inadequate
+inadequately
+inadvertently
+inane
+inappropriate
+inappropriately
+inboards
+inbound
+inbox
+inc
+inc.
+incapable
+incapacitated
+incapacitating
+incarnate
+incarnation
+incase
+incased
+incautious
+incentive
+incessantly
+inch
+inches
+inching
+incident
+incident's
+incidentally
+incidents
+incisor
+incite
+incited
+incline
+inclined
+include
+included
+includes
+including
+inclusive
+incognito
+incoherent
+income
+incoming
+incomings
+incompatible
+incompetency
+incompetent
+incomplete
+incompletes
+inconsiderate
+inconsideration
+inconsistent
+inconspicuous
+inconspicuously
+inconvenience
+inconveniencing
+inconvenient
+incorporated
+incorporeal
+incorrect
+incorrectly
+increase
+increased
+increases
+increasing
+incredible
+incredible's
+incredibles
+incredibly
+increments
+incriminate
+incriminating
+indebt
+indecisive
+indeed
+indeedy
+independence
+independent
+independently
+indestructible
+index
+indexed
+indexer
+indexers
+indexes
+indexing
+india
+indian
+indians
+indicate
+indicated
+indicates
+indicating
+indication
+indications
+indicative
+indicator
+indices
+indie
+indies
+indifference
+indifferent
+indifferently
+indigenous
+indigestion
+indigo
+indigos
+indirect
+indirectly
+indiscriminately
+indisposed
+individual
+individual's
+individuality
+individually
+individuals
+indo
+indoctrinations
+indomitable
+indoor
+indoors
+indubitab
+induced
+inducted
+inducting
+indulge
+industrial
+industrial-sized
+industrially
+industrials
+industries
+industry
+industry's
+inedible
+ineffective
+inefficient
+inept
+inertia
+inevitable
+inexpensive
+inexperience
+inexperienced
+inexplicably
+infancy
+infant
+infantile
+infantry
+infants
+infected
+infecting
+infection
+infections
+infectious
+infer
+inferior
+inferiority
+infernal
+inferno
+inferno's
+infernos
+inferring
+infest
+infestation
+infested
+infidels
+infiltrate
+infiltrated
+infinite
+infinities
+infinity
+infirmary
+inflame
+inflammation
+inflatable
+inflate
+inflated
+inflict
+inflicted
+inflicting
+infliction
+inflicts
+influence
+influenced
+influencer
+influences
+influencing
+influx
+info
+inform
+informal
+informants
+information
+information's
+informations
+informative
+informed
+informer
+informer's
+informers
+informing
+informs
+infrastructure
+infringe
+infuriating
+ingenious
+ingles
+ingot
+ingots
+ingrate
+ingredient
+ingredients
+inhabit
+inhabitated
+inhabited
+inhalation
+inhere
+inherit
+inialate
+init
+initial
+initialization
+initialized
+initially
+initials
+initiate
+initiated
+initiates
+initiating
+initiation
+initiations
+initiative
+injure
+injured
+injures
+injuries
+injuring
+injury
+injustices
+ink
+ink's
+ink-making
+ink-making-talent
+inkaflare
+inkana
+inkanapa
+inked
+inker
+inkers
+inking
+inkings
+inks
+inkwell
+inkwells
+inky
+inland
+inlanders
+inlet
+inline
+inmates
+inn
+innate
+inner
+innerly
+innkeeper
+innocence
+innocent
+innocently
+innocents
+innovative
+innovention
+innovention's
+innoventions
+inns
+innuendo
+innuendoes
+inoperable
+inpatient
+input
+input's
+inputed
+inputer
+inputing
+inputs
+inputting
+inquest
+inquire
+inquiries
+inquiring
+inquiry
+inquisitively
+ins
+insane
+insanely
+insanity
+insatiable
+insect
+insects
+insecure
+insecurities
+insecurity
+insensitive
+insert
+inserted
+inserting
+inserts
+inset
+inside
+inside's
+insider
+insider's
+insiders
+insides
+insight
+insightful
+insignia
+insignificant
+insinuate
+insinuating
+insinuation
+insist
+insisted
+insisting
+insists
+insolence
+insomnia
+insomniac
+insomniatic
+inspect
+inspected
+inspecting
+inspection
+inspections
+inspector
+inspects
+inspiration
+inspire
+inspired
+inspires
+inspiring
+inst
+inst'
+insta-grow
+instable
+install
+installation
+installed
+installer
+installing
+installment
+installs
+instance
+instanced
+instances
+instancing
+instant
+instantly
+instead
+instep
+instigate
+instigator
+instill
+instilling
+instinct
+instinctively
+instincts
+institution
+instruct
+instructed
+instruction
+instruction's
+instructions
+instructor
+instructors
+instrument
+instrument's
+instrumented
+instrumenting
+instruments
+insubordinate
+insubordinates
+insubordination
+insufferable
+insufficient
+insulates
+insulating
+insulation
+insult
+insult's
+insulted
+insulter
+insulting
+insults
+insurance
+insure
+insured
+intact
+integrate
+integrated
+integrity
+intellectual
+intellectualizing
+intelligence
+intelligent
+intend
+intended
+intender
+intending
+intends
+intense
+intensified
+intension
+intensions
+intensity
+intensive
+intent
+intention
+intention's
+intentional
+intentionally
+intentioned
+intentions
+intently
+intents
+inter
+interact
+interacting
+interaction
+interactive
+interactively
+intercept
+intercepted
+intercepten
+intercepting
+interception
+interceptive
+interceptor
+interchange
+intercom
+interconnection
+interest
+interest's
+interested
+interesting
+interestingly
+interests
+interface
+interface's
+interfaced
+interfacer
+interfaces
+interfacing
+interfere
+interference
+interferes
+interfering
+interim
+interior
+interior's
+interiorly
+interiors
+interject
+interjections
+interlopers
+intermediaries
+intermediate
+interminable
+intermission
+intermittent
+intermittently
+intern
+internal
+international
+interned
+internet
+internet's
+internets
+internship
+interpretation
+interprets
+interrogate
+interrupt
+interrupt's
+interrupted
+interrupter
+interrupters
+interrupting
+interruption
+interruptions
+interruptive
+interrupts
+intersect
+interstate
+intervals
+intervene
+intervened
+intervention
+interview
+interviewing
+interviews
+interwebz
+intimately
+intimidate
+intimidated
+intimidating
+into
+intolerant
+intranet
+intrepid
+intrigues
+intriguing
+intro
+intro's
+introduce
+introduced
+introducer
+introduces
+introducing
+introduction
+introduction's
+introductions
+introductory
+intros
+intrude
+intruder
+intruders
+intrudes
+intruding
+intrusion
+intuition
+intuitive
+inundated
+inutile
+inv
+invade
+invaded
+invader
+invaders
+invading
+invalid
+invaluable
+invasion
+invasions
+invasive
+invent
+invented
+inventing
+invention
+inventions
+inventive
+inventories
+inventory
+invents
+inverse
+invert
+inverted
+invest
+invested
+investigate
+investigated
+investigates
+investigating
+investigation
+investigations
+investigative
+investigator
+investigators
+investing
+investment
+investments
+invigorating
+invincibility
+invincible
+invincibles
+invisibility
+invisible
+invisibles
+invisibly
+invitation
+invitation's
+invitations
+invite
+invited
+invitee
+inviter
+invites
+inviting
+invoice
+invoices
+involuntarily
+involve
+involved
+involver
+involves
+involving
+invulnerability
+invulnerable
+iow
+ipod
+ipods
+iridessa
+iridessa's
+iris
+iris'
+irk
+irked
+irking
+irks
+irksome
+iron
+ironclad
+ironed
+ironhoof
+ironic
+ironically
+ironing
+ironman
+irons
+ironsides
+ironskull's
+ironwall
+ironwalls
+irrational
+irregular
+irrelevant
+irresistible
+irritable
+irritant
+irritate
+irritated
+irritates
+irritatin'
+irritating
+irritation
+is
+isabel
+isabella
+isabella's
+isabellas
+isadora
+isadore
+isaiah
+isla
+island
+island's
+islander
+islanders
+islands
+isle
+isle's
+isles
+isn't
+isnt
+isolate
+isolated
+isolating
+isometric
+isp
+issue
+issued
+issuer
+issuers
+issues
+issuing
+istilla
+it
+it'll
+it's
+italics
+italy
+itched
+itchie
+itching
+itchy
+item
+items
+its
+itself
+ivanna
+ive
+ivona
+ivor
+ivory
+ivy
+ivy's
+ivys
+ix
+izzy
+izzy's
+izzys
+j.k.
+ja
+jab
+jack
+jack's
+jackals
+jacket
+jacket's
+jackets
+jackfruit
+jackie
+jackie's
+jackies
+jackolantern
+jackolantern's
+jackolanterns
+jackpot
+jackpot's
+jackpots
+jacks
+jackson
+jackson's
+jacques
+jade
+jaded
+jado
+jafar
+jafar's
+jafars
+jagged
+jaguar
+jail
+jails
+jake
+jake's
+jakes
+jalex
+jam
+jamada
+jamago
+jamble
+james
+james'
+jamie
+jamigos
+jamilles
+jammania
+jammer
+jammin
+jammin'
+jammin's
+jammy
+jamoso
+jams
+jan
+jane
+jane's
+janes
+janet
+janice
+january
+january's
+januarys
+jar
+jar's
+jargon
+jars
+jasmine
+jasmine's
+jasmines
+jason
+jason's
+jasons
+jaw
+jaw's
+jaw-dropper
+jawed
+jaws
+jazz
+jazzed
+jazzy
+jb
+jbs
+jealous
+jealously
+jealousy
+jean
+jean's
+jeanie
+jeanne
+jeans
+jed
+jedi
+jedi's
+jedis
+jeena
+jeffrey
+jehan
+jellies
+jelly
+jellybean
+jellybean's
+jellybeans
+jellyfish
+jellyfish's
+jenny
+jeopardy
+jeremiah
+jeremy
+jerome
+jerry
+jerry's
+jerrys
+jersey
+jess
+jessamine
+jesse
+jesse's
+jesses
+jest
+jester
+jester's
+jesters
+jests
+jet
+jet's
+jetix
+jetixtreme
+jetpack
+jets
+jetsam
+jetsam's
+jetsams
+jett
+jett's
+jetts
+jeune
+jewel's
+jeweled
+jeweler
+jewelers
+jewelry
+jig
+jigsaw
+jill
+jim
+jima
+jimmie
+jimmyleg
+jingle
+jinglebells
+jingles
+jingly
+jinks
+jitterbug
+jittery
+jive
+jive_turkies
+jivin'
+jk
+joan
+jocard
+joe
+joe's
+joes
+john
+johnny
+johnny's
+johnnys
+johns
+johnson
+johnson's
+johnsons
+johny
+join
+joined
+joiner
+joiners
+joining
+joins
+jojo
+jojo's
+jojos
+joke
+joke's
+joked
+joker
+joker's
+jokers
+jokes
+jokey
+joking
+jolly
+jolly's
+jollyroger
+jollyroger's
+jollyrogers
+jolt
+jona's
+jonas
+jonathan
+jones
+jones'
+jones's
+jordan
+jos
+joseph
+joshamee
+joshuas
+josie
+journal
+journes
+journey
+journeyed
+journeying
+journeyings
+joy
+joy's
+joyful
+joyous
+joys
+jpn
+jps
+jr
+jr.
+juan
+jubilee
+judge
+judge's
+judged
+judger
+judges
+judging
+judy
+judy's
+judys
+juels
+juggernaut
+juggernauts
+juggler
+juggles
+juggling
+juiced
+juju
+jukebox
+jul
+julep
+julie
+juliet
+julius
+july
+july's
+julys
+jumba
+jumba's
+jumble
+jumbles
+jumbo
+jump
+jump's
+jumped
+jumper
+jumpers
+jumpin
+jumping
+jumps
+jumpy
+jun
+june
+june's
+juneau
+junebug
+junes
+jung
+jungle
+jungle's
+jungled
+jungles
+junior
+junior's
+juniors
+juniper
+junk
+jupiter
+jupiter's
+jupiters
+jury
+just
+just-waking-up
+juster
+justice
+justin
+justin's
+justing
+justins
+justly
+juvenile
+ka-boom
+ka-ching
+kabob
+kaboomery
+kagero
+kai
+kaken
+kamakuri
+kanga
+kanga's
+kangaroo
+kangas
+kapahala
+karakuri
+karaoke
+karat
+karate
+karbay
+karen
+karin
+karma
+karnival
+karo
+karo's
+kart
+karts
+kasumi
+kasumire
+kasumite
+kat
+kate
+kate's
+kathy
+katz
+kay
+kazaam
+kazoo
+kazoology
+kdf
+keelgrin
+keely
+keely's
+keelys
+keen
+keep
+keeper
+keeper's
+keepers
+keepin'
+keeping
+keeps
+keepsake
+keepsake's
+keepsakes
+keira
+keira's
+keiras
+keke
+kellogg
+kellogg's
+kelloggs
+kelly
+kelly's
+kellys
+kelp
+kelp-jelly
+kelsi
+kelsi's
+kelsis
+ken
+kenai
+kenai's
+kenais
+kennel
+kenny
+kenny's
+kennys
+kent
+kept
+kerchak
+kerchak's
+kerchaks
+kermie
+kermit
+kes
+ketchup
+ketobasu
+kettle
+kettles
+kevin
+kevin's
+kevins
+kewl
+key
+key's
+keyboard
+keyboard's
+keyboarding
+keyboards
+keyhole-design
+keys
+keystone
+keyword
+khaki
+khaki's
+khakis
+khamsin
+kibatekka
+kick
+kickball
+kicked
+kicker
+kickers
+kickflip
+kickin'
+kicking
+kicks
+kid
+kid's
+kidd
+kiddie
+kidding
+kids
+kidstuff
+kiely
+kiely's
+kielys
+kiki
+kiki's
+kikis
+kim
+kim's
+kimchi
+kimmunicator
+kims
+kind
+kinda
+kindergarten
+kindergarten's
+kindergartens
+kindest
+kindly
+kindness
+kinds
+king
+king's
+king-sized
+kingdom
+kingdom's
+kingdoms
+kingfisher
+kingfishers
+kingly
+kingman
+kingman's
+kings
+kingshead
+kiosk
+kiosk's
+kiosks
+kipp
+kippur
+kirby
+kirke
+kit
+kitchen
+kitchen's
+kitchener
+kitchens
+kite
+kites
+kitten
+kitten's
+kittens
+kitty
+kiwi
+kk
+klebba
+klutz
+klutzy
+knap
+knave
+kneed
+kneel
+knew
+knghts
+knight
+knightley
+knightley's
+knights
+knitting
+knock
+knockdown
+knocked
+knocking
+knockoff
+knocks
+knoll
+knots
+knotty
+know
+know-it-alls
+knower
+knowing
+knowledge
+knowledgeable
+knowledges
+known
+knows
+knowzone
+knuckle
+knucklehead
+knuckles
+koala
+koala's
+koalas
+kobi
+kobra
+koda
+koda's
+kodas
+kodiac
+kokeshi
+koko
+kokoago
+kokoros
+koleniko
+kollin
+komadoros
+komainu
+komanoto
+kong
+kong's
+kongs
+kooky
+kool
+korogeki
+koroko
+korozama
+kouki
+kp
+kp's
+kraken
+kraken's
+krakens
+krawl
+kreepers
+krew
+krewe
+krispies
+krissy
+krogager
+kronk
+kronk's
+krunklehorn
+krux
+krybots
+kubaku
+kuganon
+kugaster
+kumonn
+kung
+kuzco
+kwanzaa
+kyle
+kyle's
+kyles
+kyra
+kyto
+kyto's
+la
+lab
+label
+labels
+labor
+labyrinth
+lace
+lack
+lackadaisical
+lacked
+lacker
+lacking
+lacks
+lacrosse
+lad
+ladder
+ladder's
+ladders
+ladies
+ladies'
+ladle
+ladles
+lady
+lady's
+ladybug
+ladybug's
+ladybugs
+ladys
+laff
+laff-o-dil
+laffer
+laffs
+lag
+laggin
+lagging
+lagoon
+lagoon's
+lagoons
+laid-back
+laidel
+lake
+lake's
+laker
+lakes
+laking
+lala
+lalala
+lamanai
+lamb
+lambda
+lamberginias
+lamed
+lamely
+lamer
+lames
+lamest
+laming
+lamp
+lamp's
+lamper
+lamps
+lanai
+lance
+lance's
+lances
+land
+landed
+lander
+landers
+landing
+landings
+landlubber
+landlubbers
+landmark
+lands
+landscape
+lane
+lanes
+language
+language's
+languages
+lantern
+lantern's
+lanterns
+lanyard
+lanyard's
+lanyards
+laptop
+large
+lark
+larkspur
+larp
+larrup
+larry
+lars
+laser
+lashes
+lass
+lass'
+lassard
+lassie
+lassie's
+lassies
+lasso
+last
+lasted
+laster
+lasting
+lastly
+lasts
+late
+lated
+lately
+later
+lateral
+latered
+laters
+lates
+latest
+latia
+latin
+latrine
+laugh
+laughable
+laughed
+laugher
+laugher's
+laughers
+laughfest
+laughin'
+laughing
+laughs
+laughter
+laughters
+launch
+launched
+launcher
+launchers
+launches
+launching
+launchings
+launchpad
+laundry
+laurel
+laurel-leaf
+lauren
+lava
+lavendar
+lavender
+lavish
+law
+law's
+lawbot
+lawbot's
+lawbots
+lawful
+lawless
+lawn
+lawn's
+lawns
+lawrence
+laws
+layer
+layered
+layers
+laying
+laziness
+lazy
+lbhq
+le
+lead
+leaded
+leaden
+leader
+leader's
+leaderboard
+leaderboard's
+leaderboards
+leaders
+leadership
+leading
+leadings
+leads
+leaf's
+leaf-boat
+leaf-stack
+leafboarding
+leafed
+leafing
+leafkerchief
+leafkerchiefs
+leafs
+leafy
+league
+leagued
+leaguer
+leaguers
+leagues
+leaguing
+leaks
+lean
+leaned
+leaner
+leanest
+leaning
+leanings
+leanly
+leans
+leap
+leapfrog
+leaping
+learn
+learned
+learner
+learner's
+learners
+learning
+learnings
+learns
+least
+leatherneck
+leave
+leaved
+leaver
+leavers
+leaves
+leavin
+leaving
+leavings
+ledge
+lee
+leed
+leeta
+leeward
+left
+left-click
+left-clicking
+leftover
+lefts
+lefty
+legaba
+legaja
+legal
+legalese
+legano
+legassa
+legen
+legend
+legend's
+legendary
+legends
+leghorn
+legion
+legondary
+leibovitz
+leibovitz's
+leif
+leigons
+leisure
+leisurely
+lemme
+lemon
+lemonade
+lemons
+lemony
+lempago
+lempona
+lempos
+len
+lend
+lender
+lenders
+lending
+lends
+lengendary
+length
+lengthen
+lengths
+lenny
+lenora
+lentil
+leo
+leo's
+leon
+leons
+leopard
+leopards
+leopuba
+leota
+leota's
+leotas
+leozar
+leroy
+lerping
+les
+less
+lessen
+lessens
+lesser
+lesses
+lessing
+lesson
+lessoners
+lessons
+lest
+let
+let's
+lethargy
+lets
+letter
+letterhead
+lettering
+letterman
+letters
+lettin'
+letting
+lettuce
+level
+leveling
+levelly
+levels
+levelup
+leviathan
+levica
+levy
+lewis
+lewis'
+lex's
+li
+liar
+liar's
+liars
+libby
+liberated
+liberties
+liberty
+liberty's
+librarian
+libraries
+library
+library's
+license
+lichen
+lichens
+lid
+lie
+lied
+lies
+lieutenant
+lieutenant's
+lieutenants
+life
+life's
+lifeguard
+lifeguard's
+lifeguards
+lifejacket
+lifelong
+lifer
+lifers
+lifes
+lifestyle
+lift
+lifted
+lifter
+lifters
+lifting
+lifts
+light
+light's
+light-green
+light-t
+light-talent
+light-talents
+light-up
+lightbeams
+lightcycle
+lightcycles
+lighted
+lighten
+lightening
+lightens
+lighter
+lighters
+lightest
+lightfinders
+lighthouse
+lighthouse's
+lighthouses
+lighting
+lightly
+lightning
+lights
+lightspeed
+lightwater
+lightyear
+lightyear's
+like
+likeable
+liked
+likelier
+likeliest
+likelihood
+likely
+likes
+likest
+liki
+liking
+likings
+lil
+lil'fairy
+lila
+lilac
+lilies
+lillipop
+lilly
+lilo
+lilo's
+lily
+lily's
+lily-of-the-valley
+lily-pad
+lilypad
+lilys
+lima
+lime
+limes
+limit
+limited
+limiter
+limiters
+limiting
+limitly
+limitness
+limits
+lincoln
+lincoln's
+lincolns
+linda
+linden
+line
+line's
+lined
+linen
+linens
+liner
+liner's
+liners
+lines
+linguini
+linguini's
+linguinis
+lining
+linings
+lion
+lion's
+lione
+lions
+lip
+lipsky
+lipstick
+lipsticks
+liquidate
+liri
+lisa
+lisel
+list
+listed
+listen
+listened
+listener
+listener's
+listeners
+listening
+listens
+lister
+listers
+listing
+listings
+listners
+lists
+lit
+literal
+literally
+literature
+little
+littler
+littlest
+live
+live-action
+lived
+lively
+livens
+liver
+liver's
+livered
+livers
+lives
+livest
+liveth
+living
+livingly
+livings
+livingston
+livingston's
+livingstons
+liz
+liza
+lizard
+lizard's
+lizards
+lizzie
+lizzie's
+lizzy
+llama
+llama's
+llamas
+lloyd
+lloyd's
+lloyds
+load
+loaded
+loader
+loaders
+loading
+loadings
+loafers
+loan
+loaned
+loaner
+loaning
+loans
+loather
+lobbies
+lobby
+lobe
+lobster
+lobsters
+local
+localized
+locally
+lock
+lockbox
+lockboxes
+locked
+locker
+lockers
+locket
+locking
+lockings
+lockjaw's
+lockpick
+locks
+lockspinner's
+loco-motion
+lodge
+lodge's
+lodges
+lofty
+log
+log's
+logged
+loggers
+logging
+logical
+logout
+logs
+lol
+lola
+lola's
+lollipop
+lolo
+lolo's
+lolos
+lone
+lonelier
+loneliest
+loneliness
+lonely
+lonepirates
+loner
+loner's
+loners
+long
+longed
+longer
+longest
+longing
+longings
+longjohn
+longly
+longs
+longskirt
+look
+looked
+looker
+looker's
+lookers
+lookin
+lookin'
+looking
+lookout
+lookouts
+looks
+looksee
+lool
+loom
+loon
+loony
+loool
+looool
+looooong
+loop
+loop.
+loops
+loopy
+lord
+lord's
+lords
+lordz
+lore
+lorella
+lorenzo
+lori
+los
+lose
+losing
+loss
+loss's
+losses
+lost
+lot
+lot's
+lots
+lotsa
+lotus
+lou
+loud
+louder
+loudest
+loudly
+louie
+louie's
+louies
+louis
+louis'
+lounge
+lounged
+lounger
+lounges
+lounging
+lousy
+lovable
+love
+love's
+loved
+lovel
+lovelier
+lovelies
+loveliest
+loveliness
+lovely
+loves
+loveseat
+low
+lowbrow
+lowdown
+lower
+lowered
+lowers
+lowest
+lowing
+lowly
+lows
+loyal
+loyalty
+lt.
+ltns
+luau
+luau's
+luaus
+luc
+luc's
+lucas
+lucas'
+lucia
+luciano
+lucille
+lucinda
+luck
+lucked
+lucks
+lucky
+lucky's
+luckys
+lucs
+lucy
+lucy's
+lucys
+luff
+lug-nut
+luge
+luggage
+luigi
+luigi's
+luke
+lulla-squeak
+lullaby
+lulu
+lumber
+lumen
+lumens
+lumiere
+lumiere's
+luminous
+luna
+lunar
+lunatics
+lunch
+lunched
+luncher
+lunches
+lunching
+lunge
+lunge-n-plunge
+lupine
+lure
+lured
+lures
+lute
+lutes
+luther
+luther's
+luthers
+luxe
+luxury
+lv
+lv8
+lvl
+lye
+lying
+lympia
+lynn
+lyre
+lyric
+lyrical
+lyrics
+ma
+mac
+mac's
+macaroons
+machine
+machine's
+machined
+machines
+machining
+macho
+mack's
+mackerel
+macks
+macmalley's
+macomo
+macro
+mad
+madcap
+maddie
+maddie's
+maddies
+made
+madge
+madison
+madison's
+madisons
+madly
+madness
+madrigal
+mads
+maelstrom
+maelstrom's
+maelstroms
+magazine
+magazine's
+magazines
+magenta
+maggie
+maggie's
+maggies
+magic
+magical
+magically
+magicians
+magna
+magnet
+magnet's
+magnets
+magnificent
+magnolia
+magoo
+magoo's
+magpie
+mahalo
+mahogany
+maiara
+maiara's
+maiaras
+maid
+mail
+mailbox
+mailboxes
+main
+mainland
+mainly
+mains
+maintain
+maintained
+maintainer
+maintainers
+maintaining
+maintains
+maja
+majestic
+majesty
+major
+major's
+majored
+majoring
+majorities
+majority
+majority's
+majors
+makadoros
+makanoto
+makanui
+make
+make-a-pirate
+make-a-wish
+make-up
+makeovers
+maker
+makers
+makes
+making
+maladies
+male
+maleficent
+maleficent's
+malevolo
+malik
+malina
+malina's
+malinas
+mall
+mallet
+malley
+malt
+mama
+mama's
+mamba
+mambas
+mammoth
+man
+man's
+man-o-war
+man-o-wars
+mana
+manage
+managed
+management
+manager
+manager's
+managers
+manages
+managing
+manatees
+mancala
+mandolin
+mandolins
+mandy
+mane
+maneuver
+maneuverable
+maneuvered
+maneuvering
+maneuvers
+mango
+mania
+maniac
+manicuranda
+manner
+mannered
+mannerly
+manners
+manny
+manny's
+mannys
+mans
+mansion
+mansion's
+mansions
+mantle
+mantrador
+mantradora
+mantrados
+manu's
+manual
+manuals
+many
+map
+map's
+maple
+mapleseed
+mapped
+mapping
+maps
+mar
+mara
+marathon
+marathon's
+marathons
+marble
+marble's
+marbled
+marbler
+marbles
+marbling
+marc
+march
+marches
+marching
+mardi
+margaret
+marge
+margo
+maria
+marigold
+marigolds
+marine
+mariner
+mariner's
+mariners
+mariners'
+marines
+mario
+mario's
+marios
+mark
+mark's
+marked
+marker
+market
+market's
+marketed
+marketer
+marketing
+marketings
+marketplace
+markets
+marking
+marks
+marksman
+marksmen
+marlin
+marlin's
+marlins
+maroni
+maroon
+marooned
+marooner's
+marooning
+maroons
+marque
+marrow-mongers
+mars
+marsh
+marshall
+marshall's
+marshmallow
+mart
+martha
+martin
+martin's
+martinaba
+martinez
+martins
+marty
+marty's
+maruaders
+marvelous
+marvelously
+mary
+mary's
+marzi
+mascara
+mascot
+maserobo
+masetosu
+masetto
+mash
+mashed
+mask
+mass
+massey
+massive
+mast
+master
+master's
+mastered
+mastering
+masterings
+masterly
+masterpiece
+masters
+mastery
+mat
+matata
+match
+match-up
+matched
+matcher
+matchers
+matches
+matching
+matchings
+matchmaker
+mate
+mater
+mater's
+material
+material's
+materialistic
+materialize
+materially
+materials
+mates
+matey
+mateys
+math
+maties
+matilda
+matilda's
+matriarch
+matt's
+matter
+mattered
+matterhorn
+matterhorn's
+mattering
+matters
+matthew
+maurader's
+mauraders
+max
+maxed
+maximum
+maximus
+maxing
+maxxed
+may
+mayada
+mayano
+maybe
+mayday
+mayhem
+mayigos
+mayo
+mayola
+maze
+mazers
+mazes
+mc
+mccartney
+mccartney's
+mccraken
+mcduck
+mcduck's
+mcfury's
+mcghee
+mcgreeny
+mcguire
+mcguire's
+mcintosh
+mckee
+mcmuggin
+mcp
+mcqueen
+mcqueen's
+mcreary-timereary
+mcreedy
+mcshoe
+me
+me-self
+meadow
+meadows
+meal
+meal's
+meals
+mean
+meander
+meaner
+meanest
+meanie
+meanies
+meaning
+meaning's
+meanings
+meanly
+meanness
+means
+meant
+meantime
+meanwhile
+measure
+measured
+measurer
+measures
+measuring
+mechanism
+mechano-duster
+med
+medal
+medal's
+medallion
+medals
+meddle
+media
+media's
+medias
+medical
+medically
+medicine
+medicines
+meditate
+medium
+medium's
+mediums
+medley
+medly
+meena
+meena's
+meenas
+meep
+meg
+mega
+mega-cool
+mega-rad
+mega-rific
+megahot
+megamagic
+megamix
+megaphone
+megaphones
+megaplay
+megashare
+megawatch
+megazord
+meghan
+meh
+meido
+mel
+melanie
+melee
+melekalikimaka
+mello
+mellow
+mellowed
+mellower
+mellowing
+mellows
+melodic
+melody
+melodyland
+meltdown
+melted
+melting
+melville
+melville's
+member
+member's
+membered
+members
+membership
+membership's
+memberships
+memo
+memorial
+memorial's
+memorials
+memories
+memory
+memory's
+memos
+men
+men's
+menagerie
+menagerie's
+menageries
+mending
+menorah
+menorah's
+menorahs
+menswear
+mental
+mentally
+mention
+mentioned
+mentioner
+mentioners
+mentioning
+mentions
+mentius
+menu
+menu's
+menus
+meow
+merc
+mercantile
+mercantile's
+mercedes
+mercenaries
+mercenary
+mercenarys
+merchandise
+merchant
+merchant's
+merchants
+merchantsrevenge
+merci
+merciless
+mercs
+mercury
+mercury's
+mercy
+merigold
+merigolds
+merik
+merit
+merits
+mermaid
+mermaid's
+mermaids
+mermain
+mermish
+merry
+merrychristmas
+merryweather's
+merryweathers
+mership
+mertle
+mertle's
+mertles
+mesa
+mesabone
+mesathorn
+mesmerizing
+mess
+message
+message's
+messaged
+messages
+messaging
+messed
+messenger
+messes
+messing
+messy
+met
+metal
+metal's
+metals
+meteor
+meter
+meters
+method
+method's
+methodical
+methods
+metra
+metroville
+mettle
+mew
+mezzo
+mgm
+mgm's
+mgr
+mic
+mice
+michael
+michael's
+michaels
+michalka
+michalka's
+michalkas
+mickes
+mickey
+mickey's
+mickeys
+micromanager
+micromanagers
+microphone
+microphone's
+microphones
+middle
+middled
+middler
+middles
+middling
+middlings
+midnight
+midsummer
+midwaymarauders
+mies
+might
+mights
+mighty
+migrator
+mike
+mike's
+mikes
+mikey
+mikey's
+milan
+mild
+milden
+milder
+mildest
+mildly
+mile
+mile's
+miler
+miles
+miley
+miley's
+milian
+milian's
+military
+militia
+milk
+milks
+milkweed
+mill
+mill's
+miller
+millie
+millie's
+million
+million's
+millions
+mills
+milo
+milo's
+milos
+mim
+mim's
+mimes
+mimetoon
+mimic
+mimic's
+mimics
+mims
+min
+min.
+mincemeat
+mind
+mind-blowing
+minded
+minder
+minder's
+minders
+minding
+minds
+mindy
+mine
+mine-train
+mined
+miner
+miner's
+mineral
+minerals
+miners
+minerva
+mines
+ming
+ming's
+mingler
+minglers
+mings
+mini
+miniature
+minigame
+minigames
+minigolf
+minimum
+mining
+mining-talent
+minion
+minion's
+minions
+minipumpkins
+mink
+mink's
+minks
+minnie
+minnie's
+minnies
+minnow
+minny
+minny's
+minnys
+minor
+minotaur
+minotaur's
+minotaurs
+mint
+mint's
+mints
+minty
+minute
+minuted
+minutely
+minuter
+minutes
+minutest
+minuting
+miracle
+miracles
+miranda
+miranda's
+mirandas
+miraz
+miraz's
+mirazs
+mire
+mires
+mirror
+mirror's
+mirrors
+mischief
+mischievous
+miserable
+misery
+misfit
+mishmash
+mislead
+miss
+missed
+misses
+missing
+mission
+mission's
+missioned
+missioner
+missioning
+missions
+missive
+missives
+mist
+mist's
+mistake
+mistaker
+mistakes
+mistaking
+mistimed
+mistletoe
+mistpirates
+mistreated
+mistrustful
+mists
+misty
+misty's
+mistys
+mithos
+mitten
+mittens
+mix
+mix'n
+mix'n'match
+mixed
+mixer
+mixer's
+mixers
+mixes
+mixing
+mixmaster
+mixolydian
+mixture
+mixup
+mizzen
+mizzenmast
+mm
+mmelodyland
+mmg
+mml
+mmo
+mmorpg
+moat
+moat's
+moats
+mobilize
+moccasin
+moccasin's
+moccasins
+mocha
+mocha's
+mochas
+mochi
+mock
+mockingbird
+mockingbird's
+mockingbirds
+mod
+mode
+moded
+model
+model's
+models
+moderate
+moderated
+moderately
+moderates
+moderating
+moderation
+moderations
+moderator
+moderator's
+moderators
+modern
+modernly
+moderns
+modes
+modest
+module
+modules
+moe
+mogul
+mohawk
+moi
+moises
+mojo
+mola
+molar
+molasses
+mold
+moldy
+mole
+molecule
+molecules
+molloy
+molly
+molted
+molten
+mom
+moment
+moment's
+momently
+momentous
+moments
+momifier
+monada
+monarch
+monarchs
+monatia
+monday
+monday's
+mondays
+money
+money's
+monger
+mongers
+mongrel
+mongrels
+mongrols
+monies
+monique
+monique's
+moniques
+monk
+monk's
+monkes
+monkey
+monkey's
+monkeying
+monkeys
+monkies
+monks
+monocle
+monocles
+monorail
+monorail's
+monorails
+monos
+monroe
+monroe's
+monroes
+monster
+monster's
+monsters
+monstro
+monstro's
+monstropolis
+monstrous
+month
+months
+monument
+monumental
+moo
+mood
+mood's
+moods
+moon
+moon's
+moonbeam's
+mooning
+moonlight
+moonlighted
+moonlighter
+moonlighting
+moonlights
+moonliner
+moonlit
+moonraker
+moons
+moonwort
+mop
+mopp
+moptop
+moral
+morale
+morally-sound
+moray
+more
+morgan
+morgan's
+morgans
+morning
+morning's
+mornings
+morningstar
+morrigan
+morris
+morsel
+mortar
+mortimer
+mortimer's
+moseby
+moseby's
+mosona
+mosreau
+moss
+mossari
+mossarito
+mossax
+mossman
+mossy
+most
+mosters
+mostly
+moth
+mother's
+mother-of-pearl
+moths
+motion
+motioned
+motioner
+motioning
+motions
+motivator
+motley
+moto
+motocrossed
+motor
+motor's
+motored
+motoring
+motors
+motto
+moulding
+mound
+mountain
+mountain's
+mountains
+mountaintop
+mouse
+mouse's
+mousekadoer
+mousekadoer's
+mousekadoers
+mousekespotter
+mousekespotter's
+mousekespotters
+mouseover
+mouser
+mouses
+mousing
+moussaka
+move
+moved
+movement
+movement's
+movements
+mover
+mover's
+movers
+moves
+movie
+movie's
+moviemaker
+moviemaker's
+moviemakers
+movies
+movin'
+moving
+movingly
+movings
+mower
+mowers
+mowgli
+mowgli's
+mowglis
+moyers
+mr
+mr.
+mrs
+mrs.
+msg
+mt
+mtn
+mtr
+muaba
+muahaha
+much
+mucho
+muck
+mucks
+mucky
+mud
+mud-talents
+muddle
+muddy
+mudhands
+mudmoss
+mudpie
+muerte
+mufasa
+mufasa's
+mufasas
+mugon
+muharram
+muigos
+mukluk
+mulan
+mulan's
+mulans
+mulberry
+muldoon
+muldoon's
+mullet
+multi
+multi-barreled
+multi-colored
+multi-player
+multi-sweetwrap
+multi-wrap
+multichoice
+multiplane
+multiplayer
+multiple
+multiplex
+mum
+mum's
+mumble
+mumbleface
+mumbo
+mummies
+mummy
+mummy's
+munk
+muppet
+muppets
+muppets'
+muriel
+murky
+murrieta-animata
+musageki
+musakabu
+musarite
+musckets
+muscled
+muse
+museum
+museum's
+museums
+mush
+mushu
+mushu's
+mushus
+mushy
+music
+music's
+musica
+musical
+musical2
+musical3
+musically
+musicals
+musician
+musicians
+musics
+musket
+musketeer
+musketeer's
+musketeers
+muskets
+muslin
+mussel
+must
+mustache
+mustaches
+mutiny
+mvp
+my
+myrna
+myself
+myst
+myst-a-find
+mysteries
+mysterious
+mysteriously
+mystery
+mystery's
+mystic
+mystical
+mystik
+myth
+n-nw
+n.e.
+na
+naa_ve
+naaa
+nachos
+nada
+naggy
+nagu
+naguryu
+naguzoro
+nah
+nail
+nails
+naive
+naketas
+name
+name's
+named
+names
+naming
+nan
+nana
+nanairo
+nancy
+nani
+nano
+nanos
+nap
+nap's
+napkin
+napkins
+napmasters
+naps
+narnia
+narnia's
+narnias
+narrow
+narrowed
+narrower
+narrowest
+narrowing
+narrowly
+narrows
+nascar
+nascar's
+nascars
+nat
+nate
+nathaniel
+nation
+national
+native
+natives
+natural
+naturally
+naturals
+nature
+nature's
+natured
+natures
+nautical
+nautilus
+navago
+navermo
+navies
+navigate
+navigation
+navigator
+navigator's
+navigators
+navona
+navy
+navy's
+nay
+nave
+near
+nearby
+neared
+nearer
+nearest
+nearing
+nearly
+nears
+neat
+necessaries
+necessarily
+necessary
+necessities
+necktie
+neckties
+neckvein
+nectar
+nectarine
+ned
+ned's
+need
+needed
+needer
+needin'
+needing
+needle-bristle
+needless
+needly
+needs
+negotiate
+negotiated
+negotiates
+negotiating
+negotiation
+negotiations
+neigh
+neighbor
+neighbor's
+neighborhood
+neighborhoods
+neighbors
+neil
+neither
+nell
+nelly
+nelson
+nemesis
+nemo
+nemo's
+nemos
+neptoon's
+neptune
+neptune's
+nerve
+nerve's
+nerved
+nerves
+nerving
+nervous
+nessa
+nest
+nestor
+nestor's
+nestors
+net
+nettie
+nettle
+network
+network's
+networked
+networking
+networks
+neutral
+never
+never-before-seen
+never-ending
+neverland
+new
+new-ager
+newer
+newest
+newfound
+newly
+newport
+news
+newsletter
+newsletter's
+newsletters
+newsman
+newspaper
+newspaper's
+newspapers
+newt
+newt's
+newts
+next
+nibs
+nicada
+nice
+nicely
+nicer
+nicest
+nick
+nick's
+nickel
+nickname
+nicknamed
+nicks
+nicos
+nifty
+night
+night's
+nightbreed
+nighted
+nighters
+nightfall
+nightgown
+nightingale
+nightingale's
+nightingales
+nightkillers
+nightlife
+nightly
+nightmare
+nightmare's
+nightmares
+nights
+nightshade
+nightstalkers
+nightstand
+nighttime
+nikabrik
+nill
+nilsa
+nimue
+nimue's
+nimues
+nina
+nina's
+ninja
+ninja's
+ninjas
+nintendo
+ninth
+nissa
+nite
+nite's
+nitelight
+nites
+no
+no-fire
+no-fly
+no-nonsense
+noah
+noble
+nobodies
+nobody
+nobody's
+noctus
+nod
+nod's
+nods
+noel
+noel's
+noels
+noggin
+noggin'
+noggin's
+noho
+noir
+noise
+noised
+noisemakers
+noises
+noising
+noisy
+nokogilla
+nokogiro
+nokoko
+nomes
+nominated
+non-bat-oriented
+nona
+nonchalant
+none
+nones
+nonsense
+nonstop
+noobs
+noodle
+noodle's
+noodles
+noogy
+nook
+nope
+nor
+nor'easter
+nora
+nordic
+normal
+normally
+normals
+norman
+north
+north's
+norther
+northern
+northerner
+northerner's
+northerners
+northernly
+northers
+northing
+nose
+nosed
+noses
+nostril
+nostrils
+not
+notable
+notations
+notch
+note
+notebook
+noted
+notepad
+notepads
+noter
+notes
+noteworthy
+nothin
+nothing
+nothings
+notice
+noticed
+notices
+noticing
+notified
+noting
+notion
+notions
+notiriety
+notoriety
+notorious
+notre
+nov
+nova's
+novel
+novelty
+november
+november's
+novembers
+novemeber
+novice
+novice's
+novices
+now
+nowhere
+nowheres
+nows
+nox
+noxious
+np
+npc
+npcnames
+npcs
+nterceptor
+nugget
+numb
+numbers
+nurse
+nursery
+nurses
+nursing
+nutmeg
+nutrition
+nutronium
+nuts
+nutshell
+nvitation
+nvm
+nw
+nyra
+o'clock
+o'eight
+o'henry
+o's
+o'toole
+o-torch
+o.o
+o_o
+oak
+oak's
+oaks
+oar
+oar's
+oared
+oaring
+oars
+oasis
+oath
+oban
+obay
+obedience
+obeys
+obj
+object
+object's
+objected
+objecting
+objective
+objects
+obscure
+obsequious
+observation
+observation's
+observations
+observe
+observed
+observer
+observer's
+observers
+observes
+observing
+obsidian
+obsidians
+obstacle
+obstacle's
+obstacles
+obtain
+obtained
+obtainer
+obtainer's
+obtainers
+obtaining
+obtains
+obvious
+obviously
+occasion
+occasioned
+occasioning
+occasionings
+occasions
+occur
+occurred
+occurs
+ocean
+ocean's
+oceana
+oceanic
+oceanliner
+oceanliners
+oceans
+ocelot
+oct
+octavia
+october
+october's
+octobers
+octopus
+octopus'
+odd
+odder
+oddest
+oddly
+odds
+of
+off
+off-the-chain
+off-the-hook
+off-the-wall
+offbeat
+offend
+offended
+offender
+offender's
+offenders
+offending
+offends
+offer
+offer's
+offered
+offerer
+offerers
+offering
+offerings
+offers
+office
+office's
+officer
+officers
+offices
+official
+official's
+officially
+officials
+offing
+offkey
+offline
+offrill
+offs
+often
+oftener
+ogre
+ogre's
+ogres
+oh
+ohana
+oi
+oic
+oik
+oil
+oiled
+oiler
+oiler's
+oilers
+oiling
+oils
+oin
+oink
+ojidono
+ojimaru
+ok
+okas
+okay
+okay's
+oken
+okie
+old
+old-fashioned
+older
+oldest
+oldman
+ole
+olive
+oliver
+oliver's
+olivia
+olivier
+ollallaberry
+ollie
+ollo
+olympic
+olympics
+omalley
+omar
+ombres
+omen
+omens
+omg
+omibug
+omigosh
+omniscient
+on
+one-liner
+ongoing
+onion
+online
+only
+onscreen
+onstage
+onto
+onyx
+ood
+oodles
+oof
+oogie
+oogie's
+ooh
+oola
+oola's
+oomph
+ooo
+oops
+opal
+open
+opened
+opener
+openers
+openest
+opening
+openings
+openly
+openness
+opens
+opera
+opera's
+operas
+operate
+operated
+operates
+operating
+operation
+operations
+operative
+operator
+operator's
+operators
+opinion
+opinion's
+opinions
+opponent
+opponent's
+opponents
+opportunities
+opportunity
+opportunity's
+oppose
+opposed
+opposer
+opposes
+opposing
+opposite
+oppositely
+opposites
+opposition
+oppositions
+ops
+optics
+optimal
+optimistic
+option
+option's
+options
+optometry
+opulent
+or
+oracle
+orange
+oranges
+orbit
+orbited
+orbiter
+orbiters
+orbiting
+orbits
+orcas
+orchana
+orchard
+orchard's
+orchards
+orchestra
+orchestra's
+orchestras
+orchid
+order
+ordered
+orderer
+ordering
+orderings
+orderly
+orders
+ordinaries
+ordinary
+ore
+oregano
+organic
+organization
+organizations
+organize
+organized
+organizes
+organizing
+organs
+oriental
+original
+originally
+originals
+orinda
+orinda's
+oriole
+orleans
+ornament
+ornament's
+ornaments
+ornate
+ornery
+orphaned
+orren
+ortega
+ortega's
+ortegas
+orville
+orzoz's
+oscar
+oscar's
+oscars
+osment
+osment's
+osments
+osso
+ostrich
+ostrich's
+ostrichs
+oswald
+oswald's
+oswalds
+otencakes
+other
+other's
+others
+others'
+otherwise
+otoh
+otter
+ouch
+ought
+our
+ours
+ourselves
+out
+outback
+outcast
+outcome
+outcomes
+outdoor
+outdoors
+outed
+outer
+outerspace
+outfield
+outfit
+outfits
+outgoing
+outing
+outings
+outlandish
+outlaw
+outlawed
+outlawing
+outlaws
+outlet
+outnumber
+outnumbered
+outnumbers
+output
+output's
+outputs
+outrageous
+outriggers
+outs
+outside
+outsider
+outsiders
+outta
+outwit
+oval
+ovals
+over
+overall
+overall's
+overalls
+overbearing
+overboard
+overcoming
+overdressed
+overdue
+overhaul
+overhauled
+overhauls
+overhead
+overing
+overjoyed
+overlap
+overlap's
+overlaps
+overly
+overprotective
+overrated
+overrun
+overs
+overshoes
+overture
+overview
+ow
+owe
+owed
+owes
+owing
+owl
+owl's
+owls
+own
+owned
+owner
+owner's
+owners
+owning
+owns
+oxygen
+oyster
+oyster's
+oysters
+oz
+p.j.
+pacha
+pachelbel's
+pacific
+pack
+package
+packages
+packet
+packin'
+packing
+packs
+pad
+pad's
+padding
+paddle
+paddle's
+paddler
+paddles
+paddlewheel
+paddlewheel's
+paddlewheels
+paddock
+padre
+padres
+pads
+pago
+pagoni
+pagoyama
+pah
+pahacha
+pahaxion
+pahazoa
+paid
+pain
+paine
+pained
+paining
+pains
+paint
+paint-spattered
+paintball
+paintball's
+paintballs
+paintbrush
+painted
+painter
+painter's
+painters
+painting
+paintings
+paints
+pair
+pair's
+paired
+pairing
+pairings
+pairs
+paisley
+pajama
+pajama's
+pajamas
+pal
+pal's
+palace
+palace's
+palaces
+palatable
+pale
+paled
+paler
+palest
+palifico
+paling
+pally
+palm
+palm's
+palmer
+palms
+palms'
+pals
+pals'
+pals's
+pamela
+pan
+pan's
+pancake
+pancakes
+pancys
+panda's
+pandas
+pandora
+panel
+panel's
+panels
+panic
+panic's
+panics
+pans
+pansy
+pant
+pant's
+pantano
+panther
+panthers
+pants
+pants.
+paper
+paper's
+papercut
+papered
+paperer
+paperers
+papering
+paperings
+papers
+pappy
+paprika
+par
+par-tee
+parade
+parade's
+paraded
+parades
+paradigm
+parading
+paradise
+parakeet
+parakeets
+parallel
+parallels
+paralyzing
+paranoid
+paranoids
+parchment
+pardon
+pardoned
+pardoner
+pardoners
+pardoning
+pardons
+parender
+parent
+parents
+parfaits
+park
+park's
+parking
+parks
+parlay
+parlays
+parle
+parlor
+parlors
+paroom
+parquet
+parr
+parrot
+parrot's
+parrotfish
+parrothead
+parrots
+parry
+parsley
+part
+parted
+parter
+parter's
+parters
+participant
+participant's
+participants
+participate
+participated
+participates
+participation
+particular
+particularly
+particulars
+partied
+parties
+parting
+partings
+partly
+partner
+partner's
+partnered
+partnering
+partners
+parts
+party
+party's
+partying
+partys
+partytime
+partytime's
+partytimes
+partyzone
+partyzone's
+partyzones
+pass
+passable
+passage
+passage's
+passaged
+passages
+passaging
+passed
+passenger
+passenger's
+passengerly
+passengers
+passer
+passers
+passes
+passing
+passive
+passover
+passport
+passport's
+passports
+passwords
+past
+past's
+pasta
+paste
+pasted
+pastes
+pasting
+pastoral
+pastries
+pasts
+pataba
+patch
+patched
+patches
+patching
+patchwork
+path
+pathes
+paths
+patience
+patient
+patient's
+patiently
+patients
+patona
+patrick
+patrick's
+patricks
+patrol
+patrol's
+patrols
+patros
+patsy
+pattern
+pattern's
+patterned
+patterning
+patterns
+pattertwig
+pattertwig's
+pattertwigs
+patty
+paul
+paul's
+paula
+pauls
+pauper
+pause
+paused
+pauses
+pausing
+pawn
+paws
+pax
+pay
+pay's
+payin'
+paying
+payment
+payment's
+payments
+pays
+pb&j
+pc
+pcs
+pea
+peace
+peaceful
+peach
+peaches
+peachy
+peacock
+peal
+peanut
+peanuts
+peapod
+pear
+pearl
+pearls
+pearly
+pears
+peas
+peasant
+peasants
+peat
+pebble
+pebbles
+pecan
+peck
+pecking
+pecos
+peculiar
+pedal
+pedals
+pedro
+peek
+peek-a-boo
+peekaboo
+peeks
+peel
+peeled
+peels
+peep
+peepers
+peeps
+peesy
+pegasus
+pegleg
+peglegfleet
+pelican
+pelican's
+pelicans
+pell
+pen
+penalty
+pencil
+pencils
+pendant
+pending
+penelope
+penguin
+penguin's
+penguins
+pennies
+penny
+penny's
+penrod
+penrod's
+pens
+pentagon
+pentagon's
+pentagons
+pentameter
+peony
+people
+people's
+peopled
+peoples
+peopling
+pepe
+pepper
+pepper's
+pepperoni
+pepperonis
+peppers
+peppy
+per
+percent
+percents
+perch
+perdida
+perfect
+perfected
+perfectemente
+perfecter
+perfecting
+perfective
+perfectly
+perfects
+perform
+performance
+performance's
+performances
+performed
+performer
+performer's
+performers
+performing
+performs
+perfume
+perfumes
+perhaps
+period
+periwinkle
+perky
+perla
+perla's
+permanent
+permanently
+permission
+permissions
+permit
+permit's
+permits
+perpetua
+perseverance
+persimmon
+person
+person's
+personalize
+personalized
+personals
+persons
+persuade
+persuaded
+persuader
+persuaders
+persuades
+persuading
+pesky
+pesky's
+pest
+pestilence
+pestle
+pestles
+pet
+pet's
+petal
+petalhead
+petals
+pete
+pete's
+petel
+petels
+petit
+petite
+pets
+petshop
+petshop's
+petshops
+pettis
+pettiskirt
+petunia
+pevensie
+pewter
+pewterer
+peyton
+peyton's
+peytons
+phab
+phantom
+phantom's
+phantoms
+phase
+phased
+phaser
+phasers
+phases
+phasing
+phenomenon
+phenomenon's
+phenomenons
+phew
+phil
+phil's
+philharmagic
+philharmagics
+philip
+phill
+phillip
+phillip's
+phillips
+philosopher
+phils
+phineas
+phineas'
+phinneas
+phinnies
+phinny
+phinny's
+phinnys
+phoebe
+phoenix's
+phoenixs
+phony
+phrase
+phrases
+phrasings
+piano
+piano's
+pianos
+piarates
+pic-a-toon
+piccolo
+piccolo's
+pick
+pick-a-name
+pick-up
+picked
+picker
+pickers
+pickert
+picking
+pickings
+pickled
+pickles
+picks
+pickup
+picnic
+picnic's
+picnics
+pictured
+picturing
+piece
+pieced
+piecer
+pieces
+piecing
+pier
+pierre
+pig
+pig's
+pigeon
+pigeon's
+pigeons
+pigge
+piggy
+piggy's
+piggys
+piglet
+piglet's
+piglets
+pigments
+pigs
+pikos
+pilagers
+pile
+piledriver
+piles
+pillage
+pillager
+pillagers
+pillages
+pillaging
+pillow
+pillows
+pim
+pim's
+pin
+pinball
+pinball's
+pinballs
+pincer
+pincers
+pine
+pine-needle
+pineapple
+pineapples
+pinecone
+pinecones
+pined
+ping
+pining
+pink
+pinkie
+pinned
+pinocchio
+pinocchio's
+pinocchios
+pinorska
+pinpoint
+pinpoint's
+pinpoints
+pinprick
+pins
+pinska
+pinstripe
+pinstripes
+pint
+pintel
+pints
+pinwheel
+pinwheels
+pioneers
+pistachio
+pit
+pit-crew
+pita's
+pitas
+pitfire
+pith
+pits
+pity
+pixar
+pixar's
+pixie
+pixie's
+pixie-dust
+pixie-dusted
+pixie-dusting
+pixie-licious
+pixie-licous
+pixie-perfect
+pixies
+pizza
+pizza's
+pizzas
+pizzatron
+pj's
+pl
+place
+placed
+placement
+placer
+places
+placid
+placing
+plagued
+plaid
+plaids
+plain
+plainer
+plainest
+plainly
+plains
+plainsmen
+plan
+plan's
+plane
+plane's
+planed
+planer
+planers
+planes
+planet
+planet's
+planetarium
+planetariums
+planets
+planing
+plank
+plankbite
+planklove
+planks
+planners
+planning
+plans
+plant
+plantain
+plantains
+planted
+planter
+planters
+planting
+plantings
+plants
+plaque
+plas
+plastic
+plasticly
+plastics
+plata
+plate
+plateau
+plateaus
+plated
+plater
+platers
+plates
+platform
+platforms
+plating
+platings
+platinum
+platoon
+platoonia
+platter
+platypus
+play
+play's
+played
+player
+player's
+players
+playful
+playfulness
+playground
+playground's
+playgrounds
+playhouse
+playhouse's
+playhouses
+playin
+playing
+playlist
+playlists
+playmates
+plays
+playset
+playstation
+plaza
+plaza's
+plazas
+pleakley
+pleaklies
+pleakly
+pleakly's
+pleasant
+pleasantry
+please
+pleased
+pleasely
+pleaser
+pleaser's
+pleasers
+pleases
+pleasing
+pleasure
+pleated
+plenties
+plenty
+plop
+plows
+pls
+pluck
+plucking
+plug
+plum
+pluma
+plumbers
+plumbing
+plume
+plumeria
+plummet
+plummeting
+plummets
+plump
+plums
+plunderbutlers
+plundered
+plunderer
+plunderers
+plunderhounds
+plunderin
+plunderin'
+plundering
+plunderrs
+plunders
+plundershots
+plural
+plurals
+plus
+plush
+pluto
+pluto's
+plz
+pm
+pocahontas
+pocahontas'
+pocket
+pocketed
+pocketing
+pockets
+pocus
+pod
+podium
+podium's
+podiums
+pods
+poem
+poems
+poetry
+poforums
+point
+pointed
+pointed-toed
+pointer
+pointers
+pointing
+points
+poisend
+poish
+pokemon
+pokercheat
+pokereval
+pokergame
+polar
+policies
+policy
+policy's
+polite
+politely
+politeness
+polka
+polka's
+polkas
+poll
+pollen
+polls
+polly
+polo
+polynesian
+polynesian's
+polynesians
+pompous
+pond
+ponder
+ponds
+poney
+pong
+ponies
+pony
+pony's
+ponytail
+poodle
+pooh's
+pool
+pooled
+pooling
+pools
+poor
+poorer
+poorest
+poorly
+pop's
+popcorn
+popcorns
+poplar
+poplin
+popovers
+poppins
+poppy
+poppy-puff
+poppyseed
+pops
+popsicle
+popsicles
+popular
+popularity
+popularly
+populate
+populated
+populates
+populating
+population
+populations
+popup
+por
+porch
+porcupine
+porgy
+porkchop
+porpoise
+port
+portable
+portal
+ported
+porter
+porters
+porting
+portly
+portmouths
+portrait
+portraits
+ports
+pose
+posies
+position
+positioned
+positioning
+positions
+positive
+positively
+positives
+posse
+possess
+possessions
+possibilities
+possibility
+possibility's
+possible
+possibles
+possibly
+possum
+possum's
+possums
+post
+post-concert
+post-show
+postcard
+postcards
+posted
+poster
+posters
+posting
+postings
+postman
+postmaster
+posts
+postshow
+posy
+potato
+potato's
+potatoes
+potatos
+potc
+potential
+potentially
+potentials
+potion
+potion's
+potions
+potpies
+pots
+pots-and-pans
+potsen
+pouch
+pouches
+pounce
+pour
+pour's
+poured
+pourer
+pourers
+pouring
+pours
+pouty
+powder-burnt
+powdered
+powders
+powe
+power
+power's
+powered
+powerful
+powerfully
+powerhouse
+powering
+powers
+pox
+ppl
+practical
+practically
+practice
+practice's
+practices
+practicing
+prairie
+prairies
+prank
+pranks
+pratt
+prattle
+prawn
+pre-concert
+precious
+precipice
+precipitation
+precisely
+precocious
+predicaments
+predict
+predictometer
+predicts
+pree
+prefab
+prefer
+preference
+preferences
+preferred
+prefers
+prefix
+prefixes
+prehysterical
+premiere
+premium
+prepare
+prepared
+preparedness
+preparer
+prepares
+preparing
+prepostera
+prescription
+prescriptions
+presence
+presence's
+presences
+present
+presentation
+presentations
+presented
+presenter
+presenter's
+presenters
+presenting
+presently
+presents
+preserver
+preservers
+president
+presidents'
+press
+pressed
+presser
+presses
+pressing
+pressings
+presto
+pretend
+pretended
+pretender
+pretender's
+pretenders
+pretending
+pretends
+prettied
+prettier
+pretties
+prettiest
+pretty
+prettying
+pretzel
+pretzels
+prev
+prevent
+prevented
+preventer
+preventing
+preventive
+prevents
+preview
+previous
+previously
+priate
+price
+priced
+pricer
+pricers
+prices
+pricing
+prickly
+pride
+pride's
+prigate
+prilla
+prilla's
+prim
+primaries
+primary
+primary's
+primate
+prime
+primed
+primely
+primer
+primers
+primes
+priming
+primitive
+primp
+primrose
+prince
+prince's
+princely
+princes
+princess
+princess's
+princesses
+principal
+principal's
+principals
+principle
+principled
+principles
+prinna
+print
+printed
+printer
+printer's
+printers
+printing
+prints
+prior
+priorities
+priority
+priority's
+privacy
+privateer
+privateer's
+privateered
+privateering
+privateers
+privileges
+prix
+prize
+prized
+prizer
+prizers
+prizes
+prizing
+prizmod
+prizmod's
+pro
+proactive
+prob
+probability
+probably
+problem
+problem's
+problems
+procastinators
+proceed
+proceeded
+proceeding
+proceedings
+proceeds
+process
+process's
+processed
+processes
+processing
+proddy
+produce
+produced
+producer
+producers
+produces
+producing
+product
+product's
+production
+productive
+products
+professor
+professor's
+professors
+profile
+profiles
+profit
+profit's
+profited
+profiter
+profiters
+profiting
+profits
+program
+program's
+programed
+programing
+programs
+progress
+progressed
+progresses
+progressing
+progressive
+project
+project's
+projected
+projectile
+projecting
+projective
+projects
+prolly
+prom
+promise
+promised
+promiser
+promises
+promising
+promo
+promos
+promote
+promoted
+promoter
+promoter's
+promoters
+promotes
+promoting
+promotion
+promotions
+promotive
+prompt
+prompter
+prompters
+pronto
+proof
+proof's
+proofed
+proofer
+proofing
+proofs
+prop
+proper
+properly
+propertied
+properties
+property
+proposal
+proposal's
+proposals
+propose
+proposes
+proposition
+props
+prospect
+prospected
+prospecting
+prospective
+prospector
+prospector's
+prospects
+protect
+protected
+protecting
+protection's
+protections
+protective
+protects
+prototype
+proud
+proudest
+prove
+proved
+prover
+prover's
+provers
+proves
+provide
+provided
+providence
+provider
+providers
+provides
+providing
+proving
+provoked
+prow
+proximity
+proxy
+prudence
+prunaprismia
+prymme
+ps2
+psa
+psp
+psyched
+psychic
+psychic's
+psychics
+pt
+pt.
+ptr
+public
+public's
+publicly
+publics
+publish
+published
+publisher
+publisher's
+publishers
+publishes
+publishing
+pucca
+puccas
+puce
+pudding
+puddle
+puddles
+pudge
+pufferang
+puffle
+puffles
+puffy
+pug
+pugpratt's
+pula
+pull
+pulled
+puller
+pulling
+pullings
+pullover
+pullovers
+pulls
+pulse
+pulyurleg
+pumba
+pumba's
+pumbaa
+pumbaa's
+pumbaas
+pummel
+pump
+pumpkin
+pumpkin's
+pumpkins
+punchline
+punchlines
+punchy
+punctuality
+punctuation
+punk
+puny
+pupert
+pupert's
+puppies
+puppy
+puppy's
+purchase
+purchased
+purchaser
+purchaser's
+purchasers
+purchases
+purchasing
+pure
+purebred
+pure
+purim
+purim's
+purple
+purpose
+purposed
+purposely
+purposes
+purposing
+purposive
+purr
+purr-fect
+purr-fectly
+purr-form
+pursuit
+pursuits
+push
+pushed
+pusher
+pushers
+pushes
+pushing
+put
+putrid
+puts
+putt
+putt-putt
+putting
+putts
+puzzle
+puzzled
+puzzler
+puzzler's
+puzzlers
+puzzles
+puzzling
+puzzlings
+pvp
+pwnage
+pwned
+pyle
+pylon
+pyrate
+pyrates
+pyrats
+pyro
+qack
+quack
+quacker
+quackity
+quacks
+quacky
+quad
+quad-barrel
+quad-barrels
+quadrant
+quadrilles
+quads
+quaint
+quake
+qualification
+qualifications
+qualified
+qualifier
+qualifier's
+qualifiers
+qualifies
+qualify
+qualifying
+qualities
+quality
+quality's
+quantities
+quantity
+quantity's
+quantum
+quarry
+quarter
+quarterdeck
+quartered
+quartering
+quarterly
+quarters
+quartet
+quasimodo
+quasimodo's
+quasimodos
+quater
+quaterers
+quebecor
+queen
+queen's
+queenly
+queens
+quentin
+quentin's
+quesadilla
+quesadillas
+quest
+quested
+quester
+quester's
+questers
+questing
+question
+questioned
+questioner
+questioners
+questioning
+questionings
+questions
+quests
+queued
+queuing
+quick
+quick-rot
+quick-witted
+quicken
+quickens
+quicker
+quickest
+quickly
+quicksilver
+quiet
+quieted
+quieten
+quietens
+quieter
+quietest
+quieting
+quietly
+quiets
+quilt
+quilting
+quilts
+quintessential
+quirtle
+quit
+quite
+quits
+quitting
+quixotic
+quiz
+quizzed
+quizzes
+quizzical
+quo
+quote
+quotes
+r
+rabbit
+rabbit's
+rabbits
+raccoon
+raccoon's
+raccoons
+race
+raced
+racer
+racer's
+racers
+races
+raceway
+rachel
+racin'
+racing
+rackham
+rad
+radar
+radiant
+radiate
+radiator
+radiators
+radical
+radio
+radio's
+radioed
+radioing
+radios
+radishes
+radius
+rae
+raff
+raft
+raft's
+rafting
+rafts
+ragetti
+ragtime
+raid
+raided
+raider
+raiders
+raiding
+raids
+raikiri
+rail
+railing
+railroad
+railroaded
+railroader
+railroaders
+railroading
+railroads
+rails
+railstand
+railwas
+railway
+railway's
+rain
+rain's
+rainbow
+rainbows
+rained
+raining
+rains
+rainstorms
+rainy
+raise
+raised
+raiser
+raisers
+raises
+raising
+rake
+raked
+rakes
+raking
+rallen
+rally
+ralph
+rama
+ramadan
+ramay
+ramble
+rambleshack
+ramone
+ramone's
+ramones
+ramp
+ramps
+ran
+ranch
+ranched
+rancher
+ranchers
+ranches
+ranching
+rancid
+random
+randomizer
+range
+ranged
+ranger
+rangers
+ranges
+ranging
+rani
+rani's
+rank
+ranked
+ranker
+rankers
+rankest
+ranking
+rankings
+rankly
+ranks
+rap
+rapid
+rapid's
+rapidly
+rapids
+rappin'
+raps
+raptor
+raptors
+rare
+rarely
+rarer
+rarest
+raring
+rasberry
+rascals
+rash
+raspberries
+raspberry
+raspberry-vanilla
+raspy
+rat
+rat's
+rat-tastic
+ratatouille
+ratatouille's
+rate
+rated
+rates
+rather
+rating
+ratings
+rats
+ratskellar
+ratte
+rattle
+ratz
+raven
+raven's
+raven-symonnd
+ravenhearst
+ravenous
+ravens
+raving
+rawrimadino
+rawvoyage
+ray
+ray's
+rayna
+rayna's
+raynas
+rayos
+rays
+razorfish
+razz
+razzle
+razzorbacks
+re-captured
+re-org
+reach
+reached
+reaches
+reaching
+react
+reaction
+reactions
+reactive
+reacts
+read
+reader
+reader's
+readers
+readied
+readier
+readies
+readiest
+reading
+readings
+reads
+ready
+readying
+reagent
+reagents
+real
+real-life
+realest
+realities
+reality
+realize
+realized
+realizer
+realizer's
+realizers
+realizes
+realizing
+realizings
+really
+realm
+realms
+reals
+reaper
+reapers
+rear
+reared
+rearer
+rearing
+rearrange
+rearrangement
+rears
+rearup
+reason
+reasonable
+reasoned
+reasoner
+reasoning
+reasonings
+reasons
+reaver
+reavers
+rebellion
+rebels
+reboot
+rec
+recall
+recalled
+recalling
+recalls
+receipt
+receipts
+receive
+received
+receiver
+receiver's
+receivers
+receives
+receiving
+recent
+recently
+recess
+recess'
+recharge
+recharged
+recharging
+recipe
+recipes
+recipient
+reckon
+reckonin'
+reckoning
+reclaim
+recognize
+recognized
+recognizer
+recognizer's
+recollect
+recollection
+recombination
+recommend
+recommended
+recommends
+recon
+reconnect
+reconnection
+reconstruct
+record
+record's
+recorded
+recorder
+recorder's
+recorders
+recording
+recordings
+records
+recover
+recovered
+recoverer
+recovering
+recovers
+recovery
+recreate
+recreates
+recreation
+recruit
+recruit-a-toon
+recruite
+recruited
+rectangle
+recurse
+recycling
+red
+red's
+redassa
+redbeard's
+redeem
+redeemer
+redeems
+redefined
+redefinition
+redemption
+redemptions
+redeposit
+redevelop
+redfeathers
+redirector
+redlegs
+redone
+redonkulous
+redros
+reds
+redscurvykid
+redskulls
+reduce
+reduced
+ree
+ree's
+reed
+reed-grass
+reeds
+reef
+reefs
+reek
+reeks
+reel
+reelect
+reeled
+reeling
+reels
+reepicheep
+ref
+refer
+refered
+referee
+reference
+referenced
+referencer
+references
+referencing
+referer
+referr
+referral
+referrals
+referred
+referrer
+referrer's
+referrers
+refills
+refined
+reflect
+reflected
+reflecting
+reflection
+reflections
+reflective
+reflects
+reform
+refrain
+refresh
+refreshed
+refreshen
+refresher
+refresher's
+refreshers
+refreshes
+refreshing
+refuel
+refuge
+refugee
+refund
+refuse
+refused
+refuser
+refuses
+refusing
+regalia
+regard
+regarded
+regarding
+regards
+regatti
+reggae
+reginald
+region
+region's
+regions
+register
+registered
+registering
+registers
+registration
+regret
+regrets
+regrow
+regular
+regularly
+regulars
+regulate
+regulated
+regulates
+regulating
+regulation
+regulations
+regulative
+rehearsal
+rehearsals
+reign
+reigning
+reimburse
+reincarnations
+reindeer
+reindeer's
+reindeers
+reinvent
+reissue
+reject
+reject's
+rejected
+rejecter
+rejecting
+rejective
+rejects
+relate
+related
+relater
+relates
+relating
+relation
+relations
+relationship
+relationships
+relative
+relatively
+relatives
+relax
+relaxed
+relaxer
+relaxes
+relaxing
+release
+release's
+released
+releaser
+releases
+releasing
+relevant
+relevantly
+reliant
+relic
+relics
+relied
+relief
+reliefs
+relier
+relies
+relive
+reluctant
+reluctantly
+rely
+relying
+rem
+remain
+remained
+remaining
+remains
+remark
+remarkable
+remarked
+remarking
+remarks
+rembrandt
+remedies
+remedy
+remember
+remembered
+rememberer
+remembering
+remembers
+remind
+reminded
+reminder
+reminding
+reminds
+remix
+removal
+remove
+removed
+remover
+removes
+removing
+remy
+remy's
+remys
+rename
+rend
+render
+rendered
+rends
+renegade
+renegades
+renew
+rennd
+rent
+rental
+rentals
+rented
+renter
+renter's
+renting
+rents
+reorganize
+rep
+repaid
+repair
+repaired
+repairer
+repairer's
+repairers
+repairing
+repairs
+repeat
+repeated
+repeater
+repeater's
+repeaters
+repeating
+repeats
+replace
+replaced
+replacement
+replacements
+replacing
+replay
+replicant
+replication
+replications
+replicator
+replied
+replier
+replies
+reply
+replying
+report
+report's
+reported
+reporter
+reporter's
+reporters
+reporting
+reports
+reposition
+repository
+represent
+represents
+republic
+republish
+reputation
+reputations
+req
+request
+requested
+requesting
+requests
+require
+required
+requirement
+requirement's
+requirements
+requirer
+requires
+requiring
+requite
+reran
+rerunning
+rescue
+rescued
+rescuer
+rescuer's
+rescuers
+rescues
+rescuing
+resell
+reservation
+reservation's
+reservations
+reserve
+reserved
+reserver
+reserves
+reserving
+reset
+resets
+resetting
+residence
+resist
+resistance
+resistant
+resolute
+resolution
+resolutions
+resort
+resort's
+resorts
+resource
+resource's
+resourced
+resourceful
+resources
+resourcing
+respect
+respected
+respecter
+respectful
+respecting
+respective
+respects
+respond
+responded
+responder
+responder's
+responders
+responding
+responds
+response
+responser
+responses
+responsibility
+responsible
+responsions
+responsive
+rest
+restart
+restaurant
+restaurant's
+restaurants
+rested
+rester
+resting
+restive
+restless
+restlessness
+restock
+restocked
+restocking
+restocks
+restore
+restored
+restores
+restoring
+restraining
+rests
+result
+resulted
+resulting
+results
+resurresction
+retavick
+retire
+retired
+retires
+retiring
+retold
+retreat
+retried
+retrieve
+retrieving
+retro
+retry
+return
+return's
+returned
+returner
+returner's
+returners
+returning
+returns
+reused
+rev
+revenant
+revenants
+reverse
+revert
+review
+review's
+reviewed
+reviewer
+reviewers
+reviewing
+reviews
+revisit
+revolution
+revolution's
+revolutionaries
+revolutions
+revolve
+revolvus
+revs
+reward
+rewarded
+rewarder
+rewarding
+rewards
+rewritten
+rewrote
+rhia
+rhineworth
+rhino
+rhino's
+rhinos
+rhoda
+rhodie
+rhonda
+rhubarb
+rhyme
+rhythm
+rhythm's
+rhythms
+ribbit
+ribbon
+ribbons
+ric
+rice
+rich
+richard
+richard's
+richen
+richer
+riches
+richest
+richly
+rick
+rico
+rid
+ridden
+ridders
+ride
+rideo
+rider
+rider's
+riders
+rides
+ridge
+ridges
+ridiculous
+riding
+ridings
+riff
+riff's
+rig
+rigging
+right
+right-on-thyme
+righted
+righten
+righteous
+righter
+rightful
+righting
+rightly
+rights
+rigs
+riley
+riley's
+rileys
+rill
+ring
+ring's
+ringing
+rings
+rink
+rink's
+rinks
+rinky
+riposte
+ripple
+riptide
+rise
+riser
+riser's
+risers
+rises
+rising
+risings
+risk
+risk-takers
+risked
+risker
+risking
+risks
+risky
+rites
+ritzy
+rivalry
+rivals
+river
+river's
+riverbank
+riverbank's
+riverbanks
+rivers
+riverveil
+rizzo
+rly
+rm
+ro
+road's
+roadrunner
+roads
+roadster
+roam
+roams
+roar
+roared
+roarer
+roaring
+roars
+roast
+roasted
+roasting
+roasts
+rob
+rob's
+robber
+robber's
+robbers
+robby
+robby's
+robbys
+robed
+rober
+robers7
+robert
+robert's
+roberts
+robin
+robin's
+robing
+robins
+robinson
+robinson's
+robinsons
+robot
+robot's
+robotic
+robotomy
+robots
+robs
+robson
+robust
+rocco
+rock
+rock'n'spell
+rock'n'words
+rock's
+rocka
+rocked
+rockenpirate
+rocker
+rocker's
+rockers
+rocket
+rocket's
+rocketed
+rocketeer
+rocketing
+rockets
+rocketship
+rocketships
+rockhead
+rockhopper
+rockhopper's
+rocks
+rocky
+rocky's
+rod
+rodeo
+rodgerrodger
+rods
+roe
+rof
+rofl
+roger's
+rogers
+rogue
+rogue's
+rogues
+role
+role's
+roles
+roll
+rolle
+rolled
+roller
+roller-ramp
+rollers
+rolling
+rollo
+rollover
+rolls
+rolodex
+rom
+romana
+romany
+ron
+ron's
+rongo
+roo
+roo's
+roof
+roofed
+roofer
+roofers
+roofing
+roofs
+rook
+rookie
+rooks
+room
+room's
+roomed
+roomer
+roomers
+rooming
+rooms
+roos
+rooster
+roosters
+root
+root's
+rooting
+roots
+rope
+rope's
+ropes
+roquica
+roquos
+rory
+rosa
+rose
+rose's
+rosebush
+rosehips
+rosemary
+roses
+rosetta
+rosetta's
+rosey
+rosh
+rosy
+rotate
+rotates
+rotfl
+rotten
+rottenly
+rouge
+rouge's
+rougeport
+rouges
+roughtongue
+rougue
+roulette
+round
+round-a-bout
+round-up
+rounded
+rounder
+rounders
+roundest
+roundhouse
+rounding
+roundly
+roundness
+rounds
+roundup
+route
+routed
+router
+router's
+routers
+routes
+routines
+routing
+routings
+roux
+rove
+row
+rowdy
+rowed
+rowing
+rowlf
+rows
+royal
+royale
+royally
+royals
+royalty
+roz
+rruff
+rsnail
+ru
+rub
+rubber
+rubbery
+rubies
+ruby
+ruby's
+rubys
+rudacho
+rudatake
+rudatori
+rudder
+rudderly
+rudders
+rude
+rudy
+rudyard
+rudyard's
+rue
+ruff
+ruffians
+ruffle
+rufus
+rufus'
+rug
+rugged
+rugs
+ruin
+ruination
+ruins
+rule
+ruled
+ruler
+rulers
+rules
+ruling
+rulings
+rumble
+rumbly
+rumor
+rumors
+rumrun
+rumrunner
+rumrunner's
+run
+run's
+runawas
+runaway
+runaway's
+runaways
+rung
+runner
+runners
+runnin'
+running
+runo
+runo's
+runoff
+runos
+runs
+rural
+rush
+rushed
+rushes
+rushing
+russell
+russia
+russo
+rust
+rusted
+rusteze
+rustic
+rusty
+ruth
+rutherford
+ruthless
+ryan
+ryan's
+ryans
+rydrake
+rygazelle
+ryza
+s'mores
+s-se
+s.o.s.
+sabada
+sabago
+sabeltann
+saber
+sabona
+sabos
+sabrefish
+sabrina
+sabrina's
+sabrinas
+sacked
+sacred
+sad
+sadden
+saddens
+saddest
+saddle
+saddlebag
+saddlebags
+saddles
+sadie
+sadie's
+sadly
+sadness
+safari
+safaris
+safe
+safely
+safer
+safes
+safest
+safetied
+safeties
+safety
+safety's
+safetying
+saffron
+sage
+sahara
+said
+sail
+sailcloth
+sailed
+sailer
+sailers
+sailing
+sailing's
+sailingfoxes
+sailor
+sailor's
+sailorly
+sailors
+sails
+sailsmen
+saj
+sal
+salads
+salama
+sale
+sale's
+sales
+salesman
+salesmen
+saligos
+sally
+sally's
+sallys
+salmon
+salmons
+saloon
+salt
+salt-sifting
+saltpeter
+saludos
+salutations
+salute
+salvage
+salve
+sam
+samantha
+samba
+same
+samerobo
+sametosu
+sametto
+sample
+sample's
+sampled
+sampler
+samplers
+samples
+sampling
+samplings
+samuel
+samugeki
+samukabu
+samurite
+san
+sanassa
+sand
+sand-sorting
+sandal's
+sandals
+sandalwood
+sandcastle
+sandcastle's
+sandcastles
+sanded
+sander
+sanders
+sanding
+sandman
+sandman's
+sands
+sandwich
+sandwiches
+sandy
+sang
+sanguine
+sanila
+sanjay
+sanquilla
+sans
+santa
+santa's
+santia
+sao
+sap
+saphire
+sapphire
+sappy
+saps
+sara
+sarah
+sardine
+sardines
+sarge
+sarges
+sark
+sarong
+sas
+sash
+sasha
+sashes
+sass
+sassafras
+sassy
+sat
+satchel
+satchel's
+sated
+satellite
+satellite's
+satellites
+sating
+saturday
+saturday's
+saturn
+saturn's
+satyr
+satyrs
+sauce
+saucer
+saucers
+sauces
+savada
+savage
+savagers
+savano
+save
+saved
+saver
+savers
+saves
+saveyoursoul
+savica
+savies
+savigos
+saving
+savings
+savor
+savory
+savvy
+savvypirates
+savys
+saw
+sawdust
+sawing
+saws
+sawyer
+sawyer's
+say
+sayer
+sayer's
+sayers
+sayin'
+saying
+sayings
+says
+sbhq
+sbt
+sbtgame
+scabbard
+scabbards
+scabs
+scalawag
+scalawags
+scale
+scaled
+scaler
+scalers
+scales
+scaling
+scalings
+scaliwags
+scalleywags
+scallop
+scalloped
+scally
+scallywag
+scallywags
+scamps
+scan
+scanner
+scans
+scar
+scar's
+scards
+scare
+scared
+scarer
+scares
+scarf
+scarier
+scariest
+scarin'
+scaring
+scarlet
+scarlet's
+scarlets
+scarlett
+scarlett's
+scarletundrground
+scars
+scarves
+scary
+scatter
+scatty
+scavenger
+scavenger's
+scavengers
+scelitons
+scene
+scene's
+scenery
+scenes
+scenic
+scepter
+scepters
+schedule
+schedule's
+schedules
+scheme
+schemer
+schemes
+scheming
+schmaltzy
+schmooze
+scholarship
+scholastic
+schools
+schumann's
+sci-fi
+science
+science's
+sciences
+scientific
+scientist
+scientist's
+scientists
+scissor's
+scissorfish
+scissors
+scold
+scones
+scoop
+scooper
+scooper-ball
+scooperball
+scoops
+scoot
+scooter
+scooters
+scope
+scorch
+scorching
+scoreboard
+scoreboards
+scorn
+scorpio
+scorpion
+scorpions
+scott
+scoundrel
+scoundrels
+scourge
+scourges
+scout
+scouting
+scouts
+scowl
+scramble
+scrambled
+scrap-metal
+scrap-metal-recovery-talent
+scrapbook
+scrappy
+scratch
+scratches
+scratchier
+scratchy
+scrawled
+scrawny
+scream
+scream's
+screamed
+screamer
+screamers
+screaming
+screech
+screen
+screened
+screener
+screenhog
+screening
+screenings
+screens
+screensaver
+screwball
+screwy
+scribe
+script
+scripts
+scroll
+scrub
+scruffy
+scrumptious
+scullery
+sculpt
+sculpture
+sculpture-things
+sculptured
+sculptures
+scurrvy
+scurry
+scurvey
+scurvy
+scurvydog
+scuttle
+scuttle's
+scuttlebutt
+scuttles
+scuvy
+scythely
+sea
+seabass
+seabourne
+seachest
+seademons
+seadogs
+seadragons
+seadragonz
+seafarer
+seafarers
+seafoams
+seafood
+seafurys
+seagulls
+seahags
+seahorse
+seahorses
+seahounds
+seal
+sealands
+seaman
+seamasterfr
+seamstress
+sean
+seance
+sear
+searaiders
+search
+searchable
+searched
+searcher
+searchers
+searches
+searching
+searchings
+seas
+seashadowselite
+seashell
+seashells
+seashore
+seashore's
+seashores
+seaside
+seaside's
+seasides
+seaskulls
+seaslipperdogs
+seasnake
+season
+season's
+seasonal
+seasoned
+seasoner
+seasoners
+seasoning
+seasonings
+seasonly
+seasons
+seat
+seated
+seater
+seating
+seats
+seaweed
+seaweeds
+sebastian
+second
+seconds
+secret
+secreted
+secreting
+secretive
+secretly
+secrets
+section
+sectioned
+sectioning
+sections
+sector
+secure
+secured
+securely
+securer
+secures
+securing
+securings
+securities
+security
+see
+seed
+seedling
+seedlings
+seedpod
+seeds
+seeing
+seek
+seeker
+seekers
+seeking
+seeks
+seeley
+seem
+seemed
+seeming
+seemly
+seems
+seen
+seer
+seers
+sees
+seeya
+segu
+segulara
+segulos
+seige
+seing
+select
+selected
+selecting
+selection
+selection's
+selections
+selective
+selects
+self
+self-absorbed
+self-centered
+self-important
+self-possessed
+selfish
+selfishness
+sell
+sellbot
+sellbotfrontentrance
+sellbots
+sellbotsideentrance
+seller
+sellers
+selling
+sells
+seltzer
+seltzers
+seminar
+seminars
+semper
+send
+sender
+senders
+sending
+sends
+senior
+seniors
+senkro
+sennet
+senpu
+senpuga
+senpura
+sensation
+sense
+sensed
+senses
+sensing
+sensor
+sensors
+sent
+sentence
+sentenced
+sentences
+sentencing
+sentinel
+sentinels
+sep
+separate
+separated
+separately
+separates
+separating
+separation
+separations
+separative
+september
+sequence
+sequences
+sequin
+serena
+serendipity
+serene
+sergeant
+sergeants
+sergio
+series
+serious
+seriously
+serphants
+servants
+serve
+served
+server
+servers
+serves
+service
+service's
+serviced
+servicer
+services
+servicing
+serving
+servings
+sesame
+sesame-seed
+session
+session's
+sessions
+set
+set's
+sets
+setter
+setting
+settings
+settle
+settled
+settler
+settlers
+settles
+settling
+settlings
+setup
+sever
+several
+severally
+severals
+severed
+severely
+seville
+sew
+sewing
+sews
+sf
+sgt
+sh-boom
+shabby
+shack
+shackleby
+shackles
+shade
+shader
+shades
+shadow
+shadowcrows
+shadowed
+shadower
+shadowhunters
+shadowing
+shadowofthedead
+shadows
+shadowy
+shady
+shaggy
+shake
+shaken
+shaker
+shakers
+shakes
+shakey
+shakey's
+shakin
+shakin'
+shaking
+shakoblad
+shakor
+shaky
+shall
+shallow
+shallows
+shamrock
+shamrocks
+shane
+shane's
+shang's
+shanna
+shanna's
+shannara
+shanty
+shape
+shaped
+shaper
+shapers
+shapes
+shaping
+shard
+shards
+share
+shared
+sharer
+sharers
+shares
+sharing
+shark
+shark's
+sharkbait
+sharkhunters
+sharks
+sharky
+sharon
+sharp
+sharpay
+sharpay's
+sharpen
+sharpened
+shatter
+shazam
+she
+she'll
+she's
+sheared
+shearing
+shed
+sheep
+sheeps
+sheer
+sheila
+sheild
+shelf
+shell
+shellbacks
+shellhorns
+shells
+shelly
+shenanigans
+shep
+sheriff
+sheriff's
+sheriffs
+sherry
+shes
+shield
+shields
+shift
+shifts
+shifty
+shimadoros
+shimainu
+shimanoto
+shimmer
+shimmering
+shimmy
+shin
+shine
+shines
+shining
+shiny
+ship
+ship's
+shipman
+shipmates
+shipment
+shipments
+shippart
+shippers
+shipping
+ships
+shipwarriors
+shipwreck
+shipwrecked
+shipwreckers
+shipwrecks
+shipwright
+shipwrights
+shipyard
+shirley
+shirt
+shirt.
+shirting
+shirts
+shirtspot
+shiver
+shiverin'
+shivering
+shochett
+shock
+shocked
+shocker
+shockers
+shocking
+shockit
+shocks
+shoe
+shoe-making
+shoes
+shoeshine
+shogyo
+shone
+shook
+shop
+shop's
+shoped
+shoper
+shoping
+shoppe
+shopped
+shopper
+shopping
+shops
+shore
+shores
+short
+short-stack
+short-term
+shortcake
+shortcut
+shortcuts
+shorted
+shorten
+shortens
+shorter
+shortest
+shorting
+shortly
+shorts
+shorts.
+shortsheeter
+shorty
+shoshanna
+shot
+shots
+should
+shoulder
+shouldered
+shouldering
+shoulders
+shouldest
+shouldn'a
+shouldn't
+shout
+shouted
+shouter
+shouters
+shouting
+shouts
+shove
+shoved
+shovel
+shovels
+shoves
+shoving
+show
+show-offs
+showbiz
+showcase
+showdown
+showed
+showing
+showings
+shown
+shows
+showtime
+showy
+shrapnel
+shred
+shredding
+shriek
+shrieked
+shrieking
+shrieks
+shrill
+shrimp
+shrink
+shrinking
+shrug
+shrunk
+shrunken
+shticker
+shtickers
+shucks
+shuffle
+shuffled
+shuffles
+shuffling
+shulla
+shut
+shut-eye
+shuts
+shuttle
+shy
+siamese
+sib
+sib's
+siblings
+sick
+sickness
+sicknesses
+sid
+side
+sidearm
+sideburns
+sided
+sidekick
+sideline
+sidepipes
+sides
+sidesplitter
+sidesplitter's
+sidewalk
+sidewalk's
+sidewalks
+sidewinder
+sidewinders
+siding
+sidings
+siege
+sieges
+sienna
+siesta
+siestas
+sigh
+sighes
+sight
+sighted
+sighter
+sighting
+sightings
+sightly
+sights
+sightsee
+sightseeing
+sightsees
+sign
+signal
+signaled
+signaling
+signally
+signals
+signature
+signed
+signer
+signers
+signing
+signs
+silence
+silenced
+silences
+silencing
+silent
+silentguild
+silently
+silents
+silenus
+silhouette
+silk
+silken
+silks
+silkworm
+sillier
+silliest
+silliness
+silly
+sillypirate
+sillywillows
+silver
+silver943
+silverbell
+silvered
+silverer
+silvering
+silverly
+silvermist
+silvermist's
+silvers
+silverwolves
+silvery
+simba
+simba's
+simian
+similar
+similarly
+simmer
+simon
+simone
+simple
+simpler
+simples
+simplest
+simply
+simulator
+since
+sincere
+sing
+sing-a-longs
+sing-along
+singapore
+singaporean
+singe
+singed
+singer
+singers
+singin'
+singing
+sings
+sinjin
+sink
+sink's
+sinker
+sinkers
+sinking
+sinks
+sins
+sir
+siren
+siren's
+sirens
+siring
+sirs
+sis
+sister
+sister's
+sisterhood
+sisters
+sisters'
+sit
+sitch
+site
+site's
+sited
+sites
+siting
+sits
+sitting
+situation
+situations
+size
+sized
+sizer
+sizers
+sizes
+sizing
+sizings
+sizzle
+sizzlin
+sizzlin'
+sizzling
+skarlett
+skate
+skateboard
+skateboard's
+skateboarded
+skateboarder
+skateboarders
+skateboarding
+skateboardings
+skateboards
+skated
+skater
+skater's
+skaters
+skates
+skating
+skel
+skelecog
+skelecogs
+skeletalknights
+skeleton
+skeletoncrew
+skeletonhunters
+skeletons
+skellington
+skeptical
+sketchbook
+ski
+skid
+skied
+skier
+skiers
+skies
+skiff
+skiing
+skill
+skilled
+skillful
+skilling
+skills
+skimmers
+skinny
+skip
+skipped
+skipper
+skippers
+skipping
+skips
+skirmish
+skirmished
+skirmishes
+skirmishing
+skirt
+skirted
+skirter
+skirting
+skirts
+skis
+skits
+skulky
+skull
+skull's
+skullcap-and-comfrey
+skulled
+skullraiders
+skulls
+skunk
+skunks
+sky
+sky's
+skyak
+skydiving
+skying
+skyler
+skyrocketing
+skysail
+skyway
+skyway's
+slam
+slam-dunk
+slammin
+slammin'
+slapped
+slaps
+slate
+slater
+slates
+slaughter
+slaves
+sled
+sleds
+sleek
+sleep
+sleeper
+sleepers
+sleeping
+sleepless
+sleeps
+sleepwalking
+sleepy
+sleepy's
+sleet
+sleeting
+sleeve
+sleeveless
+sleigh
+sleighing
+sleighs
+slept
+slice
+sliced
+slicer
+slicers
+slices
+slicing
+slight
+slighted
+slighter
+slightest
+slighting
+slightly
+slights
+slim
+slim's
+slims
+slimy
+slinger
+slingers
+slingshot
+slingshots
+slipper
+slippers
+sliver
+slobs
+sloop
+sloopers
+sloops
+slot
+slots
+slow
+slow-thinker
+slowed
+slower
+slowest
+slowing
+slowly
+slows
+sludge
+sludges
+slug
+slugged
+slugging
+sluggo
+slugs
+slumber
+slumbered
+slumbering
+slumbers
+slump
+slush
+slushy
+smackdab
+small
+smallband
+smaller
+smallest
+smart
+smartguy
+smartly
+smarts
+smarty
+smarty-pants
+smash
+smashed
+smasher
+smashers
+smashes
+smashing
+smelled
+smeller
+smelling
+smells
+smile
+smiled
+smiler
+smiles
+smiling
+smirking
+smith
+smithery
+smithing
+smitty
+smitty's
+smock
+smoke
+smokes
+smokey
+smokey-blue
+smokin'
+smolder
+smoldered
+smoldering
+smolders
+smoothed
+smoothen
+smoother
+smoothers
+smoothes
+smoothest
+smoothie
+smoothing
+smoothly
+smudgy
+smulley
+smythe
+snack
+snackdown
+snag
+snag-it
+snaggletooth
+snail
+snail's
+snails
+snaked
+snakers
+snakes
+snap
+snapdragon
+snapdragons
+snapped
+snapper
+snappy
+snaps
+snapshot
+snare
+snazzy
+sneak
+sneaker
+sneakers
+sneaks
+sneaky
+sneeze
+sneezewort
+sneezy
+sneezy's
+snicker
+snippy
+snobby
+snobs
+snoop
+snooty
+snooze
+snoozin'
+snoozing
+snore
+snorer
+snores
+snorkel
+snorkeler's
+snow
+snowball
+snowballs
+snowboard
+snowboarder
+snowboarders
+snowboarding
+snowboards
+snowdragon
+snowdrift
+snowed
+snowflake
+snowflakes
+snowing
+snowman
+snowman's
+snowmen
+snowplace
+snowplows
+snows
+snowshoes
+snowy
+snug
+snuggle
+snuggles
+snuze
+so
+soaker
+soapstone
+soar
+soarin'
+soaring
+soccer
+social
+socialize
+socially
+socials
+society
+soda
+sodas
+sofa
+sofas
+sofie
+soft
+softball
+soften
+softens
+softer
+softest
+softly
+software
+software's
+soil
+soiled
+soiling
+soils
+solar
+sold
+solder
+solders
+soldier
+soldiers
+sole
+soles
+solid
+solo
+solomon
+solos
+solution
+solution's
+solutions
+solve
+sombrero
+sombreros
+some
+somebody
+somebody's
+someday
+somehow
+someone
+someone's
+somers
+something
+something's
+sometime
+sometimes
+somewhat
+somewhere
+somewheres
+son
+sonata
+sonatas
+song
+song's
+songbird
+songs
+sonic
+sons
+soon
+soon-to-be
+sooner
+soonest
+soooo
+soop
+soothing
+sopespian
+sophie
+sophie's
+sophisticated
+sorcerer
+sorcerer's
+sorcerers
+sord
+sorrier
+sorriest
+sorry
+sort
+sort's
+sorted
+sorter
+sorters
+sortie
+sorting
+sorts
+sos
+souffle
+souffl
+sought
+soul
+soulflay
+souls
+sound
+sounded
+sounder
+soundest
+sounding
+soundings
+soundly
+sounds
+soundtrack
+soup
+soups
+sour
+sour-plum
+source
+south
+south-eastern
+souther
+southern
+southerner
+southerners
+southernly
+southing
+southsea
+southside
+souvenir
+souvenirs
+sovereign
+sovreigns
+spaaarrow
+space
+space's
+space-age
+spaced
+spacer
+spacers
+spaces
+spaceship
+spaceship's
+spaceships
+spacing
+spacings
+spade
+spades
+spaghetti
+spain
+spam
+spamonia
+spanish
+spare
+spared
+sparely
+sparer
+spares
+sparest
+sparing
+spark
+sparkies
+sparkle
+sparkler
+sparklers
+sparkling
+sparkly
+sparks
+sparky
+sparky's
+sparrow
+sparrow's
+sparrow-man
+sparrows
+sparrowsfiight
+spartans
+spation
+speak
+speaker
+speaker's
+speakers
+speaking
+speaks
+special
+specially
+specials
+species
+specific
+specifics
+specified
+specify
+specifying
+speck
+speckled
+spectacular
+specter
+specters
+spectral
+spectrobe
+spectrobes
+speech
+speech's
+speeches
+speed
+speedchat
+speeded
+speeder
+speeders
+speediest
+speeding
+speedmaster
+speeds
+speedway
+speedwell
+speedy
+spell
+spelled
+speller
+spellers
+spelling
+spellings
+spells
+spend
+spender
+spenders
+spending
+spends
+spent
+spice
+spices
+spicey
+spicy
+spider
+spider's
+spider-silk
+spiders
+spiderwebs
+spiel
+spiffy
+spikan
+spikanor
+spiko
+spill
+spilled
+spilling
+spills
+spin
+spin-out
+spin-to-win
+spinach
+spines
+spinner
+spinning
+spins
+spiral
+spirit
+spirited
+spiriting
+spirits
+spit
+spiteful
+spits
+spittake
+splash
+splashed
+splasher
+splashers
+splashes
+splashing
+splashy
+splat
+splatter
+splatters
+splendid
+splinter
+splinters
+splish
+splish-splash
+splitting
+splurge
+spoiled
+spoiler
+spoke
+spoken
+spondee
+sponge
+spongy
+sponsored
+spook
+spooks
+spooky
+spoon
+spoons
+sport
+sported
+sporting
+sportive
+sports
+spot
+spot's
+spotcheek
+spotless
+spotlight
+spots
+spotted
+spotting
+spotz
+spout
+spouts
+spray
+sprays
+spree
+sprightly
+spring
+springer
+springers
+springing
+springs
+springtime
+springy
+sprinkle
+sprinkled
+sprinkler
+sprinkles
+sprinkling
+sprint
+sprinting
+sprite
+sprites
+sprocket
+sprockets
+sprouse
+sprout
+sprouter
+spruce
+spud
+spunkiness
+spunky
+spy
+spyp.o.d.
+spypod
+sqad364
+squad
+squall
+squall's
+squalls
+square
+squared
+squarely
+squares
+squaring
+squash
+squashed
+squashing
+squawk
+squawks
+squeak
+squeakity
+squeaky
+squeal
+squeeks
+squeezebox
+squid
+squid's
+squids
+squidzoid
+squiggle
+squiggly
+squillace
+squirmy
+squirrel
+squirrelfish
+squirrels
+squirt
+squirting
+squishy
+srawhats
+sri
+srry
+sry
+ssw
+st
+st.
+stack
+stackable
+stacker
+stacking
+stacks
+stadium
+stadiums
+staff
+staff's
+staffed
+staffer
+staffers
+staffing
+staffs
+stage
+staged
+stager
+stagers
+stages
+staging
+stain
+stained-glass
+stainless
+stains
+stair
+stair's
+stairs
+stake
+stalkers
+stall
+stallion
+stamp
+stamped
+stamper
+stampers
+stamping
+stamps
+stan
+stanchion
+stanchions
+stand
+stand-up
+stand-up-and-cheer
+standard
+standardly
+standards
+stander
+standing
+standings
+stands
+stanley
+stanley's
+star
+star's
+star-chaser
+star-shaped
+starboard
+starcatchers
+starch
+stardom
+stareaston
+stared
+starer
+starfire
+starfish
+stargazer
+staring
+starlight
+starring
+starry
+stars
+starscream
+start
+started
+starter
+starters
+starting
+starting-line
+starts
+stas
+stash
+stat
+statement
+statement's
+statements
+states
+station
+station's
+stationed
+stationer
+stationery
+stationing
+stations
+statler
+stats
+statuary
+statue
+statues
+statuesque
+status
+statuses
+stay
+stayed
+staying
+stayne
+stays
+steadfast
+steadman
+steady
+steak
+steal
+stealer
+stealing
+steals
+stealth
+steam
+steamboat
+steaming
+steel
+steelhawk
+steeple
+steer
+steered
+steerer
+steering
+steers
+steffi
+stella
+stem
+stench
+stenches
+stenchy
+step
+step's
+stepanek
+stephante
+stepped
+stepping
+steps
+stern
+stetson
+stevenson
+stew
+stewart
+stflush
+stick
+sticker
+stickerbook
+stickers
+sticking
+sticks
+sticky
+sticky's
+stickyfeet
+still
+stilled
+stiller
+stillest
+stilling
+stillness
+stills
+stillwater
+sting
+stinger
+stingers
+stings
+stink
+stinkbucket
+stinkbugs
+stinker
+stinking
+stinks
+stinky
+stinky's
+stir
+stitch
+stitch's
+stitched
+stitcher
+stitches
+stitching
+stock
+stocked
+stocker
+stockers
+stockier
+stocking
+stockings
+stockpile
+stocks
+stoke
+stoked
+stole
+stolen
+stomp
+stone
+stones
+stood
+stool
+stools
+stop
+stop's
+stoped
+stoppable
+stopped
+stopper
+stopping
+stops
+storage
+store
+stored
+stores
+storied
+stories
+storing
+storm
+storm's
+storm-sail
+stormbringers
+stormed
+stormers
+stormfire
+stormhaw
+stormhold
+storming
+stormlords
+stormrider
+storms
+stormy
+story
+story's
+storybook
+storybookland
+storybooks
+storying
+storylines
+storytelling
+stow
+stowaway
+str
+straight
+strait
+strand
+strands
+strange
+strangely
+stranger
+strangers
+strangest
+strategies
+strategy
+strategy's
+straw
+strawberrie
+strawberries
+strawberry
+strawhats
+strays
+stream
+streamer
+streamers
+street
+streeters
+streets
+streetwise
+strength
+strengthen
+strengthens
+stress
+stressed
+stresses
+stressing
+stretch
+stretched
+stretcher
+stretchers
+stretches
+stretching
+strict
+strictly
+striders
+strike
+striker
+strikers
+strikes
+striking
+string
+stringbean
+strings
+stringy
+strive
+stroll
+strolling
+strom
+strong
+strong-minded
+strongbox
+stronger
+strongest
+strongly
+strung
+strut
+stu
+stubborn
+stubby
+stuck
+stud
+studded
+studied
+studier
+studies
+studio
+studio's
+studios
+study
+studying
+stuff
+stuffed
+stuffer
+stuffing
+stuffings
+stuffs
+stuffy
+stumble
+stump
+stumps
+stumpy
+stun
+stunned
+stunning
+stuns
+stunts
+stupendous
+sturdy
+stut
+stutter
+stutters
+style
+style-talent
+styled
+styler
+stylers
+styles
+stylin'
+styling
+stylish
+sub
+subject
+subject's
+subjected
+subjecting
+subjective
+subjects
+sublocation
+submarine
+submarines
+submit
+submits
+submitted
+submitting
+subscribe
+subscribed
+subscribers
+subscribing
+subscription
+subscriptions
+substitute
+subtalent
+subtalents
+subtitle
+subzero
+succeed
+succeeded
+succeeder
+succeeding
+succeeds
+success
+successes
+successful
+successfully
+successive
+such
+sucha
+suckerpunch
+suction
+sudden
+suddenly
+sudoku
+sudoron
+sue
+suffice
+suffix
+suffixes
+sufrigate
+sugar
+sugarplum
+suggest
+suggested
+suggester
+suggesting
+suggestion
+suggestion's
+suggestions
+suggestive
+suggests
+suit
+suit's
+suitcase
+suitcases
+suite
+suited
+suiters
+suiting
+suits
+sulfur
+sulley
+sully
+sultan
+sum
+sum's
+sumer
+sumhajee
+summary
+summer
+summer's
+summered
+summering
+summerland
+summers
+summit
+summon
+summoned
+summoning
+summons
+sumo
+sums
+sun
+sun's
+sunburst
+sundae
+sundaes
+sunday
+sundown
+suneroo
+sunflower-seed
+sunflowers
+sung
+sunk
+sunken
+sunnies
+sunny
+sunny's
+sunrise
+suns
+sunsational
+sunscreen
+sunset
+sunsets
+sunshine's
+sunshines
+sunswept
+suoicodilaipxecitsiligarfilacrepus
+sup
+supa-star
+super
+super's
+super-cool
+super-duper
+super-powerful
+super-talented
+super-thoughtful
+superb
+supercalifragilisticexpialidocious
+supercool
+superhero
+superhero's
+superheroes
+superior
+supernatural
+supers
+superserpents
+superstar
+supervise
+supervised
+supervising
+supervisor
+supervisors
+supplication
+supplied
+supplier
+suppliers
+supplies
+supply
+supply's
+supplying
+support
+supported
+supporter
+supporters
+supporting
+supportive
+supports
+suppose
+supposed
+supposer
+supposes
+supposing
+supreme
+supremo
+supremo's
+sure
+sured
+surely
+surer
+surest
+surf
+surf's
+surface
+surfaced
+surfacer
+surfacers
+surfaces
+surfacing
+surfari
+surfboard
+surfer
+surfers
+surfin'
+surfing
+surfs
+surge
+surgeon
+surgeons
+surges
+surging
+surlee
+surprise
+surprise's
+surprised
+surpriser
+surprises
+surprising
+surprize
+surrender
+surrendered
+surrendering
+surrenders
+surround
+surrounded
+surrounding
+surroundings
+surrounds
+surves
+survey
+surveying
+survival
+survive
+survived
+surviver
+survives
+surviving
+survivor
+survivor's
+survivors
+susan
+susan's
+sushi
+suspended
+suspenders
+svaal
+svage
+sven
+svetlana
+swab
+swabbie
+swabbin'
+swabby
+swain
+swam
+swamies
+swamp
+swamps
+swan
+swanky
+swann
+swann's
+swans
+swap
+swapped
+swapping
+swaps
+swarm
+swarthy
+swash
+swashbuckler
+swashbucklers
+swashbuckling
+swashbucler
+swashbuculer
+swat
+swats
+swatted
+swatting
+sweat
+sweater
+sweaters
+sweatheart
+sweatshirt
+sweatshirts
+sweaty
+sweden
+swedish
+sweep
+sweeping
+sweeps
+sweepstakes
+sweet
+sweeten
+sweetens
+sweeter
+sweetest
+sweetgum
+sweetie
+sweeting
+sweetly
+sweetness
+sweets
+sweetums
+sweetwrap
+sweety
+swell
+swelled
+swelling
+swellings
+swells
+swept
+swervy
+swift
+swiftness
+swifty
+swig
+swim
+swimer
+swimmer
+swimming
+swimmingly
+swims
+swimwear
+swindlers
+swine
+swing
+swinger
+swingers
+swinging
+swings
+swipe
+swiped
+swipes
+swipin'
+swirl
+swirled
+swirls
+swirly
+swiss
+switch
+switch's
+switchbox
+switched
+switcher
+switcheroo
+switchers
+switches
+switching
+switchings
+swiveling
+swoop
+sword
+sword's
+swordbreakers
+swords
+swordslashers
+swordsman
+swordsmen
+sycamore
+sydney
+symbol
+symbols
+syncopation
+syndicate
+synergy
+synopsis
+syrberus
+syrup
+syrupy
+system
+system's
+systems
+t-shirt
+t-shirts
+t-squad
+t-squad's
+t.b.
+t.p.
+tab
+tabatha
+tabby
+tabitha
+tabitha's
+table
+table's
+table-setting-talent
+tabled
+tables
+tableset
+tabling
+tabs
+tabulate
+tack
+tacked
+tacking
+tackle
+tackled
+tackles
+tackling
+tacks
+tacky
+tact
+tactful
+tactics
+taffy
+tag
+tags
+tailed
+tailgater
+tailgaters
+tailgating
+tailing
+tailor
+tailored
+tailoring
+tailors
+tailpipe
+tailpipes
+tails
+tailswim
+tainted
+take
+taken
+taker
+takers
+takes
+taketh
+takin'
+taking
+takings
+takion
+tale
+tale's
+talent
+talented
+talents
+tales
+talespin
+talk
+talkative
+talked
+talker
+talkers
+talkin
+talking
+talks
+tall
+tall-tale-telling-talent
+taller
+tallest
+tally
+talon
+talons
+tamazoa
+tamers
+tammy
+tampa
+tan
+tandemfrost
+tangaroa
+tangaroa's
+tangaroa-ru
+tangaroa-ru's
+tangerine
+tangle
+tango
+tangoed
+tangoing
+tangos
+tangy
+tanith
+tank
+tanker
+tankers
+tanking
+tanks
+tanned
+tanning
+tanny
+tans
+tansy
+tap
+tap's
+tape
+taped
+taper
+tapers
+tapes
+tapestry
+taping
+tapings
+taps
+tar
+tara
+tarantula
+target
+targeted
+targeting
+targets
+tarlets
+tarnished
+tarp
+tarps
+tarred
+tarring
+tars
+tartar
+tarzan
+tarzan's
+tarzans
+task
+tasked
+tasking
+taskmaster
+taskmasters
+tasks
+taste
+tasted
+tasteful
+taster
+tasters
+tastes
+tastiest
+tasting
+tasty
+tate
+tats
+tattletales
+tattoo
+tattooed
+tattoos
+taught
+taunt
+taunted
+taunting
+taunts
+tax
+taxes
+taxi
+taxis
+taylor
+tbrrrgh
+tbt
+td
+tdl
+tea
+tea-making
+teacakes
+teach
+teacher
+teacher's
+teachers
+teaches
+teaching
+teachings
+teacups
+teakettle
+teal
+team
+team's
+teamed
+teaming
+teamo
+teams
+teamwork
+teapot
+tear
+tear's
+teared
+tearer
+tearing
+tearoom
+tears
+teas
+tech
+technical
+technically
+technique
+techniques
+techno
+technology
+ted
+teddy
+tee
+tee-hee
+teenager
+teeny
+teepee
+teepees
+teeth
+teethed
+teether
+teethes
+teething
+tegueste
+telemarketer
+telemarketers
+teleport
+teleport's
+teleportation
+teleported
+teleporter
+teleporters
+teleporting
+teleportings
+teleports
+telescope
+television
+television's
+televisions
+teli-caster
+tell
+teller
+tellers
+telling
+tellings
+tells
+telly
+telmar
+temma
+temper
+temperature
+temperatures
+templars
+template
+templates
+temple
+temple's
+templed
+temples
+templeton
+tempo
+tempo's
+temporarily
+temporary
+tend
+tended
+tendency
+tender
+tenderleaf
+tenders
+tendershoot
+tending
+tends
+tenkro
+tennis
+tenor
+tenors
+tens
+tensa
+tension
+tent
+tentacle
+tentacles
+tented
+tenter
+tenting
+tents
+terabithia
+terence
+terk
+terk's
+terks
+termite
+ternary
+teror
+terra
+terrace
+terrain
+terrains
+terrance
+terrance's
+terrible
+tessa
+test
+test's
+test1
+tested
+tester
+testers
+testing
+testings
+tests
+tetherball
+tewas
+text
+text's
+text-message
+textile-talent
+texts
+texture
+textured
+textures
+tgf
+th
+th'
+than
+thang
+thank
+thanked
+thanker
+thankful
+thanking
+thanks
+thanksgiving
+thanx
+thar
+that
+that's
+thats
+thayer
+thayers
+the
+thea
+theater
+theaters
+theatre
+theatre's
+theatres
+thee
+theifs
+their
+their's
+theirs
+theives
+thelma
+thelonius
+them
+theme
+theme's
+themes
+themselves
+then
+thenights
+theodore
+there
+there'll
+there's
+therefore
+theres
+theresa
+thermos
+thermoses
+these
+theses
+they
+they'll
+they're
+they've
+theyll
+theyre
+thicket
+thief
+thief's
+thimble
+thin
+thing
+thingamabob
+thingamabobs
+thingamajigs
+thingies
+things
+thingy
+thinicetrobarrier
+think
+thinker
+thinkers
+thinkin'
+thinking
+thinks
+thinnamin
+third
+thirst
+thirst-quenching
+thirsted
+thirster
+thirstier
+thirsts
+thirsty
+this
+thistle
+thistle-down
+thnx
+tho
+thomas
+thon
+thorhammer
+thorn
+those
+though
+thought
+thought's
+thoughtful
+thoughts
+thrashers
+thread
+threads
+threats
+threw
+thrifty
+thrill
+thriller
+thrilling
+thrills
+thrillseeker
+thrives
+throne
+through
+throughly
+throughout
+throw
+thrower
+throwing
+thrown
+throws
+thumb
+thumbs
+thumper
+thunba
+thunder
+thunderbolt
+thunderbolts
+thundering
+thunderstorms
+thundor
+thundora
+thursday
+thus
+thusly
+thx
+tia
+tiara
+tiazoa
+tibian
+tic
+tic-toc
+tick
+ticker
+ticket
+ticket's
+ticketed
+ticketing
+tickets
+ticking
+tickle
+tickled
+tickles
+tickling
+ticklish
+ticks
+tidal
+tidbits
+tide
+tidy
+tie
+tier
+ties
+tiffens
+tiger
+tiger's
+tigers
+tigger
+tigger's
+tightwad
+tightwads
+tiki
+tikis
+til
+tile
+tiled
+tiles
+till
+tilled
+tiller
+tillers
+tilling
+tills
+tilly
+tim
+timberleaf
+timbers
+timberwolves
+timbre
+time
+timed
+timeless
+timelords
+timely
+timeout
+timer
+timers
+times
+timing
+timings
+timon
+timon's
+timons
+timothy
+tin
+tina
+tina's
+tindera
+tink
+tink's
+tinker
+tinker's
+tinkerbell
+tinkerbell's
+tinkling
+tinny
+tinsel
+tint
+tinted
+tints
+tiny
+tip
+tip-top
+tipped
+tipping
+tips
+tipton
+tire
+tired
+tires
+tiring
+tisdale
+tish's
+titan
+titan's
+titanic
+titans
+title
+titles
+tkp
+tlc
+tm
+tnt
+tnts
+to
+toad
+toads
+toadstool
+toadstool-drying
+toadstools
+toast
+toasted
+toasting
+toasty
+tobasu
+tobias
+toboggan
+tobogganing
+toboggans
+toby
+todas
+today
+today's
+todd
+todd's
+toddler
+toe
+toe's
+toed
+toehooks
+toes
+tofu
+together
+toggle
+toggler
+token
+tokens
+told
+tolerable
+toll
+tom
+tom's
+tom-tom's
+tomas
+tomboy
+tomorrow
+tomorrow's
+tomorrowland
+tomorrowland's
+tomorrows
+tomswordfish
+ton
+tone
+toner
+tones
+tong
+tongs
+tonic
+tonics
+tonight
+toning
+tonite
+tons
+tony
+too
+toodles
+took
+tool
+toolbelt
+tooled
+tooler
+toolers
+tooling
+tools
+toolshed
+toon
+toon's
+toon-tastic
+toon-torial
+toon-up
+toon-ups
+toonacious
+toonerific
+toons
+toonscape
+toontanic
+toontask
+toontasks
+toontastic
+toontorial
+toontown
+toonup
+toonups
+tooth
+toothless
+toothpaste
+toothpick
+tootie
+tootles
+top
+top-ranking
+topaz
+topiary
+topic
+topics
+tops
+topsy
+topsy-turvy
+toptoon
+toptoons
+tor
+torga
+torgallup
+torgazar
+tori
+tormenta
+torn
+tortaba
+tortaire
+tortana
+torth
+tortoises
+tortos
+tos
+tosis
+toss
+tossed
+tosses
+tossing
+total
+total's
+totaled
+totaling
+totally
+totals
+tote
+totem
+totems
+tots
+toucan
+toucans
+touchdown
+touchdowns
+touge
+tough
+tough-skinned
+toughest
+toughness
+toupee
+toupees
+tour
+toured
+tourer
+tourguide
+touring
+tournament
+tournaments
+tours
+tow
+tow-mater
+toward
+towardly
+towards
+tower
+towered
+towering
+towers
+towing
+town
+town's
+towner
+towns
+townsend
+townsfolk
+townsperson
+tows
+toy
+toyer
+toying
+toys
+tp
+trace
+track
+track's
+tracked
+tracker
+trackers
+tracking
+tracks
+tracy
+trade
+tradeable
+traded
+trader
+traders
+trades
+trading
+traditions
+traffic
+traffic's
+traffics
+trail
+trailed
+trailer
+trailers
+trailing
+trailings
+trails
+train
+trained
+trainer
+trainers
+training
+trains
+trampoline
+trampolines
+tranquil
+transfer
+transfer's
+transfered
+transferred
+transferring
+transfers
+translate
+translated
+translates
+translating
+transom
+transparent
+transport
+transportation
+transported
+transporter
+transporters
+transporting
+transports
+trap
+trap's
+trapdoor
+trapdoors
+trapeze
+trapped
+traps
+trash
+trashcan
+trashcan's
+trashcans
+travel
+traveled
+traveler
+traveling
+travelling
+travels
+tray
+treacherous
+treachery
+treachery's
+tread
+treaded
+treading
+treads
+treasure
+treasurechest
+treasurechests
+treasured
+treasuremaps
+treasurer
+treasures
+treasuring
+treat
+treated
+treater
+treaters
+treating
+treatment
+treats
+treble
+tree
+tree-bark-grading
+tree-picking
+tree-picking-talent
+treehouse
+treehouses
+trees
+treetop
+trek
+trellis
+tremendous
+tremor
+trend
+trending
+trends
+trent
+trent's
+trespass
+trespasser
+tri-barrel
+tri-lock
+triad
+trial
+trial's
+trials
+triangle
+tribal
+tribe
+tribulation
+tribulations
+trick
+tricked
+tricked-out
+tricker
+tricking
+tricks
+tricky
+tried
+trier
+triers
+tries
+trifle
+triggerfish
+trike
+trim
+trims
+trinity
+trinket
+trinkets
+trio
+trip
+trip's
+triple
+triplets
+triply
+trippin'
+trippy
+trips
+tristan
+triton
+triton's
+tritons
+triumph
+triumphant
+trivia
+trixie
+troga
+trogallup
+trogazar
+trogdor
+trolley
+trolleys
+trolls
+trolly
+tron
+troop
+trophies
+trophy
+trophys
+tropic
+tropic's
+tropical
+tropically
+tropics
+trot
+troubadours
+trouble
+troubled
+troublemaker
+troubler
+troubles
+troubling
+trough
+troughes
+trounce
+trounce'em
+trout
+trove
+trowels
+troy
+truchemistry
+truck
+truckloads
+true
+trued
+truehound's
+truer
+trues
+truest
+truetosilver
+truffle
+trufflehunter
+trufflehunter's
+trufflehunters
+truffles
+truigos
+truing
+truly
+trumpet
+trumpets
+trumpkin
+trunk
+trunk's
+trunked
+trunkfish
+trunks
+truscott
+trust
+trusted
+truster
+trusting
+trusts
+trustworthy
+trusty
+truth
+truths
+try
+tryin
+trying
+tt
+ttc
+ttcentral
+ttfn
+tthe
+ttyl
+tub
+tuba
+tubas
+tube
+tuber
+tubes
+tubs
+tuesday
+tuft
+tufts
+tug-o-war
+tug-of-war
+tugs
+tulip
+tulips
+tumble
+tumbly
+tumnus
+tuna
+tunas
+tune
+tune-licious
+tuned
+tuner
+tuners
+tunes
+tuning
+tunnel
+tunneled
+tunneling
+tunnels
+turbo
+turbojugend
+turbonegro
+turf
+turk
+turkey
+turkish
+turn
+turnbull
+turnbull's
+turned
+turner
+turners
+turning
+turnings
+turnip
+turnover
+turnovers
+turns
+turnstile
+turnstiles
+turqouise
+turquoise
+turret
+turtle
+turtles
+turvey
+tusk
+tusked
+tut
+tutorial
+tutorials
+tutu
+tutupia
+tuxedo
+tuxedos
+tv
+tv's
+tvs
+twain
+tweebs
+tweedledee
+tweedledum
+tweet
+twenty
+twig
+twiggys
+twigs
+twilight
+twilightclan
+twill
+twin
+twin's
+twinkle
+twinkling
+twire
+twire's
+twirl
+twirls
+twirly
+twist
+twistable
+twisted
+twister
+twisters
+twisting
+twists
+twisty
+ty
+tycoon
+tyme
+type
+type's
+typea
+typed
+typer
+types
+typhoon
+typical
+typing
+tyra
+tyranny
+tyrant
+tyrants
+tyrone
+tyrus
+tyvm
+u
+uber
+uberdog
+uchiha
+ufo
+ugh
+ugo
+ugone
+uh
+ukelele
+ukeleles
+ukulele
+ukuleles
+ultimate
+ultimately
+ultimatum
+ultimoose
+ultra
+ultra-popular
+ultra-smart
+ultracool
+ultralord
+ultramix
+um
+umbrella
+umbrellas
+un-ignore
+unable
+unafraid
+unassuming
+unattractive
+unattune
+unattuned
+unavailable
+unaware
+unbeatable
+unbelievable
+unbelievably
+uncaught
+uncertain
+unchained
+uncharted
+uncle
+uncles
+uncomplicated
+unconcerned
+uncool
+und
+undea
+undefeated
+undemocratic
+under
+underdog
+underdogs
+underground
+underly
+undersea
+understand
+understanding
+understandingly
+understandings
+understands
+understanza
+understood
+understudies
+underwater
+underwing
+undid
+undo
+undoer
+undoes
+undoing
+undoings
+undone
+undying
+uneasily
+unequally
+unexpected
+unexpectedly
+unfaith
+unfamiliar
+unfit
+unforgettable
+unfortunate
+unfortunately
+unfortunates
+unger
+ungrateful
+unguildable
+unguilded
+unhappier
+unhappiest
+unhappily
+unhappiness
+unhappy
+unheard
+unicorn
+unification
+uniforms
+unignore
+unimportance
+union
+unique
+uniques
+unit
+unite
+united
+unity
+universe
+unjoin
+unknowingly
+unknown
+unleash
+unless
+unlikely
+unlimited
+unlock
+unlocked
+unlocking
+unlocks
+unmeant
+unmet
+unnecessary
+unpaid
+unprovided
+unreasonable
+unsaid
+unscramble
+unselfish
+unsocial
+unspent
+unsteady
+unstuck
+untamed
+until
+untold
+untouchable
+untradeable
+untried
+untrustworthy
+untruth
+unused
+unusual
+unusually
+unwritten
+up
+upbeat
+upcoming
+update
+updated
+updates
+updating
+upgrade
+upgraded
+upgrades
+uphill
+upon
+upper
+uppity
+upright
+uproot
+ups
+upset
+upsets
+upside-down
+upstairs
+upstream
+upsy
+uptick
+ur
+urban
+uriah
+urs
+ursatz
+ursula
+ursula's
+ursulas
+us
+usable
+use
+used
+useful
+usefully
+usefulness
+useless
+user
+user's
+users
+uses
+usf
+using
+usual
+usually
+utilities
+utility
+utmost
+utopian
+utter
+uway
+v-8
+v-pos
+v.p.
+v.p.s
+vacation
+vacationed
+vacationing
+vacations
+vachago
+vachilles
+vagrant
+vaild
+vain
+vale
+valentina
+valentine's
+valentoon
+valentoon's
+valentoons
+valheru
+valiant
+valid
+validated
+vallance
+vallenueva
+valley
+valleys
+valor
+valorie
+valuable
+valuables
+value
+valued
+valuer
+valuers
+values
+valuing
+vampire
+vampire's
+vampires
+van
+vane
+vanessa
+vanguard
+vanguards
+vanilla
+vanish
+vanished
+vanishes
+vanishing
+vans
+vapor
+vaporize
+variable
+variable's
+variables
+varied
+varier
+varies
+varieties
+variety
+variety's
+various
+variously
+vary
+varying
+varyings
+vase
+vases
+vasquez
+vast
+vaster
+vastest
+vastly
+vastness
+vaughan
+vault
+vedi
+veer
+vegas
+vege-tables
+vegetable
+vegetables
+vegetarian
+veggie
+veggies
+veggin'
+vehicle
+vehicle's
+vehicles
+veil
+velvet
+velvet-moss
+veni
+venture
+ventures
+venue
+venus
+verdant
+verify
+vermin
+vern
+veronique
+versatile
+version
+versions
+versus
+vertical
+very
+vessel
+vessels
+vest
+vests
+veteran
+veterans
+veterinarian
+veto
+via
+vibe
+vibes
+vibrant
+vici
+vicki
+victor
+victoria
+victories
+victorious
+victory
+victory's
+vida
+vidalia
+video
+videogame
+videoriffic
+videos
+vidia
+vidia's
+view
+viewed
+viewer
+viewers
+viewing
+viewings
+views
+vigilant
+vigor
+viking
+vikings
+vil
+vilakroma
+vilamasta
+vilanox
+vilar
+vile
+village
+village's
+villager
+villages
+villain
+villainous
+villains
+villany
+ville
+vine
+vines
+vintage
+viola
+violas
+violet
+violets
+violin
+violins
+vip
+virtual
+virtually
+virtue
+virulence
+viscous
+vision
+vision's
+visionary
+visioned
+visioning
+visions
+visious
+vista
+vitae
+vitality
+vittles
+viva
+vmk
+vocabulary
+vocal
+vocals
+vocational
+voice
+voiced
+voicer
+voicers
+voices
+voicing
+void
+volatile
+volcanic
+volcano
+volcanoes
+volcanos
+vole
+volley
+volleyball
+voltorn
+volume
+volume's
+volumed
+volumes
+voluming
+volunteer
+volunteered
+volunteering
+volunteers
+voona
+vortex
+vote
+voted
+voter
+voters
+votes
+voting
+votive
+vovage
+vowels
+voyage
+voyager
+voyagers
+voyages
+vp
+vps
+vr
+vroom
+vs
+vs.
+vu
+vulture
+vultures
+vw
+w00t
+w8
+wacky
+wackyville
+wackyzone
+waddle
+waddling
+waddup
+wade
+wag
+wager
+wagered
+wagerin'
+wagerin's
+wagers
+waggly
+wagner's
+wagon
+wagon's
+wagons
+wags
+wahoo
+wai
+wail
+wailing
+wainscoting
+wait
+waited
+waiter
+waiters
+waiting
+waits
+wakaba
+wake
+wake-up
+wake-up-talent
+waked
+waker
+wakes
+waking
+walden
+waldorf
+walk
+walked
+walker
+walkers
+walking
+walks
+wall
+wall's
+wall-e
+wallaberries
+wallaby
+wallace
+walle
+walled
+waller
+wallflower
+walling
+wallop
+wallpaper
+wallpapered
+wallpapering
+wallpapers
+walls
+walnut
+walnut-drumming
+walrus
+walruses
+walt
+walt's
+waltz
+waltzed
+waltzes
+waltzing
+wampum
+wand
+wanded
+wander
+wanderers
+wandies
+wands
+wandy
+wango
+wanna
+wannabe
+want
+wanted
+wanter
+wanting
+wants
+war
+ward
+wardrobe
+wardrobes
+ware
+warehouse
+wares
+waring
+wariors
+warlord
+warlords
+warm
+warmed
+warmer
+warmers
+warmest
+warming
+warmly
+warmongers
+warmonks
+warmth
+warn
+warned
+warner
+warning
+warnings
+warns
+warp
+warped
+warrant
+warrants
+warren
+warrior
+warriors
+warrrr
+wars
+warship
+warship's
+warships
+warships'
+warskulls
+wart
+was
+wash
+washcloths
+washed
+washer
+washers
+washes
+washing
+washings
+washington's
+wasn't
+wasnt
+wasp
+wasp-skin
+wasps
+wassup
+waste
+wasted
+waster
+wasters
+wastes
+wasting
+wat
+watch
+watched
+watcher
+watchers
+watches
+watchin
+watching
+watchings
+water
+water's
+water-talent
+watercooler
+watered
+waterer
+waterers
+waterfall
+waterfall's
+waterfalls
+waterguns
+watering
+watermelon
+watermelons
+waterpark
+waterpark's
+waterparkers
+waterproof
+waters
+waterslide
+waterslide's
+watersliders
+watkins
+wats
+wave
+waved
+waver
+waverly
+wavers
+waverunners
+waves
+waving
+way
+way's
+waylon
+ways
+wayward
+wazup
+wb
+wdig's
+wdw
+we
+we'd
+we'll
+we're
+we've
+we-evil
+weak
+weaken
+weakens
+weaker
+weakest
+weakling
+weakly
+weakness
+wealthy
+wear
+wearer
+wearing
+wears
+weasel
+weasels
+weather
+weathered
+weatherer
+weathering
+weatherly
+weathers
+weave
+weaves
+weaving
+web
+webber
+webepirates
+websight
+website
+webster
+wedding
+weddings
+wednesday
+weeded
+weeding
+weeds
+week
+week's
+weekdays
+weekend
+weekenders
+weekly
+weeks
+wego
+wei
+weigh
+weight
+weights
+weird
+weirded
+weirdings
+weirdo
+weirdos
+weiss
+welch
+welcome
+welcomed
+welcomely
+welcomer
+welcomes
+welcoming
+well
+welled
+welling
+wells
+wenches
+wenchs
+went
+were
+weren't
+werent
+west
+wester
+western
+westerner
+westerners
+westing
+westward
+wet
+wha
+whaddya
+whale
+whalebone
+whaler
+whales
+whaling
+wham
+whammo
+what
+what'cha
+what's
+what-in
+what-the-hey
+whatever
+whatever's
+whats
+wheat
+whee
+wheel
+wheelbarrow
+wheelbarrows
+wheeled
+wheeler
+wheelers
+wheeling
+wheelings
+wheels
+wheezer
+when
+when's
+whenever
+whens
+where
+where's
+wheres
+wherever
+whether
+whew
+which
+whiff
+whiffle
+whiffs
+while
+whiled
+whiles
+whiling
+whimsical
+whimsy
+whining
+whinnie
+whinnie's
+whiny
+whiplash
+whirl
+whirligig
+whirling
+whirlpool
+whirly
+whirr
+whisk
+whisked
+whisker
+whiskers
+whisper
+whispered
+whispering
+whispers
+whistle
+whistled
+whistler's
+whistles
+whistling
+white
+white's
+whitening
+whitestar
+whitewater
+who
+who'd
+who's
+whoa
+whoah
+whodunit
+whoever
+whole
+wholly
+whoop
+whoopee
+whoopie
+whoops
+whoopsie
+whoosh
+whopper
+whopping
+whos
+whose
+why
+wicked
+wicker
+wide
+widely
+wider
+widescreen
+widest
+widget
+widgets
+widow
+width
+wife
+wiffle
+wig
+wigs
+wiidburns
+wilbur
+wild
+wild-n-crazy
+wild7
+wildburns
+wildcat
+wildcats
+wilder
+wilderness
+wildest
+wildfire
+wilding
+wildly
+wildwoods
+will
+willa
+willa's
+willas
+willed
+willer
+william
+williams
+willing
+willings
+willow
+willows
+willpower
+wills
+wilma
+wilt
+wilts
+wimbleweather
+win
+winba
+winbus
+wind
+wind-racer
+windburn
+windcatcher
+winded
+winder
+winders
+winding
+windjammer
+windjammers
+windmane
+windmill
+windmills
+windora
+window
+window's
+windowed
+windowing
+windows
+winds
+windshadow
+windsor
+windswept
+windward
+windy
+wing
+wing-washing
+winged
+winger
+wingers
+winging
+wings
+wink
+winkination
+winkle
+winkle's
+winks
+winky
+winn
+winner
+winner's
+winners
+winnie
+winnie's
+winning
+winnings
+wins
+winter
+winter's
+wintered
+winterer
+wintering
+winterly
+winters
+wipeout
+wire
+wisdom
+wise
+wiseacre
+wiseacre's
+wiseacres
+wisely
+wish
+wished
+wisher
+wishers
+wishes
+wishing
+wispa
+wit
+witch
+witch's
+witches
+witching
+witchy
+with
+withdrawal
+wither
+withers
+within
+without
+witty
+wiz
+wizard
+wizard's
+wizards
+wizrd
+wo
+wobble
+wobbles
+wobbly
+wocka
+woeful
+wok
+woke
+woks
+wolf
+wolf's
+wolfbane
+wolfe
+wolfer
+wolfes
+wolfhearts
+wolfpack
+wolfs
+wolfsbane
+wolves
+woman
+women
+won
+won't
+wonder
+wonderblue
+wondered
+wonderer
+wonderers
+wonderful
+wonderfully
+wondering
+wonderings
+wonderland
+wonderland's
+wonderlands
+wonders
+wondrous
+wont
+woo
+wooded
+woodland
+woodruff
+woods
+woof
+woohoo
+woot
+woozy
+word
+word-licious
+wordbelch
+wordbug
+wordburps
+worddog
+worded
+wordeze
+wordfly
+wordglitch
+wording
+wordlo
+wordmania
+wordmeister
+wordmist
+wordpaths
+words
+words'n'stuff
+wordseek
+wordstinkers
+wordstuff
+wordwings
+wordworks
+wordworms
+woriors
+work
+work's
+worked
+worker
+workers
+workin
+working
+workings
+workout
+works
+works-in-progress
+workshop
+workshops
+world
+world's
+worlds
+worm
+worms
+worn
+worried
+worrier
+worriers
+worries
+worriors
+worry
+worrying
+worse
+worst
+worth
+worthing
+worthy
+wot
+wough
+would
+wouldest
+wouldn't
+wouldnt
+wound
+wound-up
+wounded
+wounding
+wounds
+woven
+wow
+wraith
+wraiths
+wrapper
+wrath
+wreath
+wreathes
+wreaths
+wreck
+wrecked
+wrecking
+wrecking-talents
+wrecks
+wrestling
+wretch
+wriggle
+wright
+wright's
+wringling
+wrinkle
+wrinkled
+wrinkles
+wriot
+write
+writer
+writers
+writes
+writing
+writings
+written
+wrong
+wronged
+wronger
+wrongest
+wronging
+wrongly
+wrongs
+wrote
+wtg
+wut
+wwod
+www.dgamer.com
+www.disney.go.com/dxd
+www.disneyfairies.com
+www.piratesonline.com
+www.toontown.com
+wyatt's
+wyda
+wynken
+wynn
+wynne
+wysteria
+x-shop
+x-tremely
+xbox
+xd
+xd-buy
+xdash
+xdeals
+xder
+xdibs
+xdig
+xdirect
+xdoer
+xdome
+xdot
+xdough
+xdrive
+xem
+xenops
+xiamen's
+xii
+xiii
+xmas
+xpedition
+xpend
+xpert
+xsentials
+xtra
+xtraordinary
+xtreme
+xtremely
+y'er
+ya
+ya'll
+yaarrrgghh
+yacht
+yacht's
+yachting
+yachts
+yackety-yak
+yah
+yalarad
+yall
+yang
+yang's
+yanni
+yar
+yard
+yard's
+yardage
+yardarm
+yarded
+yarding
+yards
+yardwork
+yarn
+yarr
+yarrow
+yarrr
+yasmine
+yavn
+yawn
+yawner
+yawning
+yawns
+yay
+ye
+ye'll
+ye're
+ye've
+yea
+yeah
+year
+year's
+yearbook
+yee
+yee-haw
+yeehah
+yeehaw
+yeh
+yell
+yelled
+yeller
+yelling
+yellow
+yellow-green
+yellow-orange
+yellow-shelled
+yells
+yelp
+yensid
+yensid's
+yep
+yeppers
+yer
+yerself
+yes
+yeses
+yesman
+yesmen
+yesterday
+yet
+yeti
+yeti's
+yetis
+yets
+yield
+yikes
+yin
+ying
+yippee
+yo
+yogurt
+yom
+yoo
+york
+you
+you'd
+you'll
+you're
+you've
+youd
+youll
+young
+your
+your's
+youre
+yours
+yourself
+youth
+youve
+yow
+yowl
+yoyo
+yuck
+yucks
+yufalla
+yuki
+yuki's
+yukon
+yummy
+yup
+yw
+yzma
+zaamaros
+zaamaru
+zaapi
+zabuton
+zabutons
+zac
+zach
+zack
+zack's
+zamboni
+zambonis
+zanes
+zangetsu
+zany
+zanzibarbarians
+zaoran
+zap
+zari
+zart
+zazu
+zazu's
+zazus
+zazzle
+zealous
+zebra
+zebra's
+zebras
+zeddars
+zeke
+zen
+zenon
+zephyr
+zeppelin
+zeragong
+zero
+zero-gravity
+zesty
+zeus
+zhilo
+ziba
+zigeunermusik
+ziggurat
+ziggy
+ziggy's
+zigzag
+zillion
+zimmer
+zing
+zinger
+zingers
+zinnia
+zinnias
+zip-a-dee-doo-dah
+zippity
+zippy
+zippy's
+zither
+zithers
+zizzle
+zombats
+zone
+zoner
+zones
+zonk
+zonked
+zonks
+zoo
+zoo's
+zooks
+zoology
+zoom
+zoos
+zoot
+zorna
+zorro
+zowie
+zoza
+zozane
+zozanero
+zulu
+zurg
+zuzu
+zydeco
+zyra
+zyrdrake
+zyrgazelle
+zyyk
+zzz
+zzzzzs
diff --git a/toontown/src/classicchars/.cvsignore b/toontown/src/classicchars/.cvsignore
new file mode 100644
index 0000000..985f113
--- /dev/null
+++ b/toontown/src/classicchars/.cvsignore
@@ -0,0 +1,4 @@
+.cvsignore
+Makefile
+pp.dep
+*.pyc
diff --git a/toontown/src/classicchars/CCharChatter.py b/toontown/src/classicchars/CCharChatter.py
new file mode 100644
index 0000000..efd961a
--- /dev/null
+++ b/toontown/src/classicchars/CCharChatter.py
@@ -0,0 +1,251 @@
+
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownGlobals
+
+GREETING = 0
+COMMENT = 1
+GOODBYE = 2
+
+DaisyChatter = TTLocalizer.DaisyChatter
+MickeyChatter = TTLocalizer.MickeyChatter
+VampireMickeyChatter = TTLocalizer.VampireMickeyChatter
+MinnieChatter = TTLocalizer.MinnieChatter
+GoofyChatter = TTLocalizer.GoofyChatter
+GoofySpeedwayChatter = TTLocalizer.GoofySpeedwayChatter
+DonaldChatter = TTLocalizer.DonaldChatter
+ChipChatter = TTLocalizer.ChipChatter
+DaleChatter = TTLocalizer.DaleChatter
+
+def getChatter( charName, chatterType ):
+ if charName==TTLocalizer.Mickey:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFMickeyChatter
+ elif chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterMickeyCChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS:
+ return TTLocalizer.WinterMickeyDChatter
+ elif chatterType == ToontownGlobals.VALENTINES_DAY:
+ return TTLocalizer.ValentinesMickeyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_ONE:
+ SillyMickeyChatter = MickeyChatter
+ SillyMickeyChatter[1].extend(TTLocalizer.SillyPhase1Chatter)
+ return SillyMickeyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_TWO:
+ SillyMickeyChatter = MickeyChatter
+ SillyMickeyChatter[1].extend(TTLocalizer.SillyPhase2Chatter)
+ return SillyMickeyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_THREE:
+ SillyMickeyChatter = MickeyChatter
+ SillyMickeyChatter[1].extend(TTLocalizer.SillyPhase3Chatter)
+ return SillyMickeyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_FOUR:
+ SillyMickeyChatter = MickeyChatter
+ SillyMickeyChatter[1].extend(TTLocalizer.SillyPhase4Chatter)
+ return SillyMickeyChatter
+ else:
+ return MickeyChatter
+
+ elif charName==TTLocalizer.VampireMickey:
+ return VampireMickeyChatter
+
+ elif charName==TTLocalizer.Minnie:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFMinnieChatter
+ elif chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterMinnieCChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS:
+ return TTLocalizer.WinterMinnieDChatter
+ elif chatterType == ToontownGlobals.VALENTINES_DAY:
+ return TTLocalizer.ValentinesMinnieChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_ONE:
+ SillyMinnieChatter = MinnieChatter
+ SillyMinnieChatter[1].extend(TTLocalizer.SillyPhase1Chatter)
+ return SillyMinnieChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_TWO:
+ SillyMinnieChatter = MinnieChatter
+ SillyMinnieChatter[1].extend(TTLocalizer.SillyPhase2Chatter)
+ return SillyMinnieChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_THREE:
+ SillyMinnieChatter = MinnieChatter
+ SillyMinnieChatter[1].extend(TTLocalizer.SillyPhase3Chatter)
+ return SillyMinnieChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_FOUR:
+ SillyMinnieChatter = MinnieChatter
+ SillyMinnieChatter[1].extend(TTLocalizer.SillyPhase4Chatter)
+ return SillyMinnieChatter
+ else:
+ return MinnieChatter
+
+ elif charName == TTLocalizer.WitchMinnie:
+ return TTLocalizer.WitchMinnieChatter
+
+ elif charName==TTLocalizer.Daisy:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFDaisyChatter
+ elif chatterType == ToontownGlobals.HALLOWEEN_COSTUMES:
+ return TTLocalizer.HalloweenDaisyChatter
+ elif chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterDaisyCChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS:
+ return TTLocalizer.WinterDaisyDChatter
+ elif chatterType == ToontownGlobals.VALENTINES_DAY:
+ return TTLocalizer.ValentinesDaisyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_ONE:
+ SillyDaisyChatter = DaisyChatter
+ SillyDaisyChatter[1].extend(TTLocalizer.SillyPhase1Chatter)
+ return SillyDaisyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_TWO:
+ SillyDaisyChatter = DaisyChatter
+ SillyDaisyChatter[1].extend(TTLocalizer.SillyPhase2Chatter)
+ return SillyDaisyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_THREE:
+ SillyDaisyChatter = DaisyChatter
+ SillyDaisyChatter[1].extend(TTLocalizer.SillyPhase3Chatter)
+ return SillyDaisyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_FOUR:
+ SillyDaisyChatter = DaisyChatter
+ SillyDaisyChatter[1].extend(TTLocalizer.SillyPhase4Chatter)
+ return SillyDaisyChatter
+ else:
+ return DaisyChatter
+
+ elif charName==TTLocalizer.Goofy:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFGoofySpeedwayChatter
+ elif chatterType == ToontownGlobals.CRASHED_LEADERBOARD:
+ return TTLocalizer.CLGoofySpeedwayChatter
+ elif chatterType == ToontownGlobals.CIRCUIT_RACING_EVENT:
+ return TTLocalizer.GPGoofySpeedwayChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS \
+ or chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterGoofyChatter
+ elif chatterType == ToontownGlobals.VALENTINES_DAY:
+ return TTLocalizer.ValentinesGoofyChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_ONE:
+ SillyGoofySpeedwayChatter = GoofySpeedwayChatter
+ SillyGoofySpeedwayChatter[1].extend(TTLocalizer.SillyPhase1Chatter)
+ return SillyGoofySpeedwayChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_TWO:
+ SillyGoofySpeedwayChatter = GoofySpeedwayChatter
+ SillyGoofySpeedwayChatter[1].extend(TTLocalizer.SillyPhase2Chatter)
+ return SillyGoofySpeedwayChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_THREE:
+ SillyGoofySpeedwayChatter = GoofySpeedwayChatter
+ SillyGoofySpeedwayChatter[1].extend(TTLocalizer.SillyPhase3Chatter)
+ return SillyGoofySpeedwayChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_FOUR:
+ SillyGoofySpeedwayChatter = GoofySpeedwayChatter
+ SillyGoofySpeedwayChatter[1].extend(TTLocalizer.SillyPhase4Chatter)
+ return SillyGoofySpeedwayChatter
+ else:
+ return GoofySpeedwayChatter
+
+ elif charName==TTLocalizer.SuperGoofy:
+ return TTLocalizer.SuperGoofyChatter
+
+ elif charName==TTLocalizer.Donald:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFDonaldChatter
+ elif chatterType == ToontownGlobals.HALLOWEEN_COSTUMES:
+ return TTLocalizer.HalloweenDreamlandChatter
+ elif chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterDreamlandCChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS:
+ return TTLocalizer.WinterDreamlandDChatter
+ elif chatterType == ToontownGlobals.VALENTINES_DAY:
+ return TTLocalizer.ValentinesDreamlandChatter
+ else:
+ return DonaldChatter
+
+ elif charName==TTLocalizer.DonaldDock:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFDonaldDockChatter
+ elif chatterType == ToontownGlobals.HALLOWEEN_COSTUMES:
+ return TTLocalizer.HalloweenDonaldChatter
+ elif chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterDonaldCChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS:
+ return TTLocalizer.WinterDonaldDChatter
+ elif chatterType == ToontownGlobals.VALENTINES_DAY:
+ return TTLocalizer.ValentinesDonaldChatter
+ else:
+ return None
+
+ elif charName==TTLocalizer.Pluto:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFPlutoChatter
+ elif chatterType == ToontownGlobals.HALLOWEEN_COSTUMES:
+ return TTLocalizer.WesternPlutoChatter
+ elif chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterPlutoCChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS:
+ return TTLocalizer.WinterPlutoDChatter
+ else:
+ # Pluto don't play that!
+ return None
+
+ elif charName==TTLocalizer.WesternPluto:
+ if chatterType == ToontownGlobals.HALLOWEEN_COSTUMES:
+ return TTLocalizer.WesternPlutoChatter
+ else:
+ return None
+
+ elif charName == TTLocalizer.Chip:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFChipChatter
+ elif chatterType == ToontownGlobals.HALLOWEEN_COSTUMES:
+ return TTLocalizer.HalloweenChipChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS \
+ or chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterChipChatter
+ elif chatterType == ToontownGlobals.VALENTINES_DAY:
+ return TTLocalizer.ValentinesChipChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_ONE:
+ SillyChipChatter = ChipChatter
+ SillyChipChatter[1].extend(TTLocalizer.SillyPhase1Chatter)
+ return SillyChipChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_TWO:
+ SillyChipChatter = ChipChatter
+ SillyChipChatter[1].extend(TTLocalizer.SillyPhase2Chatter)
+ return SillyChipChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_THREE:
+ SillyChipChatter = ChipChatter
+ SillyChipChatter[1].extend(TTLocalizer.SillyPhase3Chatter)
+ return SillyChipChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_FOUR:
+ SillyChipChatter = ChipChatter
+ SillyChipChatter[1].extend(TTLocalizer.SillyPhase4Chatter)
+ return SillyChipChatter
+ else:
+ return ChipChatter
+
+ elif charName == TTLocalizer.Dale:
+ if chatterType == ToontownGlobals.APRIL_FOOLS_COSTUMES:
+ return TTLocalizer.AFDaleChatter
+ elif chatterType == ToontownGlobals.HALLOWEEN_COSTUMES:
+ return TTLocalizer.HalloweenDaleChatter
+ elif chatterType == ToontownGlobals.WINTER_DECORATIONS \
+ or chatterType == ToontownGlobals.WINTER_CAROLING:
+ return TTLocalizer.WinterDaleChatter
+ elif chatterType == ToontownGlobals.VALENTINES_DAY:
+ return TTLocalizer.ValentinesDaleChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_ONE:
+ SillyDaleChatter = DaleChatter
+ SillyDaleChatter[1].extend(TTLocalizer.SillyPhase1Chatter)
+ return SillyDaleChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_TWO:
+ SillyDaleChatter = DaleChatter
+ SillyDaleChatter[1].extend(TTLocalizer.SillyPhase2Chatter)
+ return SillyDaleChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_THREE:
+ SillyDaleChatter = DaleChatter
+ SillyDaleChatter[1].extend(TTLocalizer.SillyPhase3Chatter)
+ return SillyDaleChatter
+ elif chatterType == ToontownGlobals.SILLY_CHATTER_FOUR:
+ SillyDaleChatter = DaleChatter
+ SillyDaleChatter[1].extend(TTLocalizer.SillyPhase4Chatter)
+ return SillyDaleChatter
+ else:
+ return DaleChatter
+ else:
+ assert 0, "Unknown chatter information"
diff --git a/toontown/src/classicchars/CCharPaths.py b/toontown/src/classicchars/CCharPaths.py
new file mode 100644
index 0000000..145a67f
--- /dev/null
+++ b/toontown/src/classicchars/CCharPaths.py
@@ -0,0 +1,656 @@
+from pandac.PandaModules import Point3
+from pandac.PandaModules import Vec3
+import copy
+from toontown.toonbase import TTLocalizer
+
+__mickeyPaths = {
+ # for each node:
+ # (
+ # pos, # position of node
+ # (
+ # adjacent node,
+ # adjacent node,
+ # ...
+ # )
+ # )
+ # TODO: redo these when the safe zone is final
+ 'a' : (Point3(17, -17, 4.025),
+ ('b','e')
+ ),
+ 'b' : (Point3(17.5, 7.6, 4.025),
+ ('c','e')
+ ),
+ 'c' : (Point3(85, 11.5, 4.025),
+ ('d',)
+ ),
+ 'd' : (Point3(85, -13, 4.025),
+ ('a',)
+ ),
+ 'e' : (Point3(-27.5, -5.25, 0.0), # bottom of central steps
+ ('a','b','f')
+ ),
+ 'f' : (Point3(-106.15, -4.0, -2.5), # end of bridge (opposite gazebo)
+ ('e','g','h','i',)
+ ),
+ 'g' : (Point3(-89.5, 93.5, 0.5), # sidewalk just South of Punchline Pl.
+ ('f','h'),
+ ),
+ 'h' : (Point3(-139.95, 1.69, 0.5), # sidewalk in front of Loopy Ln.
+ ('f','g','i',)
+ ),
+ 'i' : (Point3(-110.95, -68.57, 0.5), # sidewalk in front of Loopy Ln.
+ ('f','h',)
+ ),
+ }
+
+# note to self: singleton tuple requires comma: (item,)
+# this table holds the waypoints between walk nodes,
+# if any, the 3 value in for each edge is 0 if raycast
+# is not needed, 1 if raycast is needed
+__mickeyWaypoints = (
+ # from, to, waypoints
+ ('a','e',1,[]),
+ ('b','e',1,[]),
+ ('e','f',1,[Point3(-76.87, -7.85, -1.85),
+ Point3(-80.57, -4.0, -1.85),
+ ]),
+ ('f','g',1,[Point3(-106.62, 28.65, -1.5),]),
+ ('g','h',1,[Point3(-128.38, 60.27, 0.5),]),
+ #('g','h',1,[Point3(-134.96, 60.34, 0.5),]),
+ ('h','f',1,[]),
+ ('h','i',1,[Point3(-137.13, -42.79, 0.5),]),
+ ('i','f',1,[]),
+ )
+
+__minniePaths = {
+ # for each node:
+ # (
+ # pos, # position of node
+ # (
+ # adjacent node,
+ # adjacent node,
+ # ...
+ # )
+ # )
+ 'a' : (Point3(53.334,71.057,6.525), # in front of horn, near TTCentral entrance
+ ('b','r')
+ ),
+ 'b' : (Point3(127.756,58.665,-11.75), # on other side of horn
+ ('a','s','c')
+ ),
+ 'c' : (Point3(130.325,15.174,-2.003), # on piano keys
+ ('b','d')
+ ),
+ 'd' : (Point3(126.173,7.057,0.522), # higher on piano keys
+ ('c','e')
+ ),
+ 'e' : (Point3(133.843,-6.618,4.710), # higher on piano keys
+ ('d','f','g','h')
+ ),
+ 'f' : (Point3(116.876,1.119,3.304), # on drum
+ ('e')
+ ),
+ 'g' : (Point3(116.271,-41.568,3.304), # on middle drum
+ ('e','h')
+ ),
+ 'h' : (Point3(128.983,-49.656,-0.231), # on piano keys
+ ('e','g','i','j')
+ ),
+ 'i' : (Point3(106.024,-75.249,-4.498), # on other drum
+ ('h')
+ ),
+ 'j' : (Point3(135.016,-93.072,-13.376), # on ground by 2nd horn
+ ('h','k','z')
+ ),
+ 'k' : (Point3(123.966,-100.242,-10.879), # on keys for 2nd horn
+ ('j','l')
+ ),
+ 'l' : (Point3(52.859,-109.081,6.525), # on other side of 2nd horn
+ ('k','m')
+ ),
+ 'm' : (Point3(-32.071,-107.049,6.525), # on other side of Dreamland entrance
+ ('l','n')
+ ),
+ 'n' : (Point3(-40.519,-99.685,6.525), # stop at record player
+ ('m','o')
+ ),
+ 'o' : (Point3(-40.245, -88.634, 6.525 ), # further around upper area of safezone
+ ('n','p')
+ ),
+ 'p' : (Point3(-66.300, -62.192, 6.525 ), # near party gatefurther around upper area of safezone
+ ('o','q')
+ ),
+ 'q' : (Point3(-66.212, 23.069, 6.525), # further around upper area of safezone
+ ('p','r')
+ ),
+ 'r' : (Point3(-18.344,69.532,6.525), # at last turn on upper area of safezone
+ ('q','a')
+ ),
+ 's' : (Point3(91.357,44.546,-13.475), # on ground between piano and center area
+ ('b','t')
+ ),
+ 't' : (Point3(90.355, 6.279, -13.475), # in center area near piano
+ ('s','u')
+ ),
+ 'u' : (Point3(-13.765,42.362,-14.553), # in center area
+ ('t','v')
+ ),
+ 'v' : (Point3(-52.627,7.428,-14.553), # in center area opposite piano
+ ('u','w')
+ ),
+ 'w' : (Point3(-50.654,-54.879,-14.553), # in center area opposite piano
+ ('v','x')
+ ),
+ 'x' : (Point3(-3.711,-81.819,-14.553), # in center area
+ ('w','y')
+ ),
+ 'y' : (Point3(90.777, -49.714, -13.475),
+ ('z','x')
+ ),
+ 'z' : (Point3(90.059, -79.426, -13.475),
+ ('j','y')
+ ),
+ }
+
+__minnieWaypoints = (
+ ('a','b',1,[]),
+ ('k','l',1,[]),
+ ('b','c',1,[]),
+ ('c','d',1,[]),
+ ('d','e',1,[]),
+ ('e','f',1,[]),
+ ('e','g',1,[]),
+ ('e','h',1,[]),
+ ('g','h',1,[]),
+ ('h','i',1,[]),
+ ('h','j',1,[]),
+ ('s','b',1,[]),
+ ('t','u',1,[]), # curb down
+ ('x','y',1,[]), # curb up
+ )
+
+__goofyPaths = {
+ # for each node:
+ # (
+ # pos, # position of node
+ # (
+ # adjacent node,
+ # adjacent node,
+ # ...
+ # )
+ # )
+ 'a' : (Point3(64.995,169.665,10.027), # in front of TTCentral entrance
+ ('b','q')
+ ),
+ 'b' : (Point3(48.893,208.912,10.027), # by flowers
+ ('a','c')
+ ),
+ 'c' : (Point3(5.482,210.479,10.030), # in front of trolley
+ ('b','d')
+ ),
+ 'd' : (Point3(-34.153,203.284,10.029), # near construction zone entrance
+ ('c','e')
+ ),
+ 'e' : (Point3(-66.656,174.334,10.026), # front construction zone entrance
+ ('d','f')
+ ),
+ 'f' : (Point3(-55.994,162.330,10.026), # top of ramp
+ ('e','g')
+ ),
+ 'g' : (Point3(-84.554,142.099,0.027), # down below ramp
+ ('f','h')
+ ),
+ 'h' : (Point3(-92.215,96.446,0.027), # toward flower bed
+ ('g','i')
+ ),
+ 'i' : (Point3(-63.168,60.055,0.027), # in front of flower bed
+ ('h','j')
+ ),
+ 'j' : (Point3(-37.637,69.974,0.027), # next to bush
+ ('i','k')
+ ),
+ 'k' : (Point3(-3.018,26.157,0.027), # front of Cog HQ entrance
+ ('j','l','m')
+ ),
+ 'l' : (Point3(-0.711,46.843,0.027), # next to fountain
+ ('k')
+ ),
+ 'm' : (Point3(26.071,46.401,0.027), # next to pond
+ ('k','n')
+ ),
+ 'n' : (Point3(30.870,67.432,0.027), # next to bush
+ ('m','o')
+ ),
+ 'o' : (Point3(93.903,90.685,0.027), # toward ramp
+ ('n','p')
+ ),
+ 'p' : (Point3(88.129,140.575,0.027), # below ramp
+ ('o','q')
+ ),
+ 'q' : (Point3(53.988,158.232,10.027), # top of ramp
+ ('p','a')
+ ),
+ }
+
+__goofyWaypoints = (
+ # from, to, waypoints
+ ('f','g',1,[]),
+ ('p','q',1,[])
+ )
+
+__goofySpeedwayPaths = {
+ # for each node:
+ # (
+ # pos, # position of node
+ # (
+ # adjacent node,
+ # adjacent node,
+ # ...
+ # )
+ # )
+ 'a' : (Point3(-9.0,-19.517,-0.323), # near store rear entrance
+ ('b','k')
+ ),
+ 'b' : (Point3(-30.047,-1.578,-0.373), # by giant wrenches
+ ('a','c')
+ ),
+ 'c' : (Point3(-10.367,49.042,-0.373), # in front of TTC entrance
+ ('b','d')
+ ),
+ 'd' : (Point3(38.439,44.348,-0.373), # near car showoff platform
+ ('c','e')
+ ),
+ 'e' : (Point3(25.527,-2.395,-0.373), # near giant tires
+ ('d','f')
+ ),
+ 'f' : (Point3(-4.043,-59.865,-0.003), # in tunnel to track area
+ ('e','g')
+ ),
+ 'g' : (Point3(0.390,-99.475,-0.009), # in front of leaderboard
+ ('f','h')
+ ),
+ 'h' : (Point3(21.147,-109.127,-0.013), # near city race track
+ ('g','i')
+ ),
+ 'i' : (Point3(5.981,-147.606,-0.013), # near stadium race track
+ ('h','j')
+ ),
+ 'j' : (Point3(-24.898,-120.618,-0.013), # near rural race track
+ ('i','k')
+ ),
+ 'k' : (Point3(-2.710,-90.315,-0.011), # near tunnel to kart shop
+ ('j','a')
+ ),
+ }
+
+__goofySpeedwayWaypoints = (
+ # from, to, waypoints
+ ('a','k',1,[]),
+ ('k','a',1,[])
+ )
+
+__donaldPaths = {
+ # for each node:
+ # (
+ # pos, # position of node
+ # (
+ # adjacent node,
+ # adjacent node,
+ # ...
+ # )
+ # )
+ 'a' : (Point3(-94.883,-94.024,0.025), # corner near melodyland entrance
+ ('b')
+ ),
+ 'b' : (Point3(-13.962,-92.233,0.025), # front of melodyland entrance
+ ('a','h')
+ ),
+ 'c' : (Point3(68.417,-91.929,0.025), # by trolley
+ ('m','g')
+ ),
+ 'd' : (Point3(68.745,91.227,0.025), # across bed from trolley
+ ('k','i')
+ ),
+ 'e' : (Point3(4.047,94.260,0.025), # front of cog hq. entrance
+ ('i','j')
+ ),
+ 'f' : (Point3(-91.271,90.987,0.025), # corner near cog hq. entrance
+ ('j')
+ ),
+ 'g' : (Point3(43.824,-94.129,0.025), # in front of trolley
+ ('c','h')
+ ),
+ 'h' : (Point3(13.905,-91.334,0.025), # near melodyland entrance
+ ('b','g')
+ ),
+ 'i' : (Point3(43.062,88.152,0.025), # near cog hq. entrance
+ ('d','e')
+ ),
+ 'j' : (Point3(-48.960,88.565,0.025), # near cog hq. entrance
+ ('e','f')
+ ),
+ 'k' : (Point3(75.118, 52.840, -16.620), # north of party gate
+ ('d','l')
+ ),
+ 'l' : (Point3(44.677, 27.091, -15.385 ), # west of party gate
+ ('k','m')
+ ),
+ 'm' : (Point3(77.009, -16.022, -14.975 ), # south of party gate
+ ('l','c')
+ ),
+ }
+
+__donaldWaypoints = (
+ # from, to, waypoints
+ ('d','k',1,[]),
+ ('k','l',1,[]),
+ ('l','m',1,[]),
+ ('m', 'c', 1, []),
+ ('b','a',1,[Point3(-55.883,-89.0,0.025),]),
+ )
+
+__plutoPaths = {
+ # for each node:
+ # (
+ # pos, # position of node
+ # (
+ # adjacent node,
+ # adjacent node,
+ # ...
+ # )
+ # )
+ 'a' : (Point3(-110.0,-37.8,8.6), # on mound near 'North Pole'
+ ('b','c')
+ ),
+ 'b' : (Point3(-11.9,-128.2,6.2), # near entrance to sleet street
+ ('a','c')
+ ),
+ 'c' : (Point3(48.9,-14.4,6.2), # near entrance to walrus way
+ ('b','a','d')
+ ),
+ 'd' : (Point3(0.25,80.5,6.2), # near entrance to Cog HQ
+ ('c','e')
+ ),
+ 'e' : (Point3(-83.3,36.1,6.2), # near the Toon HQ igloo
+ ('d','a')
+ ),
+ }
+
+__plutoWaypoints = (
+ ('a','b',1,[Point3(-90.4,-57.2,3.0),
+ Point3(-63.6,-79.8,3.0),
+ Point3(-50.1,-89.1,6.2),]),
+ ('c','a',1,[Point3(-15.6,-25.6,6.2),
+ Point3(-37.5,-38.5,3.0),
+ Point3(-55.0,-55.0,3.0),
+ Point3(-85.0,-46.4,3.0)]),
+ ('d','e',0,[Point3(-25.8,60.,6.2),
+ Point3(-61.9,64.5,6.2)]),
+ ('e','a',1,[Point3(-77.2,28.5,6.2),
+ Point3(-76.4,12.0,3.0),
+ Point3(-93.2,-21.2,3.0),]),
+
+ )
+
+
+__daisyPaths = {
+ # for each node:
+ # (
+ # pos, # position of node
+ # (
+ # adjacent node,
+ # adjacent node,
+ # ...
+ # )
+ # )
+ 'a' : (Point3(64.995,169.665,10.027), # in front of TTCentral entrance
+ ('b','q')
+ ),
+ 'b' : (Point3(48.893,208.912,10.027), # by flowers
+ ('a','c')
+ ),
+ 'c' : (Point3(5.482,210.479,10.030), # in front of trolley
+ ('b','d')
+ ),
+ 'd' : (Point3(-34.153,203.284,10.029), # near construction zone entrance
+ ('c','e')
+ ),
+ 'e' : (Point3(-66.656,174.334,10.026), # front construction zone entrance
+ ('d','f')
+ ),
+ 'f' : (Point3(-55.994,162.330,10.026), # top of ramp
+ ('e','g')
+ ),
+ 'g' : (Point3(-84.554,142.099,0.027), # down below ramp
+ ('f','h')
+ ),
+ 'h' : (Point3(-92.215,96.446,0.027), # toward flower bed
+ ('g','i')
+ ),
+ 'i' : (Point3(-63.168,60.055,0.027), # in front of flower bed
+ ('h','j')
+ ),
+ 'j' : (Point3(-37.637,69.974,0.027), # next to bush
+ ('i','k')
+ ),
+ 'k' : (Point3(-3.018,26.157,0.027), # front of Cog HQ entrance
+ ('j','l','m')
+ ),
+ 'l' : (Point3(-0.711,46.843,0.027), # next to fountain
+ ('k')
+ ),
+ 'm' : (Point3(26.071,46.401,0.027), # next to pond
+ ('k','n')
+ ),
+ 'n' : (Point3(30.870,67.432,0.027), # next to bush
+ ('m','o')
+ ),
+ 'o' : (Point3(93.903,90.685,0.027), # toward ramp
+ ('n','p')
+ ),
+ 'p' : (Point3(88.129,140.575,0.027), # below ramp
+ ('o','q')
+ ),
+ 'q' : (Point3(53.988,158.232,10.027), # top of ramp
+ ('p','a')
+ ),
+ }
+
+__daisyWaypoints = (
+ # from, to, waypoints
+ ('f','g',1,[]),
+ ('p','q',1,[])
+ )
+
+__chipPaths = {
+ # for each node:
+ # (
+ # pos, # position of node
+ # (
+ # adjacent node,
+ # adjacent node,
+ # ...
+ # )
+ # )
+ 'a' : (Point3(50.004, 102.725, 0.6), # in front of log tunnel
+ ('b','k')),
+ 'b' : (Point3(-29.552, 112.531, 0.6), # north bridge inner side
+ ('c','a')),
+ 'c' : (Point3(-51.941, 146.155, 0.025), # north bridge outer side
+ ('d','b')),
+ 'd' : (Point3(-212.334, -3.639, 0.025), # in front of golf tunnel
+ ('e','c')),
+ 'e' : (Point3(-143.466, -67.526, 0.025), # west bridge outer side
+ ('f','d','i')),
+ 'f' : (Point3(-107.556, -62.257, 0.025), # west bridge inner side
+ ('g','e','j')),
+ 'g' : (Point3(-43.103, -71.518, 0.2734), # south bridge inner side
+ ('h','f','j')),
+ 'h' : (Point3(-40.605, -125.124, 0.025), # south bridge outer side
+ ('i','g')),
+ 'i' : (Point3(-123.05, -124.542, 0.025), # between south & west bridge
+ ('h','e')),
+ 'j' : (Point3(-40.092, 2.784, 1.268), # SW of gazebo
+ ('k','b','f','g')),
+ 'k' : (Point3(75.295, 26.715, 1.4), # SE of gazebo
+ ('a','j')),
+ }
+
+__chipWaypoints = (
+ ('a','b',1,[]),
+ ('a','k',1,[]),
+ ('b','c',1,[]),
+ ('b','j',1,[]),
+ ('c','d',1,[]),
+ ('d','e',1,[]),
+ ('e','f',1,[]),
+ ('e','i',1,[]),
+ ('f','g',1,[]),
+ ('f','j',1,[]),
+ ('g','h',1,[]),
+ ('g','j',1,[]),
+ ('h','i',1,[]),
+ ('j','k',1,[]),
+ )
+
+# when Dale is going over a bridge, have him orbit closer
+DaleOrbitDistanceOverride = {
+ ('b','c') : 2.5,
+ ('e','f') : 2.5,
+ }
+
+startNode = 'a'
+
+def getPaths(charName, location = 0):
+ if charName==TTLocalizer.Mickey:
+ return __mickeyPaths
+ elif charName==TTLocalizer.VampireMickey:
+ return __mickeyPaths
+ elif charName==TTLocalizer.Minnie:
+ return __minniePaths
+ elif charName == TTLocalizer.WitchMinnie:
+ return __minniePaths
+ elif charName==TTLocalizer.Daisy:
+ return __daisyPaths
+ elif charName==TTLocalizer.Goofy:
+ if location == 0:
+ return __goofyPaths
+ else:
+ return __goofySpeedwayPaths
+ elif charName==TTLocalizer.SuperGoofy:
+ return __goofySpeedwayPaths
+ elif charName==TTLocalizer.Donald:
+ return __donaldPaths
+ elif charName==TTLocalizer.Pluto:
+ return __plutoPaths
+ elif charName==TTLocalizer.WesternPluto:
+ return __plutoPaths
+ elif charName==TTLocalizer.Chip:
+ return __chipPaths
+ elif charName==TTLocalizer.Dale:
+ return __chipPaths
+ elif charName==TTLocalizer.DonaldDock:
+ return {'a':(Point3(0,0,0),'a')}
+ else:
+ assert 0, "Unknown path information"
+
+def __getWaypointList(paths):
+ if paths==__mickeyPaths:
+ return __mickeyWaypoints
+ elif paths==__minniePaths:
+ return __minnieWaypoints
+ elif paths==__daisyPaths:
+ return __daisyWaypoints
+ elif paths==__goofyPaths:
+ return __goofyWaypoints
+ elif paths==__goofySpeedwayPaths:
+ return __goofySpeedwayWaypoints
+ elif paths==__donaldPaths:
+ return __donaldWaypoints
+ elif paths==__plutoPaths:
+ return __plutoWaypoints
+ elif paths==__chipPaths:
+ return __chipWaypoints
+ elif paths==__dalePaths:
+ return __chipWaypoints
+ else:
+ assert 0, "Unknown waypoint information"
+
+def getNodePos(node, paths):
+ assert paths.has_key(node)
+ return paths[node][0]
+
+def getAdjacentNodes(node, paths):
+ assert paths.has_key(node)
+ return paths[node][1]
+
+def getWayPoints(fromNode, toNode, paths, wpts = None):
+ list = []
+
+ if (fromNode != toNode):
+ if wpts == None:
+ wpts = __getWaypointList(paths)
+ for path in wpts:
+ if path[0] == fromNode and path[1] == toNode:
+ for point in path[3]:
+ list.append(Point3(point))
+ break
+ elif path[0] == toNode and path[1] == fromNode:
+ # reverse the order of the list
+ for point in path[3]:
+ list = [Point3(point)] + list
+ break
+ return list
+
+def getRaycastFlag(fromNode, toNode, paths):
+ result = 0
+ if (fromNode != toNode):
+ wpts = __getWaypointList(paths)
+ for path in wpts:
+ if path[0] == fromNode and path[1] == toNode:
+ if path[2]:
+ result = 1
+ break
+ elif path[0] == toNode and path[1] == fromNode:
+ # reverse the order of the list
+ if path[2]:
+ result = 1
+ break
+ return result
+
+def getPointsFromTo(fromNode, toNode, paths):
+ startPoint = Point3(getNodePos(fromNode,paths))
+ endPoint = Point3(getNodePos(toNode,paths))
+ return [startPoint] + getWayPoints(fromNode, toNode, paths) + [endPoint]
+
+def getWalkDuration(fromNode, toNode, velocity, paths):
+
+ posPoints = getPointsFromTo(fromNode, toNode, paths)
+
+ duration = 0
+ for pointIndex in range(len(posPoints) - 1):
+ startPoint = posPoints[pointIndex]
+ endPoint = posPoints[pointIndex + 1]
+
+ # Calculate the amount of time it will take to walk
+ distance = Vec3(endPoint - startPoint).length()
+ duration += distance / velocity
+
+ return duration
+
+
+def getWalkDistance(fromNode, toNode, velocity, paths):
+ posPoints = getPointsFromTo(fromNode, toNode, paths)
+
+ retval = 0
+ for pointIndex in range(len(posPoints) - 1):
+ startPoint = posPoints[pointIndex]
+ endPoint = posPoints[pointIndex + 1]
+
+ # Calculate the amount of time it will take to walk
+ distance = Vec3(endPoint - startPoint).length()
+ retval += distance
+
+ return retval
+
+
diff --git a/toontown/src/classicchars/CharStateDatas.py b/toontown/src/classicchars/CharStateDatas.py
new file mode 100644
index 0000000..9ede4d0
--- /dev/null
+++ b/toontown/src/classicchars/CharStateDatas.py
@@ -0,0 +1,366 @@
+"""
+Contains the various state datas available to
+the classic character NPC's found in safezones
+"""
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+from direct.fsm import StateData
+from direct.directnotify import DirectNotifyGlobal
+from direct.showbase.PythonUtil import *
+from direct.task import Task
+import CCharPaths
+from toontown.toonbase import ToontownGlobals
+
+class CharNeutralState(StateData.StateData):
+ """
+ Available to a character that might get lonely
+ once in a while and just stand my his or her
+ self for a little while, responding to toons
+ that pass by.
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory("CharNeutralState")
+
+ def __init__(self, doneEvent, character):
+ StateData.StateData.__init__(self, doneEvent)
+ self.__doneEvent = doneEvent
+ self.character = character
+
+ def enter(self, startTrack=None, playRate=None):
+ StateData.StateData.enter(self)
+ self.notify.debug("Neutral " + self.character.getName() + "...")
+ self.__neutralTrack = Sequence(name = self.character.getName() + '-neutral')
+ if startTrack:
+ self.__neutralTrack.append(startTrack)
+ if playRate:
+ self.__neutralTrack.append(Func(self.character.setPlayRate, playRate, 'neutral'))
+ self.__neutralTrack.append(Func(self.character.loop, 'neutral'))
+ self.__neutralTrack.start()
+
+ def exit(self):
+ StateData.StateData.exit(self)
+ self.__neutralTrack.finish()
+
+ def __doneHandler(self):
+ # go back to the neutral state
+ doneStatus = {}
+ doneStatus['state'] = 'walk' # neutral???
+ doneStatus['status'] = 'done'
+ messenger.send(self.__doneEvent, [doneStatus])
+ return Task.done
+
+
+class CharWalkState(StateData.StateData):
+ """
+ Available to a character that might get lonely
+ once in a while and just stand my his or her
+ self for a little while, responding to toons
+ that pass by.
+ """
+ notify = DirectNotifyGlobal.directNotify.newCategory("CharWalkState")
+
+ def __init__(self, doneEvent, character, diffPath = None):
+ StateData.StateData.__init__(self, doneEvent)
+ self.doneEvent = doneEvent
+ assert(character)
+ self.character = character
+
+ if diffPath == None:
+ self.paths = CCharPaths.getPaths(character.getName(),
+ character.getCCLocation())
+ else:
+ self.paths = CCharPaths.getPaths(diffPath, character.getCCLocation())
+ self.speed = character.walkSpeed()
+ self.offsetX = 0
+ self.offsetY = 0
+ self.oldOffsetX = 0
+ self.olfOffsetY = 0
+
+ self.walkTrack = None
+
+ def enter(self, startTrack=None, playRate=None):
+ """
+ startTrack, allows us to prepend a track to the walk
+ tracks (such as a stand up).
+ playRate, sets the play rate for walk.
+
+ start walking, create intervals to make the character
+ move from start to destination
+ """
+ StateData.StateData.enter(self)
+ self.notify.debug("Walking " + self.character.getName() +
+ "... from " +
+ str(self.walkInfo[0]) + " to " +
+ str(self.walkInfo[1]))
+
+ posPoints = CCharPaths.getPointsFromTo(self.walkInfo[0],
+ self.walkInfo[1],
+ self.paths)
+ lastPos = posPoints[-1]
+ newLastPos = Point3( lastPos[0]+self.offsetX, lastPos[1]+self.offsetY, lastPos[2])
+ posPoints[-1] = newLastPos
+ firstPos = posPoints[0]
+ newFirstPos = Point3( firstPos[0]+self.oldOffsetX, firstPos[1]+self.oldOffsetY, firstPos[2])
+ posPoints[0] = newFirstPos
+
+ self.walkTrack = Sequence(name = self.character.getName() + '-walk')
+ if startTrack:
+ self.walkTrack.append(startTrack)
+
+ # Ensure we are placed at the beginning of the path before we
+ # start the track. This will put the character at the right
+ # place even if the timestamp is way stale.
+ self.character.setPos(posPoints[0])
+
+ raycast = CCharPaths.getRaycastFlag(self.walkInfo[0],
+ self.walkInfo[1],
+ self.paths)
+ moveTrack = self.makePathTrack(self.character, posPoints,
+ self.speed, raycast)
+ # make the walking animation and lerping happen at the same time
+ if playRate:
+ self.walkTrack.append(Func(self.character.setPlayRate, playRate, 'walk'))
+ self.walkTrack.append(Func(self.character.loop, 'walk'))
+ self.walkTrack.append(moveTrack)
+
+ doneEventName = self.character.getName() + 'WalkDone'
+ self.walkTrack.append(Func(messenger.send, doneEventName))
+
+ ts = globalClockDelta.localElapsedTime(self.walkInfo[2])
+ self.accept(doneEventName, self.doneHandler)
+ self.notify.debug("walkTrack.start(%s)" % (ts))
+ self.walkTrack.start(ts)
+
+ def makePathTrack(self, nodePath, posPoints, velocity, raycast=0):
+ track = Sequence()
+ assert (len(posPoints) > 1)
+ if raycast:
+ track.append(
+ Func(nodePath.enableRaycast, 1))
+
+ startHpr = nodePath.getHpr()
+ for pointIndex in range(len(posPoints) - 1):
+ startPoint = posPoints[pointIndex]
+ endPoint = posPoints[pointIndex + 1]
+ # make sure we're at the start point
+ track.append(
+ Func(nodePath.setPos, startPoint)
+ )
+
+ # Calculate the amount of time we should spend walking
+ distance = Vec3(endPoint - startPoint).length()
+ duration = distance / velocity
+
+ # calculate the destination hpr
+ curHpr = nodePath.getHpr()
+ nodePath.headsUp(endPoint[0], endPoint[1], endPoint[2])
+ destHpr = nodePath.getHpr()
+
+ # make sure we don't do any wacky 279 degree turns
+ reducedCurH = reduceAngle(curHpr[0])
+ #print "curH = ", reducedCurH
+ reducedCurHpr = Vec3(reducedCurH, curHpr[1], curHpr[2])
+ reducedDestH = reduceAngle(destHpr[0])
+ #print "destH = ", reducedDestH
+ shortestAngle = closestDestAngle(reducedCurH, reducedDestH)
+ #print "shortest angle = ", shortestAngle
+ shortestHpr = Vec3(shortestAngle, destHpr[1], destHpr[2])
+ turnTime = abs(shortestAngle)/270.
+ # we need to set this hpr temporarily to calculate the next
+ # waypoints hpr correctly
+ nodePath.setHpr(shortestHpr)
+
+ if duration - turnTime > 0.01:
+ # Lerp to face the endpoint while walking to the end point
+ track.append(
+ Parallel(Func(nodePath.loop, 'walk'),
+ LerpHprInterval(nodePath, turnTime, shortestHpr,
+ startHpr = reducedCurHpr,
+ name="lerp" + nodePath.getName() + "Hpr"),
+ LerpPosInterval(nodePath, duration=duration - turnTime,
+ pos=Point3(endPoint),
+ startPos=Point3(startPoint), fluid = 1))
+ )
+
+ nodePath.setHpr(startHpr)
+
+ if raycast:
+ track.append(
+ Func(nodePath.enableRaycast, 0))
+
+ return track
+
+ def doneHandler(self):
+ """
+ called when the character is done walking
+ """
+ # go back to the neutral state
+ doneStatus = {}
+ doneStatus['state'] = 'walk'
+ doneStatus['status'] = 'done'
+ messenger.send(self.doneEvent, [doneStatus])
+ return Task.done
+
+ def exit(self):
+ """
+ clean up intervals and tasks
+ """
+ StateData.StateData.exit(self)
+ self.ignore(self.character.getName() + 'WalkDone')
+ if self.walkTrack:
+ self.walkTrack.finish()
+ self.walkTrack = None
+
+
+ def setWalk(self, srcNode, destNode, timestamp, offsetX=0, offsetY=0):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ self.oldOffsetX = self.offsetX
+ self.oldOffsetY = self.offsetY
+ self.walkInfo = (srcNode, destNode, timestamp)
+ self.offsetX = offsetX
+ self.offsetY = offsetY
+
+
+class CharFollowChipState(CharWalkState):
+ notify = DirectNotifyGlobal.directNotify.newCategory("CharFollowChipState")
+
+ completeRevolutionDistance = 13 # how far will Chip walk for dale to go around him
+
+ def __init__(self, doneEvent, character, chipId):
+ CharWalkState.__init__(self, doneEvent,character)
+ self.offsetDict = {'a': (ToontownGlobals.DaleOrbitDistance,0)} # for each node, we maintain an offset
+ self.chipId = chipId
+
+ def setWalk(self, srcNode, destNode, timestamp, offsetX=0, offsetY=0):
+ self.offsetDict[destNode] = (offsetX, offsetY)
+ self.srcNode = srcNode
+ self.destNode = destNode
+ self.orbitDistance = ToontownGlobals.DaleOrbitDistance
+ if (srcNode, destNode) in CCharPaths.DaleOrbitDistanceOverride:
+ self.orbitDistance = CCharPaths.DaleOrbitDistanceOverride[(srcNode, destNode)]
+ elif (destNode, srcNode) in CCharPaths.DaleOrbitDistanceOverride:
+ self.orbitDistance = CCharPaths.DaleOrbitDistanceOverride[(destNode, srcNode)]
+ CharWalkState.setWalk(self, srcNode, destNode, timestamp,
+ offsetX, offsetY)
+
+
+ def makePathTrack(self, nodePath, posPoints, velocity, raycast=0):
+ """Create the interval of dale orbiting chip."""
+ retval = Sequence()
+ if raycast:
+ retval.append(
+ Func(nodePath.enableRaycast, 1))
+ chip = base.cr.doId2do.get(self.chipId)
+ self.chipPaths = CCharPaths.getPaths( chip.getName(),
+ chip.getCCLocation() )
+ self.posPoints = posPoints
+
+ #chipDuration = CCharPaths.getWalkDuration(self.srcNode,
+ # self.destNode,
+ # ToontownGlobals.ChipSpeed,
+ # self.chipPaths)
+ # using getWalkDuration returns a bigger duration than chip uses
+ chipDuration = chip.walk.walkTrack.getDuration()
+ self.notify.debug('chipDuration = %f' % chipDuration)
+ chipDistance = CCharPaths.getWalkDistance(self.srcNode,
+ self.destNode,
+ ToontownGlobals.ChipSpeed,
+ self.chipPaths)
+ #import pdb; pdb.set_trace()
+ self.revolutions = chipDistance / self.completeRevolutionDistance
+ # now lets add in the extra revs from the randomization offset
+ srcOffset = (0,0)
+ if self.srcNode in self.offsetDict:
+ srcOffset = self.offsetDict[self.srcNode]
+ srcTheta = math.atan2(srcOffset[1], srcOffset[0])
+ # srcTheta returns a value in range -pi to pi
+ if srcTheta < 0:
+ srcTheta += 2 * math.pi
+ if srcTheta > 0:
+ srcRev = ( (2 * math.pi) - srcTheta) / ( 2 * math.pi)
+ else:
+ srcRev = 0
+ self.srcTheta = srcTheta
+
+ destOffset = (0,0)
+ if self.destNode in self.offsetDict:
+ destOffset = self.offsetDict[self.destNode]
+ destTheta = math.atan2(destOffset[1], destOffset[0])
+ # destTheta returns a value in range -pi to pi
+ if destTheta < 0:
+ destTheta += 2 * math.pi
+ self.destTheta = destTheta
+
+ self.revolutions += srcRev
+ endingTheta = srcTheta+ ((self.revolutions % 1.0) * 2 * math.pi)
+ diffTheta = destTheta - endingTheta
+ destRev = diffTheta / ( 2 * math.pi)
+ self.revolutions += destRev
+
+ # really short segments might produce negative revolutions
+ while self.revolutions < 1:
+ self.revolutions += 1
+
+ def positionDale(t):
+ self.orbitChip(t)
+
+ retval.append(LerpFunctionInterval(positionDale, chipDuration))
+
+ if raycast:
+ retval.append(
+ Func(nodePath.enableRaycast, 0))
+
+ return retval
+
+ def orbitChip(self, t):
+ """Position dale orbiting chip."""
+ srcOffset = (0,0)
+ if self.srcNode in self.offsetDict:
+ srcOffset = self.offsetDict[self.srcNode]
+ chipSrcPos = Point3(self.posPoints[0][0] - srcOffset[0],
+ self.posPoints[0][1] - srcOffset[1],
+ self.posPoints[0][2])
+ destOffset = (0,0)
+ if self.destNode in self.offsetDict:
+ destOffset = self.offsetDict[self.destNode]
+ chipDestPos = Point3(self.posPoints[-1][0] - destOffset[0],
+ self.posPoints[-1][1] - destOffset[1],
+ self.posPoints[-1][2])
+
+ displacement = chipDestPos - chipSrcPos
+ displacement *= t
+ chipPos = chipSrcPos + displacement
+
+ diffTheta = (t * self.revolutions) * 2 * math.pi
+ curTheta = self.srcTheta + diffTheta
+
+ newOffsetX = math.cos(curTheta) * self.orbitDistance
+ newOffsetY = math.sin(curTheta) * self.orbitDistance
+
+ dalePos = Point3( chipPos[0] + newOffsetX, chipPos[1] + newOffsetY,
+ chipPos[2])
+
+ self.character.setPos( dalePos)
+ newHeading = rad2Deg(curTheta)
+ newHeading %= 360
+ self.character.setH( newHeading)
+
+ #self.notify.debug('t=%s diffTheta=%s curTheta=%s chip=%s dale=%s' %
+ # (t, diffTheta, curTheta, chipPos, dalePos))
+
+
+
+
+
+
+
+
+
+
diff --git a/toontown/src/classicchars/CharStateDatasAI.py b/toontown/src/classicchars/CharStateDatasAI.py
new file mode 100644
index 0000000..7f12eac
--- /dev/null
+++ b/toontown/src/classicchars/CharStateDatasAI.py
@@ -0,0 +1,652 @@
+"""CharStateDatasAI module: contains the server variation of the various
+state datas available to the classic character NPC's found in safezones"""
+#from PandaModules import *
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+
+from direct.fsm import StateData
+from direct.directnotify import DirectNotifyGlobal
+import random
+from direct.task import Task
+from toontown.toonbase import ToontownGlobals
+
+import CCharChatter
+import CCharPaths
+
+class CharLonelyStateAI(StateData.StateData):
+ """
+ ////////////////////////////////////////////////////////////////////
+ //
+ // CharLonelyStateAI: available to a character that might get lonely
+ // once in a while and just stand by his or her
+ // self for a little while, responding to toons
+ // that pass by.
+ //
+ ////////////////////////////////////////////////////////////////////
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("CharLonelyStateAI")
+
+ def __init__(self, doneEvent, character):
+ StateData.StateData.__init__(self, doneEvent)
+ self.__doneEvent = doneEvent
+ self.character = character
+
+ def enter(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: called when the character enters the lonely state
+ // create a doLater to wait a random amount of time
+ // before deciding what to do next (such as walk to a
+ // new area).
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # so lonely... oh, so lonely...
+ if hasattr( self.character, "name" ):
+ name = self.character.getName()
+ else:
+ name = "character"
+ self.notify.debug("Lonely " + self.character.getName() + "...")
+
+ # pick a random amount of time in which to stay in lonely state
+ # before deciding what to do next
+ #
+ StateData.StateData.enter(self)
+ duration = random.randint(3,15)
+ taskMgr.doMethodLater( duration,
+ self.__doneHandler,
+ self.character.taskName("startWalking") )
+
+ def exit(self):
+ StateData.StateData.exit(self)
+ taskMgr.remove(self.character.taskName("startWalking"))
+
+ def __doneHandler(self, task):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: done being lonely, send a message to notify the
+ // character and it will decide which state to go to
+ // next
+ // Parameters: task, the task that called this function
+ // Changes: none
+ // Returns: task done status
+ ////////////////////////////////////////////////////////////////////
+ """
+ doneStatus = {}
+ doneStatus['state'] = 'lonely'
+ doneStatus['status'] = 'done'
+ messenger.send(self.__doneEvent, [doneStatus])
+ return Task.done
+
+
+class CharChattyStateAI(StateData.StateData):
+ """
+ ////////////////////////////////////////////////////////////////////
+ //
+ // CharChattyStateAI: available to a character that is able to
+ // talk to players when they get close enough
+ //
+ ////////////////////////////////////////////////////////////////////
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("CharChattyStateAI")
+
+ def __init__(self, doneEvent, character):
+ StateData.StateData.__init__(self, doneEvent)
+ self.__doneEvent = doneEvent
+ self.character = character
+
+ self.__chatTaskName = "characterChat-" + str(character)
+
+ # this is set to the id of the toon that this character last
+ # spoke to
+ self.lastChatTarget = 0
+ # Character should not talk until we reach this time
+ self.nextChatTime = 0
+ # this is the last thing character said
+ self.lastMessage = [-1, -1] # category, message index
+
+ def enter(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: called when the character enters the lonely state
+ // create a doLater to wait a random amount of time
+ // before deciding what to do next (such as walk to a
+ // new area).
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # so lonely... oh, so lonely...
+ if hasattr( self.character, "name" ):
+ name = self.character.getName()
+ else:
+ name = "character"
+ self.notify.debug("Chatty " + self.character.getName() + "...")
+
+ # if the last person we talked to leaves the chat
+ # sphere and re-enters, don't greet them again
+ #self.lastChatTarget = 0
+
+ self.chatter = CCharChatter.getChatter(self.character.getName(),
+ self.character.getCCChatter())
+
+ # only spawn the chat task if this is a talking char
+ if self.chatter != None:
+ # remove any old
+ taskMgr.remove(self.__chatTaskName)
+ # spawn the new task
+ taskMgr.add(self.blather, self.__chatTaskName)
+
+ StateData.StateData.enter(self)
+
+ # pick a random message
+ def pickMsg(self, category):
+ self.getLatestChatter()
+ if self.chatter:
+ return random.randint(0, len(self.chatter[category])-1)
+ else:
+ return None
+
+ def getLatestChatter(self):
+ self.chatter = CCharChatter.getChatter(self.character.getName(),
+ self.character.getCCChatter())
+
+ def setCorrectChatter(self):
+ """Forces an update on self.chatter, possibly affected by holidays."""
+ self.chatter = CCharChatter.getChatter(self.character.getName(),
+ self.character.getCCChatter())
+
+ def blather(self, task):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: say something if it is time to
+ // Parameters: task, task which calls this function repeatedly
+ // Changes:
+ // Returns: task status
+ ////////////////////////////////////////////////////////////////////
+ """
+ now = globalClock.getFrameTime()
+ if now < self.nextChatTime:
+ return Task.cont
+
+ self.getLatestChatter()
+
+ if self.character.lostInterest():
+ # character is bored.
+ self.leave()
+ return Task.done
+
+ if not self.chatter:
+ self.notify.debug("I do not want to talk")
+ return Task.done
+
+ if not self.character.getNearbyAvatars():
+ return Task.cont
+
+ target = self.character.getNearbyAvatars()[0]
+
+ # say something profound
+ if self.lastChatTarget != target:
+ self.lastChatTarget = target
+ category = CCharChatter.GREETING
+ else:
+ category = CCharChatter.COMMENT
+
+ # avoid an index out of range crash
+ self.setCorrectChatter()
+
+ # if the category is the same as the last message,
+ # and there's more than one message, pick a different
+ # message
+ if (
+ category == self.lastMessage[0] and
+ len(self.chatter[category]) > 1
+ ):
+ # make sure character doesn't say the same thing twice
+ msg = self.lastMessage[1]
+ #while (msg == self.lastMessage[1]):
+ # look at actual msg - not index
+ lastMsgIndex = self.lastMessage[1]
+ if (lastMsgIndex < len(self.chatter[category])) and (lastMsgIndex >= 0) :
+ while (self.chatter[category][msg] == self.chatter[category][lastMsgIndex]):
+ msg = self.pickMsg(category)
+ if not msg:
+ break
+ else:
+ # our last message index was invalid, we probably switched from holiday chatter
+ # to regular chatter or vice versa
+ # very small chance we repeat, but it's better than crashing
+ msg = self.pickMsg(category)
+ else:
+ msg = self.pickMsg(category)
+
+ if msg == None:
+ self.notify.debug("I do not want to talk")
+ return Task.done
+
+ self.character.sendUpdate("setChat", [category, msg, target])
+
+ self.lastMessage = [category, msg] # category, message index
+
+ self.nextChatTime = now + 8.0 + (random.random() * 4.0)
+
+ return Task.cont
+
+ def leave(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: called when the character decides to leave and has
+ // more important things to do than being friendly
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # if we talk, say goodbye.
+ if self.chatter != None:
+ category = CCharChatter.GOODBYE
+ msg = random.randint(0,
+ len(self.chatter[CCharChatter.GOODBYE])-1)
+ target = self.character.getNearbyAvatars()[0]
+ self.character.sendUpdate("setChat", [category, msg, target])
+
+ # set up a doLater to make character walk away
+ taskMgr.doMethodLater( 1, self.doneHandler,
+ self.character.taskName("waitToFinish") )
+
+ def exit(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: leave the chatty state, clean up dangling tasks
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ StateData.StateData.exit(self)
+ taskMgr.remove(self.__chatTaskName)
+
+ def doneHandler(self, task):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: done being lonely, send a message to notify the
+ // character and it will decide which state to go to
+ // next
+ // Parameters: task, the task that called this function
+ // Changes: none
+ // Returns: task done status
+ ////////////////////////////////////////////////////////////////////
+ """
+ doneStatus = {}
+ doneStatus['state'] = 'chatty'
+ doneStatus['status'] = 'done'
+ messenger.send(self.__doneEvent, [doneStatus])
+ return Task.done
+
+
+class CharWalkStateAI(StateData.StateData):
+ """
+ ////////////////////////////////////////////////////////////////////
+ //
+ // CharWalkStateAI: available to a character that is able to
+ // walk around a predetermined set of waypoints
+ //
+ ////////////////////////////////////////////////////////////////////
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("CharWalkStateAI")
+
+ def __init__(self, doneEvent, character, diffPath = None):
+ StateData.StateData.__init__(self, doneEvent)
+ self.__doneEvent = doneEvent
+ self.character = character
+ if diffPath == None:
+ self.paths = CCharPaths.getPaths(character.getName(),
+ character.getCCLocation())
+ else:
+ self.paths = CCharPaths.getPaths(diffPath, character.getCCLocation())
+ self.speed = character.walkSpeed()
+
+ # this is the last node that the character was at
+ self.__lastWalkNode = CCharPaths.startNode
+ self.__curWalkNode = CCharPaths.startNode
+
+ def enter(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: called when the character enters the walk state
+ // create a doLater to wait a specific amount of time
+ // while character gets to the destination on all client
+ // machines.
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # choose a destination
+ # choose a new destination node, different from last
+ destNode = self.__lastWalkNode
+ choices = CCharPaths.getAdjacentNodes(self.__curWalkNode, self.paths)
+ if len(choices) == 1:
+ destNode = choices[0]
+ else:
+ while destNode == self.__lastWalkNode:
+ destNode = random.choice(
+ CCharPaths.getAdjacentNodes(self.__curWalkNode, self.paths))
+
+ self.notify.debug("Walking " +
+ self.character.getName() +
+ "... from " + \
+ str(self.__curWalkNode) + "(" +
+ str(CCharPaths.getNodePos(self.__curWalkNode,
+ self.paths))
+ + ") to " + \
+ str(destNode) + "(" +
+ str(CCharPaths.getNodePos(destNode,self.paths)) +
+ ")")
+
+ # broadcast the walk
+ self.character.sendUpdate("setWalk", [self.__curWalkNode, destNode, globalClockDelta.getRealNetworkTime()])
+
+ # set up a doLater to fire when character is done walking
+ duration = CCharPaths.getWalkDuration(self.__curWalkNode,
+ destNode,
+ self.speed,
+ self.paths)
+ t = taskMgr.doMethodLater(
+ duration,
+ self.doneHandler,
+ self.character.taskName(self.character.getName() +
+ "DoneWalking") )
+ t.newWalkNode = destNode
+
+ # keep track of the destination since dale needs to know about
+ self.destNode = destNode
+
+ def exit(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: leave the walk state, clean up dangling tasks
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ StateData.StateData.exit(self)
+ taskMgr.remove(self.character.taskName( self.character.getName() +
+ "DoneWalking"))
+
+ def getDestNode(self):
+ """Return the destination node he's walking to."""
+ # if the node hasn't been created, retunr the first node
+ if hasattr(self,"destNode") and self.destNode:
+ return self.destNode
+ else:
+ return self.__curWalkNode
+
+ def setCurNode(self, curWalkNode):
+ self.__curWalkNode = curWalkNode
+
+ def doneHandler(self, task):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: done walking, send a message to notify the
+ // character and it will decide which state to go to
+ // next
+ // Parameters: task, the task that called this function
+ // Changes: none
+ // Returns: task done status
+ ////////////////////////////////////////////////////////////////////
+ """
+ # this is called after the walk duration expires
+ # transition back to lonely or chatty, depending
+ # on the number of nearby avatars
+
+ # set the new walk node position
+ self.__lastWalkNode = self.__curWalkNode
+ self.__curWalkNode = task.newWalkNode
+
+ # Send an update that indicates the character is definitely at
+ # its node now.
+ self.character.sendUpdate("setWalk", [self.__curWalkNode, self.__curWalkNode, globalClockDelta.getRealNetworkTime()])
+
+ doneStatus = {}
+ doneStatus['state'] = 'walk'
+ doneStatus['status'] = 'done'
+ messenger.send(self.__doneEvent, [doneStatus])
+ return Task.done
+
+
+class CharFollowChipStateAI(StateData.StateData):
+ """
+ ////////////////////////////////////////////////////////////////////
+ //
+ // CharWalkStateAI: available to a character that is able to
+ // buzz around around another classic char
+ //
+ ////////////////////////////////////////////////////////////////////
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("CharFollowChipStateAI")
+
+ def __init__(self, doneEvent, character, followedChar):
+ StateData.StateData.__init__(self, doneEvent)
+ self.__doneEvent = doneEvent
+ self.character = character
+ self.followedChar = followedChar
+ self.paths = CCharPaths.getPaths( character.getName(),
+ character.getCCLocation() )
+ self.speed = character.walkSpeed()
+
+ # this is the last node that the character was at
+ self.__lastWalkNode = CCharPaths.startNode
+ self.__curWalkNode = CCharPaths.startNode
+
+ def enter(self, chipDestNode):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: called when the character enters the walk state
+ // create a doLater to wait a specific amount of time
+ // while character gets to the destination on all client
+ // machines.
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # choose a destination
+ # choose a new destination node, different from last
+ #import pdb; pdb.set_trace()
+ destNode = self.__lastWalkNode
+ choices = CCharPaths.getAdjacentNodes(self.__curWalkNode, self.paths)
+ if len(choices) == 1:
+ destNode = choices[0]
+ else:
+ while destNode == self.__lastWalkNode:
+ destNode = random.choice(
+ CCharPaths.getAdjacentNodes(self.__curWalkNode, self.paths))
+
+ destNode = chipDestNode
+ self.notify.debug("Walking " +
+ self.character.getName() +
+ "... from " + \
+ str(self.__curWalkNode) + "(" +
+ str(CCharPaths.getNodePos(self.__curWalkNode,
+ self.paths))
+ + ") to " + \
+ str(destNode) + "(" +
+ str(CCharPaths.getNodePos(destNode,self.paths)) +
+ ")")
+ # calculate an offset
+ self.offsetDistance = ToontownGlobals.DaleOrbitDistance
+ angle = random.randint(0,359)
+ self.offsetX = math.cos(deg2Rad(angle))* self.offsetDistance
+ self.offsetY = math.sin(deg2Rad(angle))* self.offsetDistance
+
+ # broadcast the walk
+ self.character.sendUpdate("setFollowChip", [self.__curWalkNode, destNode, globalClockDelta.getRealNetworkTime(), self.offsetX, self.offsetY])
+
+ # set up a doLater to fire when character is done walking
+ duration = CCharPaths.getWalkDuration(self.__curWalkNode,
+ destNode,
+ self.speed,
+ self.paths)
+ t = taskMgr.doMethodLater(
+ duration,
+ self.__doneHandler,
+ self.character.taskName(self.character.getName() +
+ "DoneWalking") )
+ t.newWalkNode = destNode
+
+ def exit(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: leave the walk state, clean up dangling tasks
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ StateData.StateData.exit(self)
+ taskMgr.remove(self.character.taskName( self.character.getName() +
+ "DoneWalking"))
+
+ def __doneHandler(self, task):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: done walking, send a message to notify the
+ // character and it will decide which state to go to
+ // next
+ // Parameters: task, the task that called this function
+ // Changes: none
+ // Returns: task done status
+ ////////////////////////////////////////////////////////////////////
+ """
+ # this is called after the walk duration expires
+ # transition back to lonely or chatty, depending
+ # on the number of nearby avatars
+
+ # set the new walk node position
+ self.__lastWalkNode = self.__curWalkNode
+ self.__curWalkNode = task.newWalkNode
+
+ # Send an update that indicates the character is definitely at
+ # its node now.
+ self.character.sendUpdate("setFollowChip", [self.__curWalkNode, self.__curWalkNode, globalClockDelta.getRealNetworkTime(), self.offsetX, self.offsetY])
+
+ doneStatus = {}
+ doneStatus['state'] = 'walk'
+ doneStatus['status'] = 'done'
+ messenger.send(self.__doneEvent, [doneStatus])
+ return Task.done
+
+
+class ChipChattyStateAI(CharChattyStateAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("ChipChattyStateAI")
+
+ def setDaleId(self, daleId):
+ """Set the dale id, so chip knows aout him."""
+ self.daleId = daleId
+ self.dale = simbase.air.doId2do.get(self.daleId)
+
+ def blather(self, task):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: say something if it is time to
+ // Parameters: task, task which calls this function repeatedly
+ // Changes:
+ // Returns: task status
+ ////////////////////////////////////////////////////////////////////
+ """
+ now = globalClock.getFrameTime()
+ if now < self.nextChatTime:
+ return Task.cont
+
+ self.getLatestChatter()
+
+ if self.character.lostInterest():
+ # character is bored.
+ self.leave()
+ return Task.done
+
+ if not self.chatter:
+ self.notify.debug("I do not want to talk")
+ return Task.done
+
+ if not self.character.getNearbyAvatars():
+ return Task.cont
+
+ target = self.character.getNearbyAvatars()[0]
+
+ # say something profound
+ if self.lastChatTarget != target:
+ self.lastChatTarget = target
+ category = CCharChatter.GREETING
+ else:
+ category = CCharChatter.COMMENT
+
+ # if the category is the same as the last message,
+ # and there's more than one message, pick a different
+ # message
+ if (
+ category == self.lastMessage[0] and
+ len(self.chatter[category]) > 1
+ ):
+ # make sure character doesn't say the same thing twice
+ msg = self.lastMessage[1]
+ #while (msg == self.lastMessage[1]):
+ # look at actual msg - not index
+ lastMsgIndex = self.lastMessage[1]
+ if (lastMsgIndex < len(self.chatter[category])) and (lastMsgIndex >= 0) :
+ while (self.chatter[category][msg] == self.chatter[category][lastMsgIndex]):
+ msg = self.pickMsg(category)
+ if not msg:
+ break
+ else:
+ msg = self.pickMsg(category)
+ #import pdb; pdb.set_trace()
+ else:
+ msg = self.pickMsg(category)
+
+ if msg == None:
+ self.notify.debug("I do not want to talk")
+ return Task.done
+
+ self.character.sendUpdate("setChat", [category, msg, target])
+
+ # inform dale what we're saying
+ if hasattr(self, 'dale') and self.dale:
+ self.dale.sendUpdate("setChat", [category, msg, target])
+
+ self.lastMessage = [category, msg] # category, message index
+
+ self.nextChatTime = now + 8.0 + (random.random() * 4.0)
+
+ return Task.cont
+
+ def leave(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: called when the character decides to leave and has
+ // more important things to do than being friendly
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ # if we talk, say goodbye.
+ if self.chatter != None:
+ category = CCharChatter.GOODBYE
+ msg = random.randint(0,
+ len(self.chatter[CCharChatter.GOODBYE])-1)
+ target = self.character.getNearbyAvatars()[0]
+ self.character.sendUpdate("setChat", [category, msg, target])
+ if hasattr(self,'dale') and self.dale:
+ self.dale.sendUpdate("setChat", [category, msg, target])
+
+
+ # set up a doLater to make character walk away
+ taskMgr.doMethodLater( 1, self.doneHandler,
+ self.character.taskName("waitToFinish") )
+
+# history
+#
+# 01Oct01 jlbutler created.
+#
diff --git a/toontown/src/classicchars/DistributedCCharBase.py b/toontown/src/classicchars/DistributedCCharBase.py
new file mode 100644
index 0000000..ba759bc
--- /dev/null
+++ b/toontown/src/classicchars/DistributedCCharBase.py
@@ -0,0 +1,428 @@
+"""DistributedCCharBase module: contains the DistributedCCharBase class"""
+
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from otp.avatar import Avatar
+from libotp import CFQuicktalker
+from toontown.char import CharDNA
+from toontown.char import DistributedChar
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from direct.controls.ControlManager import CollisionHandlerRayStart
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase.TTLocalizer import Donald, DonaldDock, WesternPluto, Pluto
+from toontown.effects import DustCloud
+import CCharChatter
+import CCharPaths
+
+import string
+import copy
+
+class DistributedCCharBase(DistributedChar.DistributedChar):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedCCharBase")
+
+ def __init__(self, cr, name, dnaName):
+ try:
+ self.DistributedCCharBase_initialized
+ return
+ except:
+ self.DistributedCCharBase_initialized = 1
+ DistributedChar.DistributedChar.__init__(self, cr)
+
+ dna = CharDNA.CharDNA()
+ dna.newChar(dnaName)
+ self.setDNA(dna)
+ self.setName(name)
+ self.setTransparency(TransparencyAttrib.MDual, 1)
+ fadeIn = self.colorScaleInterval( 0.5, Vec4(1, 1, 1, 1),
+ startColorScale = Vec4(1, 1, 1, 0),
+ blendType = 'easeInOut')
+ fadeIn.start()
+ # Where is the character walking?
+ self.diffPath = None
+ self.transitionToCostume = 0
+ self.__initCollisions()
+
+ def __initCollisions(self):
+ self.cSphere = CollisionSphere(0., 0., 0., 8.)
+ self.cSphere.setTangible(0)
+ self.cSphereNode = CollisionNode(self.getName() + 'BlatherSphere')
+ self.cSphereNode.addSolid(self.cSphere)
+ self.cSphereNodePath = self.attachNewNode(self.cSphereNode)
+ self.cSphereNodePath.hide()
+ self.cSphereNode.setCollideMask(ToontownGlobals.WallBitmask)
+
+ self.acceptOnce("enter" + self.cSphereNode.getName(),
+ self.__handleCollisionSphereEnter)
+
+ # Set up the collison ray
+ # This is a ray cast from your head down to detect floor polygons
+ # and is only turned on during specific parts of the character's path
+ self.cRay = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0)
+ self.cRayNode = CollisionNode(self.getName() + "cRay")
+ self.cRayNode.addSolid(self.cRay)
+ self.cRayNodePath = self.attachNewNode(self.cRayNode)
+ self.cRayNodePath.hide()
+ self.cRayBitMask = ToontownGlobals.FloorBitmask
+ self.cRayNode.setFromCollideMask(self.cRayBitMask)
+ self.cRayNode.setIntoCollideMask(BitMask32.allOff())
+
+ # set up floor collision mechanism
+ self.lifter = CollisionHandlerFloor()
+ self.lifter.setOffset(ToontownGlobals.FloorOffset)
+ # NOTE: a height of 10 is high, but seems to allow Minnie to walk
+ # through the horns in Melodyland without weird floor collision things
+ # happening, this can cause problems if a character tries to walk
+ # under an opening that has a floor within 10ft above.
+ self.lifter.setReach(10.0)
+
+ # Limit our rate-of-fall with the lifter.
+ # 0 means we don't want to limit the velocity, this seems
+ # to help when the ray is conflicting with a pos lerp which, for
+ # example, moves this character down a ramp in DaisyGardens.
+ self.lifter.setMaxVelocity(0.0)
+ self.lifter.addCollider(self.cRayNodePath, self)
+
+ # now use the local toon's collision traverser to handle
+ # updating collision info
+ #
+ self.cTrav = base.localAvatar.cTrav
+
+ def __deleteCollisions(self):
+ del self.cSphere
+ del self.cSphereNode
+ self.cSphereNodePath.removeNode()
+ del self.cSphereNodePath
+
+ # floor collision stuff
+ #
+ self.cRay = None
+ self.cRayNode = None
+ self.cRayNodePath = None
+ self.lifter = None
+ self.cTrav = None
+
+ def disable(self):
+ """
+ This method is called when the DistributedObject is removed from
+ active duty and stored in a cache.
+ """
+ self.stopBlink()
+ self.ignoreAll()
+ self.chatTrack.finish()
+ del self.chatTrack
+ if self.chatterDialogue:
+ self.chatterDialogue.stop()
+ del self.chatterDialogue
+ DistributedChar.DistributedChar.disable(self)
+ self.stopEarTask()
+
+ def delete(self):
+ """
+ This method is called when the DistributedObject is permanently
+ removed from the world and deleted from the cache.
+ """
+ try:
+ self.DistributedCCharBase_deleted
+ except:
+ self.setParent(NodePath("Temp"))
+ self.DistributedCCharBase_deleted = 1
+ self.__deleteCollisions()
+ DistributedChar.DistributedChar.delete(self)
+
+ def generate(self, diffPath = None):
+ """
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ DistributedChar.DistributedChar.generate(self)
+
+ if diffPath==None:
+ self.setPos(CCharPaths.getNodePos(
+ CCharPaths.startNode,
+ CCharPaths.getPaths(self.getName(), self.getCCLocation())))
+ else:
+ self.setPos(CCharPaths.getNodePos(
+ CCharPaths.startNode,
+ CCharPaths.getPaths(diffPath, self.getCCLocation())))
+
+ self.setHpr(0,0,0)
+
+ # The characters can be immediately parented to render.
+ self.setParent(ToontownGlobals.SPRender)
+
+ # hmm. does this character ever blink?
+ self.startBlink()
+ self.startEarTask()
+
+ # the character's chat track
+ self.chatTrack = Sequence()
+
+ # Currently playing dialog
+ self.chatterDialogue = None
+
+ # listen for the collision sphere enter event
+ self.acceptOnce("enter" + self.cSphereNode.getName(),
+ self.__handleCollisionSphereEnter)
+
+ # listen for safe zone exit event
+ self.accept("exitSafeZone", self.__handleExitSafeZone)
+
+ def __handleExitSafeZone(self):
+ # local avatar is leaving the safe zone
+
+ # tell the server that the local toon is leaving,
+ # regardless of whether or not the toon was close
+ # to this character
+ self.__handleCollisionSphereExit(None)
+
+ # collision sphere
+ def __handleCollisionSphereEnter(self, collEntry):
+ self.notify.debug("Entering collision sphere...")
+ # tell the server that the local toon has come
+ # within blathering range
+ self.sendUpdate("avatarEnter", [])
+
+ # listen for chat events
+ self.accept('chatUpdate', self.__handleChatUpdate)
+ self.accept('chatUpdateSC', self.__handleChatUpdateSC)
+ self.accept('chatUpdateSCCustom', self.__handleChatUpdateSCCustom)
+ self.accept('chatUpdateSCToontask', self.__handleChatUpdateSCToontask)
+
+ # put nametag in the transparent layer so toon nametags can render on top of it.
+ self.nametag3d.setBin('transparent',100)
+
+ # listen for the exit event
+ self.acceptOnce("exit" + self.cSphereNode.getName(),
+ self.__handleCollisionSphereExit)
+
+ def __handleCollisionSphereExit(self, collEntry):
+ self.notify.debug("Exiting collision sphere...")
+ # tell the server that the local toon has left
+ # blathering range
+ self.sendUpdate("avatarExit", [])
+
+ # stop listening for chat events
+ self.ignore('chatUpdate')
+ self.ignore('chatUpdateSC')
+ self.ignore('chatUpdateSCCustom')
+ self.ignore('chatUpdateSCToontask')
+
+ # listen for the enter event
+ self.acceptOnce("enter" + self.cSphereNode.getName(),
+ self.__handleCollisionSphereEnter)
+
+ # someday, classic characters might respond in some way to
+ # *what* you're saying.
+ def __handleChatUpdate(self, msg, chatFlags):
+ self.sendUpdate("setNearbyAvatarChat", [msg])
+
+ def __handleChatUpdateSC(self, msgIndex):
+ self.sendUpdate('setNearbyAvatarSC', [msgIndex])
+
+ def __handleChatUpdateSCCustom(self, msgIndex):
+ self.sendUpdate('setNearbyAvatarSCCustom', [msgIndex])
+
+ def __handleChatUpdateSCToontask(self,
+ taskId, toNpcId, toonProgress, msgIndex):
+ self.sendUpdate('setNearbyAvatarSCToontask',
+ [taskId, toNpcId, toonProgress, msgIndex])
+
+ # network messages from the server
+ def makeTurnToHeadingTrack(self, heading):
+ curHpr = self.getHpr()
+ destHpr = self.getHpr()
+ destHpr.setX(heading)
+
+ # make sure the dest heading is not more than 180
+ # degrees from the cur heading
+ if ((destHpr[0] - curHpr[0]) > 180.):
+ destHpr.setX(destHpr[0] - 360)
+ elif ((destHpr[0] - curHpr[0]) < -180.):
+ destHpr.setX(destHpr[0] + 360)
+
+ # figure out how long it should take for the character to turn
+ turnSpeed = 180. # degrees/sec
+ time = abs(destHpr[0] - curHpr[0])/turnSpeed
+
+ # create a track
+ turnTracks = Parallel()
+ # don't animate if we don't need to turn much (or at all)
+ if time > 0.2:
+ turnTracks.append(
+ Sequence(Func(self.loop, 'walk'),
+ Wait(time),
+ Func(self.loop, 'neutral'))
+ )
+ turnTracks.append(
+ LerpHprInterval(self, time, destHpr,
+ name="lerp" + self.getName() + "Hpr")
+ )
+ return turnTracks
+
+ def setChat(self, category, msg, avId):
+ if self.cr.doId2do.has_key(avId):
+ avatar = self.cr.doId2do[avId]
+ chatter = CCharChatter.getChatter(self.getName(), self.getCCChatter())
+ if category >= len(chatter):
+ self.notify.debug("Chatter's changed")
+ return
+ elif len(chatter[category]) <= msg:
+ self.notify.debug("Chatter's changed")
+ return
+ str = chatter[category][msg]
+ if '%' in str:
+ # make our own copy of the message
+ str = copy.deepcopy(str)
+ # get the avatar's name
+ avName = avatar.getName()
+ # slap it in
+ str = string.replace(str, '%', avName)
+
+ track = Sequence()
+
+ # Character doesn't bother to turn to you when saying goodbye
+ if category != CCharChatter.GOODBYE:
+ # turn to face the avatar
+ # calculate the destination hpr
+ curHpr = self.getHpr()
+ self.headsUp(avatar)
+ destHpr = self.getHpr()
+ self.setHpr(curHpr)
+ track.append(self.makeTurnToHeadingTrack(destHpr[0]))
+
+ #Change the animation for vampire mickey
+ #if self == base.cr.doFind("vampire_mickey"):
+ # self.loop('chat')
+
+ # chat flags are different for DL Donald
+ if self.getName() == Donald or self.getName() == WesternPluto or self.getName() == Pluto:
+ chatFlags = CFThought | CFTimeout
+
+ # Make Pluto talk during April Toons' Week.
+ if hasattr(base.cr, "newsManager") and base.cr.newsManager:
+ holidayIds = base.cr.newsManager.getHolidayIdList()
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds:
+ if self.getName() == Pluto:
+ chatFlags = CFTimeout | CFSpeech
+
+ elif self.getName() == DonaldDock:
+ chatFlags = CFTimeout | CFSpeech
+ self.nametag3d.hide()
+ else:
+ chatFlags = CFTimeout | CFSpeech
+
+ # Figure out appropriate audio file here, which will get used
+ # in setChatAbsolute below
+ # getChatterDialogue defined in Char.py so that chatter can
+ # be loaded simultaneously with default char dialogue
+ self.chatterDialogue = self.getChatterDialogue(category, msg)
+
+ # make a track to say the message
+ track.append(
+ Func(self.setChatAbsolute, str, chatFlags, self.chatterDialogue)
+ )
+
+ self.chatTrack.finish()
+ self.chatTrack = track
+ self.chatTrack.start()
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ # meant to be over-ridden by children that need walk notifications
+ pass
+
+ def walkSpeed(self):
+ return 0.1
+
+ def enableRaycast(self, enable=1):
+ """
+ enable/disable raycast, useful for when we know
+ when the char will change elevations
+ """
+ if (not self.cTrav
+ or not hasattr(self, "cRayNode")
+ or not self.cRayNode):
+ self.notify.debug("raycast info not found for " + self.getName())
+ return
+
+ self.cTrav.removeCollider(self.cRayNodePath)
+ if enable:
+ if self.notify.getDebug():
+ self.notify.debug("enabling raycast for " + self.getName())
+ self.cTrav.addCollider(self.cRayNodePath, self.lifter)
+ else:
+ if self.notify.getDebug():
+ self.notify.debug("disabling raycast for " + self.getName())
+
+ def getCCLocation(self):
+ return 0
+
+ def getCCChatter(self):
+ self.handleHolidays()
+ return self.CCChatter
+
+ def handleHolidays(self):
+ """
+ Handle Holiday specific behaviour
+ """
+ self.CCChatter = 0
+ if hasattr(base.cr, "newsManager") and base.cr.newsManager:
+ holidayIds = base.cr.newsManager.getHolidayIdList()
+ if ToontownGlobals.CRASHED_LEADERBOARD in holidayIds:
+ self.CCChatter = ToontownGlobals.CRASHED_LEADERBOARD
+ elif ToontownGlobals.CIRCUIT_RACING_EVENT in holidayIds:
+ self.CCChatter = ToontownGlobals.CIRCUIT_RACING_EVENT
+ elif ToontownGlobals.WINTER_CAROLING in holidayIds:
+ self.CCChatter = ToontownGlobals.WINTER_CAROLING
+ elif ToontownGlobals.WINTER_DECORATIONS in holidayIds:
+ self.CCChatter = ToontownGlobals.WINTER_DECORATIONS
+ elif ToontownGlobals.VALENTINES_DAY in holidayIds:
+ self.CCChatter = ToontownGlobals.VALENTINES_DAY
+ elif ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds:
+ self.CCChatter = ToontownGlobals.APRIL_FOOLS_COSTUMES
+ elif ToontownGlobals.SILLY_CHATTER_ONE in holidayIds:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_ONE
+ elif ToontownGlobals.SILLY_CHATTER_TWO in holidayIds:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_TWO
+ elif ToontownGlobals.SILLY_CHATTER_THREE in holidayIds:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_THREE
+ elif ToontownGlobals.SILLY_CHATTER_FOUR in holidayIds:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_FOUR
+ elif ToontownGlobals.SILLY_CHATTER_FIVE in holidayIds:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_FOUR
+
+ def fadeAway(self):
+ fadeOut = self.colorScaleInterval( 0.5, Vec4(1, 1, 1, 0.5),
+ startColorScale = Vec4(1, 1, 1, 1),
+ blendType = 'easeInOut')
+ fadeOut.start()
+ self.loop("neutral")
+ if(self.fsm):
+ self.fsm.addState(State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']))
+ self.fsm.request("TransitionToCostume", force=1)
+ self.ignoreAll()
+
+ def enterTransitionToCostume(self):
+ def getDustCloudIval():
+ dustCloud = DustCloud.DustCloud(fBillboard=0,wantSound=1)
+ dustCloud.setBillboardAxis(2.)
+ dustCloud.setZ(4)
+ dustCloud.setScale(0.6)
+ dustCloud.createTrack()
+ return Sequence(
+ Func(dustCloud.reparentTo, self),
+ dustCloud.track,
+ Func(dustCloud.destroy),
+ name = 'dustCloadIval'
+ )
+
+ dust = getDustCloudIval()
+ dust.start()
+
+ def exitTransitionToCostume(self):
+ pass
diff --git a/toontown/src/classicchars/DistributedCCharBaseAI.py b/toontown/src/classicchars/DistributedCCharBaseAI.py
new file mode 100644
index 0000000..0a54629
--- /dev/null
+++ b/toontown/src/classicchars/DistributedCCharBaseAI.py
@@ -0,0 +1,276 @@
+"""DistributedCCharBase module: contains the DistributedCCharBase class"""
+
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+from otp.avatar import DistributedAvatarAI
+from direct.directnotify import DirectNotifyGlobal
+from toontown.toonbase import ToontownGlobals
+
+class DistributedCCharBaseAI(DistributedAvatarAI.DistributedAvatarAI):
+ """
+ ////////////////////////////////////////////////////////////////////
+ //
+ // DistributedCCharBase: base class for all classic characters
+ // such as Mickey and Minnie, who hang out
+ // in the safezones
+ //
+ ////////////////////////////////////////////////////////////////////
+ """
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedCCharBaseAI")
+
+ def __init__(self, air, name):
+ DistributedAvatarAI.DistributedAvatarAI.__init__(self, air)
+ self.setName(name)
+ self.exitOff()
+
+ # We do not want to move into the transitionCostume state unless signalled to do so.
+ self.transitionToCostume = 0
+ self.diffPath = None
+
+ def delete(self):
+ self.ignoreAll()
+ DistributedAvatarAI.DistributedAvatarAI.delete(self)
+
+ def exitOff(self):
+ self.__initAttentionSpan()
+ self.__clearNearbyAvatars()
+
+ ## network messages
+ def avatarEnter(self):
+ avId = self.air.getAvatarIdFromSender()
+ # this avatar has come within blathering range
+ self.notify.debug("adding avatar " + str(avId) +
+ " to the nearby avatar list")
+
+ # check if he's already on the list
+ if avId not in self.nearbyAvatars:
+ # add new avatar to nearby list
+ self.nearbyAvatars.append(avId)
+ else:
+ # This should not happen
+ self.air.writeServerEvent('suspicious', avId, 'CCharBase.avatarEnter')
+ self.notify.warning("Avatar %s already in nearby avatars!"
+ % (avId))
+
+
+ # create an info dict for the avatar
+ self.nearbyAvatarInfoDict[avId] = {}
+ self.nearbyAvatarInfoDict[avId]['enterTime'] = globalClock.getRealTime()
+ self.nearbyAvatarInfoDict[avId]['lastChatTime'] = 0
+
+ # re-sort the list
+ self.sortNearbyAvatars()
+
+ self.__interestingAvatarEventOccured()
+
+ # Hang a hook to handle this avatar exiting
+ avExitEvent = self.air.getAvatarExitEvent(avId)
+ self.acceptOnce(avExitEvent, self.__handleExitedAvatar, [avId])
+
+ self.avatarEnterNextState()
+
+ def avatarExit(self):
+ avId = self.air.getAvatarIdFromSender()
+ self.__doAvatarExit(avId)
+
+ def __doAvatarExit(self, avId):
+ avId = self.air.getAvatarIdFromSender()
+ # this avatar has made his escape
+ self.notify.debug("removing avatar " + str(avId) +
+ " from the nearby avatar list")
+ # is the avatar on the list...?
+ if not avId in self.nearbyAvatars:
+ # this is OK, mickeys always send an exit
+ # for their local toon on destruction
+ self.notify.debug("avatar " + str(avId) +
+ " not in the nearby avatar list")
+ else:
+ # get rid of the exit handler
+ avExitEvent = self.air.getAvatarExitEvent(avId)
+ self.ignore(avExitEvent)
+
+ del self.nearbyAvatarInfoDict[avId]
+ self.nearbyAvatars.remove(avId)
+
+ self.avatarExitNextState()
+
+ def avatarEnterNextState():
+ # meant to be over-ridden by a child class that has a state machine
+ pass
+ def avatarExitNextState():
+ # meant to be over-ridden by a child class that has a state machine
+ pass
+
+ def __clearNearbyAvatars(self):
+ self.nearbyAvatars = []
+ self.nearbyAvatarInfoDict = {}
+
+ def sortNearbyAvatars(self):
+ def nAv_compare(a, b, nAvIDict = self.nearbyAvatarInfoDict):
+ # if a has been around longer than b,
+ # a is 'greater' than b
+ # for integers, result would be (a-b)
+ # so if a < b, we return <0
+ # for timestamps, if ts1 < ts2, ts1 is older
+ # and therefore 'greater', so result is ts2 - ts1
+ tsA = nAvIDict[a]['enterTime']
+ tsB = nAvIDict[b]['enterTime']
+ if tsA == tsB:
+ return 0
+ elif tsA < tsB:
+ return -1
+ else:
+ return 1
+
+ self.nearbyAvatars.sort(nAv_compare)
+
+ def getNearbyAvatars(self):
+ return self.nearbyAvatars
+
+ def __avatarSpoke(self, avId):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: called when the character detects that an avatar near
+ // it has spoken, this allows the character to respond
+ // Parameters: avId, the avatar that spoke
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ now = globalClock.getRealTime()
+ if self.nearbyAvatarInfoDict.has_key(avId):
+ self.nearbyAvatarInfoDict[avId]['lastChatTime'] = now
+ self.__interestingAvatarEventOccured()
+
+ # Mickey attention span simulator
+ def __initAttentionSpan(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: initialize the attention span simulator properly
+ // Parameters:
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ self.__avatarTimeoutBase = 0
+
+ def __interestingAvatarEventOccured(self, t=None):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: this should be called when we want the character
+ // to respond to something the avatar has done such
+ // as walked up next to the character or spoken while
+ // near the character
+ // Parameters: t, time at which this event occured
+ // Changes: self.__avatarTimeoutBase
+ ////////////////////////////////////////////////////////////////////
+ """
+ if t == None:
+ t = globalClock.getRealTime()
+ self.__avatarTimeoutBase = t
+
+ def lostInterest(self):
+ """
+ ////////////////////////////////////////////////////////////////////
+ // Function: check to see if this character has lost interest
+ // in whatever it is currently doing
+ // Parameters: none
+ // Changes:
+ ////////////////////////////////////////////////////////////////////
+ """
+ now = globalClock.getRealTime()
+ if now > (self.__avatarTimeoutBase + 50.):
+ return 1
+ return 0
+
+ def __handleExitedAvatar(self, avId):
+ # avatar went away
+ self.__doAvatarExit(avId)
+
+ def setNearbyAvatarChat(self, msg):
+ avId = self.air.getAvatarIdFromSender()
+ self.notify.debug("setNearbyAvatarChat: avatar "
+ + str(avId) + " said " + str(msg))
+ self.__avatarSpoke(avId)
+
+ def setNearbyAvatarSC(self, msgIndex):
+ avId = self.air.getAvatarIdFromSender()
+ self.notify.debug(
+ "setNearbyAvatarSC: avatar %s said SpeedChat phrase %s" %
+ (avId, msgIndex))
+ self.__avatarSpoke(avId)
+
+ def setNearbyAvatarSCCustom(self, msgIndex):
+ avId = self.air.getAvatarIdFromSender()
+ self.notify.debug(
+ "setNearbyAvatarSCCustom: avatar %s said custom "
+ "SpeedChat phrase %s" % (avId, msgIndex))
+ self.__avatarSpoke(avId)
+
+ def setNearbyAvatarSCToontask(self,
+ taskId, toNpcId, toonProgress, msgIndex):
+ avId = self.air.getAvatarIdFromSender()
+ self.notify.debug(
+ "setNearbyAvatarSCToontask: avatar %s said %s" %
+ (avId, (taskId, toNpcId, toonProgress, msgIndex)))
+ self.__avatarSpoke(avId)
+
+ def getWalk(self):
+ # This is called when the char is created on the AI to return
+ # the initial walk source and destination points. It doesn't
+ # return anything meaningful, but the empty string is the code
+ # to a client to hang out and wait for a subsequent message.
+ return ('', '', 0)
+
+ def walkSpeed(self):
+ return 0.1
+
+ def handleHolidays(self):
+ self.CCChatter = 0
+ if hasattr(simbase.air, "holidayManager"):
+ if ToontownGlobals.CRASHED_LEADERBOARD in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.CRASHED_LEADERBOARD
+ elif ToontownGlobals.CIRCUIT_RACING_EVENT in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.CIRCUIT_RACING_EVENT
+ elif ToontownGlobals.WINTER_CAROLING in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.WINTER_CAROLING
+ elif ToontownGlobals.WINTER_DECORATIONS in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.WINTER_DECORATIONS
+ elif ToontownGlobals.VALENTINES_DAY in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.VALENTINES_DAY
+ elif ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.APRIL_FOOLS_COSTUMES
+ elif ToontownGlobals.SILLY_CHATTER_ONE in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_ONE
+ elif ToontownGlobals.SILLY_CHATTER_TWO in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_TWO
+ elif ToontownGlobals.SILLY_CHATTER_THREE in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_THREE
+ elif ToontownGlobals.SILLY_CHATTER_FOUR in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_FOUR
+ elif ToontownGlobals.SILLY_CHATTER_FIVE in simbase.air.holidayManager.currentHolidays:
+ self.CCChatter = ToontownGlobals.SILLY_CHATTER_FOUR
+
+ def getCCLocation(self):
+ # This function is used to differentiate between the same classic
+ # char in different locations. Sub class should override this
+ # function to return a location number other than zero.
+ return 0
+
+ def getCCChatter(self):
+ self.handleHolidays()
+ return self.CCChatter
+
+ #################################################################
+ # This function is used to call it's counterpart on the client
+ # end to fade the character away
+ #################################################################
+ def fadeAway(self):
+ self.sendUpdate("fadeAway", [])
+
+ ############################################################
+ # In order to transition to the next costume, precedence
+ # must be given to transitionCostume during
+ # __decideNextState function
+ ############################################################
+ def transitionCostume(self):
+ self.transitionToCostume = 1
diff --git a/toontown/src/classicchars/DistributedChip.py b/toontown/src/classicchars/DistributedChip.py
new file mode 100644
index 0000000..1ed77bf
--- /dev/null
+++ b/toontown/src/classicchars/DistributedChip.py
@@ -0,0 +1,126 @@
+"""DistributedDaisy module: contains the DistributedDaisy class"""
+
+from direct.showbase.ShowBaseGlobal import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+
+class DistributedChip(DistributedCCharBase.DistributedCCharBase):
+ """DistributedChip class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedChip")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedChip_initialized
+ except:
+ self.DistributedChip_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Chip,
+ 'ch')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ del self.neutralDoneEvent
+ del self.neutral
+ del self.walkDoneEvent
+ del self.walk
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove Chip and state data information
+ """
+ try:
+ self.DistributedChip_deleted
+ except:
+ del self.fsm
+ self.DistributedChip_deleted = 1
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+
+ def generate( self ):
+ """
+ create Chip and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self)
+ name = self.getName()
+ self.neutralDoneEvent = self.taskName(name + '-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(
+ self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self)
+ self.fsm.request('Neutral')
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.neutral.enter()
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def walkSpeed(self):
+ return ToontownGlobals.ChipSpeed
diff --git a/toontown/src/classicchars/DistributedChipAI.py b/toontown/src/classicchars/DistributedChipAI.py
new file mode 100644
index 0000000..99364db
--- /dev/null
+++ b/toontown/src/classicchars/DistributedChipAI.py
@@ -0,0 +1,192 @@
+"""DistributedDaisyAI module: contains the DistributedDaisyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import CharStateDatasAI
+
+class DistributedChipAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedChipAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Chip)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedChipAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.dale = None
+
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+
+ def generate( self ):
+ # all state data's that Chip will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ name = self.getName()
+ self.lonelyDoneEvent = self.taskName(name + '-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName(name + '-chatty-done')
+ self.chatty = CharStateDatasAI.ChipChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self)
+
+ def walkSpeed(self):
+ return ToontownGlobals.ChipSpeed
+
+ # this function kicks off Chip
+ def start(self):
+ # poor lonely Chip
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Chip is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Chip"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+ if self.dale:
+ self.dale.chipEnteringState(self.fsm.getCurrentState().getName())
+
+ def exitLonely(self):
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+ if self.dale:
+ self.dale.chipLeavingState(self.fsm.getCurrentState().getName())
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+ if self.dale:
+ self.dale.chipEnteringState(self.fsm.getCurrentState().getName())
+
+ def exitChatty(self):
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+ if self.dale:
+ self.dale.chipLeavingState(self.fsm.getCurrentState().getName())
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+ if self.dale:
+ self.dale.chipEnteringState(self.fsm.getCurrentState().getName())
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+ if self.dale:
+ self.dale.chipLeavingState(self.fsm.getCurrentState().getName())
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Chip
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near Chip
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Lonely')
+
+ def setDaleId(self, daleId):
+ """Set the doId for Dale."""
+ self.daleId = daleId
+ self.dale = self.air.doId2do.get(daleId)
+ self.chatty.setDaleId(self.daleId)
diff --git a/toontown/src/classicchars/DistributedDaisy.py b/toontown/src/classicchars/DistributedDaisy.py
new file mode 100644
index 0000000..02a893c
--- /dev/null
+++ b/toontown/src/classicchars/DistributedDaisy.py
@@ -0,0 +1,141 @@
+"""DistributedDaisy module: contains the DistributedDaisy class"""
+
+from direct.showbase.ShowBaseGlobal import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from toontown.hood import TTHood
+
+class DistributedDaisy(DistributedCCharBase.DistributedCCharBase):
+ """DistributedDaisy class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedDaisy")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedDaisy_initialized
+ except:
+ self.DistributedDaisy_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Daisy,
+ 'dd')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ del self.neutralDoneEvent
+ del self.neutral
+ del self.walkDoneEvent
+ del self.walk
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove Daisy and state data information
+ """
+ try:
+ self.DistributedDaisy_deleted
+ except:
+ del self.fsm
+ self.DistributedDaisy_deleted = 1
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+
+ def generate( self ):
+ """
+ create Daisy and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self, self.diffPath)
+ name = self.getName()
+ self.neutralDoneEvent = self.taskName(name + '-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(
+ self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self, self.diffPath)
+ self.fsm.request('Neutral')
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.neutral.enter()
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def walkSpeed(self):
+ return ToontownGlobals.DaisySpeed
+
+ def handleHolidays(self):
+ """
+ Handle holiday specific behaviour
+ """
+ DistributedCCharBase.DistributedCCharBase.handleHolidays(self)
+ if hasattr(base.cr, "newsManager") and base.cr.newsManager:
+ holidayIds = base.cr.newsManager.getHolidayIdList()
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds and isinstance(self.cr.playGame.hood, TTHood.TTHood):
+ self.diffPath = TTLocalizer.Mickey
diff --git a/toontown/src/classicchars/DistributedDaisyAI.py b/toontown/src/classicchars/DistributedDaisyAI.py
new file mode 100644
index 0000000..6c3433c
--- /dev/null
+++ b/toontown/src/classicchars/DistributedDaisyAI.py
@@ -0,0 +1,215 @@
+"""DistributedDaisyAI module: contains the DistributedDaisyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import CharStateDatasAI
+
+class DistributedDaisyAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedDaisyAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Daisy)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedDaisyAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+
+ def generate( self ):
+ # all state data's that Daisy will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ name = self.getName()
+ self.lonelyDoneEvent = self.taskName(name + '-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName(name + '-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self, self.diffPath)
+
+ def walkSpeed(self):
+ return ToontownGlobals.DaisySpeed
+
+ # this function kicks off Daisy
+ def start(self):
+ # poor lonely Daisy
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Daisy is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+
+ if(self.transitionToCostume == 1):
+ curWalkNode = self.walk.getDestNode()
+ if simbase.air.holidayManager:
+ if ToontownGlobals.HALLOWEEN_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ elif ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no costume holiday')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no holiday Manager')
+
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Daisy"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Daisy
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near Daisy
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Lonely')
+
+ ##TransitionToCostumeState##
+ def enterTransitionToCostume(self):
+ pass
+
+ def exitTransitionToCostume(self):
+ pass
+
+ def handleHolidays(self):
+ """
+ Handle holiday specific behaviour
+ """
+ DistributedCCharBaseAI.DistributedCCharBaseAI.handleHolidays(self)
+ if hasattr(simbase.air, "holidayManager"):
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays:
+ if simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES] != None \
+ and simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].getRunningState():
+ self.diffPath = TTLocalizer.Mickey
diff --git a/toontown/src/classicchars/DistributedDale.py b/toontown/src/classicchars/DistributedDale.py
new file mode 100644
index 0000000..6e50159
--- /dev/null
+++ b/toontown/src/classicchars/DistributedDale.py
@@ -0,0 +1,153 @@
+"""DistributedDaisy module: contains the DistributedDaisy class"""
+
+from direct.showbase.ShowBaseGlobal import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+
+class DistributedDale(DistributedCCharBase.DistributedCCharBase):
+ """DistributedDale class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedDale")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedDale_initialized
+ except:
+ self.DistributedDale_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Dale,
+ 'da')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ del self.neutralDoneEvent
+ del self.neutral
+ del self.walkDoneEvent
+ if self.walk:
+ self.walk.exit()
+ del self.walk
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove Dale and state data information
+ """
+ try:
+ self.DistributedDale_deleted
+ except:
+ del self.fsm
+ self.DistributedDale_deleted = 1
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+
+ def generate( self ):
+ """
+ create Dale and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self)
+ # shift him a bit so he's not on top of chip
+ self.setX(self.getX() + ToontownGlobals.DaleOrbitDistance)
+ name = self.getName()
+ self.neutralDoneEvent = self.taskName(name + '-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(
+ self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ self.fsm.request('Neutral')
+
+ def announceGenerate(self):
+ """We have all the required fields, do stuff dependent on it."""
+ DistributedCCharBase.DistributedCCharBase.announceGenerate(self)
+ self.walk = CharStateDatas.CharFollowChipState(
+ self.walkDoneEvent, self, self.chipId)
+
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.neutral.enter()
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp, offsetX=0, offsetY=0):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp, offsetX, offsetY)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def walkSpeed(self):
+ return ToontownGlobals.DaleSpeed
+
+ def setFollowChip(self, srcNode, destNode, timestamp, offsetX, offsetY):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp, offsetX, offsetY)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def setChipId(self, chipId):
+ """Set the chipId as dictated by the AI."""
+ self.chipId = chipId
diff --git a/toontown/src/classicchars/DistributedDaleAI.py b/toontown/src/classicchars/DistributedDaleAI.py
new file mode 100644
index 0000000..0fc4da0
--- /dev/null
+++ b/toontown/src/classicchars/DistributedDaleAI.py
@@ -0,0 +1,232 @@
+"""DistributedDaisyAI module: contains the DistributedDaisyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import CharStateDatasAI
+
+class DistributedDaleAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedDaleAI")
+
+ def __init__(self, air, chipId):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Dale)
+ self.chipId =chipId
+ self.chip = air.doId2do.get(chipId)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedDaleAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'FollowChip', 'Walk']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'FollowChip', 'Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty']),
+ State.State('FollowChip',
+ self.enterFollowChip,
+ self.exitFollowChip,
+ ['Lonely', 'Chatty', 'FollowChip']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+
+ def generate( self ):
+ # all state data's that Dale will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+
+ # the old CharStateDatasAI won't work since dale is dependent on chip
+ # lets create hooks to accept when he's entering and leaving states
+ #chip = simbase.air.doId2do.get(self.chipId)
+ #name = chip.getName()
+ #self.lonelyDoneEvent = self.taskName(name + '-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ None, self)
+
+ #self.chattyDoneEvent = self.taskName(name + '-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ None, self)
+
+ #self.walkDoneEvent = self.taskName(name + '-walk-done')
+ #self.walk = CharStateDatasAI.CharWalkStateAI(
+ # self.walkDoneEvent, self)
+
+ #self.followChipDoneEvent = self.taskName(name + '-follow-done')
+ self.followChip = CharStateDatasAI.CharFollowChipStateAI(
+ None, self, self.chip)
+
+ def walkSpeed(self):
+ return ToontownGlobals.DaleSpeed
+
+ # this function kicks off Dale
+ def start(self):
+ # poor lonely Dale
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Dale is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert self.notify.debugStateCall(self)
+ assert(doneStatus.has_key('status'))
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Dale"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ assert self.notify.debugStateCall(self)
+ self.lonely.enter()
+ #self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ assert self.notify.debugStateCall(self)
+ #self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ #self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ #self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ ### Follow Chip state ###
+ def enterFollowChip(self):
+ self.notify.debug("enterFollowChip")
+ #import pdb; pdb.set_trace()
+ walkState = self.chip.walk
+ destNode = walkState.getDestNode()
+ self.followChip.enter(destNode)
+ #self.acceptOnce(self.Event, self.__decideNextState)
+
+ def exitFollowChip(self):
+ #self.ignore(self.walkDoneEvent)
+ self.notify.debug('exitFollowChip')
+ self.followChip.exit()
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Dale
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if False: #self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near Dale
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ #self.fsm.request('Lonely')
+ pass
+
+ def chipEnteringState(self, newState):
+ """Handle chip entering a new state."""
+ assert self.notify.debugStateCall(self)
+ if newState == 'Walk':
+ self.doFollowChip()
+ #elif newState == 'Chatty':
+ # self.doChatty()
+
+ def chipLeavingState(self, oldState):
+ """Handle chip leaving his state."""
+ assert self.notify.debugStateCall(self)
+
+ def doFollowChip(self):
+ """Actually make dale follow chip."""
+ walkState = self.chip.walk
+ destNode = walkState.getDestNode()
+ #import pdb; pdb.set_trace()
+ self.fsm.request('FollowChip')
+
+ def doChatty(self):
+ """Make Dale's chatter sync with Chip's."""
+ #self.fsm.request('Chatty')
+ pass
+
+ def getChipId(self):
+ """Return chip's doId."""
+ return self.chipId
diff --git a/toontown/src/classicchars/DistributedDonald.py b/toontown/src/classicchars/DistributedDonald.py
new file mode 100644
index 0000000..effbb5b
--- /dev/null
+++ b/toontown/src/classicchars/DistributedDonald.py
@@ -0,0 +1,159 @@
+"""DistributedDonald module: contains the DistributedDonald class"""
+
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+import CharStateDatas
+import CCharChatter
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from toontown.hood import GSHood
+
+class DistributedDonald(DistributedCCharBase.DistributedCCharBase):
+ """DistributedDonald class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedDonald")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedDonald_initialized
+ except:
+ self.DistributedDonald_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Donald,
+ 'd')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ del self.neutralDoneEvent
+ del self.neutral
+ del self.walkDoneEvent
+ del self.walk
+ del self.walkStartTrack
+ del self.neutralStartTrack
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove Donald and state data information
+ """
+ try:
+ self.DistributedDonald_deleted
+ except:
+ self.DistributedDonald_deleted = 1
+ del self.fsm
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+ #self.disable()
+
+ def generate( self ):
+ """
+ create Donald and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self, self.diffPath)
+ name = self.getName()
+ self.neutralDoneEvent = self.taskName(name + '-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(
+ self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self, self.diffPath)
+ self.walkStartTrack = self.actorInterval("trans-back")
+ self.neutralStartTrack = self.actorInterval("trans")
+ self.fsm.request('Neutral')
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.notify.debug("Neutral " + self.getName() + "...")
+ self.neutral.enter(startTrack = self.neutralStartTrack, playRate = 0.5)
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("Walking " + self.getName() + "...")
+ self.walk.enter(startTrack = self.walkStartTrack)
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ """
+ Parameters: srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp)
+ self.fsm.request("Walk")
+
+ def walkSpeed(self):
+ return ToontownGlobals.DonaldSpeed
+
+ def handleHolidays(self):
+ """
+ Handle Holiday specific behaviour
+ """
+ DistributedCCharBase.DistributedCCharBase.handleHolidays(self)
+ if hasattr(base.cr, "newsManager") and base.cr.newsManager:
+ holidayIds = base.cr.newsManager.getHolidayIdList()
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds and isinstance(self.cr.playGame.hood, GSHood.GSHood):
+ self.diffPath = TTLocalizer.Goofy
+
+ def getCCLocation(self):
+ if self.diffPath != None:
+ return 1
+ else:
+ return 0
+
+ def getCCChatter(self):
+ self.handleHolidays()
+ return self.CCChatter
diff --git a/toontown/src/classicchars/DistributedDonaldAI.py b/toontown/src/classicchars/DistributedDonaldAI.py
new file mode 100644
index 0000000..0851dfc
--- /dev/null
+++ b/toontown/src/classicchars/DistributedDonaldAI.py
@@ -0,0 +1,221 @@
+"""DistributedDonaldAI module: contains the DistributedDonaldAI class"""
+
+from otp.ai.AIBaseGlobal import *
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import CharStateDatasAI
+
+class DistributedDonaldAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedDonaldAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Donald)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedDonaldAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+
+ def generate( self ):
+ # all state data's that Donald will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ name = self.getName()
+ self.lonelyDoneEvent = self.taskName(name + '-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName(name + '-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self, self.diffPath)
+
+ def walkSpeed(self):
+ return ToontownGlobals.DonaldSpeed
+
+ # this function kicks off Donald
+ def start(self):
+ # poor lonely Donald
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Donald is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+
+ if(self.transitionToCostume == 1):
+ curWalkNode = self.walk.getDestNode()
+ if simbase.air.holidayManager:
+ if ToontownGlobals.HALLOWEEN_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ elif ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no costume holiday')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no holiday Manager')
+
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Donald"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Donald
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near Donald
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Lonely')
+
+ def handleHolidays(self):
+ """
+ Handle holiday specific behaviour
+ """
+ DistributedCCharBaseAI.DistributedCCharBaseAI.handleHolidays(self)
+ if hasattr(simbase.air, "holidayManager"):
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays:
+ if simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES] != None \
+ and simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].getRunningState():
+ self.diffPath = TTLocalizer.Goofy
+
+ def getCCLocation(self):
+ if self.diffPath != None:
+ return 1
+ else:
+ return 0
+
+ ##TransitionToCostumeState##
+ def enterTransitionToCostume(self):
+ pass
+
+ def exitTransitionToCostume(self):
+ pass
diff --git a/toontown/src/classicchars/DistributedDonaldDock.py b/toontown/src/classicchars/DistributedDonaldDock.py
new file mode 100644
index 0000000..063a40c
--- /dev/null
+++ b/toontown/src/classicchars/DistributedDonaldDock.py
@@ -0,0 +1,105 @@
+"""DistributedDonaldDock module: contains the DistributedDonaldDock class"""
+
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.toonbase import ToontownGlobals
+import CharStateDatas
+from direct.fsm import StateData
+from direct.task import Task
+from toontown.toonbase import TTLocalizer
+
+class DistributedDonaldDock(DistributedCCharBase.DistributedCCharBase):
+ """DistributedDonaldDock class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedDonaldDock')
+
+ def __init__(self, cr):
+ try:
+ self.DistributedDonaldDock_initialized
+ except:
+ self.DistributedDonaldDock_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.DonaldDock,
+ 'dw')
+ self.fsm = ClassicFSM.ClassicFSM('DistributedDonaldDock',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+ # We want him to show up as Donald
+ self.nametag.setName(TTLocalizer.Donald)
+
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ taskMgr.remove('enterNeutralTask')
+ del self.neutralDoneEvent
+ del self.neutral
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove DonaldDock and state data information
+ """
+ try:
+ self.DistributedDonaldDock_deleted
+ except:
+ self.DistributedDonaldDock_deleted = 1
+ del self.fsm
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+
+ def generate( self ):
+ """
+ create DonaldDock and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self)
+
+ boat = base.cr.playGame.hood.loader.boat
+
+ self.setPos(0,-1,3.95)
+ self.reparentTo(boat)
+
+ self.neutralDoneEvent = self.taskName('DonaldDock-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(self.neutralDoneEvent, self)
+ self.fsm.request('Neutral')
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.notify.debug("Neutral " + self.getName() + "...")
+ self.neutral.enter()
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
diff --git a/toontown/src/classicchars/DistributedDonaldDockAI.py b/toontown/src/classicchars/DistributedDonaldDockAI.py
new file mode 100644
index 0000000..33d3b2a
--- /dev/null
+++ b/toontown/src/classicchars/DistributedDonaldDockAI.py
@@ -0,0 +1,151 @@
+"""DistributedDonaldDockAI module: contains the DistributedDonaldDockAI class"""
+
+from otp.ai.AIBaseGlobal import *
+
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+import CharStateDatasAI
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+
+class DistributedDonaldDockAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedDonaldDockAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.DonaldDock)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedDonaldDockAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+
+ def generate( self ):
+ # all state data's that DonaldDock will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ self.lonelyDoneEvent = self.taskName('DonaldDock-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName('DonaldDock-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ # this function kicks off DonaldDock
+ def start(self):
+ # poor lonely DonaldDock
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state DonaldDock is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for DonaldDock"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.notify.debug("Entering Lonely")
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ self.notify.debug("Exiting Lonely")
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.notify.debug("Entering Chatty")
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ self.notify.debug("Exiting Chatty")
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near DonaldDock
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near DonaldDock
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ self.fsm.request('Lonely')
+
+ ##TransitionToCostumeState##
+ def enterTransitionToCostume(self):
+ pass
+
+ def exitTransitionToCostume(self):
+ pass
diff --git a/toontown/src/classicchars/DistributedGoofy.py b/toontown/src/classicchars/DistributedGoofy.py
new file mode 100644
index 0000000..7dd4837
--- /dev/null
+++ b/toontown/src/classicchars/DistributedGoofy.py
@@ -0,0 +1,126 @@
+"""DistributedGoofy module: contains the DistributedGoofy class"""
+
+from pandac.PandaModules import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+
+class DistributedGoofy(DistributedCCharBase.DistributedCCharBase):
+ """DistributedGoofy class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedGoofy")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedGoofy_initialized
+ except:
+ self.DistributedGoofy_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Goofy,
+ 'g')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ del self.neutralDoneEvent
+ del self.neutral
+ del self.walkDoneEvent
+ del self.walk
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove Goofy and state data information
+ """
+ try:
+ self.DistributedGoofy_deleted
+ except:
+ del self.fsm
+ self.DistributedGoofy_deleted = 1
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+
+ def generate( self ):
+ """
+ create Goofy and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self)
+ name = self.getName()
+ self.neutralDoneEvent = self.taskName(name + '-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(
+ self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self)
+ self.fsm.request('Neutral')
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.neutral.enter()
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def walkSpeed(self):
+ return ToontownGlobals.GoofySpeed
diff --git a/toontown/src/classicchars/DistributedGoofyAI.py b/toontown/src/classicchars/DistributedGoofyAI.py
new file mode 100644
index 0000000..0f4eec6
--- /dev/null
+++ b/toontown/src/classicchars/DistributedGoofyAI.py
@@ -0,0 +1,172 @@
+"""DistributedGoofyAI module: contains the DistributedGoofyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import CharStateDatasAI
+
+class DistributedGoofyAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedGoofyAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Goofy)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedGoofyAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+
+ def generate( self ):
+ # all state data's that Goofy will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ name = self.getName()
+ self.lonelyDoneEvent = self.taskName(name + '-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName(name + '-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self)
+
+ def walkSpeed(self):
+ return ToontownGlobals.GoofySpeed
+
+ # this function kicks off Goofy
+ def start(self):
+ # poor lonely Goofy
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Goofy is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Goofy"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Goofy
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near Goofy
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Lonely')
+
diff --git a/toontown/src/classicchars/DistributedGoofySpeedway.py b/toontown/src/classicchars/DistributedGoofySpeedway.py
new file mode 100644
index 0000000..a045aae
--- /dev/null
+++ b/toontown/src/classicchars/DistributedGoofySpeedway.py
@@ -0,0 +1,147 @@
+"""DistributedGoofy module: contains the DistributedGoofy class"""
+
+from pandac.PandaModules import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from toontown.hood import DLHood
+
+class DistributedGoofySpeedway(DistributedCCharBase.DistributedCCharBase):
+ """DistributedGoofySpeedway class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedGoofySpeedway")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedGoofySpeedway_initialized
+ except:
+ self.DistributedGoofySpeedway_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Goofy,
+ 'g')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ del self.neutralDoneEvent
+ del self.neutral
+ del self.walkDoneEvent
+ del self.walk
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove Goofy and state data information
+ """
+ try:
+ self.DistributedGoofySpeedway_deleted
+ except:
+ del self.fsm
+ self.DistributedGoofySpeedway_deleted = 1
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+
+ def generate( self ):
+ """
+ create Goofy and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self, self.diffPath)
+ name = self.getName()
+ self.neutralDoneEvent = self.taskName(name + '-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(
+ self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self, self.diffPath)
+ self.fsm.request('Neutral')
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.neutral.enter()
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def walkSpeed(self):
+ return ToontownGlobals.GoofySpeed
+
+ def handleHolidays(self):
+ """
+ Handle Holiday specific behaviour
+ """
+ DistributedCCharBase.DistributedCCharBase.handleHolidays(self)
+ if hasattr(base.cr, "newsManager") and base.cr.newsManager:
+ holidayIds = base.cr.newsManager.getHolidayIdList()
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds and isinstance(self.cr.playGame.hood, DLHood.DLHood):
+ self.diffPath = TTLocalizer.Donald
+
+ def getCCLocation(self):
+ if self.diffPath == None:
+ return 1
+ else:
+ return 0
\ No newline at end of file
diff --git a/toontown/src/classicchars/DistributedGoofySpeedwayAI.py b/toontown/src/classicchars/DistributedGoofySpeedwayAI.py
new file mode 100644
index 0000000..0bec8e7
--- /dev/null
+++ b/toontown/src/classicchars/DistributedGoofySpeedwayAI.py
@@ -0,0 +1,221 @@
+"""DistributedGoofyAI module: contains the DistributedGoofyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import CharStateDatasAI
+
+class DistributedGoofySpeedwayAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedGoofySpeedwayAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Goofy)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedGoofySpeedwayAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+
+ def generate( self ):
+ # all state data's that Goofy will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ name = self.getName()
+ self.lonelyDoneEvent = self.taskName(name + '-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName(name + '-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self, self.diffPath)
+
+ def walkSpeed(self):
+ return ToontownGlobals.GoofySpeed
+
+ # this function kicks off Goofy
+ def start(self):
+ # poor lonely Goofy
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Goofy is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+
+ if(self.transitionToCostume == 1):
+ curWalkNode = self.walk.getDestNode()
+ if simbase.air.holidayManager:
+ if ToontownGlobals.HALLOWEEN_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ elif ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no costume holiday')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no holiday Manager')
+
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Goofy"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Goofy
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near Goofy
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Lonely')
+
+ def handleHolidays(self):
+ """
+ Handle Holiday specific behaviour
+ """
+ DistributedCCharBaseAI.DistributedCCharBaseAI.handleHolidays(self)
+ if hasattr(simbase.air, "holidayManager"):
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays:
+ if simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES] != None \
+ and simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].getRunningState():
+ self.diffPath = TTLocalizer.Donald
+
+ def getCCLocation(self):
+ if self.diffPath == None:
+ return 1
+ else:
+ return 0
+
+ ##TransitionToCostumeState##
+ def enterTransitionToCostume(self):
+ pass
+
+ def exitTransitionToCostume(self):
+ pass
diff --git a/toontown/src/classicchars/DistributedMickey.py b/toontown/src/classicchars/DistributedMickey.py
new file mode 100644
index 0000000..dafdc6b
--- /dev/null
+++ b/toontown/src/classicchars/DistributedMickey.py
@@ -0,0 +1,144 @@
+"""DistributedMickey module: contains the DistributedMickey class"""
+
+from pandac.PandaModules import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+#import MickeyChatter
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from toontown.hood import DGHood
+
+class DistributedMickey(DistributedCCharBase.DistributedCCharBase):
+ """DistributedMickey class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedMickey")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedMickey_initialized
+ except:
+ self.DistributedMickey_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Mickey,
+ 'mk')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ self.neutralDoneEvent = None
+ self.neutral = None
+ self.walkDoneEvent = None
+ self.walk = None
+ self.fsm.requestFinalState()
+ self.notify.debug("Mickey Disbled")
+
+ def delete(self):
+ """
+ remove Mickey and state data information
+ """
+ try:
+ self.DistributedMickey_deleted
+ except:
+ self.DistributedMickey_deleted = 1
+ del self.fsm
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+ self.notify.debug("Mickey Deleted")
+
+ def generate( self ):
+ """
+ create Mickey and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self, self.diffPath)
+ name = self.getName()
+ self.neutralDoneEvent = self.taskName(name + '-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(
+ self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self, self.diffPath)
+ self.fsm.request('Neutral')
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.neutral.enter()
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def walkSpeed(self):
+ return ToontownGlobals.MickeySpeed
+
+ def handleHolidays(self):
+ """
+ Handle holiday specific behaviour
+ """
+ DistributedCCharBase.DistributedCCharBase.handleHolidays(self)
+ if hasattr(base.cr, "newsManager") and base.cr.newsManager:
+ holidayIds = base.cr.newsManager.getHolidayIdList()
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds and isinstance(self.cr.playGame.hood, DGHood.DGHood):
+ self.diffPath = TTLocalizer.Daisy
diff --git a/toontown/src/classicchars/DistributedMickeyAI.py b/toontown/src/classicchars/DistributedMickeyAI.py
new file mode 100644
index 0000000..7519090
--- /dev/null
+++ b/toontown/src/classicchars/DistributedMickeyAI.py
@@ -0,0 +1,217 @@
+"""DistributedMickeyAI module: contains the DistributedMickeyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+import CharStateDatasAI
+from toontown.toonbase import TTLocalizer
+
+class DistributedMickeyAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedMickeyAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Mickey)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedMickeyAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ del self.fsm
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+ self.notify.debug("MickeyAI Deleted")
+
+ def generate( self ):
+ # all state data's that Mickey will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ name = self.getName()
+ self.lonelyDoneEvent = self.taskName(name + '-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName(name + '-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self, self.diffPath)
+
+ def walkSpeed(self):
+ return ToontownGlobals.MickeySpeed
+
+ # this function kicks off Mickey
+ def start(self):
+ # poor lonely Mickey
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Mickey is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+
+ if(self.transitionToCostume == 1):
+ curWalkNode = self.walk.getDestNode()
+ if simbase.air.holidayManager:
+ if ToontownGlobals.HALLOWEEN_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ elif ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no costume holiday')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no holiday Manager')
+
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Mickey"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Mickey
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near mickey
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Lonely')
+
+ ##TransitionToCostumeState##
+ def enterTransitionToCostume(self):
+ pass
+
+ def exitTransitionToCostume(self):
+ pass
+
+ def handleHolidays(self):
+ """
+ Handle holiday specific behavior
+ """
+ DistributedCCharBaseAI.DistributedCCharBaseAI.handleHolidays(self)
+ if hasattr(simbase.air, "holidayManager"):
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays and simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES] != None \
+ and simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].getRunningState():
+ self.diffPath = TTLocalizer.Daisy
diff --git a/toontown/src/classicchars/DistributedMinnie.py b/toontown/src/classicchars/DistributedMinnie.py
new file mode 100644
index 0000000..1d081d0
--- /dev/null
+++ b/toontown/src/classicchars/DistributedMinnie.py
@@ -0,0 +1,141 @@
+"""DistributedMinnie module: contains the DistributedMinnie class"""
+
+from pandac.PandaModules import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from toontown.hood import BRHood
+
+class DistributedMinnie(DistributedCCharBase.DistributedCCharBase):
+ """DistributedMinnie class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedMinnie")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedMinnie_initialized
+ except:
+ self.DistributedMinnie_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Minnie,
+ 'mn')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ self.neutralDoneEvent = None
+ self.neutral = None
+ self.walkDoneEvent = None
+ self.walk = None
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove Minnie and state data information
+ """
+ try:
+ self.DistributedMinnie_deleted
+ except:
+ self.DistributedMinnie_deleted = 1
+ del self.fsm
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+ #self.disable()
+
+ def generate( self ):
+ """
+ create Minnie and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self, self.diffPath)
+ self.neutralDoneEvent = self.taskName('minnie-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(
+ self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName('minnie-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self, self.diffPath)
+ self.fsm.request('Neutral')
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.neutral.enter()
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def walkSpeed(self):
+ return ToontownGlobals.MinnieSpeed
+
+ def handleHolidays(self):
+ """
+ Handle Holiday specific behaviour
+ """
+ DistributedCCharBase.DistributedCCharBase.handleHolidays(self)
+ if hasattr(base.cr, "newsManager") and base.cr.newsManager:
+ holidayIds = base.cr.newsManager.getHolidayIdList()
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds and isinstance(self.cr.playGame.hood, BRHood.BRHood):
+ self.diffPath = TTLocalizer.Pluto
diff --git a/toontown/src/classicchars/DistributedMinnieAI.py b/toontown/src/classicchars/DistributedMinnieAI.py
new file mode 100644
index 0000000..28b4653
--- /dev/null
+++ b/toontown/src/classicchars/DistributedMinnieAI.py
@@ -0,0 +1,214 @@
+"""DistributedMinnieAI module: contains the DistributedMinnieAI class"""
+
+from otp.ai.AIBaseGlobal import *
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import CharStateDatasAI
+
+class DistributedMinnieAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedMinnieAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Minnie)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedMinnieAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+
+ def generate( self ):
+ # all state data's that Minnie will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ name = self.getName()
+ self.lonelyDoneEvent = self.taskName(name + '-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName(name + '-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ self.walkDoneEvent = self.taskName(name + '-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self, self.diffPath)
+
+ def walkSpeed(self):
+ return ToontownGlobals.MinnieSpeed
+
+ # this function kicks off Minnie
+ def start(self):
+ # poor lonely Minnie
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Minnie is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+
+ if(self.transitionToCostume == 1):
+ curWalkNode = self.walk.getDestNode()
+ if simbase.air.holidayManager:
+ if ToontownGlobals.HALLOWEEN_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ elif ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no costume holiday')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no holiday Manager')
+
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Minnie"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Minnie
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near Minnie
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Lonely')
+
+ def handleHolidays(self):
+ """
+ Handle Holiday specific behavior
+ """
+ DistributedCCharBaseAI.DistributedCCharBaseAI.handleHolidays(self)
+ if hasattr(simbase.air, "holidayManager"):
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays and simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES] != None \
+ and simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].getRunningState():
+ self.diffPath = TTLocalizer.Pluto
+
+ ##TransitionToCostumeState##
+ def enterTransitionToCostume(self):
+ pass
+
+ def exitTransitionToCostume(self):
+ pass
diff --git a/toontown/src/classicchars/DistributedPluto.py b/toontown/src/classicchars/DistributedPluto.py
new file mode 100644
index 0000000..37ab9f1
--- /dev/null
+++ b/toontown/src/classicchars/DistributedPluto.py
@@ -0,0 +1,173 @@
+"""DistributedPluto module: contains the DistributedPluto class"""
+
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.toonbase import ToontownGlobals
+import CharStateDatas
+from direct.fsm import StateData
+from direct.task import Task
+from toontown.toonbase import TTLocalizer
+from toontown.hood import MMHood
+
+class DistributedPluto(DistributedCCharBase.DistributedCCharBase):
+ """DistributedPluto class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPluto')
+
+ def __init__(self, cr):
+ try:
+ self.DistributedPluto_initialized
+ except:
+ self.DistributedPluto_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.Pluto,
+ 'p')
+ self.fsm = ClassicFSM.ClassicFSM('DistributedPluto',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBase.DistributedCCharBase.disable(self)
+
+ taskMgr.remove('enterNeutralTask')
+ taskMgr.remove('enterWalkTask')
+ del self.neutralDoneEvent
+ del self.neutral
+ del self.walkDoneEvent
+ del self.walk
+ del self.neutralStartTrack
+ del self.walkStartTrack
+ self.fsm.requestFinalState()
+
+ def delete(self):
+ """
+ remove Pluto and state data information
+ """
+ try:
+ self.DistributedPluto_deleted
+ except:
+ self.DistributedPluto_deleted = 1
+ del self.fsm
+ DistributedCCharBase.DistributedCCharBase.delete(self)
+
+ def generate( self ):
+ """
+ create Pluto and state data information
+ """
+ DistributedCCharBase.DistributedCCharBase.generate(self, self.diffPath)
+ self.neutralDoneEvent = self.taskName('pluto-neutral-done')
+ self.neutral = CharStateDatas.CharNeutralState(self.neutralDoneEvent, self)
+ self.walkDoneEvent = self.taskName('pluto-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatas.CharWalkState(
+ self.walkDoneEvent, self, self.diffPath)
+ self.walkStartTrack = Sequence(self.actorInterval('stand'),
+ Func(self.stand))
+ self.neutralStartTrack = Sequence(self.actorInterval('sit'),
+ Func(self.sit))
+ self.fsm.request('Neutral')
+
+ # Pluto's geometry changes dramatically from sitting to standing so
+ # we will perform some subtle transformations upon transitions
+ def stand(self):
+ self.dropShadow.setScale(0.9, 1.35, 0.9)
+ if hasattr(self, 'collNodePath'):
+ self.collNodePath.setScale(1.0, 1.5, 1.0)
+
+ def sit(self):
+ self.dropShadow.setScale(0.9)
+ if hasattr(self, 'collNodePath'):
+ self.collNodePath.setScale(1.0)
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ ### Neutral state ###
+ def enterNeutral(self):
+ self.notify.debug("Neutral " + self.getName() + "...")
+ # pass in the stand track to the walk state data
+ self.neutral.enter(self.neutralStartTrack)
+ self.acceptOnce(self.neutralDoneEvent, self.__decideNextState)
+
+ def exitNeutral(self):
+ self.ignore(self.neutralDoneEvent)
+ self.neutral.exit()
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("Walking " + self.getName() + "...")
+ # pass in the stand track to the walk state data
+ self.walk.enter(self.walkStartTrack)
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+ def __decideNextState(self, doneStatus):
+ self.fsm.request('Neutral')
+
+ def setWalk(self, srcNode, destNode, timestamp):
+ """
+ srcNode, were to walk from
+ destNode, where to walk to
+ timestamp, when server started walk
+
+ message sent from the server to say that this
+ character should now go into walk state
+ """
+ if destNode and (not destNode == srcNode):
+ self.walk.setWalk(srcNode, destNode, timestamp)
+ # request to enter walk if we have a state machine
+ self.fsm.request('Walk')
+
+ def walkSpeed(self):
+ return ToontownGlobals.PlutoSpeed
+
+ def handleHolidays(self):
+ """
+ Handle Holiday specific behaviour
+ """
+ DistributedCCharBase.DistributedCCharBase.handleHolidays(self)
+ if hasattr(base.cr, "newsManager") and base.cr.newsManager:
+ holidayIds = base.cr.newsManager.getHolidayIdList()
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in holidayIds and isinstance(self.cr.playGame.hood, MMHood.MMHood):
+ self.diffPath = TTLocalizer.Minnie
+
+ def getCCLocation(self):
+ if self.diffPath == None:
+ return 1
+ else:
+ return 0
diff --git a/toontown/src/classicchars/DistributedPlutoAI.py b/toontown/src/classicchars/DistributedPlutoAI.py
new file mode 100644
index 0000000..ef366b1
--- /dev/null
+++ b/toontown/src/classicchars/DistributedPlutoAI.py
@@ -0,0 +1,217 @@
+"""DistributedPlutoAI module: contains the DistributedPlutoAI class"""
+
+from otp.ai.AIBaseGlobal import *
+
+import DistributedCCharBaseAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+import CharStateDatasAI
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+
+class DistributedPlutoAI(DistributedCCharBaseAI.DistributedCCharBaseAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedPlutoAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.Pluto)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedPlutoAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+ self.handleHolidays()
+
+ def delete(self):
+ self.fsm.requestFinalState()
+ DistributedCCharBaseAI.DistributedCCharBaseAI.delete(self)
+
+ self.lonelyDoneEvent = None
+ self.lonely = None
+ self.chattyDoneEvent = None
+ self.chatty = None
+ self.walkDoneEvent = None
+ self.walk = None
+
+ def generate( self ):
+ # all state data's that Pluto will need
+ #
+ DistributedCCharBaseAI.DistributedCCharBaseAI.generate(self)
+ self.lonelyDoneEvent = self.taskName('pluto-lonely-done')
+ self.lonely = CharStateDatasAI.CharLonelyStateAI(
+ self.lonelyDoneEvent, self)
+
+ self.chattyDoneEvent = self.taskName('pluto-chatty-done')
+ self.chatty = CharStateDatasAI.CharChattyStateAI(
+ self.chattyDoneEvent, self)
+
+ self.walkDoneEvent = self.taskName('pluto-walk-done')
+ if self.diffPath == None:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self)
+ else:
+ self.walk = CharStateDatasAI.CharWalkStateAI(
+ self.walkDoneEvent, self, self.diffPath)
+
+
+ def walkSpeed(self):
+ return ToontownGlobals.PlutoSpeed
+
+ # this function kicks off Pluto
+ def start(self):
+ # poor lonely Pluto
+ self.fsm.request('Lonely')
+
+ def __decideNextState(self, doneStatus):
+ """
+ doneStatus, info about the finished state
+
+ called when the current state Pluto is in
+ decides that it is finished and a new state should
+ be transitioned into
+ """
+ assert(doneStatus.has_key('status'))
+
+ if(self.transitionToCostume == 1):
+ curWalkNode = self.walk.getDestNode()
+ if simbase.air.holidayManager:
+ if ToontownGlobals.HALLOWEEN_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.HALLOWEEN_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ elif ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays and \
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES]:
+ simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].triggerSwitch(curWalkNode, self)
+ self.fsm.request('TransitionToCostume')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no costume holiday')
+ else:
+ self.notify.warning('transitionToCostume == 1 but no holiday Manager')
+
+ if doneStatus['state'] == 'lonely' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'chatty' and \
+ doneStatus['status'] == 'done':
+ self.fsm.request('Walk')
+ elif doneStatus['state'] == 'walk' and \
+ doneStatus['status'] == 'done':
+ if len(self.nearbyAvatars) > 0:
+ self.fsm.request('Chatty')
+ else:
+ self.fsm.request('Lonely')
+ else:
+ assert 0, "Unknown status for Pluto"
+
+ ### Off state ###
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.exitOff(self)
+
+ ### Lonely state ###
+ def enterLonely(self):
+ self.lonely.enter()
+ self.acceptOnce(self.lonelyDoneEvent, self.__decideNextState)
+
+ def exitLonely(self):
+ self.ignore(self.lonelyDoneEvent)
+ self.lonely.exit()
+
+ def __goForAWalk(self, task):
+ self.notify.debug("going for a walk")
+ self.fsm.request('Walk')
+ return Task.done
+
+
+ ### Chatty state ###
+ def enterChatty(self):
+ self.chatty.enter()
+ self.acceptOnce(self.chattyDoneEvent, self.__decideNextState)
+
+ def exitChatty(self):
+ self.ignore(self.chattyDoneEvent)
+ self.chatty.exit()
+
+
+ ### Walk state ###
+ def enterWalk(self):
+ self.notify.debug("going for a walk")
+ self.walk.enter()
+ self.acceptOnce(self.walkDoneEvent, self.__decideNextState)
+
+ def exitWalk(self):
+ self.ignore(self.walkDoneEvent)
+ self.walk.exit()
+
+
+ def avatarEnterNextState(self):
+ """
+ decide what to do with the state machine when
+ a toon gets near Pluto
+ """
+ # if this is the only avatar, start talking
+ if len(self.nearbyAvatars) == 1:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Chatty')
+ else:
+ self.notify.debug("avatarEnterNextState: in walk state")
+ else:
+ self.notify.debug("avatarEnterNextState: num avatars: " +
+ str(len(self.nearbyAvatars)))
+
+ def avatarExitNextState(self):
+ """
+ decide what to do with the state machine when a
+ toon is no longer near Pluto
+ """
+ # no need to re-sort av list after removal
+ if len(self.nearbyAvatars) == 0:
+ if self.fsm.getCurrentState().getName() != 'Walk':
+ self.fsm.request('Lonely')
+
+ def handleHolidays(self):
+ """
+ Handle Holiday specific behaviour
+ """
+ DistributedCCharBaseAI.DistributedCCharBaseAI.handleHolidays(self)
+ if hasattr(simbase.air, "holidayManager"):
+ if ToontownGlobals.APRIL_FOOLS_COSTUMES in simbase.air.holidayManager.currentHolidays:
+ if simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES] != None \
+ and simbase.air.holidayManager.currentHolidays[ToontownGlobals.APRIL_FOOLS_COSTUMES].getRunningState():
+ self.diffPath = TTLocalizer.Minnie
+
+ ##TransitionToCostumeState##
+ def enterTransitionToCostume(self):
+ pass
+
+ def exitTransitionToCostume(self):
+ pass
diff --git a/toontown/src/classicchars/DistributedSuperGoofy.py b/toontown/src/classicchars/DistributedSuperGoofy.py
new file mode 100644
index 0000000..e50450c
--- /dev/null
+++ b/toontown/src/classicchars/DistributedSuperGoofy.py
@@ -0,0 +1,53 @@
+"""DistributedSuperGoofy module: contains the DistributedSuperGoofy class"""
+
+from pandac.PandaModules import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.classicchars import DistributedGoofySpeedway
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import DistributedCCharBase
+
+class DistributedSuperGoofy(DistributedGoofySpeedway.DistributedGoofySpeedway):
+ """DistributedSuperGoofy class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedSuperGoofy")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedGoofySpeedway_initialized
+ except:
+ self.DistributedGoofySpeedway_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.SuperGoofy,
+ 'sg')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+ # We want him to show up as Goofy
+ self.nametag.setName(TTLocalizer.Goofy)
+
+ def walkSpeed(self):
+ return ToontownGlobals.SuperGoofySpeed
diff --git a/toontown/src/classicchars/DistributedSuperGoofyAI.py b/toontown/src/classicchars/DistributedSuperGoofyAI.py
new file mode 100644
index 0000000..d133354
--- /dev/null
+++ b/toontown/src/classicchars/DistributedSuperGoofyAI.py
@@ -0,0 +1,54 @@
+"""DistributedSuperGoofyAI module: contains the DistributedMickeyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+from toontown.classicchars import DistributedGoofySpeedwayAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+import DistributedCCharBaseAI
+import CharStateDatasAI
+from toontown.toonbase import TTLocalizer
+
+class DistributedSuperGoofyAI(DistributedGoofySpeedwayAI.DistributedGoofySpeedwayAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedSuperGoofyAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.SuperGoofy)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedSuperGoofyAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ # We do not want to move into the transitionCostume state unless signalled to do so.
+ self.transitionToCostume = 0
+ self.fsm.enterInitialState()
+
+ def walkSpeed(self):
+ return ToontownGlobals.SuperGoofySpeed
\ No newline at end of file
diff --git a/toontown/src/classicchars/DistributedVampireMickey.py b/toontown/src/classicchars/DistributedVampireMickey.py
new file mode 100644
index 0000000..825e33a
--- /dev/null
+++ b/toontown/src/classicchars/DistributedVampireMickey.py
@@ -0,0 +1,53 @@
+"""DistributedVampireMickey module: contains the DistributedVampireMickey class"""
+
+from pandac.PandaModules import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.classicchars import DistributedMickey
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import DistributedCCharBase
+
+class DistributedVampireMickey(DistributedMickey.DistributedMickey):
+ """DistributedVampireMickey class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedVampireMickey")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedMickey_initialized
+ except:
+ self.DistributedMickey_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.VampireMickey,
+ 'vmk')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+ # We want him to show up as Mickey
+ self.nametag.setName(TTLocalizer.Mickey)
+
+ def walkSpeed(self):
+ return ToontownGlobals.VampireMickeySpeed
diff --git a/toontown/src/classicchars/DistributedVampireMickeyAI.py b/toontown/src/classicchars/DistributedVampireMickeyAI.py
new file mode 100644
index 0000000..89588d8
--- /dev/null
+++ b/toontown/src/classicchars/DistributedVampireMickeyAI.py
@@ -0,0 +1,54 @@
+"""DistributedVampireMickeyAI module: contains the DistributedMickeyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+from toontown.classicchars import DistributedMickeyAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+import DistributedCCharBaseAI
+import CharStateDatasAI
+from toontown.toonbase import TTLocalizer
+
+class DistributedVampireMickeyAI(DistributedMickeyAI.DistributedMickeyAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedVampireMickeyAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.VampireMickey)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedVampireMickeyAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ # We do not want to move into the transitionCostume state unless signalled to do so.
+ self.transitionToCostume = 0
+ self.fsm.enterInitialState()
+
+ def walkSpeed(self):
+ return ToontownGlobals.VampireMickeySpeed
\ No newline at end of file
diff --git a/toontown/src/classicchars/DistributedWesternPluto.py b/toontown/src/classicchars/DistributedWesternPluto.py
new file mode 100644
index 0000000..00f8df5
--- /dev/null
+++ b/toontown/src/classicchars/DistributedWesternPluto.py
@@ -0,0 +1,52 @@
+"""DistributedWesternPluto module: contains the DistributedWesternPluto class"""
+
+from pandac.PandaModules import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.classicchars import DistributedPluto
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import DistributedCCharBase
+
+class DistributedWesternPluto(DistributedPluto.DistributedPluto):
+ """DistributedPluto class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistributedWesternPluto')
+
+ def __init__(self, cr):
+ try:
+ self.DistributedPluto_initialized
+ except:
+ self.DistributedPluto_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.WesternPluto,
+ 'wp')
+ self.fsm = ClassicFSM.ClassicFSM('DistributedWesternPluto',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+ self.nametag.setName(TTLocalizer.Pluto)
+
+ def walkSpeed(self):
+ return ToontownGlobals.WesternPlutoSpeed
diff --git a/toontown/src/classicchars/DistributedWesternPlutoAI.py b/toontown/src/classicchars/DistributedWesternPlutoAI.py
new file mode 100644
index 0000000..7f2b995
--- /dev/null
+++ b/toontown/src/classicchars/DistributedWesternPlutoAI.py
@@ -0,0 +1,52 @@
+"""DistributedWesternPlutoAI module: contains the DistributedWesternPlutoAI class"""
+
+from otp.ai.AIBaseGlobal import *
+from toontown.classicchars import DistributedPlutoAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+import DistributedCCharBaseAI
+import CharStateDatasAI
+from toontown.toonbase import TTLocalizer
+
+class DistributedWesternPlutoAI(DistributedPlutoAI.DistributedPlutoAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedWesternPlutoAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.WesternPluto)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedWesternPlutoAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+ def walkSpeed(self):
+ return ToontownGlobals.WesternPlutoSpeed
\ No newline at end of file
diff --git a/toontown/src/classicchars/DistributedWitchMinnie.py b/toontown/src/classicchars/DistributedWitchMinnie.py
new file mode 100644
index 0000000..aa2be90
--- /dev/null
+++ b/toontown/src/classicchars/DistributedWitchMinnie.py
@@ -0,0 +1,53 @@
+"""DistributedWitchMinnie module: contains the DistributedWitchMinnie class"""
+
+from pandac.PandaModules import *
+import DistributedCCharBase
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.classicchars import DistributedMinnie
+import CharStateDatas
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+import DistributedCCharBase
+
+class DistributedWitchMinnie(DistributedMinnie.DistributedMinnie):
+ """DistributedWitchMinnie class"""
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedWitchMinnie")
+
+ def __init__(self, cr):
+ try:
+ self.DistributedMinnie_initialized
+ except:
+ self.DistributedMinnie_initialized = 1
+ DistributedCCharBase.DistributedCCharBase.__init__(self, cr,
+ TTLocalizer.WitchMinnie,
+ 'wmn')
+ self.fsm = ClassicFSM.ClassicFSM(self.getName(),
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Neutral']),
+ State.State('Neutral',
+ self.enterNeutral,
+ self.exitNeutral,
+ ['Walk']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Neutral']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ self.fsm.enterInitialState()
+
+ # We want him to show up as Minnie
+ self.nametag.setName(TTLocalizer.Minnie)
+
+ def walkSpeed(self):
+ return ToontownGlobals.WitchMinnieSpeed
diff --git a/toontown/src/classicchars/DistributedWitchMinnieAI.py b/toontown/src/classicchars/DistributedWitchMinnieAI.py
new file mode 100644
index 0000000..4befe4e
--- /dev/null
+++ b/toontown/src/classicchars/DistributedWitchMinnieAI.py
@@ -0,0 +1,54 @@
+"""DistributedWitchMinnieAI module: contains the DistributedMickeyAI class"""
+
+from otp.ai.AIBaseGlobal import *
+from toontown.classicchars import DistributedMinnieAI
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from direct.task import Task
+import random
+from toontown.toonbase import ToontownGlobals
+import DistributedCCharBaseAI
+import CharStateDatasAI
+from toontown.toonbase import TTLocalizer
+
+class DistributedWitchMinnieAI(DistributedMinnieAI.DistributedMinnieAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedWitchMinnieAI")
+
+ def __init__(self, air):
+ DistributedCCharBaseAI.DistributedCCharBaseAI.__init__(self, air, TTLocalizer.WitchMinnie)
+ self.fsm = ClassicFSM.ClassicFSM('DistributedWitchMinnieAI',
+ [State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Lonely', 'TransitionToCostume', 'Walk']),
+ State.State('Lonely',
+ self.enterLonely,
+ self.exitLonely,
+ ['Chatty', 'Walk', 'TransitionToCostume']),
+ State.State('Chatty',
+ self.enterChatty,
+ self.exitChatty,
+ ['Lonely', 'Walk', 'TransitionToCostume']),
+ State.State('Walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['Lonely', 'Chatty', 'TransitionToCostume']),
+ State.State('TransitionToCostume',
+ self.enterTransitionToCostume,
+ self.exitTransitionToCostume,
+ ['Off']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ # We do not want to move into the transitionCostume state unless signalled to do so.
+ self.transitionToCostume = 0
+ self.fsm.enterInitialState()
+
+ def walkSpeed(self):
+ return ToontownGlobals.WitchMinnieSpeed
\ No newline at end of file
diff --git a/toontown/src/classicchars/Sources.pp b/toontown/src/classicchars/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/toontown/src/classicchars/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/toontown/src/classicchars/__init__.py b/toontown/src/classicchars/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/coderedemption/Sources.pp b/toontown/src/coderedemption/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/toontown/src/coderedemption/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/toontown/src/coderedemption/StressTestAvIds.txt b/toontown/src/coderedemption/StressTestAvIds.txt
new file mode 100644
index 0000000..18a6e95
--- /dev/null
+++ b/toontown/src/coderedemption/StressTestAvIds.txt
@@ -0,0 +1,35388 @@
+100000010
+100000026
+100000028
+100000040
+100008020
+100000066
+100000074
+100000108
+100000122
+100000650
+100000130
+100000134
+100000146
+100000156
+100000166
+100000168
+100000174
+100000184
+100000186
+100000632
+100000200
+100000210
+100000216
+100000232
+100000240
+100000242
+100000244
+100000246
+100000250
+100000258
+100001340
+100000280
+100000284
+100000294
+100000296
+100000300
+100000316
+100000318
+100000324
+100000714
+100000334
+100000342
+100000348
+100000350
+100000356
+100000370
+100000376
+100000380
+100000382
+100000394
+100000422
+100000412
+100000420
+100000426
+100000432
+100000434
+100000438
+100000442
+100001214
+100000464
+100000466
+100000470
+100000476
+100000480
+100000486
+100000488
+100000492
+100000494
+100047706
+100012486
+100000508
+100000512
+100000518
+100000520
+100000522
+100000530
+100000534
+100000538
+100000540
+100001298
+100000548
+100000550
+100000556
+100000558
+100000560
+100000570
+100006196
+100001612
+100000584
+100000590
+100001668
+100000594
+100000600
+100000602
+100000604
+100001076
+100000608
+100000614
+100000620
+100000628
+100000636
+100000642
+100000646
+100000648
+100000652
+100000654
+100000658
+100000662
+100000664
+100000672
+100000676
+100000684
+100000688
+100000698
+100000818
+100000712
+100000718
+100000722
+100000724
+100000726
+100000728
+100000732
+100000734
+100000736
+100000738
+100000740
+100004308
+100000748
+100001506
+100000756
+100057242
+100048478
+100000768
+100000774
+100000778
+100000782
+100000786
+100000790
+100000794
+100000796
+100000800
+100000804
+100000806
+100000808
+100000814
+100000822
+100000824
+100000828
+100000832
+100000836
+100000838
+100000842
+100042352
+100000852
+100000856
+100000868
+100000878
+100000894
+100000896
+100000906
+100000922
+100000926
+100000936
+100076866
+100000944
+100000948
+100000962
+100000972
+100000994
+100000998
+100001002
+100001012
+100001016
+100001018
+100001028
+100001040
+100001044
+100001048
+100001056
+100001060
+100001064
+100001066
+100001072
+100001082
+100001086
+100001088
+100001096
+100001104
+100001108
+100001142
+100001114
+100001116
+100001120
+100001128
+100001134
+100001138
+100001140
+100001146
+100001150
+100001154
+100001160
+100001166
+100001168
+100015678
+100001448
+100001178
+100019774
+100001184
+100001190
+100001196
+100001206
+100001212
+100001220
+100001226
+100001230
+100001234
+100001240
+100001248
+100001270
+100001292
+100001268
+100001288
+100001290
+100001278
+100001302
+100001304
+100001306
+100001314
+100001320
+100001328
+100001334
+100001344
+100001348
+100005752
+100001364
+100001382
+100017206
+100001392
+100001402
+100001416
+100001418
+100001420
+100001424
+100001428
+100001430
+100001434
+100001438
+100001440
+100004784
+100001444
+100018928
+100058390
+100001462
+100007886
+100001468
+100001474
+100001496
+100001504
+100001514
+100114138
+100001526
+100001602
+100001534
+100001536
+100052822
+100001556
+100001564
+100001604
+100048476
+100088986
+100001620
+100001626
+100001634
+100076844
+100001656
+100001660
+100001664
+100001672
+100001674
+100001790
+100001798
+100001800
+100001832
+100001838
+100001840
+100001842
+100001846
+100001888
+100001898
+100001904
+100001920
+100001926
+100001930
+100001932
+100001942
+100001954
+100001978
+100001988
+100001990
+100002002
+100002016
+100002018
+100002024
+100002046
+100002056
+100002076
+100002084
+100002086
+100002090
+100002110
+100019032
+100002120
+100002124
+100002150
+100002154
+100002156
+100002162
+100002172
+100002184
+100002212
+100002228
+100002232
+100002244
+100002252
+100002258
+100002294
+100002792
+100002300
+100002306
+100002314
+100002320
+100002326
+100002330
+100018226
+100002344
+100002362
+100002372
+100002382
+100002390
+100002406
+100002426
+100002438
+100002440
+100002442
+100002444
+100002448
+100002652
+100002462
+100024850
+100002480
+100002490
+100002494
+100045196
+100002508
+100007030
+100002534
+100006252
+100002572
+100002576
+100002604
+100002608
+100002614
+100002624
+100002626
+100002630
+100002642
+100002648
+100002656
+100002668
+100002780
+100002672
+100002676
+100002682
+100002706
+100002696
+100002710
+100002712
+100002714
+100002716
+100028680
+100002722
+100002726
+100002746
+100002860
+100061574
+100002772
+100002812
+100002816
+100067836
+100002824
+100002830
+100002836
+100002854
+100002876
+100002878
+100002890
+100002894
+100002896
+100002902
+100002904
+100005182
+100057476
+100002926
+100002948
+100002954
+100002970
+100002974
+100002976
+100002980
+100007866
+100007334
+100056952
+100012344
+100007596
+100054876
+100041450
+100017130
+100017146
+100017162
+100011266
+100076792
+100012968
+100017622
+100007688
+100012954
+100007794
+100003826
+100003828
+100114584
+100008136
+100076788
+100004074
+100004072
+100011670
+100048466
+100019272
+100007202
+100042612
+100007948
+100011942
+100007894
+100019012
+100018152
+100004554
+100004600
+100004802
+100007770
+100004620
+100018092
+100004666
+100017592
+100004986
+100005044
+100005048
+100005064
+100007178
+100005122
+100076764
+100011442
+100017320
+100005202
+100005220
+100005238
+100007048
+100011768
+100016830
+100005360
+100005364
+100005366
+100005426
+100005452
+100062400
+100018512
+100005540
+100005626
+100005672
+100025870
+100018080
+100005818
+100010958
+100015770
+100007750
+100005936
+100024896
+100099570
+100006048
+100006050
+100006170
+100006192
+100006230
+100028868
+100006234
+100006254
+100006258
+100006292
+100006296
+100061454
+100008140
+100025928
+100006312
+100007572
+100006346
+100006348
+100006358
+100006360
+100006362
+100006370
+100006372
+100006374
+100006418
+100016832
+100006436
+100006484
+100006522
+100018096
+100006568
+100006620
+100006650
+100006652
+100006672
+100006682
+100028676
+100006686
+100006688
+100006706
+100006710
+100007978
+100006734
+100007014
+100006746
+100006762
+100011870
+100006782
+100006786
+100006812
+100006820
+100006850
+100006854
+100006860
+100007980
+100006872
+100006880
+100006884
+100006888
+100006934
+100016290
+100006964
+100006982
+100006984
+100007002
+100007004
+100007006
+100007164
+100007058
+100007060
+100026270
+100007156
+100007140
+100007142
+100007146
+100007218
+100013238
+100011998
+100007324
+100007330
+100007332
+100007340
+100007342
+100007346
+100007348
+100007350
+100007610
+100007370
+100013084
+100007402
+100016508
+100007860
+100018154
+100007476
+100008236
+100025912
+100007512
+100007522
+100007528
+100048452
+100007654
+100007672
+100007868
+100032162
+100007736
+100048450
+100050412
+100089664
+100011278
+100047696
+100007828
+100007856
+100007960
+100007888
+100007890
+100007892
+100007902
+100025516
+100007924
+100007926
+100007950
+100007936
+100007952
+100007956
+100007986
+100007990
+100007994
+100012022
+100058954
+100019692
+100008156
+100008162
+100008182
+100008186
+100017442
+100008204
+100008206
+100017292
+100008244
+100008246
+100008268
+100008270
+100008288
+100008316
+100024846
+100008438
+100017554
+100012004
+100012604
+100008576
+100008578
+100008580
+100017434
+100020076
+100058884
+100008656
+100008690
+100008742
+100044134
+100016850
+100015628
+100015622
+100008898
+100009402
+100052824
+100018458
+100009092
+100009172
+100009208
+100009210
+100044234
+100009350
+100013036
+100011678
+100020022
+100009436
+100050488
+100009456
+100009498
+100009526
+100043264
+100009640
+100009708
+100009710
+100091040
+100019330
+100009810
+100009822
+100018934
+100059524
+100009890
+100009894
+100076630
+100010998
+100075590
+100019116
+100009974
+100015756
+100012504
+100010004
+100010024
+100010068
+100010104
+100010106
+100049672
+100010168
+100012606
+100012908
+100019736
+100025354
+100010264
+100062798
+100010270
+100010274
+100010286
+100010310
+100010318
+100016502
+100020020
+100012664
+100011938
+100010440
+100010506
+100010522
+100010552
+100010554
+100017282
+100017584
+100010598
+100045500
+100012028
+100010660
+100010664
+100010666
+100010714
+100010716
+100046280
+100017550
+100010786
+100010792
+100011702
+100010812
+100010848
+100010884
+100010888
+100010920
+100019568
+100058444
+100011190
+100011022
+100011030
+100076538
+100076830
+100011758
+100011066
+100091810
+100011086
+100011090
+100011194
+100011110
+100017930
+100025574
+100011244
+100019184
+100049600
+100017350
+100025218
+100020018
+100048430
+100023794
+100051380
+100019490
+100011386
+100011388
+100011390
+100011392
+100011430
+100011434
+100012426
+100011468
+100011474
+100048426
+100011508
+100028772
+100020012
+100016932
+100011612
+100076302
+100011732
+100018120
+100063026
+100011764
+100011766
+100018700
+100011786
+100018486
+100011830
+100044956
+100011912
+100011914
+100011920
+100011994
+100012014
+100012042
+100012064
+100012066
+100012094
+100012102
+100016234
+100016232
+100025542
+100028966
+100012168
+100012184
+100012186
+100012188
+100012216
+100012220
+100018530
+100018436
+100012334
+100012404
+100047308
+100012464
+100076240
+100012496
+100012574
+100012576
+100012578
+100016504
+100012648
+100012654
+100025390
+100012738
+100017500
+100012764
+100017916
+100017498
+100115112
+100012798
+100119582
+100012806
+100012842
+100012846
+100019740
+100012888
+100012890
+100012976
+100012938
+100012948
+100012950
+100012964
+100012984
+100013018
+100013020
+100013088
+100013090
+100019164
+100013174
+100013176
+100013212
+100016384
+100013290
+100013310
+100013346
+100013416
+100013424
+100013494
+100017418
+100016842
+100013618
+100075502
+100017184
+100013720
+100013760
+100115774
+100013804
+100013822
+100013860
+100089776
+100013942
+100013950
+100013952
+100013968
+100076208
+100055082
+100014118
+100014142
+100014148
+100014170
+100014176
+100014178
+100016048
+100014202
+100016456
+100014234
+100014240
+100016380
+100017832
+100017824
+100016644
+100014350
+100014352
+100014470
+100014488
+100048404
+100014494
+100014524
+100015878
+100014584
+100016394
+100014614
+100014658
+100014660
+100014898
+100014758
+100019442
+100016040
+100017618
+100015006
+100015014
+100015016
+100015020
+100015024
+100015026
+100015042
+100016190
+100051258
+100015126
+100015176
+100015178
+100015232
+100018438
+100015396
+100015402
+100015404
+100015426
+100015470
+100015476
+100015484
+100015500
+100015502
+100016540
+100015778
+100015652
+100016212
+100025756
+100017986
+100018534
+100024622
+100017126
+100015828
+100018134
+100015884
+100015928
+100015930
+100019446
+100016094
+100017260
+100016028
+100016030
+100016044
+100016046
+100016056
+100019448
+100020176
+100016084
+100016096
+100018648
+100016192
+100016230
+100016236
+100016238
+100016244
+100016260
+100016268
+100016288
+100016322
+100019900
+100048376
+100016440
+100016444
+100016480
+100016494
+100016498
+100016512
+100016514
+100016532
+100016536
+100016580
+100016600
+100016602
+100016618
+100016620
+100016650
+100016674
+100016676
+100016682
+100016684
+100076186
+100019434
+100016712
+100016750
+100017336
+100017944
+100092692
+100016836
+100016900
+100016920
+100016990
+100017312
+100017008
+100017010
+100049544
+100017060
+100017062
+100019294
+100017788
+100017074
+100018072
+100017078
+100017082
+100019472
+100017208
+100018140
+100017232
+100017234
+100017262
+100017290
+100017302
+100017338
+100017342
+100017354
+100017358
+100017368
+100018790
+100017400
+100017424
+100076126
+100017478
+100017486
+100017488
+100017490
+100017502
+100017506
+100017510
+100017512
+100049278
+100017536
+100017546
+100017560
+100017562
+100045502
+100017636
+100017642
+100017734
+100017738
+100017754
+100017758
+100077788
+100017834
+100017840
+100017844
+100052828
+100017912
+100018228
+100024820
+100017968
+100017992
+100076104
+100018010
+100018020
+100018046
+100018068
+100018122
+100027262
+100018142
+100061646
+100090770
+100113100
+100058658
+100018302
+100018340
+100025298
+100018344
+100018346
+100025138
+100018372
+100018426
+100076082
+100018440
+100076074
+100091242
+100019118
+100019880
+100023798
+100043608
+100039000
+100117562
+100076968
+100076030
+100018802
+100018762
+100018770
+100018792
+100018794
+100076026
+100012686
+100019756
+100018880
+100076010
+100018882
+100018884
+100018886
+100023786
+100019016
+100019018
+100019030
+100076006
+100019160
+100013998
+100077612
+100048336
+100046924
+100017932
+100022434
+100077118
+100049156
+100075960
+100020718
+100020538
+100075942
+100026904
+100020922
+100020342
+100025568
+100020376
+100088426
+100020394
+100024914
+100023492
+100021806
+100020546
+100021034
+100048326
+100020700
+100075940
+100024446
+100093456
+100046930
+100020596
+100020614
+100020696
+100020702
+100020860
+100021180
+100020746
+100075930
+100024966
+100021514
+100020900
+100060042
+100025858
+100021044
+100021046
+100043106
+100025924
+100022270
+100076288
+100045252
+100021270
+100023510
+100021280
+100021360
+100021946
+100021206
+100075796
+100021386
+100075928
+100091000
+100021434
+100021452
+100078572
+100022308
+100021772
+100032112
+100021742
+100021526
+100021528
+100021976
+100022990
+100043638
+100025782
+100025428
+100046264
+100076348
+100025472
+100021802
+100021808
+100025714
+100046840
+100023008
+100021830
+100025032
+100028458
+100024044
+100022062
+100021990
+100022000
+100023772
+100022198
+100024724
+100022246
+100022064
+100025790
+100026314
+100022126
+100022132
+100022702
+100022154
+100023970
+100022310
+100059930
+100022468
+100025484
+100024374
+100024580
+100037762
+100075726
+100024696
+100042410
+100042944
+100022518
+100022556
+100027716
+100025538
+100025286
+100076296
+100022622
+100050166
+100023974
+100022704
+100023788
+100022746
+100024690
+100022788
+100022120
+100023694
+100024584
+100027384
+100022894
+100022916
+100025786
+100022950
+100024112
+100022972
+100023778
+100022992
+100024012
+100092866
+100023046
+100023050
+100023054
+100060598
+100023060
+100023062
+100042470
+100023088
+100023090
+100023110
+100023126
+100023144
+100057404
+100052818
+100023152
+100023154
+100023180
+100075704
+100040874
+100025478
+100075678
+100023232
+100023434
+100023822
+100023246
+100023264
+100023268
+100024048
+100023322
+100024760
+100023368
+100023366
+100023372
+100023388
+100025316
+100023414
+100048306
+100023838
+100058858
+100023420
+100023886
+100023724
+100048276
+100024450
+100023768
+100023782
+100023830
+100025398
+100023928
+100024096
+100024014
+100043470
+100024342
+100116902
+100044308
+100024198
+100058240
+100025022
+100024202
+100024242
+100024378
+100025356
+100024282
+100024286
+100025334
+100024398
+100024432
+100024424
+100024428
+100024436
+100024442
+100024844
+100024754
+100024504
+100024560
+100061756
+100024598
+100025540
+100025806
+100024702
+100025808
+100024852
+100025324
+100024862
+100024968
+100106356
+100075600
+100049666
+100025564
+100025220
+100025222
+100028462
+100025420
+100025436
+100025754
+100048252
+100051194
+100025522
+100025594
+100025910
+100075396
+100048246
+100062582
+100047128
+100062124
+100025846
+100025850
+100048242
+100075136
+100048234
+100056958
+100045916
+100076328
+100048266
+100058558
+100071606
+100074908
+100048206
+100048198
+100048192
+100074842
+100060886
+100050350
+100074720
+100046670
+100001312
+100074786
+100075050
+100048160
+100042012
+100074750
+100059522
+100048586
+100074724
+100074668
+100075140
+100068048
+100091140
+100063634
+100048294
+100007122
+100074620
+100092402
+100061076
+100047958
+100023908
+100058642
+100026322
+100050536
+100074544
+100075530
+100019674
+100058820
+100049410
+100047676
+100047928
+100047926
+100057132
+100045646
+100074848
+100047924
+100047922
+100047532
+100060798
+100098892
+100074422
+100047916
+100047912
+100041936
+100047906
+100052122
+100097172
+100074342
+100026500
+100095584
+100074338
+100026524
+100045566
+100026532
+100047892
+100047884
+100052102
+100047880
+100047878
+100047876
+100075850
+100078522
+100047874
+100050816
+100047870
+100047868
+100048026
+100089782
+100054118
+100047266
+100074488
+100041576
+100062978
+100046552
+100074060
+100049868
+100046674
+100075458
+100095080
+100025290
+100077692
+100050534
+100046828
+100047672
+100042456
+100046046
+100063572
+100050340
+100043892
+100092344
+100028782
+100094432
+100073892
+100047746
+100073870
+100059258
+100109386
+100056434
+100050330
+100074644
+100047742
+100007910
+100073812
+100089864
+100047734
+100026968
+100117320
+100091024
+100047724
+100078366
+100047704
+100076598
+100047692
+100047642
+100030492
+100058232
+100027066
+100027070
+100061892
+100047638
+100076324
+100047632
+100046808
+100058814
+100047536
+100051060
+100052732
+100058684
+100074562
+100047496
+100057528
+100106406
+100090764
+100027274
+100073290
+100059540
+100073936
+100073174
+100073286
+100062546
+100073102
+100047508
+100047472
+100049724
+100073080
+100049962
+100047466
+100050206
+100027364
+100073044
+100027378
+100047450
+100117038
+100047818
+100096656
+100047442
+100005920
+100050254
+100047424
+100047426
+100095382
+100039632
+100098578
+100073284
+100043592
+100047298
+100072842
+100047404
+100027576
+100060792
+100047398
+100014794
+100050260
+100050328
+100027634
+100002684
+100027984
+100045864
+100046520
+100057408
+100049890
+100073352
+100047386
+100052406
+100050390
+100043956
+100021012
+100057984
+100027702
+100027742
+100076934
+100072698
+100049388
+100027832
+100072684
+100047350
+100072680
+100047666
+100027930
+100027936
+100047340
+100047334
+100058538
+100095300
+100051386
+100042454
+100072616
+100028102
+100072614
+100028120
+100073694
+100028166
+100062118
+100037086
+100058008
+100060556
+100059570
+100028230
+100047558
+100047276
+100028480
+100072532
+100028342
+100034760
+100028364
+100028416
+100044064
+100049598
+100072638
+100074778
+100063478
+100028500
+100076382
+100050756
+100028522
+100028622
+100028590
+100059298
+100050044
+100043594
+100063282
+100072274
+100072314
+100040962
+100051520
+100028700
+100072268
+100047156
+100028750
+100121158
+100072228
+100028774
+100047220
+100072740
+100072296
+100047132
+100047716
+100059374
+100028886
+100028890
+100050972
+100038862
+100048038
+100047122
+100029032
+100036576
+100029912
+100046150
+100047376
+100029152
+100047092
+100088144
+100032184
+100072118
+100034912
+100047088
+100050928
+100072102
+100072100
+100029224
+100073698
+100042588
+100072160
+100029420
+100037824
+100072850
+100029914
+100035962
+100091472
+100085488
+100072176
+100041216
+100047052
+100090762
+100047050
+100051056
+100041578
+100089158
+100044590
+100034780
+100047032
+100041178
+100040246
+100031624
+100019076
+100071862
+100051852
+100030156
+100041998
+100040830
+100043732
+100030776
+100071494
+100062996
+100074230
+100097070
+100045004
+100030364
+100041748
+100030402
+100090442
+100071602
+100042052
+100049944
+100071672
+100047512
+100042018
+100051248
+100047678
+100030456
+100032646
+100046992
+100038216
+100059334
+100047834
+100075320
+100047800
+100030804
+100039682
+100030686
+100050858
+100051328
+100071648
+100071242
+100071906
+100049184
+100050744
+100071218
+100045266
+100072210
+100040866
+100046524
+100044640
+100042074
+100030220
+100093952
+100070856
+100071014
+100047116
+100042680
+100050448
+100046826
+100046824
+100052826
+100041724
+100076650
+100115970
+100088376
+100046490
+100042016
+100061124
+100090714
+100070348
+100041092
+100062046
+100031646
+100059050
+100069742
+100070766
+100039046
+100037912
+100046766
+100038766
+100043226
+100048876
+100037102
+100032754
+100058412
+100044600
+100032908
+100048806
+100062412
+100038268
+100032220
+100043948
+100071056
+100046732
+100035000
+100044560
+100071618
+100061044
+100032758
+100032554
+100068600
+100096236
+100046742
+100046704
+100032470
+100047814
+100074198
+100039052
+100033344
+100058602
+100032702
+100033064
+100034826
+100032766
+100034778
+100046650
+100086836
+100050182
+100046646
+100046580
+100040878
+100041416
+100027332
+100033070
+100044006
+100000624
+100033080
+100036438
+100040054
+100061902
+100044446
+100048352
+100046728
+100043400
+100061626
+100047288
+100046296
+100088044
+100038430
+100035276
+100077362
+100033314
+100041986
+100017582
+100062606
+100062378
+100067788
+100046932
+100046632
+100033506
+100033512
+100061792
+100061662
+100066560
+100039318
+100093318
+100033890
+100050860
+100033798
+100041096
+100062122
+100040200
+100046564
+100061428
+100036002
+100050462
+100042650
+100002968
+100047238
+100034220
+100091198
+100046562
+100037692
+100061332
+100046678
+100046612
+100047792
+100061322
+100050520
+100037672
+100041384
+100046506
+100006198
+100061320
+100046680
+100035004
+100034494
+100059558
+100049760
+100056558
+100092292
+100042236
+100035574
+100041696
+100036700
+100064106
+100006232
+100117942
+100058026
+100046794
+100036742
+100031938
+100049038
+100061642
+100049526
+100047124
+100035238
+100047668
+100093008
+100026988
+100059084
+100046478
+100046430
+100052332
+100113836
+100041412
+100046428
+100041012
+100039220
+100046422
+100050336
+100047320
+100035636
+100046450
+100060982
+100035756
+100072318
+100046402
+100046400
+100046398
+100044032
+100046396
+100037972
+100037480
+100113514
+100051540
+100060446
+100043708
+100047652
+100043506
+100042468
+100074390
+100039856
+100054310
+100060708
+100046386
+100036204
+100047680
+100050454
+100036244
+100046374
+100047048
+100060686
+100046114
+100097018
+100039274
+100060656
+100032550
+100046342
+100033892
+100036612
+100046998
+100039436
+100033536
+100058108
+100067298
+100046846
+100034976
+100046438
+100094896
+100037826
+100036784
+100046330
+100036806
+100036844
+100031702
+100038762
+100070612
+100052528
+100046188
+100043788
+100038520
+100043346
+100046262
+100037108
+100046166
+100037452
+100051296
+100046842
+100045648
+100037322
+100061182
+100046410
+100047802
+100092122
+100037468
+100037470
+100057940
+100037426
+100062734
+100047314
+100046274
+100037516
+100046118
+100044588
+100041268
+100051108
+100046116
+100089382
+100037578
+100047674
+100060334
+100035826
+100057178
+100037752
+100067218
+100038634
+100049054
+100037834
+100039768
+100037812
+100050256
+100046066
+100056126
+100039432
+100060510
+100114828
+100049360
+100063842
+100037810
+100075358
+100038174
+100076346
+100056068
+100033442
+100018090
+100046050
+100052668
+100038180
+100060312
+100062780
+100021388
+100045978
+100046870
+100060310
+100047434
+100044942
+100046108
+100058450
+100064262
+100074028
+100037512
+100041316
+100043090
+100093054
+100038394
+100047230
+100055824
+100054840
+100032704
+100057266
+100050052
+100046000
+100042464
+100047422
+100066372
+100041128
+100038734
+100037084
+100076052
+100074420
+100045994
+100038654
+100057244
+100045986
+100046530
+100066366
+100055816
+100059632
+100038276
+100044110
+100051014
+100045970
+100043102
+100044198
+100062678
+100039688
+100045224
+100059430
+100055556
+100046622
+100055532
+100038938
+100046384
+100039752
+100049458
+100057340
+100039120
+100049434
+100045974
+100039146
+100058062
+100061466
+100042574
+100056404
+100051550
+100043354
+100039364
+100062260
+100039394
+100055686
+100096358
+100051280
+100051250
+100039420
+100057360
+100045924
+100091594
+100040308
+100041966
+100045746
+100112080
+100050434
+100072212
+100076096
+100045910
+100047312
+100061804
+100039654
+100045912
+100050670
+100038126
+100043406
+100040712
+100065624
+100039698
+100067868
+100039738
+100039732
+100057552
+100037126
+100047552
+100065614
+100038484
+100043660
+100041098
+100036882
+100039906
+100039928
+100050436
+100046132
+100040290
+100049256
+100051392
+100040030
+100059388
+100046070
+100044380
+100044608
+100041006
+100040104
+100045860
+100040182
+100043460
+100046024
+100075256
+100060188
+100042984
+100054834
+100040248
+100044554
+100061956
+100055344
+100002946
+100040372
+100054124
+100072466
+100060828
+100051188
+100045162
+100051360
+100000140
+100040466
+100066542
+100040822
+100040560
+100046098
+100040568
+100056528
+100063286
+100045824
+100045822
+100042444
+100041454
+100040660
+100099632
+100040740
+100055552
+100054596
+100045812
+100048394
+100060590
+100040946
+100041478
+100043802
+100042354
+100057264
+100041796
+100058956
+100060176
+100041118
+100056554
+100040184
+100043312
+100063492
+100062334
+100046694
+100045782
+100087132
+100056920
+100041278
+100057076
+100042374
+100044718
+100070234
+100043510
+100041426
+100041428
+100036780
+100041786
+100046762
+100041500
+100050426
+100045742
+100041588
+100041650
+100050410
+100099026
+100051338
+100042032
+100055312
+100041744
+100061766
+100041774
+100041776
+100045726
+100065680
+100046658
+100041836
+100041838
+100041842
+100068936
+100072848
+100053484
+100045714
+100050314
+100041912
+100042022
+100053788
+100042024
+100042026
+100021208
+100042044
+100042046
+100058028
+100062848
+100049896
+100042102
+100042104
+100078244
+100070824
+100042146
+100043996
+100052198
+100057428
+100042348
+100046196
+100059650
+100029542
+100042508
+100042510
+100046618
+100067398
+100042616
+100053418
+100042626
+100052182
+100042634
+100050580
+100042732
+100042734
+100042736
+100042738
+100042748
+100058210
+100067722
+100023058
+100042824
+100052664
+100075440
+100052984
+100042946
+100042978
+100045722
+100061672
+100063178
+100052318
+100049318
+100048498
+100048506
+100051222
+100048514
+100061034
+100048526
+100048566
+100049428
+100048538
+100048542
+100038056
+100048564
+100027194
+100048572
+100030904
+100048612
+100048594
+100077392
+100060740
+100048632
+100048634
+100048636
+100048640
+100048642
+100059936
+100055596
+100051218
+100048666
+100059928
+100030518
+100052878
+100048710
+100048722
+100057384
+100059914
+100048734
+100048736
+100048738
+100051214
+100048762
+100043994
+100056916
+100059862
+100061558
+100059968
+100048786
+100048790
+100076356
+100048822
+100060600
+100049838
+100068462
+100048834
+100048836
+100048840
+100048862
+100089380
+100058612
+100049390
+100049342
+100048888
+100048896
+100048898
+100048900
+100051210
+100048914
+100048950
+100058868
+100059818
+100062476
+100048980
+100050280
+100048984
+100048994
+100029214
+100061046
+100057738
+100051206
+100118882
+100051202
+100049062
+100049064
+100049066
+100050862
+100062994
+100049092
+100049100
+100049108
+100049602
+100049584
+100049178
+100116492
+100076286
+100049230
+100050408
+100047326
+100058822
+100066626
+100049620
+100049308
+100049334
+100049338
+100058738
+100049344
+100051198
+100049370
+100074352
+100049430
+100050492
+100049446
+100049448
+100049464
+100049466
+100049470
+100049472
+100049474
+100065790
+100049500
+100049502
+100075488
+100049564
+100092068
+100049862
+100049576
+100061560
+100065174
+100049640
+100092834
+100049670
+100060010
+100049698
+100117578
+100049742
+100049762
+100058454
+100049780
+100049772
+100050134
+100067072
+100063284
+100049796
+100049822
+100049816
+100022642
+100049912
+100058952
+100049946
+100049938
+100090766
+100049942
+100049952
+100049954
+100049956
+100050058
+100049960
+100049970
+100049978
+100050036
+100050108
+100062860
+100060594
+100057644
+100058272
+100059564
+100058192
+100050142
+100075572
+100050292
+100062854
+100050242
+100050250
+100040432
+100050332
+100050334
+100050358
+100050362
+100050494
+100060254
+100066562
+100076122
+100062790
+100099628
+100050444
+100100060
+100051364
+100050560
+100051100
+100051158
+100050574
+100050586
+100062580
+100050600
+100023212
+100050700
+100050674
+100093786
+100050684
+100051036
+100050696
+100078108
+100086686
+100077018
+100077066
+100020168
+100078402
+100077170
+100077296
+100090974
+100063880
+100092110
+100077338
+100077356
+100077358
+100090758
+100091766
+100077480
+100091066
+100092390
+100077502
+100078404
+100077554
+100090604
+100073154
+100086374
+100077658
+100059048
+100078242
+100077712
+100077884
+100077888
+100090268
+100078072
+100078136
+100097574
+100110350
+100078076
+100078080
+100097582
+100078122
+100078124
+100078126
+100078140
+100099254
+100078154
+100078148
+100061812
+100078270
+100078280
+100100010
+100078360
+100078432
+100078452
+100099602
+100078562
+100099220
+100087326
+100078904
+100078786
+100078742
+100078760
+100078780
+100091240
+100078806
+100078848
+100078852
+100087848
+100079396
+100090586
+100079330
+100086254
+100099034
+100079028
+100079040
+100079126
+100079394
+100079214
+100079420
+100082906
+100079348
+100098322
+100116766
+100079662
+100115586
+100083178
+100092566
+100087140
+100092660
+100079822
+100085456
+100091450
+100080136
+100080028
+100080288
+100080120
+100080192
+100080248
+100083034
+100086098
+100080346
+100088576
+100081888
+100090760
+100080544
+100080514
+100089666
+100080794
+100090520
+100081036
+100081040
+100081044
+100081048
+100081050
+100090560
+100085002
+100081058
+100081078
+100088948
+100081174
+100090424
+100081454
+100084284
+100081318
+100091018
+100115592
+100084220
+100082910
+100085588
+100093058
+100089364
+100082412
+100084424
+100083348
+100082602
+100091546
+100088184
+100094948
+100091544
+100082718
+100082756
+100082778
+100089434
+100099664
+100083186
+100083078
+100088528
+100083208
+100083204
+100085460
+100085858
+100083864
+100083302
+100083480
+100083522
+100088432
+100083484
+100086948
+100084054
+100083560
+100083576
+100083582
+100083590
+100083636
+100089164
+100083788
+100083808
+100096740
+100083924
+100073156
+100080158
+100084958
+100085230
+100084776
+100085912
+100084566
+100085196
+100098872
+100090518
+100090478
+100084928
+100084386
+100094682
+100091502
+100085038
+100117914
+100085056
+100087052
+100088892
+100089534
+100088272
+100086096
+100090754
+100086436
+100085766
+100090730
+100086120
+100086674
+100085940
+100091010
+100085950
+100085952
+100086340
+100085970
+100086004
+100086256
+100064890
+100086124
+100049486
+100086132
+100086148
+100092890
+100086356
+100092080
+100086454
+100089568
+100115914
+100086510
+100095486
+100087536
+100087890
+100094448
+100086684
+100096736
+100089838
+100086848
+100087486
+100115902
+100090996
+100088328
+100117246
+100087236
+100087286
+100087252
+100087256
+100087282
+100060102
+100087288
+100087292
+100117574
+100095484
+100090712
+100087408
+100092726
+100087374
+100087490
+100116224
+100094922
+100087538
+100068106
+100088064
+100075910
+100087636
+100087632
+100087634
+100087638
+100087648
+100087892
+100096038
+100088302
+100091606
+100087992
+100090768
+100088040
+100089214
+100088092
+100088628
+100088232
+100113066
+100088262
+100088340
+100088492
+100091656
+100088370
+100097782
+100088548
+100089258
+100088752
+100007480
+100088878
+100089710
+100089272
+100089294
+100090816
+100090798
+100090222
+100091400
+100091528
+100120920
+100091624
+100093298
+100049900
+100091746
+100115620
+100091848
+100097210
+100031008
+100092464
+100091918
+100092544
+100061234
+100097280
+100094892
+100092090
+100092186
+100092226
+100092230
+100031198
+100092336
+100094024
+100092382
+100092482
+100092486
+100092488
+100092564
+100094192
+100077850
+100092734
+100092740
+100097278
+100092806
+100093212
+100093010
+100093012
+100093122
+100093158
+100093228
+100093460
+100093480
+100108964
+100093520
+100093524
+100093526
+100093536
+100093602
+100093696
+100093740
+100093758
+100093760
+100093764
+100093766
+100093768
+100093770
+100093792
+100093796
+100099462
+100114346
+100094614
+100060444
+100093954
+100096186
+100098048
+100098186
+100093996
+100088508
+100094168
+100094290
+100094296
+100094298
+100095986
+100094338
+100094386
+100113786
+100096754
+100094854
+100117946
+100099052
+100028672
+100115706
+100095062
+100089784
+100095342
+100095352
+100113898
+100095504
+100095542
+100078728
+100095620
+100095658
+100095698
+100095738
+100122664
+100095912
+100095988
+100099634
+100077704
+100097190
+100096042
+100113944
+100097846
+100096094
+100111498
+100096100
+100096102
+100096148
+100096156
+100096226
+100038494
+100096420
+100119226
+100097164
+100097248
+100097334
+100097422
+100097478
+100098056
+100098086
+100098218
+100098318
+100098336
+100098410
+100098428
+100098450
+100098470
+100098482
+100098502
+100121982
+100099284
+100098596
+100099256
+100098660
+100098788
+100099604
+100099138
+100099362
+100099364
+100099408
+100099450
+100099614
+100100070
+100099622
+100114432
+100099890
+100099908
+100099946
+100115328
+100100284
+100100476
+100103708
+100100336
+100106772
+100100396
+100106212
+100100512
+100109590
+100103434
+100112690
+100101022
+100105354
+100101290
+100111474
+100101232
+100091936
+100101094
+100101124
+100101362
+100101264
+100116906
+100101308
+100107058
+100101434
+100105506
+100115352
+100101556
+100106832
+100101572
+100101978
+100101750
+100101748
+100115720
+100102014
+100110732
+100104590
+100101906
+100101926
+100103648
+100102058
+100102108
+100107004
+100102010
+100102012
+100102016
+100102018
+100102100
+100102104
+100103756
+100102262
+100120006
+100109876
+100109460
+100105502
+100103202
+100102584
+100104698
+100102710
+100102772
+100107038
+100106526
+100104576
+100115138
+100108864
+100103192
+100103200
+100103212
+100103216
+100103234
+100103256
+100103272
+100103274
+100103294
+100103606
+100103456
+100107758
+100103626
+100104438
+100104514
+100104552
+100103772
+100103854
+100120108
+100103888
+100094744
+100106250
+100107230
+100104548
+100106254
+100104266
+100104596
+100114034
+100104682
+100104372
+100107192
+100104394
+100115290
+100105158
+100107532
+100104906
+100117844
+100116760
+100105296
+100107764
+100105752
+100117314
+100106666
+100106090
+100112904
+100106192
+100106260
+100107200
+100107006
+100106704
+100106466
+100107308
+100107440
+100109438
+100107560
+100107594
+100119916
+100108320
+100108654
+100107844
+100113452
+100107898
+100111426
+100110382
+100114452
+100108162
+100110300
+100108624
+100115146
+100108446
+100108756
+100116526
+100108846
+100110030
+100108990
+100109028
+100109298
+100109224
+100109274
+100110900
+100109608
+100109628
+100109638
+100109642
+100109660
+100111804
+100109906
+100109978
+100114498
+100118966
+100112528
+100119340
+100113718
+100043620
+100110460
+100112574
+100110660
+100110738
+100111146
+100111992
+100111544
+100111330
+100116992
+100114458
+100083968
+100111724
+100114682
+100111934
+100111968
+100112972
+100112136
+100113938
+100112276
+100112722
+100112356
+100112830
+100112850
+100107134
+100113114
+100113286
+100113156
+100108942
+100113212
+100115136
+100113230
+100113332
+100113540
+100114132
+100113690
+100113820
+100113954
+100116136
+100114264
+100114020
+100114426
+100118406
+100114560
+100119760
+100114686
+100114808
+100114958
+100115792
+100117060
+100115228
+100116590
+100115346
+100025720
+100115406
+100118180
+100115412
+100115418
+100121650
+100117614
+100115564
+100121182
+100115866
+100115724
+100115718
+100115794
+100115808
+100115832
+100118988
+100115988
+100115992
+100115594
+100116418
+100116124
+100116140
+100116176
+100116200
+100116488
+100117064
+100116618
+100117660
+100119718
+100117318
+100117334
+100117336
+100117418
+100117438
+100117518
+100117684
+100117716
+100117732
+100119010
+100109326
+100117860
+100117864
+100117888
+100117916
+100117918
+100118016
+100117992
+100118072
+100118642
+100118338
+100118384
+100119042
+100118504
+100121898
+100120464
+100119384
+100119198
+100121820
+100119302
+100119354
+100119454
+100119706
+100120106
+100119872
+100119936
+100120028
+100106038
+100120140
+100120338
+100120256
+100120258
+100120466
+100120482
+100120674
+100120682
+100120728
+100120730
+100120756
+100120762
+100117354
+100121312
+100121400
+100064142
+100106136
+100122046
+100122092
+100002208
+100049580
+100020374
+100024616
+100037274
+100022350
+100044136
+100044968
+100041324
+100123484
+100067676
+100110992
+100087176
+100087436
+100082434
+100067830
+100064460
+100077902
+100110916
+100052820
+100031372
+100110930
+100063264
+100084778
+100105210
+100074504
+100087092
+100103732
+100059934
+100104734
+100068882
+100018820
+100049730
+100093800
+100052684
+100062140
+100145962
+100071456
+100000002
+100290610
+100000014
+100000018
+100184638
+100000038
+100000044
+100299158
+100000052
+100000064
+100283096
+100000098
+100000100
+100000102
+100000104
+100000106
+100000114
+100073702
+100293570
+100000138
+100058970
+100000142
+100000154
+100061562
+100000180
+100000188
+100000192
+100000204
+100000206
+100242260
+100299140
+100000234
+100000236
+100198478
+100000262
+100000322
+100000268
+100156224
+100000272
+100000306
+100299134
+100118274
+100000310
+100124290
+100287912
+100000344
+100001342
+100000362
+100000402
+100000408
+100000416
+100000448
+100000458
+100000462
+100000490
+100061712
+100000506
+100000510
+100000528
+100000542
+100000544
+100007884
+100184622
+100248292
+100000586
+100000618
+100031852
+100000630
+100000640
+100000666
+100000690
+100000704
+100000706
+100000746
+100000772
+100000812
+100000830
+100000860
+100313912
+100000880
+100000888
+100238692
+100213554
+100184620
+100000910
+100000916
+100000930
+100000940
+100011924
+100000956
+100000966
+100000968
+100000976
+100000982
+100000986
+100000990
+100001004
+100001008
+100001026
+100001032
+100211508
+100001050
+100001054
+100046662
+100001092
+100001094
+100284662
+100001124
+100001198
+100001204
+100001238
+100019796
+100002788
+100342248
+100001286
+100001308
+100193822
+100007912
+100221800
+100001354
+100001356
+100305280
+100001390
+100221798
+100001396
+100017168
+100001406
+100001410
+100018628
+100001480
+100001608
+100001610
+100001500
+100001510
+100352501
+100001546
+100001554
+100001598
+100001600
+100019216
+100017472
+100001676
+100001678
+100001680
+100001682
+100332783
+100002116
+100002118
+100002312
+100124142
+100184612
+100002178
+100002186
+100126522
+100002192
+100002196
+100002210
+100002214
+100002216
+100299128
+100002224
+100316135
+100002246
+100288646
+100002262
+100002266
+100182150
+100002280
+100002304
+100002316
+100303168
+100273560
+100002346
+100032224
+100002354
+100002790
+100002364
+100002368
+100002512
+100280024
+100002394
+100002398
+100002400
+100002558
+100002412
+100002416
+100007598
+100002422
+100268456
+100002468
+100002704
+100002478
+100125528
+100115142
+100002488
+100002496
+100002498
+100002502
+100002516
+100098054
+100156440
+100135116
+100324779
+100056618
+100002574
+100002582
+100352397
+100213872
+100002602
+100355824
+100002982
+100002632
+100002644
+100002650
+100002658
+100002680
+100156522
+100299080
+100002700
+100002730
+100002738
+100083492
+100002784
+100002940
+100002802
+100002806
+100002822
+100002828
+100268198
+100277466
+100012572
+100002870
+100184610
+100002928
+100002930
+100061388
+100002960
+100323307
+100012458
+100015144
+100016710
+100288520
+100011498
+100015586
+100010116
+100302568
+100017462
+100009646
+100184594
+100008164
+100010734
+100184592
+100042394
+100007024
+100161508
+100003942
+100017460
+100283902
+100007574
+100007898
+100007116
+100018156
+100008000
+100184590
+100309186
+100156706
+100350335
+100004952
+100012862
+100013268
+100013094
+100016274
+100005310
+100221794
+100015588
+100005442
+100307578
+100013052
+100005718
+100293408
+100012076
+100005852
+100038232
+100006004
+100006194
+100122218
+100007120
+100011812
+100006452
+100006468
+100006486
+100006492
+100012632
+100007686
+100156708
+100006744
+100006792
+100006794
+100006816
+100007074
+100007170
+100007172
+100299048
+100209902
+100278298
+100018848
+100019000
+100016982
+100007344
+100007408
+100015882
+100015936
+100007464
+100007474
+100204358
+100117072
+100007768
+100123358
+100017928
+100127392
+100012376
+100012794
+100007922
+100210004
+100036678
+100007954
+100007970
+100008166
+100007996
+100127090
+100156570
+100016280
+100204674
+100008174
+100008176
+100008178
+100299044
+100012156
+100008292
+100008298
+100008318
+100008320
+100184544
+100329361
+100008402
+100150954
+100306730
+100008458
+100008464
+100012252
+100008502
+100023560
+100008538
+100008540
+100009330
+100017552
+100012152
+100011378
+100008572
+100109824
+100215444
+100249346
+100054618
+100008660
+100008684
+100008694
+100008696
+100287448
+100008764
+100299038
+100125614
+100008814
+100012244
+100011004
+100201490
+100008836
+100008866
+100008948
+100009404
+100009002
+100009008
+100009010
+100009642
+100009030
+100013274
+100213600
+100009100
+100298968
+100275966
+100009152
+100009228
+100009250
+100009504
+100298966
+100011294
+100298948
+100009400
+100009476
+100009478
+100009496
+100012088
+100009572
+100009574
+100009576
+100156814
+100009632
+100009634
+100030864
+100009662
+100009664
+100009700
+100184542
+100009706
+100009714
+100328932
+100140460
+100009802
+100009814
+100009816
+100184496
+100009826
+100009842
+100010756
+100009862
+100009864
+100184492
+100009900
+100009952
+100022354
+100011152
+100039572
+100010026
+100184490
+100127244
+100339997
+100010084
+100012424
+100337122
+100011224
+100011676
+100010214
+100035538
+100204818
+100010278
+100010304
+100010308
+100010312
+100010322
+100011002
+100104812
+100010370
+100010402
+100010404
+100010406
+100010408
+100010456
+100010488
+100015784
+100010524
+100010530
+100010536
+100010572
+100010574
+100011476
+100010620
+100010624
+100010642
+100010658
+100010738
+100011336
+100298942
+100278740
+100010862
+100010908
+100010982
+100055690
+100299032
+100011056
+100011760
+100061988
+100011118
+100011120
+100011146
+100244374
+100011320
+100011396
+100011440
+100011448
+100011452
+100057274
+100011510
+100011592
+100274556
+100011694
+100156398
+100282362
+100011628
+100011632
+100016756
+100161380
+100013060
+100351990
+100011860
+100011892
+100011908
+100012036
+100012096
+100012166
+100012214
+100012218
+100012224
+100195460
+100135244
+100012280
+100012500
+100012292
+100012294
+100296386
+100012332
+100012366
+100012422
+100109254
+100052258
+100012600
+100044410
+100017586
+100012670
+100070036
+100012694
+100012708
+100012712
+100012722
+100012768
+100176552
+100012840
+100012852
+100012946
+100012960
+100012970
+100012982
+100013048
+100111812
+100013126
+100355077
+100013196
+100018446
+100184472
+100013306
+100013308
+100078924
+100055728
+100013426
+100109976
+100013432
+100013452
+100013456
+100013492
+100013498
+100013502
+100013568
+100015996
+100013626
+100130560
+100351435
+100013696
+100269006
+100298914
+100013758
+100018014
+100013802
+100013824
+100320380
+100322856
+100013886
+100211842
+100013908
+100013912
+100184466
+100013920
+100013922
+100013940
+100013948
+100014000
+100014018
+100184540
+100014054
+100184464
+100014076
+100014078
+100014098
+100014122
+100184422
+100014166
+100343321
+100014242
+100014244
+100014262
+100014282
+100016160
+100014332
+100014340
+100115548
+100014346
+100014354
+100014390
+100019054
+100014412
+100014414
+100014450
+100014468
+100014534
+100014536
+100014568
+100014678
+100014696
+100014714
+100014734
+100048432
+100014776
+100266900
+100014820
+100014838
+100211270
+100014844
+100014860
+100014864
+100014904
+100017634
+100014958
+100014960
+100014966
+100014998
+100015002
+100015018
+100015046
+100015064
+100022970
+100057650
+100015104
+100015122
+100298912
+100263192
+100015250
+100300738
+100281956
+100015306
+100015308
+100015422
+100015474
+100015480
+100015526
+100015594
+100234446
+100015598
+100204648
+100015602
+100015604
+100015682
+100015704
+100015706
+100182236
+100025872
+100145664
+100015736
+100015740
+100122376
+100015826
+100299078
+100204814
+100302672
+100018202
+100016038
+100016104
+100295878
+100016126
+100016146
+100221764
+100016172
+100016208
+100016222
+100016266
+100016298
+100016342
+100016452
+100016478
+100016482
+100016510
+100016558
+100298868
+100018198
+100016610
+100016624
+100184366
+100016732
+100016760
+100016762
+100017542
+100017246
+100298866
+100016950
+100017014
+100184362
+100017044
+100184360
+100017098
+100017142
+100017370
+100017530
+100017538
+100298844
+100184356
+100017664
+100061540
+100184352
+100212726
+100184328
+100017756
+100205628
+100017904
+100017970
+100184292
+100000938
+100193818
+100242696
+100210592
+100018132
+100018150
+100018166
+100210058
+100018178
+100018180
+100121888
+100066042
+100018216
+100018268
+100205368
+100221770
+100018476
+100018492
+100171328
+100018556
+100024238
+100298812
+100018500
+100311702
+100019140
+100062988
+100016306
+100034828
+100016862
+100019040
+100002872
+100012428
+100063652
+100002542
+100326590
+100227268
+100018174
+100013214
+100007098
+100017212
+100201958
+100290836
+100025054
+100282260
+100060490
+100257804
+100283162
+100287440
+100018532
+100006228
+100007354
+100125448
+100018288
+100013368
+100285780
+100019166
+100171952
+100019388
+100058464
+100279032
+100047786
+100049214
+100125070
+100016062
+100056322
+100019622
+100298796
+100243392
+100019060
+100019458
+100034896
+100290932
+100308572
+100193808
+100019700
+100147042
+100045950
+100126644
+100156364
+100011322
+100019604
+100019990
+100020014
+100015592
+100202066
+100020224
+100023472
+100019866
+100022784
+100020752
+100362758
+100021494
+100021550
+100125854
+100241018
+100021188
+100111736
+100020428
+100021552
+100021362
+100021518
+100020150
+100298774
+100022058
+100040086
+100052598
+100024246
+100020170
+100184252
+100020750
+100328091
+100022134
+100221744
+100151108
+100021274
+100326845
+100021612
+100020720
+100024402
+100207570
+100015106
+100021430
+100023934
+100023952
+100022398
+100207564
+100212082
+100020344
+100021784
+100022088
+100062230
+100156550
+100283202
+100025780
+100023466
+100324354
+100319897
+100023318
+100245378
+100023234
+100121358
+100184220
+100235896
+100025116
+100021654
+100123648
+100221742
+100021070
+100020560
+100023766
+100023890
+100019872
+100328013
+100237736
+100023592
+100021978
+100023512
+100023804
+100122244
+100015710
+100019430
+100024640
+100019778
+100022696
+100023392
+100023424
+100024524
+100062162
+100184216
+100303172
+100021048
+100281152
+100021358
+100022588
+100023756
+100021800
+100022004
+100020816
+100021460
+100024132
+100024200
+100023010
+100040924
+100184206
+100189888
+100261624
+100047784
+100024866
+100020078
+100212430
+100007326
+100184182
+100092222
+100021282
+100021838
+100025230
+100021778
+100024620
+100010210
+100020556
+100046940
+100260150
+100209240
+100184146
+100024522
+100021810
+100012508
+100127832
+100184130
+100024280
+100184128
+100184124
+100025266
+100015080
+100268504
+100184120
+100024770
+100025322
+100024762
+100022274
+100024998
+100021902
+100027160
+100024222
+100184112
+100020362
+100020558
+100022414
+100022538
+100023644
+100020840
+100017580
+100025572
+100025200
+100025666
+100199008
+100025454
+100025500
+100043930
+100024942
+100025162
+100044908
+100016400
+100012432
+100127008
+100311754
+100022912
+100021524
+100056722
+100148134
+100192568
+100025918
+100205944
+100056990
+100022736
+100021072
+100018878
+100030728
+100034202
+100184088
+100023760
+100022598
+100031764
+100021090
+100020336
+100007124
+100039108
+100025684
+100330980
+100009440
+100017382
+100029510
+100325561
+100014492
+100052410
+100184058
+100044782
+100019098
+100017598
+100033232
+100021856
+100016362
+100300326
+100103414
+100022066
+100022378
+100033076
+100019462
+100291382
+100154114
+100302558
+100021646
+100209218
+100026528
+100288060
+100184056
+100346470
+100020364
+100011314
+100211330
+100021652
+100018182
+100021836
+100326514
+100184052
+100056232
+100022020
+100027132
+100052630
+100023558
+100023488
+100184016
+100035932
+100146656
+100024936
+100026842
+100243974
+100022842
+100125258
+100214152
+100060584
+100018430
+100020926
+100049376
+100027300
+100024542
+100184014
+100024816
+100024806
+100022734
+100043002
+100026408
+100208404
+100322515
+100046326
+100187154
+100011058
+100057522
+100020160
+100027982
+100184012
+100183978
+100027320
+100023666
+100155582
+100046250
+100028146
+100027896
+100250442
+100011220
+100020016
+100008482
+100010640
+100017602
+100028000
+100040812
+100027948
+100100242
+100028114
+100183974
+100023992
+100028350
+100020136
+100302508
+100221732
+100025490
+100024860
+100025972
+100123996
+100024064
+100025760
+100060318
+100029170
+100027600
+100270418
+100016164
+100053508
+100026828
+100023698
+100019008
+100028722
+100022604
+100278126
+100027086
+100306114
+100020554
+100183970
+100043000
+100183968
+100028708
+100020552
+100047246
+100022380
+100156512
+100027186
+100027406
+100043950
+100027868
+100028820
+100021918
+100028542
+100028254
+100007798
+100264378
+100183906
+100056598
+100028854
+100029550
+100029888
+100221738
+100127632
+100029672
+100029038
+100043966
+100029024
+100029172
+100150802
+100123144
+100030162
+100031442
+100095266
+100334048
+100122644
+100183902
+100031286
+100029030
+100062952
+100016506
+100030822
+100274204
+100029074
+100018644
+100037390
+100126030
+100082954
+100029088
+100031090
+100221726
+100029006
+100029938
+100031804
+100157340
+100030996
+100032054
+100327084
+100029614
+100033964
+100031452
+100032008
+100033172
+100030770
+100115264
+100030660
+100030080
+100188990
+100033516
+100320855
+100050576
+100033500
+100267460
+100125254
+100033432
+100274168
+100035232
+100031836
+100298708
+100051678
+100122384
+100190334
+100055196
+100033626
+100033908
+100303120
+100046706
+100156810
+100298700
+100256806
+100032218
+100361348
+100030924
+100287380
+100031066
+100263964
+100056650
+100262228
+100033532
+100122162
+100153378
+100183814
+100281316
+100029000
+100032984
+100256084
+100322047
+100059680
+100029824
+100052792
+100293188
+100052836
+100031644
+100033430
+100033622
+100029828
+100131996
+100040232
+100035040
+100032912
+100031004
+100183812
+100030980
+100045766
+100031376
+100183810
+100030292
+100263216
+100044404
+100287894
+100029600
+100257528
+100325987
+100283924
+100301936
+100174632
+100033438
+100029458
+100031420
+100035568
+100035432
+100032734
+100052874
+100033398
+100078372
+100030022
+100044804
+100031144
+100035972
+100048062
+100032346
+100156866
+100110514
+100148196
+100030460
+100032166
+100033360
+100035822
+100033174
+100062178
+100035966
+100034358
+100033104
+100031608
+100034596
+100031122
+100030218
+100034678
+100122578
+100041660
+100032138
+100151176
+100050012
+100031400
+100030628
+100032052
+100058148
+100204690
+100221722
+100036090
+100030296
+100036136
+100029580
+100046648
+100142288
+100353544
+100211776
+100253140
+100029476
+100030064
+100043152
+100183806
+100240992
+100030406
+100059396
+100038178
+100036680
+100036240
+100029110
+100034998
+100183804
+100175230
+100034646
+100076246
+100031558
+100032164
+100183792
+100033838
+100036972
+100058560
+100036704
+100034864
+100029736
+100316182
+100036310
+100362144
+100038848
+100034200
+100029452
+100335845
+100183786
+100282692
+100353140
+100045310
+100032764
+100036228
+100031458
+100035904
+100033028
+100015772
+100123138
+100213632
+100033714
+100156752
+100039070
+100045700
+100029156
+100032354
+100037082
+100034776
+100360038
+100037654
+100240474
+100027826
+100183784
+100056612
+100030268
+100037838
+100032158
+100035212
+100034900
+100022600
+100337774
+100125704
+100036788
+100263188
+100034656
+100034222
+100118206
+100037504
+100183782
+100038518
+100030404
+100029456
+100343703
+100150748
+100156518
+100033144
+100123030
+100038512
+100035382
+100039560
+100034936
+100183780
+100221718
+100040302
+100036974
+100038768
+100029036
+100033082
+100036182
+100183776
+100056994
+100024706
+100020084
+100035674
+100038890
+100037290
+100046238
+100341159
+100183774
+100037220
+100051062
+100034672
+100038392
+100032782
+100258196
+100030134
+100320706
+100038936
+100320580
+100055028
+100036942
+100028112
+100035910
+100030632
+100122774
+100055244
+100029168
+100319593
+100123932
+100031270
+100037806
+100037218
+100038072
+100038098
+100050184
+100030780
+100039874
+100183772
+100021406
+100038410
+100123778
+100039650
+100183770
+100029696
+100035646
+100042300
+100032308
+100046186
+100032214
+100183750
+100183744
+100036618
+100033584
+100300646
+100334514
+100029394
+100018914
+100038722
+100252474
+100031214
+100037648
+100282656
+100052716
+100032698
+100121878
+100032760
+100037430
+100034260
+100046338
+100039280
+100123612
+100038630
+100034198
+100034218
+100211290
+100038132
+100123232
+100113606
+100302788
+100030908
+100361588
+100183720
+100038710
+100270230
+100260322
+100242918
+100282358
+100039002
+100031664
+100040088
+100303288
+100093114
+100294952
+100126784
+100035642
+100048768
+100037844
+100032928
+100040792
+100326270
+100102128
+100033170
+100039690
+100037416
+100183704
+100039980
+100328419
+100039004
+100037860
+100125426
+100046844
+100307560
+100033628
+100041116
+100033720
+100036046
+100323037
+100041358
+100025804
+100041172
+100026166
+100024898
+100094264
+100040900
+100038632
+100030678
+100041230
+100020154
+100293160
+100042460
+100029150
+100041066
+100036872
+100039972
+100007804
+100221716
+100281740
+100274004
+100039258
+100033216
+100037990
+100327044
+100037458
+100022766
+100023014
+100041860
+100278742
+100213298
+100030058
+100041014
+100038922
+100034968
+100041354
+100044798
+100292666
+100193928
+100047942
+100183662
+100023186
+100047224
+100035466
+100264242
+100041608
+100000674
+100041490
+100218968
+100315632
+100039040
+100041716
+100031124
+100027356
+100295786
+100005942
+100037564
+100042114
+100114912
+100100416
+100061568
+100035086
+100300574
+100042572
+100283816
+100036578
+100123486
+100034518
+100352101
+100212552
+100284452
+100294716
+100026404
+100036870
+100036032
+100042358
+100042390
+100150974
+100252898
+100056250
+100038192
+100293148
+100042396
+100042238
+100048942
+100028626
+100056992
+100046856
+100183592
+100037820
+100329354
+100023664
+100183590
+100046776
+100030418
+100040566
+100029380
+100035244
+100151680
+100033342
+100164102
+100037008
+100030944
+100035024
+100059718
+100032788
+100031234
+100052570
+100320829
+100042302
+100028764
+100321995
+100040402
+100020164
+100277294
+100183588
+100045058
+100042392
+100033498
+100032156
+100055126
+100037272
+100036460
+100033624
+100318510
+100183584
+100040050
+100041486
+100192504
+100061374
+100268030
+100041852
+100026470
+100122490
+100025158
+100298934
+100041102
+100185494
+100048248
+100260368
+100042666
+100293144
+100023044
+100047250
+100047838
+100011762
+100035390
+100036544
+100295118
+100061910
+100183578
+100035486
+100023716
+100035638
+100048126
+100021982
+100037556
+100028940
+100183576
+100037566
+100032586
+100156756
+100041106
+100333857
+100058648
+100019002
+100043902
+100046288
+100016734
+100043026
+100042552
+100061006
+100041940
+100043078
+100031106
+100146186
+100042964
+100034472
+100018298
+100221710
+100049854
+100187220
+100187952
+100300120
+100221708
+100039036
+100011996
+100183558
+100268424
+100045260
+100246988
+100024768
+100220314
+100294764
+100293146
+100313744
+100040824
+100183550
+100043080
+100018742
+100018740
+100043242
+100028742
+100121368
+100183528
+100249310
+100041260
+100310612
+100204810
+100042506
+100029004
+100321913
+100015478
+100026736
+100039072
+100048422
+100188930
+100213556
+100046582
+100043844
+100039812
+100043330
+100042712
+100294994
+100057088
+100044690
+100043864
+100042630
+100030858
+100302206
+100043568
+100142586
+100057138
+100041124
+100124014
+100038860
+100041320
+100035570
+100183520
+100019238
+100204656
+100043622
+100038764
+100046744
+100061392
+100043398
+100209248
+100046738
+100299988
+100199902
+100042902
+100156480
+100030334
+100038628
+100058256
+100263172
+100029072
+100294576
+100319547
+100057506
+100122354
+100207674
+100040226
+100035816
+100043904
+100183496
+100043912
+100043712
+100357674
+100191428
+100243308
+100245962
+100360707
+100183462
+100270880
+100059850
+100056208
+100298656
+100023086
+100294990
+100044122
+100027912
+100287284
+100043368
+100044082
+100019576
+100183434
+100076182
+100040898
+100029718
+100042458
+100358645
+100039170
+100208640
+100353596
+100210424
+100183430
+100285760
+100047762
+100040298
+100294956
+100032280
+100183426
+100017632
+100045740
+100278784
+100044054
+100269608
+100335849
+100249540
+100042770
+100041242
+100043738
+100040090
+100044252
+100030926
+100183392
+100034654
+100043058
+100044344
+100025792
+100221698
+100044376
+100044462
+100148198
+100043940
+100332633
+100278744
+100183330
+100044370
+100049032
+100052714
+100043808
+100035360
+100049966
+100040592
+100121950
+100032608
+100250898
+100183310
+100183304
+100044604
+100044612
+100044298
+100044500
+100183256
+100044658
+100183596
+100310818
+100043288
+100044506
+100040658
+100043426
+100082848
+100019414
+100061566
+100300484
+100059342
+100288004
+100044686
+100044720
+100044050
+100303332
+100339348
+100205938
+100305844
+100307568
+100044776
+100039814
+100268846
+100044802
+100275624
+100280686
+100057558
+100058640
+100183248
+100043914
+100032044
+100317960
+100044850
+100210832
+100248494
+100052734
+100044112
+100271288
+100267430
+100035516
+100324378
+100049106
+100050110
+100041234
+100147088
+100041848
+100041784
+100044958
+100024842
+100044738
+100090778
+100211588
+100125060
+100045002
+100049940
+100254380
+100332902
+100043916
+100293012
+100042930
+100221674
+100285164
+100044378
+100049332
+100109454
+100054814
+100058326
+100028724
+100044206
+100022638
+100040286
+100125450
+100327020
+100045090
+100062026
+100017182
+100039458
+100275600
+100318236
+100032360
+100050366
+100028308
+100052138
+100044852
+100037266
+100279572
+100021736
+100056918
+100183150
+100076384
+100323916
+100025320
+100244084
+100359770
+100190618
+100048250
+100048702
+100049634
+100045292
+100213670
+100293194
+100057912
+100292988
+100047094
+100126530
+100183116
+100209592
+100327080
+100045856
+100045942
+100327426
+100112628
+100057562
+100292970
+100046764
+100234076
+100203558
+100045952
+100045960
+100154436
+100058946
+100045806
+100041360
+100173430
+100362208
+100183114
+100046928
+100045798
+100150752
+100046170
+100045948
+100029504
+100046372
+100046246
+100046202
+100046014
+100056534
+100045818
+100046230
+100215254
+100239438
+100046448
+100296574
+100001646
+100047100
+100221640
+100046472
+100049550
+100045712
+100045800
+100304488
+100045692
+100183104
+100292914
+100047394
+100292616
+100046016
+100321013
+100046312
+100046736
+100292912
+100172188
+100046160
+100049968
+100049648
+100046394
+100266510
+100046408
+100046944
+100046546
+100045858
+100046814
+100343000
+100047096
+100326334
+100046860
+100046496
+100046570
+100256420
+100242906
+100049916
+100281490
+100183108
+100046084
+100045402
+100046882
+100046900
+100047346
+100029692
+100098950
+100224246
+100024322
+100282554
+100047150
+100302414
+100023266
+100046920
+100288660
+100249036
+100018108
+100066040
+100046750
+100045278
+100070252
+100045862
+100152074
+100050570
+100325420
+100122264
+100046176
+100056568
+100046416
+100278348
+100039570
+100183078
+100210496
+100047944
+100242628
+100048034
+100048302
+100037726
+100047976
+100270508
+100301240
+100292910
+100048090
+100048172
+100048278
+100056848
+100049948
+100058382
+100048854
+100311622
+100048176
+100048652
+100018724
+100204392
+100048264
+100049090
+100048654
+100258666
+100045962
+100050538
+100048494
+100046110
+100183048
+100048716
+100307040
+100050474
+100049130
+100049224
+100049792
+100183286
+100060256
+100045322
+100048454
+100208516
+100048214
+100221636
+100189186
+100049060
+100044970
+100048532
+100048362
+100047014
+100048892
+100210506
+100047478
+100056104
+100115964
+100121772
+100047540
+100118892
+100049424
+100156500
+100185524
+100059438
+100175306
+100271626
+100292694
+100182992
+100211352
+100048358
+100141838
+100045710
+100049068
+100353048
+100044418
+100324587
+100049514
+100049290
+100055246
+100046064
+100046044
+100045708
+100046036
+100361076
+100048620
+100048438
+100048754
+100353095
+100046460
+100309316
+100049234
+100047234
+100045662
+100042750
+100049056
+100049856
+100182966
+100049014
+100049188
+100223564
+100182964
+100273996
+100252738
+100046752
+100048516
+100050732
+100046138
+100030530
+100048128
+100045776
+100182968
+100045938
+100063054
+100050768
+100048382
+100047542
+100175528
+100292346
+100049834
+100046952
+100048746
+100046818
+100045788
+100037642
+100235624
+100039926
+100047010
+100276544
+100049522
+100046588
+100030688
+100038130
+100049044
+100335926
+100339069
+100049088
+100044312
+100047112
+100046554
+100182950
+100285432
+100050064
+100049818
+100048474
+100046344
+100046258
+100047622
+100059976
+100050282
+100046054
+100041800
+100060210
+100043442
+100048902
+100287112
+100048664
+100221634
+100088368
+100182946
+100050902
+100050240
+100032364
+100029216
+100049146
+100221632
+100048364
+100182942
+100041950
+100046348
+100303518
+100182940
+100046232
+100182936
+100050006
+100049642
+100313316
+100043792
+100206666
+100050890
+100269644
+100048708
+100156914
+100017790
+100049366
+100221630
+100048174
+100051070
+100012012
+100050686
+100362319
+100182916
+100070236
+100049322
+100047058
+100272374
+100047656
+100310848
+100264882
+100018284
+100184934
+100047828
+100049562
+100050480
+100042862
+100050214
+100036554
+100124952
+100042592
+100192478
+100050420
+100034914
+100023182
+100033778
+100350097
+100050546
+100182904
+100033066
+100115414
+100294756
+100022998
+100304066
+100062608
+100028498
+100047206
+100050790
+100208512
+100035394
+100061936
+100046214
+100062198
+100027828
+100042398
+100050124
+100032260
+100047832
+100029078
+100049926
+100325394
+100023150
+100280696
+100182902
+100292672
+100182860
+100246414
+100049906
+100043546
+100048946
+100046638
+100051372
+100047522
+100193804
+100047648
+100123324
+100324187
+100212266
+100174200
+100051292
+100046798
+100049918
+100328780
+100047210
+100045280
+100182818
+100221612
+100051456
+100048164
+100051470
+100298646
+100280098
+100029512
+100182814
+100049998
+100051058
+100010266
+100050922
+100042516
+100182800
+100279398
+100047162
+100047254
+100047764
+100042674
+100243514
+100025702
+100045802
+100254908
+100156574
+100050440
+100182774
+100048488
+100046700
+100049236
+100046698
+100296642
+100047414
+100047700
+100047590
+100051614
+100049238
+100050652
+100016800
+100052232
+100210214
+100051734
+100051736
+100182764
+100069682
+100051704
+100275802
+100291596
+100089862
+100294210
+100123896
+100047882
+100203992
+100051832
+100052024
+100051964
+100309818
+100124058
+100051278
+100052046
+100052082
+100052084
+100052104
+100046458
+100052142
+100052176
+100051702
+100048588
+100074402
+100315394
+100052226
+100079056
+100207874
+100039490
+100052276
+100052256
+100303528
+100040240
+100049078
+100046464
+100203406
+100146324
+100213992
+100059590
+100285132
+100049700
+100214652
+100125856
+100051696
+100051684
+100048802
+100241130
+100182762
+100052294
+100182732
+100052388
+100052386
+100039602
+100028424
+100051694
+100010880
+100267628
+100047640
+100233962
+100042926
+100261798
+100051176
+100046310
+100046574
+100051408
+100060450
+100049898
+100182730
+100052476
+100045446
+100282194
+100052494
+100356158
+100045498
+100182728
+100310766
+100353139
+100311756
+100047720
+100221606
+100276450
+100155282
+100321045
+100221604
+100048604
+100048770
+100055638
+100182720
+100034536
+100048868
+100126902
+100052144
+100221598
+100221596
+100050510
+100250410
+100221570
+100052620
+100052672
+100021410
+100043974
+100296394
+100054556
+100052880
+100052858
+100110798
+100053168
+100053040
+100052976
+100121814
+100053166
+100052922
+100053258
+100307522
+100053172
+100023080
+100053428
+100053254
+100053042
+100053474
+100164384
+100190722
+100059008
+100053170
+100311458
+100053450
+100317720
+100122288
+100053588
+100206664
+100053592
+100053638
+100320688
+100056050
+100053672
+100061250
+100076488
+100303086
+100053526
+100053698
+100053696
+100053770
+100259678
+100053632
+100053810
+100279086
+100051098
+100112178
+100221532
+100054020
+100054038
+100182624
+100053956
+100210108
+100210714
+100054134
+100054138
+100048658
+100221530
+100182582
+100054294
+100054308
+100054304
+100214118
+100054344
+100150826
+100054434
+100124922
+100053786
+100260294
+100070010
+100055260
+100053976
+100054562
+100124942
+100054566
+100303860
+100309052
+100054568
+100054522
+100124768
+100054740
+100054192
+100204672
+100182568
+100221510
+100205578
+100211568
+100298642
+100288006
+100051406
+100230554
+100054140
+100054964
+100246170
+100054946
+100293900
+100054948
+100182564
+100311508
+100290428
+100298638
+100055036
+100290602
+100054084
+100280654
+100247174
+100280602
+100055158
+100096300
+100053290
+100054992
+100055184
+100053460
+100268610
+100151938
+100054724
+100333456
+100055278
+100055276
+100055386
+100055384
+100053044
+100055472
+100054884
+100362984
+100055560
+100054818
+100055604
+100318294
+100052972
+100231098
+100221508
+100053326
+100055618
+100123962
+100054540
+100055770
+100210710
+100182554
+100055808
+100124628
+100055844
+100289664
+100055878
+100055932
+100054194
+100055964
+100127420
+100170502
+100182536
+100053466
+100054974
+100268970
+100056190
+100323819
+100056210
+100203552
+100053164
+100195690
+100054872
+100056264
+100250362
+100055962
+100054572
+100055876
+100056372
+100056258
+100212590
+100203932
+100298636
+100321709
+100055730
+100273756
+100304496
+100055766
+100055826
+100056610
+100056608
+100053054
+100127166
+100318360
+100305530
+100291564
+100056702
+100332928
+100046030
+100055848
+100046092
+100291560
+100037268
+100045386
+100352551
+100055124
+100024500
+100056742
+100255330
+100055854
+100196536
+100243984
+100058446
+100056552
+100056122
+100307518
+100053282
+100055192
+100181860
+100359042
+100340385
+100057160
+100244500
+100057338
+100182484
+100037024
+100245930
+100063496
+100263132
+100052784
+100054806
+100211986
+100057386
+100212742
+100057606
+100231606
+100057686
+100055746
+100057148
+100360851
+100190574
+100056352
+100284050
+100042900
+100282668
+100291552
+100268346
+100139190
+100208948
+100057868
+100121490
+100045876
+100032386
+100252582
+100204808
+100053936
+100057894
+100054782
+100291046
+100049478
+100052860
+100351202
+100241972
+100044498
+100053284
+100060160
+100208472
+100300104
+100055038
+100053590
+100061316
+100299260
+100045638
+100124622
+100037472
+100052854
+100049182
+100050650
+100262218
+100221442
+100073950
+100058054
+100057074
+100041036
+100057194
+100053038
+100056532
+100001252
+100267396
+100051348
+100325502
+100044876
+100241988
+100182428
+100058264
+100291534
+100075876
+100052146
+100094328
+100303976
+100332615
+100016658
+100293202
+100182426
+100182424
+100291530
+100311228
+100029478
+100057282
+100234184
+100057292
+100284560
+100280678
+100057314
+100058386
+100057318
+100049546
+100057512
+100052136
+100291402
+100204314
+100054812
+100058438
+100176518
+100059132
+100043504
+100221410
+100057904
+100058088
+100058420
+100273968
+100182414
+100026670
+100291528
+100058422
+100058514
+100150672
+100052906
+100156788
+100025840
+100058608
+100041372
+100307190
+100182384
+100322039
+100278958
+100291526
+100053550
+100058686
+100281918
+100047974
+100058092
+100115040
+100280826
+100057284
+100058706
+100329375
+100056474
+100284358
+100025050
+100182376
+100047494
+100182358
+100028776
+100241012
+100058398
+100281420
+100058796
+100322221
+100263130
+100221380
+100060932
+100058578
+100058842
+100245714
+100303540
+100058944
+100182332
+100182324
+100050164
+100046772
+100056984
+100265454
+100058824
+100059094
+100182304
+100055966
+100152160
+100059006
+100051724
+100058834
+100286824
+100059296
+100059042
+100059040
+100156462
+100059120
+100325919
+100241740
+100295096
+100059044
+100210492
+100182300
+100059092
+100058770
+100053690
+100328175
+100051168
+100045902
+100263110
+100057316
+100058128
+100058816
+100059526
+100046038
+100042518
+100058968
+100056942
+100037814
+100056354
+100049872
+100340967
+100059382
+100053730
+100046734
+100058926
+100308366
+100182294
+100036358
+100059666
+100048144
+100058372
+100273504
+100275582
+100208698
+100042954
+100052208
+100321296
+100055960
+100046492
+100059074
+100049272
+100058830
+100059174
+100182292
+100221360
+100058784
+100058494
+100059422
+100090050
+100304630
+100032604
+100059154
+100042958
+100060104
+100059660
+100174814
+100221354
+100321412
+100060358
+100053344
+100042142
+100060386
+100060430
+100327056
+100205936
+100060438
+100059118
+100267362
+100060470
+100060458
+100059838
+100207550
+100203440
+100078626
+100210756
+100120434
+100050042
+100312360
+100059614
+100149128
+100182252
+100308964
+100182232
+100147370
+100057298
+100232008
+100340476
+100060674
+100182226
+100286192
+100030762
+100182224
+100025030
+100047740
+100284348
+100263336
+100143222
+100048670
+100050466
+100061354
+100182218
+100303400
+100165712
+100018494
+100059750
+100235954
+100182214
+100294768
+100060772
+100327040
+100060606
+100039784
+100208856
+100318160
+100182170
+100326282
+100097314
+100060980
+100060988
+100026034
+100031958
+100061002
+100060394
+100182162
+100263804
+100051548
+100060926
+100045786
+100150390
+100182106
+100298592
+100061554
+100333464
+100149400
+100044606
+100124986
+100137708
+100201832
+100061008
+100037542
+100267378
+100060054
+100314426
+100058260
+100056732
+100303252
+100182100
+100046424
+100061590
+100303276
+100182062
+100210234
+100029158
+100179270
+100060346
+100015880
+100235608
+100113550
+100061580
+100058656
+100333455
+100254734
+100044132
+100325496
+100221334
+100061550
+100048848
+100061536
+100176136
+100061266
+100018212
+100060136
+100057158
+100061004
+100108764
+100061410
+100123392
+100094738
+100060710
+100263108
+100053470
+100056824
+100062188
+100061840
+100201648
+100046850
+100038464
+100049848
+100061908
+100182032
+100058802
+100285144
+100323723
+100291732
+100275212
+100322379
+100347407
+100291252
+100061026
+100189692
+100055936
+100047750
+100062126
+100062148
+100062158
+100061728
+100062192
+100182026
+100333180
+100062268
+100062144
+100062276
+100062280
+100061178
+100062166
+100049228
+100208568
+100317830
+100131450
+100060282
+100206358
+100263164
+100061940
+100043682
+100301262
+100319010
+100062340
+100061594
+100048536
+100357066
+100182002
+100182000
+100263112
+100221326
+100077262
+100106312
+100016994
+100055180
+100181958
+100059114
+100025892
+100303008
+100076046
+100051054
+100030662
+100201730
+100264018
+100066178
+100221324
+100082414
+100122724
+100013906
+100057280
+100055640
+100181944
+100044284
+100058950
+100062428
+100061448
+100059754
+100093484
+100047022
+100214122
+100059812
+100061600
+100058766
+100043664
+100077622
+100059950
+100291502
+100181942
+100062478
+100062480
+100256610
+100060960
+100040068
+100059918
+100049764
+100327410
+100062536
+100293920
+100039910
+100058580
+100062554
+100062560
+100033050
+100057938
+100327390
+100050072
+100126594
+100036250
+100181922
+100294064
+100221316
+100181896
+100116350
+100016688
+100050948
+100039126
+100116198
+100181894
+100072168
+100060770
+100012800
+100062660
+100062666
+100181892
+100060074
+100040450
+100280376
+100241724
+100358637
+100062702
+100221296
+100246648
+100061464
+100182486
+100110038
+100034394
+100062050
+100181854
+100060240
+100048878
+100055900
+100046096
+100051422
+100360021
+100269152
+100000048
+100060712
+100045560
+100061040
+100221294
+100062218
+100056252
+100063194
+100062840
+100062850
+100062852
+100181850
+100059292
+100062058
+100321354
+100060670
+100098280
+100298582
+100356768
+100018574
+100060602
+100106082
+100181848
+100221278
+100294572
+100201538
+100314446
+100277518
+100062962
+100045844
+100062964
+100208648
+100062968
+100179460
+100201722
+100181788
+100068896
+100063002
+100291496
+100263106
+100063022
+100063024
+100063012
+100333185
+100059502
+100077070
+100198096
+100062332
+100151202
+100062934
+100306750
+100055858
+100063270
+100062012
+100063084
+100152500
+100096098
+100063098
+100062234
+100059682
+100285510
+100181774
+100062588
+100295408
+100016604
+100181754
+100063150
+100045110
+100027544
+100245716
+100291494
+100257414
+100151364
+100181746
+100063174
+100219402
+100279058
+100063196
+100063198
+100018676
+100063218
+100063226
+100291492
+100245922
+100063246
+100085036
+100063110
+100061152
+100279988
+100227646
+100061504
+100204982
+100339860
+100231658
+100040918
+100062406
+100181714
+100325388
+100063292
+100274144
+100016608
+100063154
+100063298
+100086648
+100063328
+100063332
+100063336
+100201474
+100063354
+100063386
+100063368
+100063428
+100209654
+100063364
+100290092
+100063482
+100075610
+100063512
+100063570
+100064286
+100358842
+100203506
+100063958
+100063836
+100063388
+100063382
+100095528
+100307474
+100063684
+100311104
+100063758
+100063742
+100221130
+100072954
+100304188
+100181626
+100063644
+100181676
+100063844
+100349230
+100298580
+100127732
+100063480
+100064016
+100122736
+100151010
+100355869
+100060216
+100063378
+100213490
+100181624
+100063462
+100063754
+100181616
+100064076
+100181614
+100181612
+100309830
+100181610
+100064042
+100064234
+100340388
+100122026
+100064316
+100250662
+100064238
+100070926
+100064340
+100064336
+100063614
+100298576
+100064280
+100221114
+100181606
+100271272
+100275562
+100064254
+100300254
+100107550
+100275734
+100181604
+100064256
+100075302
+100181602
+100338275
+100064690
+100064768
+100074058
+100121576
+100064774
+100064666
+100181600
+100064422
+100064686
+100064282
+100247356
+100093610
+100064298
+100063810
+100335514
+100064802
+100071252
+100156978
+100201482
+100065120
+100064430
+100145860
+100058042
+100064770
+100064894
+100064264
+100182602
+100123654
+100065136
+100064896
+100065262
+100047432
+100064924
+100001074
+100254954
+100085818
+100266924
+100064888
+100072538
+100353471
+100352740
+100065208
+100065328
+100065360
+100360592
+100065140
+100339403
+100279462
+100146832
+100065414
+100100766
+100268730
+100065154
+100065536
+100168298
+100065444
+100066710
+100065570
+100064724
+100089612
+100187016
+100066664
+100065416
+100181572
+100065620
+100181570
+100063718
+100221112
+100320530
+100065412
+100065430
+100065510
+100065532
+100065618
+100063566
+100307472
+100064296
+100065786
+100065870
+100291460
+100065578
+100181568
+100065554
+100065886
+100065900
+100065632
+100066152
+100322784
+100302766
+100065974
+100076768
+100066094
+100066158
+100221094
+100355057
+100127662
+100065936
+100066642
+100070292
+100046112
+100199134
+100211648
+100112994
+100066092
+100065992
+100024150
+100066478
+100065424
+100130986
+100267510
+100181514
+100066256
+100066206
+100066156
+100181512
+100066580
+100150112
+100231666
+100066434
+100221092
+100066214
+100066494
+100065788
+100066556
+100124308
+100064796
+100304894
+100063922
+100063878
+100066602
+100065770
+100298794
+100064338
+100066544
+100071454
+100066582
+100291454
+100066708
+100122166
+100066246
+100066622
+100066766
+100201492
+100324374
+100066818
+100213270
+100066796
+100076300
+100353576
+100065156
+100117110
+100181440
+100066902
+100067678
+100063812
+100067028
+100067140
+100065596
+100065892
+100067158
+100324308
+100064078
+100115122
+100067302
+100067318
+100067098
+100181438
+100065622
+100063712
+100066648
+100046896
+100065172
+100123656
+100065664
+100291448
+100323611
+100270678
+100064334
+100067382
+100067198
+100362985
+100181426
+100121632
+100150412
+100067336
+100066558
+100067472
+100067646
+100065612
+100065990
+100293272
+100067252
+100244602
+100067492
+100234134
+100066186
+100067774
+100290442
+100064772
+100107450
+100271610
+100064026
+100067784
+100298546
+100066578
+100067832
+100299464
+100068100
+100151286
+100221086
+100329344
+100059330
+100187120
+100256840
+100067874
+100067914
+100064324
+100064922
+100181402
+100068144
+100266750
+100067598
+100063808
+100181400
+100067912
+100067488
+100067700
+100206764
+100001158
+100128352
+100181396
+100153052
+100303430
+100068198
+100149460
+100068376
+100203686
+100181392
+100351088
+100068252
+100068422
+100067442
+100339742
+100068288
+100181388
+100068388
+100068284
+100076232
+100068438
+100068514
+100068508
+100065388
+100068596
+100181384
+100209378
+100205708
+100068352
+100181382
+100307462
+100068382
+100068698
+100069122
+100181340
+100181324
+100068264
+100066008
+100181344
+100201720
+100279314
+100068794
+100068322
+100068886
+100068932
+100231744
+100221080
+100068888
+100290216
+100068974
+100342135
+100211412
+100181314
+100070668
+100221078
+100278850
+100304890
+100198388
+100334851
+100281136
+100258638
+100068516
+100213776
+100069120
+100302556
+100068642
+100069088
+100291438
+100069116
+100069082
+100057910
+100069158
+100069166
+100072394
+100078620
+100353741
+100069092
+100069260
+100265218
+100069336
+100181292
+100263098
+100069242
+100069186
+100068232
+100287332
+100249520
+100009112
+100069518
+100069592
+100071226
+100069256
+100069584
+100181286
+100201510
+100064424
+100094646
+100073998
+100067334
+100234326
+100069740
+100307458
+100352948
+100332762
+100069404
+100069644
+100069722
+100310440
+100156770
+100069630
+100069724
+100075580
+100073480
+100067696
+100069688
+100249034
+100305444
+100258204
+100290488
+100265738
+100338892
+100070052
+100238840
+100277432
+100070088
+100321175
+100201684
+100069266
+100351717
+100070144
+100328149
+100258598
+100268386
+100206554
+100208760
+100070288
+100113942
+100067214
+100146912
+100152792
+100039728
+100227290
+100067644
+100068176
+100067872
+100070610
+100070526
+100068174
+100070648
+100191552
+100070350
+100358542
+100068510
+100045536
+100069232
+100124914
+100070558
+100317838
+100070822
+100070524
+100070706
+100070850
+100068890
+100329166
+100209612
+100070904
+100050386
+100016854
+100181236
+100070956
+100307456
+100070806
+100237654
+100121310
+100062586
+100069816
+100291434
+100343045
+100334768
+100069614
+100071144
+100071244
+100067756
+100181216
+100181214
+100071554
+100239448
+100125192
+100066600
+100070962
+100071250
+100063356
+100192412
+100115588
+100071390
+100213470
+100151478
+100245844
+100068286
+100193888
+100067250
+100257428
+100071914
+100071686
+100071780
+100071616
+100305348
+100071608
+100071844
+100071592
+100071918
+100068878
+100275538
+100071786
+100071652
+100066252
+100072042
+100073100
+100071976
+100067556
+100221040
+100181156
+100071460
+100090794
+100327811
+100304650
+100065382
+100068150
+100072044
+100323607
+100342914
+100072464
+100065078
+100071388
+100070020
+100181136
+100072230
+100072596
+100204092
+100070712
+100077266
+100064442
+100072700
+100068824
+100057406
+100326300
+100070900
+100181134
+100093670
+100073062
+100338280
+100072864
+100073118
+100072694
+100276298
+100307450
+100291430
+100241424
+100293370
+100072186
+100072790
+100285082
+100265942
+100073246
+100306316
+100073554
+100071938
+100073134
+100250428
+100073298
+100148140
+100074068
+100068634
+100073524
+100068800
+100073064
+100190286
+100063588
+100197654
+100066220
+100077616
+100285192
+100300580
+100070684
+100073560
+100072868
+100268020
+100122964
+100073570
+100291422
+100071350
+100321107
+100073654
+100291420
+100073864
+100035172
+100163076
+100327765
+100181310
+100073926
+100066820
+100072426
+100253488
+100248124
+100263962
+100072656
+100073496
+100270374
+100291418
+100065932
+100159374
+100064684
+100300426
+100241600
+100201714
+100071484
+100073562
+100075700
+100211958
+100072952
+100072276
+100071064
+100071932
+100073700
+100073428
+100117964
+100074312
+100211610
+100074064
+100070314
+100034648
+100071940
+100073834
+100074334
+100074026
+100074242
+100072504
+100073650
+100068566
+100280600
+100311370
+100074542
+100357074
+100074568
+100294398
+100070702
+100283848
+100307444
+100074186
+100072448
+100063708
+100356726
+100074276
+100313032
+100074516
+100026080
+100074500
+100245120
+100207560
+100074494
+100181076
+100298506
+100073676
+100067608
+100074760
+100263160
+100285030
+100181080
+100259172
+100075934
+100070146
+100073446
+100074784
+100075010
+100070584
+100291406
+100286386
+100073528
+100074718
+100069300
+100074978
+100291516
+100146682
+100118300
+100072568
+100074754
+100075108
+100075116
+100069084
+100070630
+100073504
+100073716
+100073780
+100266226
+100072644
+100331276
+100245696
+100131056
+100282104
+100363317
+100278080
+100181050
+100075322
+100075362
+100075368
+100219190
+100131114
+100108944
+100323649
+100308414
+100058380
+100048628
+100074982
+100083712
+100201500
+100060316
+100270726
+100075874
+100075608
+100072530
+100180994
+100073396
+100075720
+100074512
+100072424
+100122186
+100221010
+100070788
+100060742
+100075456
+100067772
+100074862
+100076080
+100074128
+100075766
+100180976
+100075976
+100047788
+100307436
+100086434
+100072418
+100071084
+100063862
+100300208
+100244170
+100086136
+100205970
+100060236
+100076290
+100075210
+100065300
+100074224
+100075872
+100074540
+100074492
+100270044
+100320837
+100257490
+100281470
+100314236
+100363087
+100304256
+100253344
+100076152
+100360626
+100076490
+100209150
+100076808
+100074692
+100049722
+100335352
+100076306
+100121396
+100076834
+100074640
+100076896
+100298504
+100076902
+100076758
+100211288
+100067740
+100070520
+100072854
+100061382
+100076998
+100077010
+100073242
+100077022
+100076120
+100076754
+100077072
+100208396
+100077126
+100089180
+100220994
+100063266
+100077242
+100074642
+100077178
+100329734
+100301784
+100077308
+100275536
+100076078
+100061624
+100077206
+100077212
+100180944
+100291392
+100075998
+100077450
+100073652
+100321029
+100077952
+100318022
+100077716
+100059108
+100077772
+100274236
+100077764
+100077662
+100077880
+100096138
+100073060
+100194934
+100076974
+100077852
+100062758
+100077890
+100076950
+100291376
+100076234
+100078738
+100116312
+100077272
+100074836
+100126556
+100071560
+100078144
+100078106
+100060100
+100207382
+100058902
+100071214
+100077508
+100313860
+100075118
+100298634
+100314924
+100180898
+100181244
+100247366
+100254080
+100214058
+100270006
+100237570
+100326932
+100078110
+100078446
+100084092
+100078428
+100072920
+100332494
+100055346
+100072266
+100076872
+100296636
+100083380
+100180860
+100075782
+100124934
+100078732
+100193062
+100074782
+100133662
+100203106
+100175132
+100291372
+100081242
+100078854
+100079532
+100180858
+100201718
+100079252
+100283670
+100079332
+100079976
+100079612
+100079786
+100310634
+100079250
+100079510
+100079760
+100079422
+100237486
+100190830
+100080096
+100079870
+100080292
+100093918
+100018236
+100080328
+100080154
+100291380
+100243236
+100181560
+100080224
+100296540
+100065898
+100080488
+100246778
+100080156
+100079666
+100080612
+100201652
+100334761
+100080888
+100080818
+100079932
+100080948
+100067216
+100203550
+100078484
+100322323
+100081084
+100181644
+100273888
+100080090
+100214890
+100081354
+100081228
+100080960
+100095742
+100211526
+100180780
+100079354
+100103326
+100303918
+100083254
+100081478
+100081552
+100081600
+100081738
+100249754
+100081620
+100293834
+100263080
+100325404
+100075932
+100081834
+100125096
+100081472
+100080610
+100260746
+100079658
+100197878
+100255860
+100258240
+100074020
+100122372
+100081924
+100082020
+100111738
+100079980
+100351988
+100081362
+100082166
+100301694
+100082118
+100314918
+100062802
+100212984
+100078662
+100080678
+100081226
+100348696
+100082406
+100082030
+100182770
+100082530
+100359154
+100084360
+100263096
+100269964
+100337033
+100083188
+100181734
+100081076
+100080550
+100235792
+100000900
+100081618
+100081528
+100082872
+100108538
+100339620
+100081008
+100082916
+100079916
+100296300
+100080152
+100082592
+100080456
+100258760
+100285294
+100253478
+100080680
+100124310
+100083216
+100332755
+100083180
+100180660
+100078300
+100317792
+100079300
+100083212
+100082200
+100334931
+100095908
+100079782
+100080510
+100309774
+100083732
+100083786
+100313672
+100291364
+100112968
+100083652
+100317876
+100278254
+100081996
+100260228
+100199506
+100048378
+100151618
+100083884
+100084954
+100080332
+100083168
+100284110
+100077862
+100278430
+100293642
+100082696
+100082224
+100084376
+100305462
+100081316
+100073502
+100213972
+100302074
+100311572
+100183222
+100081054
+100250826
+100180640
+100291358
+100317990
+100084294
+100084454
+100066746
+100180598
+100084450
+100049630
+100083814
+100284098
+100281494
+100083368
+100084614
+100083564
+100211182
+100138810
+100081278
+100360603
+100282618
+100180490
+100073492
+100083432
+100082678
+100084780
+100084570
+100319939
+100180488
+100071994
+100240630
+100084156
+100236920
+100084808
+100084384
+100180486
+100100540
+100071552
+100087178
+100079910
+100085166
+100307424
+100084678
+100298124
+100320434
+100082276
+100083376
+100074102
+100152370
+100043648
+100085242
+100292124
+100237626
+100085240
+100079268
+100078466
+100080344
+100079934
+100082698
+100084048
+100085404
+100085386
+100077492
+100125464
+100180482
+100085306
+100220920
+100180462
+100310488
+100085522
+100261906
+100180458
+100082116
+100308548
+100057766
+100209802
+100083682
+100334826
+100079526
+100279900
+100147178
+100180398
+100122608
+100082280
+100084810
+100084430
+100074310
+100154234
+100083250
+100082772
+100079664
+100085808
+100011528
+100080088
+100074292
+100085938
+100063280
+100249578
+100272608
+100084900
+100086046
+100360627
+100085692
+100180370
+100289032
+100210864
+100125954
+100086006
+100293198
+100098028
+100083574
+100073236
+100086200
+100086182
+100152390
+100085788
+100071874
+100086272
+100110048
+100086150
+100085892
+100186604
+100086090
+100083312
+100086918
+100079176
+100085592
+100084788
+100077814
+100086278
+100086162
+100078534
+100069852
+100340942
+100082222
+100180350
+100086380
+100086104
+100167530
+100086060
+100081416
+100291338
+100083074
+100085650
+100123310
+100266380
+100086528
+100086610
+100086630
+100287436
+100085316
+100180344
+100086820
+100085874
+100072524
+100064800
+100202646
+100159096
+100261002
+100080058
+100180338
+100291336
+100280044
+100197910
+100073530
+100079754
+100087006
+100220898
+100207542
+100095444
+100080922
+100180334
+100086898
+100212654
+100272820
+100220894
+100085506
+100180304
+100180302
+100087466
+100061114
+100085946
+100114882
+100151900
+100078302
+100077400
+100180286
+100203496
+100087210
+100248562
+100180236
+100072340
+100085336
+100124238
+100081234
+100153586
+100087332
+100087472
+100078198
+100086964
+100361405
+100259120
+100087028
+100315479
+100087542
+100070592
+100284380
+100291334
+100180234
+100287438
+100220892
+100087208
+100286110
+100106486
+100220884
+100080758
+100282510
+100087372
+100149250
+100258806
+100180210
+100085640
+100091500
+100214042
+100180186
+100186698
+100318988
+100077012
+100080660
+100087572
+100085006
+100075770
+100082416
+100274804
+100180182
+100088116
+100088150
+100311118
+100088208
+100084982
+100241778
+100320877
+100072338
+100085976
+100273200
+100087756
+100291330
+100072664
+100052804
+100088362
+100079140
+100090416
+100180176
+100040748
+100088420
+100088042
+100338934
+100212268
+100286946
+100220876
+100078528
+100162816
+100081704
+100088904
+100338542
+100087760
+100268864
+100291326
+100085678
+100088342
+100081912
+100231656
+100359240
+100088546
+100082234
+100118068
+100117366
+100088258
+100220872
+100088646
+100209036
+100255126
+100213552
+100088332
+100088702
+100088750
+100081056
+100081120
+100175142
+100079802
+100281606
+100063192
+100180116
+100078034
+100088872
+100087338
+100084178
+100156690
+100082782
+100080964
+100333125
+100127648
+100299118
+100324753
+100075410
+100180110
+100088794
+100017534
+100084926
+100180114
+100180108
+100074200
+100236194
+100180562
+100089004
+100083904
+100088684
+100092174
+100089052
+100344113
+100089070
+100089094
+100220868
+100082952
+100220866
+100268046
+100257374
+100290214
+100081038
+100116936
+100285264
+100310932
+100180140
+100180066
+100088620
+100126706
+100089192
+100080512
+100332675
+100089224
+100180064
+100220864
+100283084
+100086756
+100254268
+100085914
+100085738
+100088324
+100307398
+100153584
+100089132
+100240604
+100089314
+100084716
+100091648
+100062672
+100181218
+100180040
+100154018
+100084732
+100090362
+100088118
+100050264
+100083306
+100086850
+100074722
+100115058
+100089384
+100268776
+100269422
+100084632
+100086346
+100089818
+100079414
+100291318
+100190602
+100122596
+100085434
+100089486
+100089484
+100087146
+100212034
+100024344
+100081140
+100088414
+100156228
+100191802
+100089376
+100074838
+100201570
+100085204
+100180016
+100089542
+100220858
+100179994
+100179988
+100082534
+100277840
+100179986
+100179984
+100179980
+100258812
+100076870
+100089604
+100088976
+100082356
+100089494
+100087932
+100055880
+100179978
+100086968
+100089654
+100078330
+100237704
+100361421
+100150284
+100084648
+100332609
+100267802
+100077524
+100246756
+100289684
+100089610
+100338883
+100083052
+100307392
+100073708
+100087852
+100073488
+100089764
+100275846
+100355054
+100085054
+100089842
+100076244
+100089848
+100080104
+100263512
+100119828
+100074570
+100298456
+100085016
+100073810
+100089616
+100088000
+100089856
+100218648
+100291296
+100087428
+100272628
+100087144
+100317341
+100179958
+100086692
+100065080
+100250996
+100089792
+100087482
+100087478
+100093604
+100359222
+100275882
+100311336
+100357229
+100086536
+100201944
+100304110
+100265698
+100081740
+100179940
+100310280
+100254968
+100312042
+100323111
+100262240
+100291102
+100300380
+100120416
+100268716
+100333444
+100211316
+100091204
+100179916
+100089820
+100082232
+100179914
+100044294
+100074628
+100220832
+100090282
+100089730
+100086902
+100086534
+100179910
+100087646
+100272728
+100090318
+100090106
+100076900
+100076712
+100179892
+100086774
+100332654
+100179884
+100089152
+100090368
+100290140
+100225838
+100294840
+100255918
+100201942
+100083714
+100321099
+100319673
+100201646
+100062858
+100078782
+100286906
+100287492
+100092648
+100082358
+100013062
+100279884
+100227476
+100074588
+100155544
+100330772
+100345950
+100179816
+100179814
+100085866
+100179812
+100087398
+100187368
+100179756
+100257412
+100208930
+100266850
+100244316
+100090524
+100088694
+100179794
+100066224
+100179806
+100201938
+100089706
+100090486
+100090556
+100298426
+100201936
+100090646
+100186310
+100243248
+100090184
+100077694
+100298424
+100090694
+100086422
+100179778
+100266754
+100093316
+100149368
+100290624
+100090726
+100179726
+100082350
+100090698
+100090740
+100089796
+100320754
+100090742
+100179708
+100092732
+100203988
+100077402
+100179704
+100209032
+100182738
+100211310
+100220776
+100083072
+100120056
+100298422
+100090826
+100179676
+100081208
+100092264
+100090704
+100090860
+100090892
+100244910
+100179672
+100076250
+100071176
+100090900
+100090796
+100327791
+100009718
+100244700
+100090756
+100090958
+100087578
+100283398
+100246316
+100310840
+100273418
+100051074
+100242714
+100148528
+100353410
+100037216
+100235948
+100325292
+100292668
+100067428
+100085074
+100084756
+100046768
+100196038
+100083910
+100089632
+100093116
+100218122
+100091092
+100049632
+100091106
+100091050
+100246196
+100179664
+100179648
+100091150
+100280486
+100067882
+100089436
+100179628
+100086154
+100094902
+100059534
+100071828
+100091202
+100359488
+100090264
+100086690
+100087834
+100091268
+100091272
+100179622
+100091108
+100307368
+100323575
+100123266
+100295776
+100042078
+100220720
+100091244
+100084586
+100300656
+100318414
+100078114
+100071996
+100179624
+100082694
+100243060
+100291270
+100080362
+100080548
+100101380
+100338724
+100082786
+100238844
+100091332
+100084452
+100088296
+100091372
+100332957
+100179570
+100156860
+100073946
+100085730
+100240388
+100086142
+100179562
+100341259
+100309078
+100223110
+100226698
+100179526
+100082784
+100070964
+100071012
+100082606
+100086336
+100179524
+100087928
+100049568
+100282258
+100212394
+100093092
+100283854
+100357846
+100240602
+100313244
+100280934
+100220664
+100249694
+100076068
+100091658
+100079372
+100091666
+100082410
+100091668
+100118398
+100359841
+100090782
+100211726
+100089440
+100211586
+100330043
+100298402
+100268368
+100314964
+100085658
+100212916
+100249562
+100179408
+100091792
+100050372
+100025326
+100091854
+100076514
+100071016
+100091876
+100091734
+100049440
+100091900
+100090898
+100091794
+100195334
+100122202
+100114008
+100086420
+100092086
+100092058
+100077270
+100263044
+100091718
+100072442
+100091238
+100332677
+100077302
+100087206
+100092228
+100077112
+100357626
+100090002
+100086780
+100091444
+100091910
+100092224
+100092126
+100179350
+100092376
+100092318
+100156916
+100045636
+100037890
+100291258
+100092436
+100086676
+100275248
+100092354
+100092212
+100154364
+100092444
+100353580
+100285122
+100091662
+100243776
+100179326
+100206488
+100091856
+100079138
+100267382
+100302426
+100046062
+100262174
+100092360
+100303766
+100179322
+100283454
+100088768
+100179318
+100087510
+100179316
+100073376
+100092928
+100239042
+100179274
+100016792
+100092650
+100088422
+100260126
+100179272
+100076568
+100069078
+100287624
+100092776
+100091432
+100092916
+100079216
+100092822
+100307358
+100091818
+100092898
+100045206
+100092988
+100092196
+100289660
+100092084
+100252434
+100092982
+100068868
+100092922
+100273132
+100043646
+100156856
+100087474
+100093084
+100168346
+100080334
+100210740
+100266726
+100093072
+100071438
+100091538
+100074846
+100074858
+100086502
+100061128
+100092852
+100322081
+100080462
+100093222
+100093220
+100091720
+100074150
+100220662
+100322427
+100093226
+100088490
+100338618
+100220658
+100093302
+100179230
+100156790
+100091328
+100253656
+100179222
+100088454
+100232338
+100093380
+100206490
+100024114
+100047836
+100093202
+100093390
+100236884
+100093500
+100179220
+100248826
+100087004
+100093608
+100179216
+100093342
+100060846
+100179598
+100260348
+100323617
+100298324
+100319234
+100227842
+100291246
+100340762
+100093830
+100093836
+100281536
+100179148
+100310524
+100086526
+100344423
+100093634
+100093868
+100201910
+100093002
+100275174
+100130970
+100093790
+100301216
+100220654
+100220652
+100135328
+100179108
+100179088
+100347285
+100324769
+100093980
+100084500
+100179084
+100249614
+100094030
+100094028
+100093866
+100093180
+100242300
+100077864
+100094202
+100276330
+100179076
+100179074
+100094118
+100094086
+100094142
+100201650
+100095258
+100075524
+100298394
+100147272
+100283872
+100094120
+100093224
+100094224
+100049220
+100323813
+100079108
+100245118
+100179018
+100094294
+100210524
+100291240
+100049454
+100298318
+100324751
+100320175
+100274262
+100094326
+100179016
+100071782
+100088936
+100094478
+100211308
+100094492
+100094506
+100060422
+100220648
+100076512
+100117982
+100094404
+100094124
+100094530
+100094542
+100178978
+100121630
+100178974
+100220632
+100254122
+100292198
+100178968
+100330391
+100094528
+100093854
+100091452
+100298316
+100307356
+100311796
+100178966
+100093128
+100094292
+100179166
+100343927
+100065512
+100341289
+100332254
+100060844
+100252130
+100345021
+100087376
+100274598
+100258126
+100095000
+100338823
+100075220
+100095022
+100094852
+100094966
+100284148
+100209512
+100094308
+100122534
+100093100
+100094612
+100095144
+100308172
+100209804
+100094012
+100077898
+100292480
+100093276
+100093076
+100089960
+100093080
+100152866
+100140698
+100085224
+100094964
+100090670
+100362762
+100095124
+100290394
+100178954
+100082438
+100302910
+100276368
+100095146
+100095302
+100095346
+100178952
+100145306
+100313854
+100091548
+100115260
+100095476
+100239386
+100091194
+100178934
+100358231
+100092694
+100324320
+100095500
+100077354
+100055608
+100302042
+100300544
+100360568
+100090034
+100307352
+100307350
+100095626
+100293478
+100095312
+100329238
+100095734
+100248188
+100197936
+100201900
+100178930
+100320043
+100095790
+100275050
+100095860
+100095832
+100095934
+100327048
+100241650
+100178922
+100095176
+100267602
+100217632
+100303244
+100122804
+100094938
+100094942
+100095602
+100087894
+100291346
+100079978
+100291224
+100076928
+100089044
+100284684
+100205596
+100095754
+100096162
+100241970
+100091336
+100178920
+100095976
+100329955
+100096004
+100095752
+100094314
+100094486
+100114644
+100291220
+100078146
+100095994
+100095990
+100119604
+100285614
+100096470
+100056410
+100248932
+100087816
+100096338
+100047806
+100298266
+100067826
+100302040
+100291216
+100076236
+100280206
+100241688
+100096594
+100080872
+100095628
+100096968
+100094218
+100321153
+100080420
+100111750
+100092532
+100291222
+100078596
+100030416
+100083372
+100178916
+100096006
+100094940
+100087844
+100217640
+100094256
+100242474
+100280832
+100083110
+100178884
+100063100
+100302054
+100210774
+100097440
+100269428
+100097486
+100094616
+100057548
+100301562
+100121648
+100201908
+100093780
+100082878
+100097590
+100162226
+100097614
+100052580
+100094516
+100155004
+100242008
+100019236
+100276670
+100178822
+100090070
+100073942
+100048310
+100203514
+100097744
+100097762
+100096204
+100178816
+100094136
+100298262
+100097860
+100341694
+100085358
+100338159
+100092466
+100150986
+100178798
+100178796
+100085590
+100097934
+100291206
+100091754
+100080764
+100095972
+100087990
+100201882
+100091408
+100076898
+100046404
+100095982
+100178780
+100210402
+100048886
+100319685
+100178776
+100178774
+100310718
+100052608
+100245362
+100096466
+100098000
+100307344
+100032578
+100062548
+100288912
+100291204
+100091934
+100097428
+100047018
+100217634
+100209352
+100095222
+100081276
+100201486
+100220626
+100120518
+100302056
+100300762
+100300486
+100084152
+100098662
+100078982
+100291200
+100289212
+100072598
+100098750
+100234906
+100098776
+100096182
+100098804
+100085030
+100091752
+100089910
+100178752
+100098390
+100091622
+100211292
+100091366
+100220630
+100313506
+100098408
+100093856
+100217580
+100178662
+100098624
+100351401
+100291192
+100228334
+100095784
+100095782
+100335047
+100063608
+100098214
+100050040
+100086050
+100098888
+100207240
+100178654
+100096410
+100098970
+100098972
+100091932
+100099000
+100274524
+100203162
+100099350
+100327188
+100083912
+100098308
+100283880
+100068152
+100099074
+100017476
+100098062
+100097936
+100099114
+100099072
+100092634
+100226734
+100248510
+100099174
+100324995
+100098588
+100049874
+100039378
+100178594
+100338014
+100095748
+100094066
+100337742
+100190302
+100207534
+100213252
+100088612
+100220568
+100178522
+100275040
+100242192
+100081832
+100025600
+100323395
+100154226
+100343655
+100291188
+100099410
+100099412
+100286424
+100256190
+100098834
+100091002
+100178488
+100099116
+100099068
+100285102
+100099468
+100284614
+100092944
+100278008
+100248536
+100343441
+100099568
+100099550
+100295760
+100099502
+100099484
+100099324
+100099022
+100095940
+100290160
+100298234
+100099442
+100094444
+100098138
+100220538
+100281594
+100178430
+100282724
+100178428
+100097320
+100072732
+100072498
+100098078
+100090022
+100082882
+100099600
+100025552
+100099672
+100099678
+100099768
+100178418
+100213684
+100093414
+100099802
+100288224
+100325755
+100178398
+100099824
+100092294
+100273296
+100088436
+100178396
+100207524
+100098928
+100211076
+100178394
+100252970
+100099876
+100099844
+100099898
+100178378
+100201874
+100288048
+100050658
+100095056
+100293676
+100099972
+100099988
+100100000
+100220508
+100100014
+100178374
+100076876
+100331799
+100088912
+100302880
+100100136
+100178306
+100097748
+100180782
+100099826
+100238498
+100291182
+100178300
+100100078
+100284480
+100098768
+100077008
+100089252
+100091082
+100098210
+100097764
+100100162
+100100166
+100212178
+100204222
+100323980
+100178290
+100220506
+100291350
+100097184
+100100482
+100198422
+100093568
+100100302
+100100386
+100303178
+100100338
+100277342
+100100382
+100099654
+100242812
+100278170
+100150110
+100101050
+100298232
+100069238
+100100656
+100060762
+100209070
+100100634
+100269584
+100290538
+100101446
+100100884
+100290748
+100100694
+100129702
+100357336
+100101262
+100281996
+100101048
+100101046
+100101054
+100102300
+100101276
+100154376
+100101524
+100101728
+100127390
+100101564
+100101668
+100101384
+100101410
+100309426
+100241832
+100101408
+100352738
+100101202
+100110140
+100094270
+100040920
+100102002
+100102204
+100102222
+100201348
+100102220
+100220500
+100242942
+100102352
+100178176
+100278896
+100100478
+100175258
+100330445
+100101278
+100102368
+100161832
+100333917
+100102688
+100198028
+100249240
+100227394
+100283798
+100258960
+100102726
+100101448
+100102900
+100208594
+100100294
+100102978
+100239300
+100213988
+100219856
+100103032
+100185698
+100127636
+100103396
+100103124
+100114134
+100141196
+100102944
+100103028
+100090610
+100105150
+100174064
+100271056
+100103310
+100050432
+100103416
+100102112
+100328429
+100103454
+100320438
+100108036
+100103540
+100103580
+100279698
+100177996
+100208486
+100104482
+100100936
+100283596
+100103726
+100103684
+100177948
+100182188
+100306142
+100256246
+100245102
+100258950
+100057342
+100103840
+100101804
+100103990
+100124940
+100103770
+100103736
+100359486
+100275416
+100104230
+100101726
+100291178
+100357189
+100104146
+100104380
+100104148
+100301968
+100307338
+100104386
+100104284
+100104056
+100117380
+100104420
+100102432
+100275258
+100336073
+100303514
+100249538
+100167900
+100224328
+100104052
+100101056
+100157686
+100103036
+100101086
+100104636
+100263030
+100256792
+100023416
+100197540
+100104976
+100100676
+100298198
+100104930
+100095516
+100209362
+100102036
+100348996
+100213898
+100104818
+100102874
+100105054
+100100630
+100101416
+100177938
+100152586
+100287552
+100104368
+100057796
+100103292
+100100296
+100101672
+100105194
+100296820
+100294710
+100220480
+100105292
+100104232
+100177872
+100177870
+100105056
+100127502
+100100422
+100104816
+100208632
+100275026
+100062142
+100183098
+100105454
+100227704
+100105754
+100105542
+100105528
+100283926
+100105066
+100250704
+100303412
+100105504
+100192748
+100105658
+100022678
+100105876
+100122192
+100102790
+100102836
+100105544
+100105358
+100105190
+100104696
+100177864
+100279448
+100119394
+100289276
+100106134
+100309594
+100101856
+100103764
+100106094
+100198402
+100103774
+100107136
+100103560
+100102856
+100279118
+100177818
+100147048
+100101752
+100127228
+100105786
+100103926
+100320540
+100313476
+100107882
+100180404
+100106422
+100307324
+100220420
+100106440
+100253438
+100101582
+100134266
+100103824
+100106558
+100354810
+100106316
+100101632
+100104758
+100218856
+100106668
+100101584
+100106638
+100153050
+100282752
+100106310
+100106664
+100106708
+100106838
+100105656
+100177780
+100120934
+100024180
+100104364
+100177778
+100241558
+100106642
+100107950
+100105984
+100328908
+100334127
+100106942
+100106982
+100268582
+100181566
+100177754
+100190970
+100107104
+100208870
+100177738
+100106372
+100118410
+100107096
+100328501
+100099818
+100177720
+100107204
+100107298
+100107260
+100107304
+100262574
+100146652
+100314244
+100294598
+100177714
+100102980
+100103506
+100294614
+100149030
+100106084
+100153256
+100107488
+100104694
+100307320
+100226862
+100300648
+100104638
+100201502
+100280996
+100177694
+100177692
+100106014
+100360875
+100105562
+100107660
+100105580
+100103628
+100234342
+100107678
+100107890
+100220370
+100310940
+100107936
+100253784
+100220368
+100107952
+100100054
+100291158
+100340183
+100152144
+100101074
+100123190
+100108072
+100180896
+100108184
+100052602
+100177680
+100108262
+100108204
+100177932
+100107692
+100108038
+100220352
+100107102
+100102942
+100318246
+100323671
+100107368
+100108424
+100108476
+100235130
+100177644
+100177636
+100107556
+100313532
+100177614
+100177570
+100278350
+100108616
+100108498
+100270108
+100105298
+100108670
+100109704
+100177566
+100187912
+100108704
+100108808
+100322511
+100112000
+100151468
+100108806
+100319791
+100107338
+100108940
+100106340
+100291156
+100267892
+100150100
+100156956
+100269056
+100107586
+100109156
+100173486
+100177486
+100109244
+100109246
+100122524
+100301964
+100177484
+100208526
+100361990
+100291154
+100314596
+100105676
+100103790
+100109482
+100088072
+100109516
+100247908
+100177476
+100105526
+100177474
+100130678
+100208638
+100291148
+100132430
+100109700
+100106380
+100109418
+100061310
+100177448
+100109802
+100323797
+100307312
+100109222
+100109800
+100110328
+100249258
+100291150
+100109678
+100109956
+100248832
+100277200
+100107940
+100108606
+100177438
+100147210
+100107972
+100156640
+100105538
+100253088
+100110232
+100120168
+100109446
+100298196
+100177408
+100104318
+100110378
+100046584
+100246898
+100360575
+100327657
+100208528
+100108156
+100177406
+100313590
+100110064
+100110230
+100220350
+100245100
+100104716
+100106836
+100179682
+100104910
+100110764
+100114282
+100105836
+100110640
+100304662
+100341758
+100194026
+100110906
+100102922
+100122826
+100177384
+100344293
+100110926
+100257894
+100109148
+100177380
+100332636
+100111096
+100119788
+100258268
+100111106
+100110588
+100111180
+100107194
+100110090
+100062904
+100109508
+100108374
+100291142
+100177368
+100115788
+100152496
+100256962
+100109634
+100278460
+100177334
+100194150
+100209160
+100363055
+100111434
+100111432
+100101392
+100108836
+100201008
+100111418
+100098998
+100281912
+100111452
+100110936
+100111490
+100111536
+100111332
+100049330
+100111548
+100148204
+100107602
+100111650
+100111690
+100110536
+100177332
+100112794
+100107392
+100111496
+100111730
+100111756
+100109952
+100109612
+100283682
+100000952
+100107954
+100111226
+100145744
+100157998
+100113068
+100111814
+100177250
+100092764
+100111786
+100109632
+100248198
+100111154
+100104890
+100075046
+100193204
+100252464
+100272716
+100111144
+100296632
+100107040
+100254034
+100315586
+100177246
+100110880
+100101930
+100112090
+100109568
+100281532
+100177240
+100298194
+100259028
+100110166
+100147884
+100112270
+100192480
+100112252
+100112272
+100210438
+100098090
+100316031
+100108762
+100111888
+100111718
+100110740
+100291128
+100204800
+100097454
+100260152
+100111550
+100093162
+100109712
+100303270
+100177146
+100111618
+100275162
+100110590
+100112498
+100177144
+100151780
+100097114
+100112516
+100109324
+100110066
+100112568
+100112520
+100154042
+100177122
+100101910
+100102346
+100153522
+100112648
+100112050
+100315536
+100156896
+100136504
+100177120
+100110164
+100208850
+100313716
+100112746
+100111734
+100112826
+100112274
+100106256
+100275006
+100294848
+100111902
+100107700
+100105532
+100350603
+100110032
+100112426
+100112004
+100111870
+100118582
+100177118
+100113022
+100245052
+100277958
+100277752
+100355934
+100220292
+100154224
+100245090
+100113154
+100074900
+100109958
+100177142
+100112742
+100113312
+100089812
+100112738
+100177114
+100248060
+100113188
+100113432
+100360450
+100153144
+100177112
+100269852
+100357888
+100291680
+100114742
+100355635
+100114130
+100220276
+100221834
+100322431
+100102436
+100107202
+100220252
+100113664
+100113686
+100105306
+100113746
+100113398
+100113728
+100301998
+100047420
+100201870
+100097636
+100190578
+100177072
+100113792
+100107512
+100298192
+100092060
+100254730
+100177058
+100112566
+100177038
+100309060
+100113892
+100292338
+100105586
+100318092
+100115202
+100113886
+100112438
+100146094
+100113798
+100155006
+100177008
+100220236
+100113490
+100112288
+100257192
+100208972
+100098372
+100256296
+100114104
+100100850
+100108556
+100176930
+100220234
+100204798
+100112866
+100112706
+100111094
+100114174
+100113800
+100083310
+100335776
+100114066
+100263430
+100113976
+100112310
+100114172
+100220232
+100126116
+100298190
+100109998
+100104588
+100113888
+100176912
+100114420
+100116180
+100114386
+100107326
+100174894
+100299026
+100099860
+100293456
+100114456
+100110108
+100114492
+100114524
+100011552
+100176910
+100114556
+100282360
+100176906
+100117422
+100198352
+100114582
+100114598
+100275172
+100114910
+100291104
+100104750
+100114532
+100176896
+100114638
+100114230
+100301438
+100015830
+100355357
+100176892
+100291210
+100154546
+100108650
+100101770
+100114634
+100105206
+100111184
+100114812
+100173332
+100291100
+100245718
+100114860
+100156822
+100176858
+100102506
+100113090
+100091230
+100278188
+100101862
+100108646
+100126262
+100205922
+100303402
+100113258
+100270372
+100176856
+100298188
+100176860
+100123374
+100320458
+100210134
+100237076
+100314326
+100352984
+100303638
+100111764
+100298126
+100108444
+100108986
+100176848
+100105022
+100176828
+100107062
+100152818
+100106928
+100111970
+100176812
+100115308
+100101540
+100117728
+100280296
+100114154
+100089914
+100115314
+100176802
+100115152
+100266764
+100107558
+100307278
+100116000
+100111658
+100115174
+100111090
+100108644
+100176800
+100101768
+100257288
+100100076
+100115460
+100081470
+100115436
+100081352
+100176790
+100113784
+100116274
+100122248
+100083176
+100298106
+100305150
+100097148
+100115660
+100101520
+100157732
+100115716
+100101648
+100115454
+100095336
+100356084
+100176786
+100115768
+100073802
+100231464
+100119830
+100176736
+100186476
+100115760
+100250366
+100115742
+100176710
+100211312
+100149802
+100298088
+100312204
+100115088
+100115630
+100115822
+100115864
+100115312
+100176708
+100115868
+100255156
+100089912
+100211034
+100090306
+100304964
+100109512
+100101998
+100210846
+100115898
+100176704
+100220228
+100043756
+100110478
+100033010
+100176670
+100168408
+100176668
+100305978
+100241690
+100220226
+100112914
+100105278
+100324360
+100116046
+100100270
+100291122
+100203110
+100178012
+100106686
+100146298
+100273410
+100108994
+100074572
+100120724
+100116266
+100115590
+100249032
+100116018
+100293942
+100095654
+100323365
+100116104
+100286018
+100275000
+100112704
+100115772
+100116326
+100298086
+100095386
+100116380
+100174768
+100287364
+100116412
+100116096
+100108838
+100114518
+100114408
+100327885
+100116554
+100327500
+100274704
+100323934
+100211966
+100116596
+100231600
+100100356
+100294678
+100152168
+100178058
+100092472
+100192566
+100116682
+100106004
+100058160
+100116670
+100092668
+100332843
+100293270
+100306582
+100116776
+100178098
+100320305
+100111102
+100234592
+100116916
+100116006
+100116358
+100178292
+100115086
+100115998
+100153142
+100108322
+100220208
+100174708
+100108354
+100117006
+100106098
+100201868
+100213196
+100178038
+100260546
+100101306
+100252122
+100117114
+100117062
+100277366
+100353558
+100102400
+100288150
+100329102
+100174680
+100117240
+100294532
+100311486
+100094786
+100111546
+100319336
+100298070
+100114554
+100324252
+100117414
+100294554
+100339089
+100327903
+100103320
+100187124
+100324571
+100246278
+100047188
+100045204
+100124500
+100047144
+100090534
+100124928
+100220148
+100327492
+100076810
+100117248
+100136340
+100198466
+100175082
+100079824
+100174666
+100174658
+100332946
+100220146
+100061018
+100220144
+100273546
+100307270
+100174614
+100089850
+100115884
+100220168
+100117950
+100117892
+100115128
+100178040
+100116868
+100019902
+100117988
+100118024
+100118022
+100284664
+100245792
+100115834
+100118076
+100023826
+100000308
+100211326
+100235928
+100251852
+100117984
+100288604
+100116942
+100116754
+100202924
+100247892
+100173894
+100210616
+100257464
+100289656
+100298052
+100236856
+100291060
+100118364
+100118336
+100116356
+100205920
+100192596
+100118366
+100220106
+100118404
+100094576
+100210672
+100269612
+100220012
+100220010
+100112246
+100239532
+100174548
+100305488
+100319374
+100107484
+100110124
+100039294
+100118736
+100111810
+100323803
+100100096
+100118706
+100174550
+100219990
+100299828
+100118694
+100302562
+100046470
+100117724
+100266706
+100072586
+100327488
+100274984
+100118796
+100298026
+100355396
+100193800
+100029398
+100294542
+100117824
+100117712
+100174538
+100175590
+100180332
+100119054
+100174540
+100210304
+100152572
+100311792
+100112248
+100174444
+100119124
+100173324
+100108912
+100118330
+100335732
+100116306
+100284694
+100253728
+100192488
+100120100
+100327486
+100116308
+100118292
+100327550
+100119358
+100205704
+100174442
+100119258
+100119342
+100117424
+100119344
+100119352
+100113162
+100119036
+100116098
+100150192
+100012720
+100099904
+100119444
+100119460
+100118810
+100110462
+100115990
+100355384
+100174440
+100262924
+100111890
+100274976
+100175206
+100251006
+100119558
+100119510
+100219988
+100275648
+100119566
+100099472
+100117874
+100099624
+100212104
+100119614
+100292972
+100282674
+100174430
+100119600
+100174428
+100176658
+100291044
+100219972
+100298238
+100327480
+100150358
+100118334
+100111848
+100117250
+100119792
+100119912
+100112822
+100124394
+100048402
+100119924
+100115420
+100119608
+100119692
+100119094
+100118552
+100307250
+100119016
+100327729
+100103966
+100110398
+100119908
+100161968
+100119824
+100286596
+100120050
+100147080
+100120094
+100296622
+100057800
+100113194
+100292072
+100355051
+100120202
+100118112
+100082354
+100174400
+100272706
+100119374
+100117460
+100277300
+100314956
+100120284
+100309796
+100120288
+100119470
+100168554
+100120110
+100198464
+100058132
+100208540
+100113586
+100328341
+100176654
+100276300
+100119432
+100285916
+100298024
+100307242
+100327466
+100240556
+100174370
+100327635
+100104016
+100355669
+100210434
+100174366
+100104322
+100086832
+100089588
+100276232
+100120514
+100120510
+100211272
+100091816
+100275092
+100311994
+100311426
+100117572
+100119418
+100118048
+100284284
+100327462
+100120568
+100083810
+100174336
+100298022
+100174360
+100119652
+100174334
+100186808
+100273678
+100291034
+100161844
+100069436
+100307240
+100120760
+100219970
+100298012
+100212838
+100247742
+100119154
+100291032
+100081798
+100186810
+100301404
+100327484
+100081916
+100154686
+100337933
+100045890
+100174314
+100088776
+100328283
+100174306
+100219944
+100211150
+100260022
+100120024
+100211928
+100120914
+100291026
+100110962
+100174286
+100120956
+100120964
+100119540
+100111414
+100174270
+100317968
+100333574
+100119724
+100176650
+100325196
+100174732
+100174264
+100085870
+100174256
+100115570
+100120744
+100000178
+100121090
+100320462
+100168406
+100049820
+100083494
+100120708
+100119684
+100121126
+100024380
+100091688
+100147736
+100095776
+100343482
+100121226
+100174240
+100213572
+100121176
+100017436
+100323089
+100174202
+100175680
+100055818
+100320770
+100204152
+100110816
+100297984
+100174262
+100211990
+100181012
+100121344
+100017318
+100299214
+100206228
+100121294
+100121366
+100028872
+100115258
+100323585
+100120174
+100324633
+100076002
+100121424
+100121338
+100116524
+100260108
+100120618
+100121156
+100255538
+100289154
+100261284
+100121486
+100249860
+100120498
+100297192
+100266862
+100256998
+100262862
+100100266
+100112906
+100121596
+100335325
+100111834
+100219942
+100174668
+100288020
+100074630
+100241936
+100291024
+100255200
+100121594
+100207522
+100208874
+100121712
+100322399
+100327448
+100120172
+100120310
+100327725
+100176644
+100209888
+100242790
+100121834
+100098690
+100065648
+100238696
+100291020
+100231416
+100121054
+100285188
+100048070
+100174034
+100333179
+100209968
+100121964
+100219926
+100291018
+100094240
+100121952
+100235920
+100269924
+100091974
+100355852
+100120752
+100106080
+100118936
+100293626
+100209774
+100077060
+100095262
+100314428
+100174004
+100122130
+100122136
+100122138
+100092236
+100174000
+100322373
+100040504
+100122172
+100256170
+100173984
+100266284
+100241016
+100066882
+100059508
+100106560
+100120712
+100119022
+100286792
+100086094
+100122204
+100291010
+100119802
+100079218
+100116698
+100151922
+100203454
+100260082
+100327442
+100194928
+100112500
+100107424
+100103210
+100212576
+100070338
+100234668
+100174166
+100332616
+100073730
+100122250
+100071052
+100016016
+100101478
+100327440
+100122254
+100193796
+100091364
+100323363
+100122262
+100122080
+100300804
+100320478
+100122280
+100117238
+100250020
+100208394
+100122304
+100122306
+100099806
+100122308
+100244366
+100328085
+100059598
+100211592
+100288662
+100332718
+100257766
+100122338
+100173940
+100122342
+100122344
+100111626
+100173934
+100122348
+100156496
+100122352
+100313652
+100122358
+100122360
+100210406
+100328065
+100155560
+100262860
+100086904
+100108166
+100172190
+100358597
+100187478
+100078282
+100176642
+100122392
+100219924
+100122396
+100297950
+100202262
+100122400
+100098326
+100173910
+100252764
+100122406
+100173908
+100122410
+100022200
+100301004
+100328604
+100176046
+100173868
+100077572
+100109304
+100110068
+100096208
+100119772
+100122424
+100122428
+100209504
+100332603
+100150674
+100122430
+100122432
+100122434
+100116284
+100211368
+100122442
+100173860
+100122446
+100122450
+100282134
+100122454
+100315622
+100007134
+100122460
+100063860
+100048804
+100279930
+100359484
+100019798
+100173840
+100122464
+100122466
+100096166
+100037066
+100292476
+100173838
+100173836
+100119450
+100173804
+100291136
+100122478
+100283572
+100122484
+100122486
+100074988
+100275404
+100122498
+100122500
+100077858
+100077188
+100122502
+100122504
+100122506
+100020104
+100275732
+100122530
+100122532
+100268712
+100099838
+100327761
+100122538
+100109300
+100102230
+100122542
+100122544
+100173788
+100122848
+100275544
+100122548
+100260522
+100122558
+100098898
+100050422
+100262858
+100291124
+100126526
+100034116
+100208970
+100313324
+100074868
+100290992
+100173784
+100208822
+100122582
+100119686
+100047412
+100173782
+100120684
+100122584
+100122586
+100117708
+100173764
+100290990
+100122592
+100297930
+100122594
+100122598
+100122600
+100328039
+100276878
+100310448
+100280770
+100086134
+100121362
+100122616
+100122618
+100118806
+100122620
+100173762
+100039122
+100122628
+100328265
+100328041
+100290988
+100173738
+100282610
+100122646
+100122648
+100122650
+100110468
+100277268
+100121134
+100175864
+100333791
+100173722
+100283016
+100103016
+100103588
+100063962
+100156530
+100283468
+100085064
+100332292
+100100394
+100115766
+100122668
+100211332
+100122674
+100122676
+100292650
+100025640
+100291242
+100117262
+100050032
+100083966
+100328015
+100093644
+100173710
+100361315
+100246658
+100121892
+100050868
+100173676
+100122706
+100122708
+100122710
+100122712
+100327460
+100173670
+100122720
+100122722
+100038658
+100272864
+100122730
+100122734
+100192928
+100122752
+100062272
+100276374
+100173646
+100360591
+100173642
+100291006
+100307216
+100148238
+100173636
+100122810
+100122812
+100122818
+100122822
+100060706
+100211436
+100122828
+100094526
+100257910
+100122832
+100219838
+100333177
+100111800
+100354258
+100294836
+100122840
+100290980
+100300272
+100173626
+100173610
+100117592
+100094602
+100173608
+100121962
+100086432
+100282466
+100122852
+100309584
+100251712
+100256230
+100348626
+100279228
+100122860
+100294862
+100176050
+100061238
+100173560
+100173514
+100122870
+100103250
+100234104
+100281082
+100122874
+100122878
+100327382
+100209760
+100211382
+100122902
+100122910
+100323183
+100046082
+100120986
+100122926
+100122928
+100211892
+100208530
+100081072
+100122932
+100102440
+100121414
+100307212
+100049072
+100122936
+100173508
+100122940
+100297914
+100327388
+100117140
+100212286
+100173500
+100095882
+100122984
+100122986
+100260640
+100173496
+100122994
+100122996
+100050076
+100361270
+100227262
+100322413
+100329611
+100327368
+100123006
+100219820
+100155454
+100212158
+100122088
+100114706
+100123050
+100210950
+100099886
+100173466
+100106008
+100315178
+100173432
+100192786
+100101082
+100326791
+100123062
+100302794
+100121990
+100275388
+100329146
+100287468
+100124956
+100219818
+100173202
+100042840
+100123082
+100123084
+100173200
+100123088
+100297896
+100123096
+100095474
+100123098
+100123100
+100173196
+100173194
+100336053
+100123112
+100259068
+100123116
+100025344
+100310556
+100271720
+100123124
+100123128
+100086056
+100123130
+100175676
+100322033
+100242338
+100123136
+100023714
+100297874
+100123142
+100275956
+100056960
+100083562
+100293696
+100190912
+100193794
+100016018
+100123156
+100113348
+100123158
+100123160
+100113478
+100116446
+100000816
+100123168
+100092638
+100151872
+100173160
+100327362
+100123184
+100123186
+100290960
+100173210
+100209670
+100123194
+100069612
+100046080
+100304148
+100123202
+100123206
+100290308
+100202252
+100290458
+100116512
+100016026
+100021456
+100001466
+100219816
+100041516
+100302552
+100266888
+100051276
+100209900
+100125454
+100123254
+100293210
+100123268
+100123270
+100133764
+100027636
+100077462
+100083936
+100077882
+100123284
+100123286
+100043384
+100119918
+100085784
+100275938
+100040192
+100280204
+100123304
+100077640
+100027814
+100296318
+100244028
+100328087
+100296048
+100123326
+100318746
+100123330
+100211016
+100083152
+100173056
+100199430
+100123340
+100139272
+100328073
+100173036
+100123354
+100123356
+100320496
+100120978
+100327933
+100168428
+100123368
+100078028
+100173032
+100147368
+100206460
+100278336
+100324765
+100028640
+100115818
+100219810
+100327372
+100290944
+100328948
+100325344
+100123396
+100211010
+100123404
+100208724
+100123408
+100091518
+100280976
+100123428
+100176500
+100360628
+100303214
+100123436
+100123444
+100209258
+100123448
+100123450
+100173982
+100123456
+100172978
+100297828
+100024176
+100274040
+100290942
+100092118
+100015714
+100123488
+100123490
+100027142
+100120984
+100123502
+100123526
+100123532
+100123534
+100125552
+100123540
+100072916
+100123542
+100172996
+100100696
+100327324
+100306102
+100123576
+100037148
+100123580
+100363375
+100123584
+100172976
+100083552
+100108914
+100327322
+100123596
+100194146
+100123598
+100327306
+100172972
+100267770
+100200366
+100123608
+100305976
+100219714
+100123614
+100324362
+100327304
+100077016
+100284978
+100066202
+100123626
+100292834
+100187086
+100123628
+100002128
+100046996
+100280236
+100172946
+100172942
+100291352
+100172914
+100172910
+100123666
+100123670
+100123672
+100172906
+100172902
+100217642
+100327300
+100047526
+100260014
+100206650
+100172868
+100327554
+100211798
+100123696
+100219650
+100172844
+100123706
+100107054
+100123708
+100172840
+100123712
+100243066
+100297820
+100254056
+100051284
+100123730
+100172806
+100086824
+100175722
+100123748
+100291868
+100297788
+100153614
+100213562
+100173980
+100329176
+100091532
+100019038
+100123800
+100172796
+100123808
+100219646
+100151782
+100118530
+100076154
+100123814
+100123820
+100069860
+100325773
+100123844
+100123850
+100123852
+100172764
+100248578
+100197410
+100123858
+100209764
+100123862
+100172734
+100252018
+100205902
+100123866
+100123868
+100175688
+100123872
+100091032
+100241034
+100123880
+100123886
+100068976
+100303764
+100123890
+100123892
+100321258
+100124180
+100123898
+100209998
+100123902
+100266292
+100123908
+100329320
+100039482
+100123912
+100123916
+100123922
+100123924
+100120644
+100351497
+100256960
+100123956
+100332037
+100123960
+100172654
+100123976
+100123978
+100257000
+100175666
+100123992
+100283170
+100203484
+100172630
+100266496
+100124012
+100124016
+100280366
+100297784
+100119550
+100124026
+100124028
+100242622
+100172628
+100102372
+100219594
+100124038
+100107548
+100242370
+100119150
+100074018
+100258550
+100124054
+100172596
+100124060
+100172594
+100124064
+100124072
+100278768
+100281984
+100045356
+100172562
+100124078
+100172560
+100147932
+100145476
+100290928
+100338046
+100107740
+100263702
+100290926
+100219574
+100124102
+100172536
+100124106
+100172526
+100101852
+100333794
+100326242
+100294850
+100124120
+100334762
+100212050
+100209488
+100076592
+100115360
+100211772
+100307206
+100144010
+100172486
+100049368
+100095540
+100124138
+100115712
+100067878
+100210458
+100118798
+100327282
+100258264
+100172476
+100048346
+100172474
+100124150
+100172524
+100124156
+100112548
+100030766
+100328802
+100124170
+100049144
+100124212
+100354721
+100293534
+100219542
+100099790
+100051152
+100124186
+100281294
+100322818
+100172462
+100261984
+100324440
+100103254
+100275378
+100172460
+100252258
+100282102
+100124202
+100172532
+100124206
+100096088
+100111726
+100245436
+100124218
+100124222
+100124220
+100095480
+100124240
+100124256
+100339416
+100172440
+100328499
+100184916
+100144746
+100124268
+100272352
+100327290
+100297254
+100103924
+100124276
+100273694
+100172402
+100124284
+100046692
+100124292
+100116290
+100229880
+100327274
+100268710
+100095786
+100193784
+100268792
+100290916
+100095678
+100124300
+100219524
+100275632
+100118060
+100118350
+100190734
+100127748
+100172348
+100248466
+100124324
+100219522
+100176118
+100124330
+100294568
+100149382
+100245888
+100267146
+100124344
+100111456
+100312814
+100088392
+100327272
+100048314
+100094726
+100124348
+100210060
+100319366
+100124364
+100124366
+100280764
+100171992
+100330985
+100124388
+100124390
+100172216
+100293268
+100124404
+100040596
+100173270
+100044848
+100124410
+100124416
+100075574
+100328193
+100171934
+100211826
+100124428
+100121768
+100124432
+100171930
+100171998
+100200856
+100322293
+100327266
+100057514
+100124476
+100338313
+100210066
+100099302
+100208780
+100124494
+100171918
+100124498
+100124502
+100124504
+100072048
+100171832
+100046586
+100124526
+100124528
+100327264
+100260930
+100124538
+100124540
+100250120
+100124546
+100116822
+100107332
+100124562
+100124564
+100310846
+100273348
+100124572
+100171788
+100171786
+100045900
+100171778
+100124582
+100124584
+100124586
+100199056
+100244660
+100124594
+100275510
+100107968
+100124598
+100237816
+100124614
+100124616
+100124618
+100096224
+100124632
+100124636
+100124638
+100219496
+100124644
+100124648
+100219478
+100319388
+100171736
+100318760
+100171720
+100144036
+100108784
+100075222
+100327256
+100323393
+100124862
+100171686
+100124686
+100269476
+100048608
+100064438
+100124708
+100124710
+100099296
+100325957
+100219474
+100327250
+100327248
+100303876
+100291470
+100124738
+100176440
+100124744
+100124752
+100124754
+100101476
+100124760
+100112280
+100209092
+100124796
+100290938
+100171636
+100171632
+100171628
+100171598
+100332805
+100048874
+100124816
+100124818
+100210092
+100171562
+100088108
+100124832
+100120348
+100171560
+100297766
+100274242
+100330390
+100312616
+100171558
+100171538
+100183046
+100095800
+100187050
+100124864
+100219442
+100142318
+100192696
+100124874
+100061208
+100124876
+100256418
+100124880
+100348178
+100262798
+100171494
+100076468
+100306820
+100355848
+100124888
+100274592
+100297746
+100274590
+100333778
+100125090
+100121284
+100302284
+100175610
+100174798
+100171468
+100124974
+100334731
+100125736
+100328365
+100092788
+100124988
+100124990
+100211008
+100319256
+100171724
+100125024
+100125026
+100090950
+100281428
+100171454
+100279932
+100125066
+100050512
+100156526
+100062376
+100125262
+100126984
+100125074
+100042784
+100290904
+100125080
+100125082
+100297744
+100176632
+100125086
+100125088
+100125092
+100029604
+100175080
+100150798
+100125118
+100260880
+100171444
+100125128
+100114356
+100125130
+100077974
+100175662
+100244218
+100150758
+100125146
+100272860
+100107566
+100328457
+100125158
+100125162
+100064742
+100219438
+100125186
+100266470
+100171394
+100014532
+100171390
+100302800
+100125200
+100152842
+100125204
+100328413
+100327232
+100219420
+100248158
+100207500
+100119172
+100050980
+100249244
+100171386
+100101020
+100171384
+100328325
+100125238
+100329659
+100171382
+100312216
+100213162
+100125246
+100125248
+100125256
+100099040
+100113484
+100125266
+100175700
+100125270
+100106410
+100125278
+100125290
+100260436
+100091972
+100101092
+100171358
+100151248
+100125726
+100171356
+100290898
+100327214
+100125314
+100125316
+100327212
+100145016
+100325370
+100127176
+100127544
+100171352
+100327210
+100139804
+100206492
+100211980
+100171348
+100307186
+100125340
+100203492
+100125344
+100310646
+100061426
+100239478
+100240482
+100303304
+100325116
+100287522
+100305824
+100117720
+100290892
+100109108
+100125402
+100125406
+100125408
+100266460
+100201786
+100125428
+100234236
+100171330
+100120558
+100116428
+100015666
+100125452
+100174836
+100201616
+100130340
+100081100
+100060258
+100349472
+100061496
+100125484
+100125486
+100111792
+100101606
+100078342
+100062076
+100125490
+100274468
+100253054
+100219410
+100125496
+100171322
+100043314
+100125502
+100151276
+100004704
+100340148
+100091978
+100171320
+100294114
+100125532
+100290888
+100025926
+100171300
+100125538
+100127200
+100125542
+100002640
+100206638
+100125548
+100327208
+100120654
+100045956
+100120432
+100045398
+100120678
+100125554
+100078350
+100304174
+100125560
+100175316
+100125844
+100171294
+100119410
+100193780
+100208616
+100219404
+100266366
+100037110
+100209584
+100241692
+100327202
+100171264
+100301568
+100250242
+100125604
+100098544
+100125608
+100153042
+100171260
+100323071
+100320768
+100125622
+100171254
+100125626
+100290880
+100125634
+100125636
+100125642
+100171224
+100167930
+100272244
+100126420
+100171206
+100041898
+100125676
+100175726
+100125678
+100116298
+100351377
+100125686
+100120564
+100125688
+100171204
+100171180
+100171178
+100125698
+100171174
+100193778
+100125706
+100171140
+100320528
+100171106
+100046284
+100125720
+100171730
+100061104
+100171104
+100125730
+100125738
+100254580
+100125740
+100294858
+100243084
+100072944
+100113116
+100171102
+100056800
+100046056
+100236850
+100125764
+100125766
+100292830
+100171080
+100171072
+100171068
+100171052
+100125782
+100125784
+100171026
+100248502
+100171018
+100171000
+100125798
+100293322
+100208536
+100257236
+100058066
+100112114
+100217506
+100171022
+100109154
+100028520
+100049574
+100269782
+100125824
+100225226
+100190636
+100303786
+100125848
+100286406
+100046120
+100327947
+100170972
+100278296
+100001144
+100192422
+100170948
+100155686
+100249424
+100125894
+100125896
+100125904
+100259818
+100088416
+100058064
+100173268
+100219394
+100091360
+100125910
+100084652
+100244294
+100170916
+100168246
+100125922
+100201638
+100119292
+100125932
+100112242
+100249986
+100170894
+100224740
+100170876
+100219388
+100125966
+100170870
+100266702
+100170920
+100258336
+100219358
+100125990
+100170862
+100126010
+100193774
+100120020
+100191194
+100092092
+100120874
+100126906
+100126036
+100297724
+100275208
+100292472
+100066920
+100170864
+100170858
+100126054
+100170856
+100170854
+100270122
+100170852
+100120058
+100126074
+100126080
+100058470
+100176608
+100294830
+100126086
+100126088
+100126090
+100126094
+100300492
+100126606
+100211488
+100219356
+100170814
+100126134
+100126136
+100102488
+100170812
+100126154
+100170810
+100126164
+100170806
+100126182
+100126184
+100126186
+100126188
+100061500
+100067880
+100170804
+100219354
+100290866
+100089536
+100126280
+100126204
+100126206
+100126208
+100170800
+100126212
+100209890
+100126216
+100126220
+100098802
+100090956
+100126222
+100171124
+100170790
+100126226
+100176576
+100126236
+100170788
+100298938
+100075982
+100127226
+100126258
+100107592
+100244642
+100127634
+100126294
+100291314
+100126296
+100115368
+100150492
+100186980
+100174494
+100126310
+100107008
+100186976
+100105872
+100107002
+100076762
+100137370
+100126336
+100126338
+100327226
+100126358
+100176558
+100176536
+100170706
+100252008
+100170704
+100172062
+100290630
+100126400
+100327192
+100170702
+100115880
+100126412
+100126414
+100170700
+100257040
+100126440
+100336226
+100354893
+100323944
+100247340
+100279824
+100111906
+100126470
+100297720
+100150762
+100101328
+100242290
+100170660
+100358833
+100126502
+100278252
+100126504
+100338820
+100007056
+100210186
+100211590
+100126532
+100087396
+100170640
+100046162
+100051294
+100118000
+100210696
+100170636
+100354354
+100178152
+100093574
+100303176
+100170630
+100290834
+100173716
+100240558
+100172198
+100170622
+100275380
+100170620
+100126608
+100354125
+100193772
+100126612
+100310356
+100126694
+100120052
+100206356
+100126638
+100126640
+100120054
+100170612
+100170608
+100126650
+100209568
+100126654
+100232994
+100212052
+100090968
+100126664
+100072124
+100275362
+100126668
+100098150
+100126670
+100126696
+100126698
+100126700
+100126702
+100126704
+100073674
+100126710
+100204178
+100274808
+100126722
+100170562
+100170560
+100126738
+100170554
+100208562
+100170532
+100170530
+100126748
+100037954
+100126756
+100148514
+100173148
+100186846
+100263698
+100290842
+100126816
+100219300
+100264656
+100126840
+100329300
+100212694
+100258118
+100295064
+100208964
+100290824
+100261084
+100096988
+100302564
+100181716
+100268304
+100193770
+100156384
+100354123
+100234216
+100208368
+100126880
+100126898
+100273736
+100128924
+100170312
+100170310
+100173232
+100170300
+100186956
+100153714
+100329270
+100126934
+100257312
+100171526
+100276100
+100297716
+100170242
+100275206
+100170240
+100170186
+100327160
+100170182
+100327158
+100126994
+100126998
+100174986
+100208722
+100047528
+100127006
+100127010
+100203452
+100178696
+100127016
+100290808
+100127020
+100127022
+100248248
+100170136
+100170132
+100258392
+100170112
+100127044
+100170106
+100178100
+100332702
+100036038
+100127058
+100170188
+100073478
+100170072
+100219278
+100219276
+100127070
+100170036
+100074510
+100127076
+100116778
+100329220
+100170004
+100127086
+100127088
+100127094
+100127096
+100290806
+100219274
+100150796
+100127114
+100150638
+100116970
+100169998
+100104846
+100203546
+100127120
+100169976
+100096948
+100169934
+100156958
+100169880
+100241710
+100169930
+100285314
+100171758
+100209334
+100327162
+100170108
+100127146
+100169876
+100169874
+100278536
+100273838
+100127172
+100127180
+100152350
+100127182
+100144890
+100169864
+100127188
+100148412
+100127194
+100169832
+100169828
+100127208
+100169808
+100053158
+100127216
+100262788
+100127220
+100098594
+100169776
+100163802
+100163800
+100127236
+100297714
+100333150
+100054652
+100100286
+100363319
+100169774
+100169772
+100210422
+100131622
+100176510
+100283786
+100169744
+100169742
+100340387
+100139868
+100127292
+100305994
+100198404
+100127330
+100327745
+100013122
+100127338
+100219254
+100071870
+100301868
+100002922
+100127344
+100324759
+100089770
+100127368
+100290798
+100031012
+100031592
+100242620
+100319136
+100127384
+100127386
+100127388
+100293206
+100248704
+100169698
+100112594
+100047858
+100169696
+100219230
+100169694
+100169692
+100321083
+100169676
+100237662
+100169672
+100219210
+100298660
+100127438
+100100820
+100301844
+100203448
+100169648
+100263474
+100289454
+100169646
+100240552
+100169642
+100175696
+100169740
+100112828
+100112898
+100169632
+100262770
+100127510
+100327170
+100127518
+100127522
+100290796
+100127540
+100127580
+100255362
+100136328
+100127552
+100127564
+100127696
+100312700
+100035916
+100169522
+100169518
+100292462
+100247134
+100219192
+100307174
+100127630
+100327172
+100301912
+100279570
+100169462
+100169456
+100127646
+100169454
+100266270
+100137220
+100263808
+100290792
+100291726
+100169440
+100332011
+100127674
+100335671
+100127680
+100127682
+100290790
+100303966
+100174878
+100127692
+100127698
+100127700
+100102098
+100127704
+100289548
+100337965
+100169430
+100127712
+100127714
+100129742
+100133876
+100077464
+100127716
+100141558
+100018904
+100169366
+100104186
+100169364
+100127728
+100169362
+100276088
+100265290
+100117596
+100273048
+100100388
+100127786
+100214268
+100265154
+100169332
+100127804
+100148006
+100275566
+100207388
+100127824
+100100048
+100209152
+100255658
+100127838
+100302410
+100283244
+100263624
+100169306
+100201778
+100127866
+100127868
+100127870
+100267932
+100169284
+100127888
+100127892
+100183614
+100127906
+100152764
+100127914
+100127926
+100127928
+100127930
+100061542
+100127934
+100127938
+100127942
+100127944
+100127948
+100290788
+100327252
+100169262
+100258072
+100169260
+100127988
+100169258
+100128016
+100128018
+100128020
+100128024
+100128028
+100128030
+100128032
+100175838
+100293504
+100149722
+100128062
+100128064
+100128068
+100128086
+100128090
+100239672
+100307166
+100128104
+100286908
+100128126
+100128138
+100128144
+100324426
+100264912
+100153886
+100128166
+100128168
+100282156
+100128178
+100128180
+100128182
+100128188
+100128190
+100169186
+100128202
+100258494
+100128234
+100128236
+100128256
+100128260
+100128276
+100315078
+100335120
+100265136
+100086352
+100128324
+100128328
+100327118
+100128334
+100169162
+100290786
+100128408
+100128414
+100154284
+100169160
+100292290
+100060820
+100128466
+100128470
+100128472
+100150494
+100128514
+100288806
+100128522
+100128524
+100176448
+100128532
+100128534
+100128550
+100325996
+100128564
+100169154
+100128596
+100128598
+100128600
+100209658
+100338925
+100169130
+100128612
+100128614
+100128618
+100128624
+100128628
+100128648
+100128668
+100128674
+100128694
+100175204
+100128750
+100213344
+100128772
+100128774
+100128776
+100128778
+100248346
+100128784
+100219114
+100205686
+100128794
+100275194
+100128798
+100328798
+100128802
+100128804
+100128808
+100128810
+100128812
+100128832
+100249214
+100128852
+100219092
+100128856
+100327092
+100128894
+100292878
+100290782
+100128980
+100129012
+100129014
+100129022
+100129024
+100129028
+100129030
+100143284
+100207672
+100043806
+100129070
+100129072
+100129076
+100129078
+100129080
+100129096
+100129098
+100129102
+100169148
+100129124
+100129128
+100129130
+100129146
+100169098
+100199004
+100129154
+100289030
+100129160
+100129162
+100359074
+100129190
+100129204
+100129206
+100342560
+100129230
+100129232
+100129234
+100129238
+100268072
+100129242
+100304086
+100129246
+100326857
+100129282
+100297688
+100334971
+100169070
+100129294
+100129296
+100129300
+100129302
+100129306
+100129308
+100129310
+100129314
+100129318
+100129322
+100129326
+100294922
+100129330
+100327090
+100129348
+100129370
+100129374
+100129436
+100129438
+100327124
+100327881
+100129458
+100260186
+100295066
+100129502
+100035754
+100129536
+100169034
+100129568
+100169032
+100198532
+100168968
+100129578
+100129580
+100129582
+100129600
+100129616
+100307158
+100168966
+100168964
+100168958
+100319084
+100153752
+100046954
+100168956
+100129766
+100129712
+100129714
+100129716
+100129718
+100129720
+100129736
+100168952
+100294084
+100129782
+100129784
+100219014
+100283588
+100129796
+100129798
+100129832
+100129836
+100129840
+100129858
+100129860
+100327166
+100168936
+100211098
+100129870
+100129876
+100341231
+100168916
+100129884
+100129900
+100129920
+100247678
+100129946
+100129948
+100129950
+100186924
+100129984
+100314892
+100129990
+100201546
+100129996
+100193584
+100130000
+100130002
+100130004
+100130008
+100130028
+100130032
+100130034
+100130036
+100130038
+100321765
+100130044
+100171784
+100130064
+100130066
+100290768
+100168866
+100130092
+100130094
+100130110
+100168860
+100117704
+100130142
+100187224
+100327086
+100130166
+100130168
+100191006
+100310924
+100130232
+100130248
+100258472
+100261088
+100219012
+100200982
+100168832
+100130292
+100277112
+100209108
+100274446
+100130300
+100290364
+100201598
+100292454
+100327122
+100130348
+100320678
+100327677
+100130384
+100183460
+100130400
+100130402
+100300474
+100130406
+100176330
+100130410
+100130414
+100280084
+100239394
+100130436
+100327695
+100130442
+100024870
+100240162
+100218990
+100168780
+100320113
+100130466
+100180664
+100297670
+100130476
+100052592
+100168776
+100130482
+100130486
+100130488
+100130526
+100218988
+100323333
+100130580
+100235582
+100168750
+100205884
+100130604
+100169074
+100218972
+100130658
+100130698
+100130706
+100130708
+100297640
+100292898
+100218970
+100130736
+100130738
+100130740
+100130756
+100168692
+100130762
+100290760
+100130816
+100130818
+100262746
+100130838
+100193766
+100218950
+100130874
+100130884
+100290758
+100168646
+100130920
+100130936
+100130938
+100168644
+100130948
+100168564
+100210052
+100130960
+100130964
+100291022
+100130968
+100206378
+100256226
+100095850
+100131026
+100320610
+100209484
+100131046
+100131052
+100242292
+100145960
+100131060
+100131062
+100218948
+100168594
+100311038
+100131338
+100131130
+100131132
+100131134
+100150902
+100131150
+100290750
+100333313
+100194524
+100272776
+100131210
+100294608
+100245894
+100327068
+100210736
+100284522
+100131224
+100168224
+100131244
+100131246
+100131264
+100334801
+100168206
+100131276
+100209934
+100168186
+100207492
+100131318
+100131322
+100131354
+100252992
+100303084
+100131390
+100274440
+100292266
+100131412
+100131454
+100131458
+100131474
+100131482
+100270680
+100131488
+100168164
+100131550
+100131554
+100131556
+100043658
+100168162
+100131564
+100131570
+100131572
+100131574
+100322656
+100168098
+100131582
+100355550
+100131604
+100323829
+100131592
+100209236
+100313206
+100193678
+100168094
+100174938
+100168160
+100131616
+100175372
+100098728
+100131640
+100131644
+100131648
+100131650
+100149630
+100131786
+100176430
+100131702
+100280260
+100313126
+100131754
+100327074
+100131812
+100131828
+100131830
+100213838
+100131836
+100252902
+100328311
+100249534
+100263458
+100176312
+100131936
+100131942
+100131948
+100131950
+100131952
+100328123
+100131970
+100131960
+100131964
+100151328
+100131974
+100131976
+100131984
+100131986
+100327078
+100168060
+100197800
+100290742
+100132100
+100132050
+100061848
+100132120
+100132136
+100167998
+100132140
+100132144
+100132160
+100132202
+100167980
+100320742
+100136664
+100148304
+100320466
+100201776
+100132246
+100210232
+100132250
+100132258
+100132260
+100132290
+100132346
+100290738
+100132378
+100132380
+100132386
+100196252
+100152598
+100132414
+100198406
+100239806
+100192708
+100132428
+100239390
+100132446
+100132480
+100132484
+100132502
+100132506
+100148040
+100132510
+100167924
+100132514
+100132516
+100132546
+100132548
+100132612
+100132628
+100132630
+100219966
+100167918
+100132654
+100167908
+100291850
+100132696
+100297638
+100132716
+100191916
+100132748
+100167906
+100132754
+100132756
+100132758
+100210646
+100132800
+100132804
+100132806
+100268960
+100167902
+100132820
+100132828
+100132834
+100132836
+100132840
+100172256
+100167874
+100307142
+100327180
+100132854
+100167842
+100251014
+100209104
+100270402
+100132910
+100167848
+100132930
+100132948
+100132966
+100132968
+100132970
+100132972
+100297636
+100176402
+100292422
+100133038
+100358724
+100133044
+100133130
+100135490
+100326318
+100133068
+100267878
+100327837
+100167810
+100244300
+100167806
+100292074
+100133132
+100133180
+100133148
+100133188
+100133196
+100133198
+100271282
+100324199
+100133218
+100290814
+100133256
+100266684
+100133260
+100133320
+100316616
+100133326
+100048510
+100167754
+100288562
+100133366
+100322748
+100333175
+100148018
+100133388
+100167752
+100133406
+100183298
+100189112
+100133412
+100279496
+100177166
+100291974
+100092854
+100322746
+100324264
+100133486
+100133492
+100133494
+100133528
+100167708
+100251220
+100203510
+100133586
+100133602
+100254608
+100133614
+100167692
+100167686
+100167680
+100262740
+100283676
+100133686
+100133690
+100133694
+100202622
+100133712
+100311406
+100133730
+100320905
+100167660
+100133750
+100133752
+100352957
+100133758
+100234230
+100297628
+100133766
+100133768
+100133770
+100242900
+100272686
+100277338
+100133826
+100358665
+100133848
+100297626
+100297594
+100150714
+100133860
+100293678
+100133880
+100133934
+100133932
+100133938
+100135942
+100133942
+100133946
+100133978
+100322828
+100261862
+100100400
+100249750
+100167588
+100134008
+100240122
+100167586
+100134020
+100308794
+100134046
+100134048
+100167584
+100134080
+100203556
+100134110
+100307126
+100134114
+100167570
+100134122
+100134124
+100134128
+100134132
+100258206
+100198394
+100134176
+100149934
+100167568
+100167566
+100134202
+100353278
+100134206
+100134208
+100134210
+100154222
+100134256
+100134260
+100297578
+100134264
+100134270
+100134272
+100267216
+100134296
+100322732
+100134316
+100167482
+100134340
+100134346
+100134348
+100265664
+100246230
+100134374
+100134376
+100167478
+100274508
+100353963
+100134420
+100134422
+100295012
+100134426
+100134428
+100134430
+100134432
+100348585
+100268262
+100134474
+100167474
+100271062
+100134494
+100134544
+100191836
+100134560
+100134562
+100238636
+100134568
+100134588
+100134590
+100134592
+100134624
+100134628
+100134630
+100199058
+100245024
+100134642
+100305868
+100134646
+100290714
+100274410
+100134736
+100134738
+100091742
+100134740
+100134756
+100167454
+100134760
+100134762
+100160022
+100134774
+100134776
+100265616
+100135180
+100134780
+100192704
+100134802
+100134834
+100218872
+100167432
+100277246
+100201472
+100211834
+100134848
+100134850
+100290706
+100176298
+100218868
+100167428
+100134864
+100134868
+100134870
+100134930
+100362713
+100134966
+100134938
+100167360
+100045038
+100140478
+100134946
+100173112
+100327809
+100156638
+100288942
+100134988
+100135006
+100167352
+100135166
+100325484
+100135028
+100135030
+100135046
+100322702
+100201480
+100135056
+100172118
+100156604
+100135062
+100135064
+100325989
+100167350
+100135084
+100252180
+100267646
+100135096
+100135118
+100324352
+100135126
+100135128
+100135144
+100135148
+100135150
+100135172
+100135178
+100322619
+100167346
+100135192
+100135198
+100135200
+100135216
+100135222
+100175456
+100135240
+100167344
+100135248
+100135250
+100135254
+100167324
+100329190
+100135266
+100135268
+100167322
+100135286
+100235872
+100206458
+100135296
+100176396
+100135304
+100301962
+100352476
+100135338
+100257056
+100292100
+100183802
+100239418
+100135348
+100135350
+100167314
+100135356
+100135360
+100327042
+100355737
+100135380
+100297574
+100135400
+100168516
+100323757
+100210528
+100220982
+100135444
+100218836
+100135450
+100135452
+100167278
+100135484
+100167238
+100167204
+100135500
+100135502
+100135504
+100167202
+100324809
+100214068
+100135586
+100135588
+100135604
+100327116
+100262724
+100167168
+100266916
+100167166
+100135632
+100148258
+100135652
+100211356
+100135656
+100190910
+100135666
+100305328
+100135668
+100235916
+100322579
+100278532
+100135688
+100135690
+100167156
+100317948
+100167152
+100135734
+100167148
+100135738
+100135740
+100135744
+100135746
+100135748
+100135768
+100135774
+100307850
+100135780
+100135784
+100135786
+100135794
+100135796
+100135798
+100135800
+100167128
+100135804
+100137262
+100295784
+100135810
+100354531
+100135814
+100135830
+100135832
+100135848
+100135864
+100135866
+100213640
+100135872
+100135874
+100135876
+100213200
+100135896
+100135914
+100167116
+100135934
+100135936
+100213568
+100135940
+100135944
+100167114
+100136416
+100135954
+100135956
+100135958
+100135964
+100323793
+100135974
+100275386
+100167106
+100167102
+100135998
+100325234
+100136000
+100167100
+100136004
+100280854
+100176290
+100136024
+100136026
+100136044
+100136046
+100136054
+100136088
+100136106
+100218834
+100136112
+100269200
+100252918
+100270900
+100136122
+100136124
+100136126
+100323427
+100136134
+100206510
+100136156
+100271694
+100167096
+100274002
+100103846
+100136186
+100282280
+100136190
+100136192
+100209924
+100136230
+100136232
+100136234
+100136238
+100136254
+100136258
+100210442
+100136262
+100179372
+100136284
+100136286
+100297568
+100275108
+100266554
+100136314
+100199514
+100167090
+100313694
+100136322
+100186958
+100082556
+100324414
+100322650
+100136336
+100172172
+100136342
+100266340
+100136344
+100167020
+100136396
+100136422
+100211142
+100136440
+100136442
+100136444
+100167000
+100136448
+100290692
+100176254
+100136458
+100098464
+100136488
+100136492
+100136496
+100166996
+100166976
+100210302
+100329138
+100136506
+100136508
+100136542
+100136544
+100218796
+100207488
+100136584
+100136604
+100292420
+100285042
+100136628
+100136630
+100136632
+100136634
+100297560
+100318506
+100250124
+100203224
+100136660
+100328952
+100136672
+100166972
+100136678
+100145360
+100166970
+100136682
+100136684
+100351517
+100136690
+100136692
+100136708
+100166968
+100136712
+100136714
+100136744
+100166964
+100174144
+100136782
+100166962
+100166958
+100159206
+100283984
+100136828
+100136848
+100166918
+100136858
+100311900
+100166916
+100136868
+100203240
+100323159
+100247040
+100166896
+100136878
+100218762
+100157304
+100201774
+100249970
+100287480
+100360343
+100136934
+100136938
+100136942
+100166874
+100322367
+100286528
+100136966
+100136970
+100136996
+100136972
+100196014
+100136974
+100240688
+100297544
+100137058
+100253098
+100137072
+100303698
+100137090
+100166824
+100137108
+100137124
+100166822
+100137130
+100166806
+100137162
+100201504
+100137168
+100137184
+100308666
+100137194
+100137196
+100166802
+100320470
+100045544
+100137206
+100141530
+100137212
+100137214
+100137216
+100137218
+100218758
+100266896
+100137230
+100329120
+100328093
+100137308
+100137342
+100363095
+100256202
+100137352
+100166796
+100329365
+100166776
+100137394
+100137396
+100137414
+100137412
+100137430
+100137432
+100166756
+100210446
+100358179
+100137468
+100211260
+100137478
+100137560
+100275134
+100309602
+100166730
+100137534
+100137538
+100137540
+100212370
+100137542
+100146346
+100157798
+100322593
+100137568
+100281516
+100137572
+100137574
+100291348
+100261720
+100137686
+100302728
+100239416
+100137612
+100137628
+100295000
+100137646
+100254930
+100256596
+100280910
+100147276
+100137694
+100313082
+100137690
+100166694
+100137696
+100268070
+100218732
+100263606
+100137710
+100218714
+100137714
+100166676
+100166660
+100137750
+100137756
+100137772
+100137774
+100137776
+100137780
+100137814
+100137860
+100137892
+100137898
+100137900
+100166658
+100137976
+100137978
+100137982
+100312000
+100137986
+100166546
+100218712
+100137996
+100166528
+100138002
+100138004
+100138008
+100118492
+100151790
+100138024
+100166526
+100280742
+100201772
+100138038
+100138040
+100166522
+100166504
+100138048
+100138070
+100120802
+100138078
+100138138
+100260130
+100166488
+100323936
+100138184
+100166484
+100322638
+100138188
+100166482
+100142038
+100138198
+100301898
+100138206
+100301194
+100064400
+100138228
+100166476
+100138232
+100227436
+100138236
+100138238
+100138242
+100200512
+100138276
+100138278
+100138280
+100166470
+100138302
+100287866
+100290678
+100166464
+100138324
+100138326
+100138358
+100262728
+100138378
+100181306
+100295992
+100138406
+100138400
+100329064
+100248226
+100290676
+100138410
+100138412
+100138414
+100330891
+100138434
+100304162
+100301932
+100138488
+100150444
+100138494
+100138496
+100363039
+100193200
+100218710
+100318232
+100166438
+100262914
+100319985
+100138538
+100138540
+100138542
+100353289
+100138562
+100138564
+100138566
+100138568
+100297534
+100138586
+100277120
+100166418
+100278540
+100275810
+100138614
+100138616
+100138618
+100138620
+100232634
+100174936
+100138628
+100320386
+100290728
+100267998
+100138652
+100138654
+100138656
+100323874
+100307888
+100318880
+100214232
+100166376
+100138686
+100138688
+100138690
+100275080
+100352750
+100138698
+100138728
+100209958
+100197674
+100175472
+100284090
+100138758
+100138760
+100166358
+100207698
+100218706
+100138768
+100209040
+100113276
+100175064
+100138812
+100050104
+100273788
+100138816
+100138818
+100138820
+100292416
+100324488
+100139616
+100176304
+100138832
+100166328
+100138836
+100138854
+100138856
+100290680
+100166326
+100201568
+100166292
+100153694
+100262706
+100138902
+100271756
+100193762
+100138922
+100281386
+100166278
+100355929
+100097388
+100175310
+100176196
+100218704
+100138952
+100293666
+100281166
+100138986
+100138990
+100166272
+100166256
+100139012
+100317315
+100322359
+100166208
+100139036
+100175478
+100142698
+100139074
+100139076
+100139078
+100139098
+100139100
+100139102
+100166198
+100166180
+100211178
+100139138
+100176194
+100139142
+100139144
+100332638
+100139166
+100139182
+100139188
+100057848
+100291906
+100139196
+100271094
+100139242
+100139274
+100139276
+100240548
+100190546
+100139316
+100139320
+100166094
+100139324
+100139354
+100151298
+100327038
+100139374
+100139376
+100323860
+100139410
+100139412
+100139414
+100139416
+100249320
+100139420
+100139422
+100139424
+100176192
+100147724
+100139430
+100139432
+100139450
+100139454
+100139456
+100139458
+100197754
+100139464
+100326342
+100139488
+100166072
+100139492
+100218624
+100139496
+100166070
+100139528
+100139530
+100213584
+100139562
+100210396
+100166068
+100328171
+100235870
+100139598
+100166066
+100139636
+100139638
+100166050
+100092754
+100205800
+100139658
+100139660
+100139662
+100139664
+100175302
+100139668
+100166046
+100166044
+100204538
+100273366
+100287016
+100101622
+100248814
+100312194
+100139708
+100121956
+100139712
+100139714
+100139730
+100139732
+100139734
+100139738
+100139742
+100292814
+100139760
+100166024
+100139764
+100318284
+100139768
+100290658
+100166020
+100249536
+100313370
+100139842
+100301146
+100204796
+100165980
+100217576
+100207490
+100139860
+100139862
+100256460
+100165978
+100153502
+100139870
+100192672
+100139918
+100139920
+100139922
+100139942
+100165942
+100258364
+100250992
+100284244
+100227350
+100310974
+100139958
+100139960
+100213918
+100139964
+100165922
+100139968
+100322521
+100297500
+100139978
+100165900
+100192794
+100282328
+100139988
+100292392
+100319609
+100140040
+100140056
+100114630
+100140086
+100140088
+100140090
+100165878
+100182356
+100165826
+100140096
+100140140
+100176186
+100140144
+100140146
+100307434
+100140166
+100140168
+100140170
+100140174
+100140176
+100140180
+100140182
+100361841
+100165808
+100190868
+100218558
+100140240
+100165772
+100148458
+100140246
+100165768
+100322517
+100275170
+100165766
+100140258
+100300250
+100140282
+100140288
+100140308
+100165746
+100140312
+100140314
+100140316
+100165728
+100285890
+100306264
+100140344
+100302268
+100140348
+100140350
+100241066
+100140356
+100140362
+100140378
+100198572
+100176178
+100261108
+100324420
+100165700
+100140406
+100140408
+100140410
+100140412
+100083400
+100140432
+100140434
+100239318
+100149090
+100297478
+100140468
+100140470
+100293552
+100248698
+100140474
+100322461
+100140480
+100212690
+100247510
+100140506
+100165668
+100042068
+100140502
+100165636
+100140508
+100140532
+100140512
+100165634
+100140522
+100140524
+100140526
+100140528
+100165626
+100143330
+100140536
+100322451
+100258904
+100204682
+100165606
+100322435
+100165602
+100140586
+100140588
+100165598
+100303144
+100140598
+100140600
+100258362
+100218556
+100322415
+100140640
+100322383
+100218554
+100154586
+100140646
+100140648
+100360954
+100140666
+100140672
+100140674
+100140684
+100140686
+100165576
+100140692
+100201770
+100319394
+100140700
+100140702
+100140732
+100142368
+100310764
+100140738
+100140744
+100005178
+100175318
+100165574
+100140770
+100248512
+100140774
+100165528
+100218546
+100322381
+100354432
+100161412
+100302952
+100140812
+100041236
+100140852
+100288676
+100140856
+100140864
+100140866
+100140882
+100140884
+100019904
+100165522
+100307098
+100140922
+100165518
+100274634
+100140930
+100165510
+100165502
+100165498
+100322335
+100212592
+100142132
+100240360
+100165496
+100140966
+100140968
+100212580
+100165448
+100218512
+100140992
+100261702
+100141028
+100141030
+100141034
+100141036
+100141038
+100141058
+100141060
+100141064
+100218510
+100141068
+100141070
+100165432
+100192630
+100165426
+100338056
+100043344
+100141130
+100093040
+100157610
+100206722
+100141154
+100141176
+100296070
+100165418
+100245952
+100141198
+100141200
+100156758
+100165384
+100175554
+100141254
+100212482
+100297410
+100262698
+100141308
+100174874
+100165320
+100141282
+100259634
+100141288
+100165318
+100141314
+100165316
+100209930
+100165310
+100141324
+100141326
+100260428
+100301246
+100340390
+100286872
+100141346
+100141348
+100331864
+100141356
+100165248
+100290634
+100322333
+100141366
+100289180
+100078512
+100141392
+100141394
+100141396
+100165196
+100305948
+100168942
+100141418
+100141420
+100292042
+100290828
+100271634
+100248168
+100088264
+100277748
+100141492
+100322814
+100312158
+100323697
+100141534
+100141550
+100141552
+100165176
+100141560
+100361763
+100141598
+100141602
+100236866
+100175356
+100165170
+100094222
+100141610
+100165168
+100193756
+100165060
+100174490
+100141622
+100274478
+100165042
+100201766
+100141634
+100141636
+100141638
+100141640
+100165022
+100141644
+100218436
+100141650
+100141654
+100141656
+100141658
+100113346
+100141662
+100305196
+100210124
+100141670
+100141672
+100165010
+100058540
+100218414
+100141708
+100141710
+100141712
+100111052
+100141714
+100164970
+100349899
+100164968
+100141724
+100141726
+100141728
+100270196
+100141748
+100141780
+100164946
+100141798
+100141814
+100208844
+100218410
+100141820
+100195462
+100141846
+100164942
+100141850
+100141852
+100141854
+100312074
+100141860
+100164938
+100164890
+100141866
+100141868
+100141884
+100141886
+100141888
+100322277
+100244224
+100141896
+100141898
+100141900
+100141902
+100288270
+100285154
+100141954
+100164866
+100141958
+100133734
+100164860
+100141990
+100214310
+100249650
+100142042
+100290626
+100269554
+100142080
+100164854
+100142082
+100274300
+100142086
+100164850
+100285460
+100164788
+100164728
+100142122
+100279034
+100142126
+100297408
+100144352
+100164710
+100175432
+100142138
+100164708
+100218332
+100321051
+100164706
+100020550
+100186916
+100142156
+100315332
+100142162
+100142172
+100142164
+100164702
+100142168
+100286848
+100142210
+100274264
+100190732
+100266644
+100164674
+100323117
+100164656
+100254110
+100142216
+100164632
+100218298
+100164628
+100274930
+100142232
+100257006
+100142236
+100142238
+100164592
+100293164
+100142290
+100142294
+100153410
+100142300
+100292504
+100142320
+100340696
+100164576
+100142340
+100311304
+100142414
+100142372
+100142378
+100322257
+100142380
+100248426
+100164524
+100318444
+100142428
+100164522
+100297406
+100174780
+100246888
+100142496
+100209672
+100164482
+100291114
+100209406
+100164474
+100250846
+100164472
+100113334
+100164390
+100142592
+100164388
+100142596
+100164386
+100155148
+100164380
+100325458
+100210204
+100164360
+100151158
+100142654
+100164344
+100297404
+100163696
+100218282
+100142694
+100325923
+100142702
+100211124
+100142720
+100175592
+100142750
+100306248
+100164304
+100142760
+100142766
+100282488
+100142788
+100142792
+100164284
+100164282
+100271138
+100246836
+100270620
+100142834
+100284304
+100164254
+100208762
+100218274
+100142888
+100164232
+100142892
+100142922
+100142924
+100193754
+100303236
+100322229
+100142950
+100186478
+100323389
+100142956
+100164210
+100142962
+100142964
+100142994
+100143012
+100143014
+100143032
+100143038
+100276566
+100143048
+100218210
+100143052
+100143054
+100274288
+100218208
+100143060
+100351462
+100176166
+100143072
+100143088
+100002168
+100143092
+100325262
+100152300
+100143098
+100143100
+100290604
+100327196
+100143134
+100143136
+100143142
+100218200
+100143146
+100143148
+100143152
+100218182
+100164184
+100176844
+100143180
+100322225
+100143184
+100332978
+100143188
+100192620
+100143192
+100143194
+100143196
+100290600
+100164154
+100211306
+100118486
+100322223
+100176394
+100143278
+100164106
+100167378
+100085654
+100164086
+100143248
+100213526
+100164066
+100164060
+100143256
+100281356
+100143260
+100266820
+100241388
+100338143
+100163970
+100143272
+100163968
+100181900
+100193064
+100307090
+100143304
+100143308
+100330799
+100143312
+100143332
+100143346
+100143336
+100247312
+100143340
+100143344
+100143350
+100143352
+100143356
+100163894
+100143358
+100143364
+100091604
+100297386
+100272356
+100201764
+100208444
+100143390
+100163700
+100203498
+100163698
+100143432
+100220372
+100329856
+100202800
+100235744
+100274972
+100213570
+100143452
+100163664
+100335627
+100297380
+100099366
+100300266
+100163644
+100303154
+100168338
+100257872
+100143494
+100163606
+100198458
+100323940
+100143504
+100187930
+100143524
+100250670
+100143544
+100155848
+100248068
+100143566
+100143568
+100234300
+100143586
+100143590
+100143592
+100143596
+100143598
+100143602
+100176156
+100271496
+100143610
+100320662
+100143630
+100244650
+100143638
+100274492
+100143642
+100143644
+100143648
+100252006
+100143652
+100143668
+100143670
+100218156
+100247720
+100210640
+100190304
+100163528
+100314804
+100218154
+100302796
+100143738
+100143740
+100143744
+100143746
+100143778
+100143780
+100143782
+100143784
+100143800
+100143802
+100163512
+100163504
+100356101
+100126930
+100273538
+100143834
+100322207
+100116668
+100143840
+100143842
+100322648
+100163500
+100241080
+100307086
+100143856
+100220076
+100144336
+100292930
+100163470
+100143910
+100261760
+100286522
+100186826
+100171932
+100143924
+100143926
+100143930
+100143948
+100163466
+100144912
+100198356
+100211416
+100163446
+100144004
+100163444
+100265838
+100260284
+100144016
+100172042
+100144022
+100290882
+100250650
+100144030
+100163428
+100144034
+100262692
+100144040
+100176134
+100144048
+100144050
+100249112
+100163422
+100163416
+100144064
+100190872
+100354935
+100144072
+100144074
+100144078
+100177328
+100266828
+100278508
+100292414
+100144104
+100252932
+100151770
+100144112
+100144116
+100163318
+100144120
+100175476
+100218148
+100144126
+100163316
+100144130
+100249606
+100166202
+100144152
+100290592
+100144178
+100144180
+100234982
+100068892
+100163292
+100313530
+100144218
+100330790
+100144268
+100190804
+100144288
+100144306
+100163284
+100144358
+100144360
+100283064
+100218146
+100144384
+100144400
+100163280
+100144406
+100218140
+100163278
+100163496
+100322205
+100144450
+100144452
+100163272
+100218136
+100322201
+100237454
+100218134
+100322872
+100074388
+100144468
+100270382
+100250240
+100144476
+100144478
+100218132
+100144484
+100163074
+100320780
+100144490
+100305556
+100144494
+100205630
+100144532
+100144548
+100144550
+100144552
+100175918
+100203624
+100144564
+100144566
+100205614
+100262702
+100307082
+100289834
+100201518
+100218120
+100342012
+100051920
+100244940
+100348068
+100261166
+100283510
+100176128
+100218114
+100144628
+100266174
+100144652
+100162960
+100210250
+100144672
+100144674
+100046206
+100241216
+100162932
+100162930
+100218112
+100144722
+100324209
+100286864
+100239530
+100162928
+100144790
+100001294
+100162924
+100218108
+100276768
+100271324
+100274574
+100144818
+100149700
+100341889
+100201582
+100162904
+100144842
+100157032
+100144864
+100144868
+100144894
+100322197
+100144898
+100302692
+100144902
+100144908
+100144962
+100162890
+100327973
+100260542
+100152288
+100328083
+100277118
+100145988
+100144998
+100145000
+100233796
+100145024
+100362309
+100145026
+100151554
+100268446
+100145048
+100145050
+100277364
+100145056
+100145058
+100297360
+100204094
+100290578
+100145068
+100218092
+100213824
+100145074
+100145076
+100162780
+100292410
+100145080
+100322185
+100162764
+100145096
+100145098
+100084722
+100145104
+100322181
+100145112
+100146674
+100358244
+100145116
+100145146
+100145148
+100145150
+100145154
+100145156
+100218090
+100162746
+100327016
+100145184
+100118508
+100322171
+100145194
+100218060
+100211248
+100162724
+100290566
+100162698
+100219874
+100145212
+100145216
+100145234
+100145236
+100145254
+100145256
+100145258
+100145260
+100145262
+100162682
+100162634
+100145324
+100218040
+100145328
+100145334
+100145342
+100363136
+100145344
+100162618
+100145348
+100145350
+100145352
+100218024
+100145364
+100162600
+100148260
+100145386
+100324133
+100245048
+100145406
+100247240
+100145428
+100337689
+100145438
+100162592
+100145442
+100145444
+100148232
+100317604
+100145468
+100276494
+100145890
+100145478
+100176122
+100145484
+100207114
+100218002
+100218000
+100162560
+100182934
+100322101
+100145522
+100162556
+100145528
+100145532
+100281962
+100322165
+100217996
+100235856
+100297340
+100145562
+100210622
+100339715
+100209700
+100162504
+100145602
+100354226
+100145608
+100145610
+100145612
+100115710
+100290554
+100201596
+100217978
+100145684
+100090496
+100145686
+100145688
+100145690
+100145692
+100162470
+100145698
+100162436
+100162434
+100273624
+100145742
+100145748
+100145752
+100145768
+100245046
+100145774
+100162376
+100246374
+100145780
+100217942
+100357666
+100145810
+100145812
+100162326
+100162296
+100253150
+100190802
+100145850
+100145854
+100210146
+100145856
+100162294
+100145896
+100297338
+100322157
+100256694
+100183882
+100341716
+100217936
+100145910
+100145912
+100145932
+100145934
+100205062
+100149882
+100162220
+100339961
+100145942
+100162218
+100193746
+100317804
+100145954
+100145956
+100256502
+100335244
+100145982
+100241028
+100145990
+100193744
+100146008
+100146010
+100290548
+100162162
+100146018
+100162140
+100146526
+100146026
+100146028
+100175136
+100162180
+100146036
+100191872
+100162084
+100212744
+100162066
+100311270
+100302928
+100150690
+100162064
+100363401
+100313642
+100335786
+100162034
+100162032
+100146086
+100211372
+100146090
+100162024
+100322103
+100162022
+100323267
+100161988
+100217934
+100146120
+100146122
+100146124
+100161970
+100201534
+100046914
+100146136
+100282694
+100146140
+100322351
+100207274
+100208386
+100161934
+100292760
+100217916
+100156812
+100217914
+100108810
+100205398
+100146240
+100076970
+100146246
+100314180
+100146264
+100146266
+100146268
+100326082
+100146272
+100210100
+100146296
+100275096
+100022592
+100078100
+100146302
+100281236
+100211428
+100051102
+100146310
+100161860
+100146316
+100285120
+100161856
+100075870
+100242492
+100303152
+100290540
+100161852
+100146390
+100161850
+100217912
+100320233
+100146410
+100161756
+100198358
+100161736
+100146422
+100146442
+100146444
+100156240
+100183556
+100161712
+100327002
+100283292
+100071370
+100323695
+100176112
+100267544
+100297336
+100314644
+100302790
+100270324
+100161620
+100146508
+100161616
+100146514
+100324647
+100307064
+100161584
+100146522
+100146524
+100161580
+100146594
+100217852
+100282912
+100089284
+100146826
+100161562
+100146560
+100149854
+100146582
+100310358
+100323882
+100282372
+100146598
+100330738
+100146602
+100161510
+100041338
+100330737
+100290822
+100146626
+100161460
+100281330
+100292394
+100161438
+100146678
+100146680
+100161404
+100302020
+100185222
+100047248
+100260538
+100146698
+100327623
+100146700
+100146704
+100146708
+100146712
+100149582
+100247560
+100241520
+100295218
+100161348
+100282914
+100161346
+100146764
+100146768
+100308360
+100259646
+100146774
+100107628
+100090310
+100161326
+100246430
+100146814
+100306810
+100328151
+100146828
+100281604
+100146856
+100217850
+100146860
+100147044
+100161316
+100120046
+100161312
+100176036
+100150848
+100281982
+100147308
+100161282
+100245780
+100161280
+100169974
+100161276
+100327939
+100161212
+100161194
+100175648
+100161176
+100175024
+100146954
+100146956
+100147012
+100161144
+100210594
+100146966
+100146968
+100154482
+100210200
+100146974
+100323653
+100161112
+100055998
+100146986
+100161108
+100146994
+100256110
+100147000
+100147010
+100150150
+100154584
+100281030
+100149260
+100151874
+100147034
+100277208
+100156312
+100196944
+100161104
+100161086
+100147060
+100147068
+100217848
+100318436
+100071048
+100147086
+100241984
+100320566
+100259648
+100307052
+100289642
+100161050
+100147148
+100293668
+100262680
+100161046
+100258456
+100302016
+100322065
+100326793
+100291202
+100161044
+100360696
+100025318
+100161028
+100283308
+100175840
+100161024
+100161006
+100160986
+100147218
+100160984
+100284750
+100147230
+100148548
+100217844
+100147254
+100100062
+100147256
+100306634
+100160970
+100262676
+100186886
+100253636
+100187000
+100186866
+100147310
+100318782
+100160968
+100217826
+100147320
+100160940
+100147334
+100212254
+100283364
+100160936
+100147342
+100344297
+100210614
+100211024
+100250916
+100147352
+100170566
+100160890
+100160886
+100091700
+100323507
+100198454
+100160834
+100147404
+100160800
+100160768
+100160764
+100294820
+100254712
+100160762
+100240542
+100160760
+100160756
+100147430
+100147434
+100147436
+100147442
+100243280
+100147460
+100160750
+100160736
+100262674
+100214228
+100160718
+100297294
+100160672
+100160670
+100283964
+100147700
+100289704
+100147530
+100160660
+100255976
+100147540
+100147544
+100161308
+100175584
+100147580
+100217816
+100160638
+100150488
+100119256
+100147614
+100147626
+100160622
+100160620
+100147632
+100273696
+100214066
+100160550
+100273612
+100147640
+100363109
+100160534
+100147668
+100217810
+100217800
+100217796
+100151942
+100160482
+100160480
+100160464
+100160462
+100217794
+100356551
+100160456
+100199382
+100147766
+100147764
+100147770
+100147772
+100147774
+100160424
+100147780
+100256382
+100176008
+100262854
+100160418
+100147790
+100160412
+100303580
+100153632
+100160396
+100160364
+100147802
+100147810
+100160334
+100156830
+100254570
+100301218
+100321983
+100249314
+100160242
+100157598
+100147934
+100160234
+100160232
+100160224
+100147956
+100217792
+100320177
+100244036
+100235134
+100160230
+100332726
+100160202
+100160198
+100361840
+100148010
+100148012
+100148234
+100160188
+100182538
+100267108
+100148024
+100148030
+100148032
+100256910
+100217772
+100148038
+100024734
+100046972
+100160170
+100207478
+100249168
+100148072
+100152566
+100156938
+100148088
+100149164
+100148090
+100284454
+100250348
+100217752
+100148116
+100152632
+100148122
+100151034
+100148124
+100153514
+100274526
+100148138
+100269304
+100258850
+100148144
+100160104
+100160102
+100160096
+100148154
+100160094
+100148166
+100201196
+100148182
+100300470
+100148184
+100148186
+100148190
+100245940
+100148462
+100183024
+100160090
+100300072
+100282490
+100322880
+100148246
+100148248
+100259514
+100148252
+100148302
+100160078
+100160074
+100148300
+100058646
+100160066
+100160062
+100160046
+100321973
+100303170
+100182276
+100160024
+100249564
+100116296
+100057290
+100297292
+100156834
+100197990
+100160020
+100268670
+100148468
+100299230
+100160010
+100253110
+100148434
+100148440
+100208858
+100148984
+100266880
+100273592
+100266222
+100266156
+100148464
+100320444
+100148492
+100305670
+100148510
+100245794
+100285118
+100157772
+100217740
+100217738
+100180424
+100061214
+100148530
+100159994
+100317311
+100159990
+100318818
+100276704
+100272966
+100159984
+100159980
+100242670
+100209210
+100304126
+100290494
+100322953
+100150624
+100148618
+100148620
+100247928
+100149536
+100148624
+100148626
+100148738
+100318152
+100159958
+100148646
+100155260
+100312092
+100327835
+100327312
+100288086
+100299228
+100281232
+100148684
+100159898
+100159874
+100260536
+100159872
+100148740
+100047488
+100148742
+100247122
+100148744
+100356131
+100159868
+100159836
+100148764
+100273500
+100159834
+100148772
+100203646
+100148796
+100148848
+100217716
+100148856
+100210410
+100148880
+100204558
+100168514
+100159826
+100159824
+100201754
+100312502
+100159822
+100057010
+100321959
+100192418
+100325174
+100159816
+100282556
+100148948
+100148950
+100148954
+100231640
+100159812
+100159810
+100159808
+100148982
+100151114
+100159804
+100117948
+100318088
+100217326
+100149008
+100181406
+100322059
+100255406
+100213558
+100159776
+100301916
+100149060
+100150912
+100284290
+100217324
+100149066
+100159772
+100149072
+100301272
+100149080
+100149084
+100328363
+100149092
+100149094
+100149096
+100149098
+100175794
+100149100
+100243018
+100075576
+100192126
+100175792
+100149218
+100159734
+100284036
+100289430
+100267142
+100208564
+100159720
+100281656
+100159702
+100159680
+100325781
+100325747
+100159646
+100149580
+100321953
+100100122
+100149440
+100323341
+100159642
+100265026
+100159640
+100302208
+100159618
+100159616
+100217292
+100159606
+100284566
+100149396
+100266250
+100046122
+100208778
+100297160
+100159604
+100159588
+100159584
+100176374
+100241944
+100327647
+100159540
+100159508
+100159506
+100149586
+100244800
+100113242
+100210792
+100288278
+100300082
+100202966
+100255894
+100209366
+100191650
+100205710
+100290620
+100160260
+100159398
+100149674
+100293214
+100159378
+100159376
+100260796
+100150152
+100237658
+100159372
+100159370
+100292388
+100196572
+100159368
+100323459
+100159332
+100098842
+100149780
+100272894
+100307230
+100275570
+100151448
+100159308
+100192636
+100159298
+100201806
+100217242
+100321933
+100094392
+100205696
+100280676
+100113596
+100159248
+100149958
+100149952
+100149960
+100070828
+100211426
+100271966
+100150040
+100159244
+100159242
+100294882
+100150000
+100159240
+100264402
+100159204
+100159202
+100301362
+100150016
+100294894
+100150164
+100159200
+100239500
+100217238
+100159158
+100283288
+100150318
+100150320
+100159154
+100306670
+100150400
+100150408
+100150708
+100201750
+100157590
+100159120
+100150466
+100159118
+100159116
+100159114
+100150526
+100186864
+100159098
+100217208
+100289142
+100175786
+100195142
+100159088
+100291076
+100193734
+100217202
+100203436
+100159074
+100355441
+100291892
+100151260
+100217174
+100301462
+100285206
+100201362
+100099348
+100159064
+100159048
+100273218
+100151042
+100207746
+100172278
+100301860
+100159044
+100361284
+100217172
+100159024
+100159022
+100272668
+100151194
+100151794
+100151204
+100159020
+100151210
+100151306
+100321929
+100270042
+100159016
+100292200
+100209232
+100193732
+100210432
+100192072
+100151246
+100151312
+100151316
+100159006
+100151334
+100208860
+100151380
+100205864
+100151560
+100158958
+100158950
+100158930
+100217156
+100151370
+100161550
+100151636
+100267118
+100217136
+100175780
+100191240
+100151738
+100186452
+100248424
+100158914
+100182864
+100217130
+100213578
+100217098
+100151870
+100158910
+100158908
+100158906
+100158904
+100154890
+100290472
+100151952
+100287012
+100158856
+100158848
+100323421
+100158832
+100209112
+100321927
+100217082
+100169336
+100344851
+100294610
+100152086
+100158806
+100198832
+100277956
+100332732
+100154302
+100258034
+100170618
+100266230
+100213126
+100192050
+100175536
+100302824
+100292370
+100290996
+100217062
+100158794
+100158734
+100213588
+100201592
+100094452
+100217060
+100172126
+100276452
+100152318
+100174140
+100323463
+100217028
+100217026
+100316156
+100152352
+100175776
+100152376
+100158704
+100158688
+100316170
+100229350
+100152386
+100178656
+100217024
+100217022
+100158624
+100158620
+100330983
+100157452
+100158564
+100152422
+100321891
+100325348
+100204726
+100152470
+100152474
+100335063
+100051682
+100307016
+100158646
+100267994
+100155120
+100297154
+100152588
+100236804
+100161414
+100158552
+100311728
+100119290
+100152622
+100240384
+100156356
+100152746
+100217020
+100321887
+100247364
+100216988
+100158548
+100323715
+100324038
+100284004
+100158542
+100158538
+100173568
+100158534
+100158530
+100272376
+100158528
+100158526
+100259990
+100244748
+100266692
+100158522
+100157454
+100158520
+100216978
+100158446
+100152864
+100175774
+100354578
+100152876
+100175768
+100329010
+100152878
+100271392
+100075114
+100152882
+100259954
+100153628
+100158436
+100333213
+100152892
+100292366
+100216956
+100247448
+100216938
+100158424
+100175760
+100216928
+100152952
+100314848
+100162086
+100297152
+100321881
+100173106
+100324195
+100046796
+100209756
+100153106
+100278414
+100240186
+100320514
+100153116
+100258164
+100158378
+100223392
+100158376
+100210114
+100153162
+100326722
+100292362
+100158372
+100216906
+100153166
+100197446
+100210504
+100153172
+100216888
+100158258
+100212896
+100324328
+100153192
+100153214
+100321879
+100207466
+100157746
+100153222
+100278988
+100153252
+100158256
+100158254
+100158252
+100203622
+100158246
+100292174
+100254038
+100158242
+100302586
+100209828
+100325835
+100361497
+100283242
+100182622
+100249876
+100284554
+100153278
+100120738
+100216886
+100293466
+100216884
+100158228
+100269774
+100318062
+100203168
+100263016
+100216882
+100047822
+100216864
+100153358
+100109152
+100316224
+100216858
+100239334
+100216852
+100158168
+100153388
+100323878
+100180958
+100158164
+100158146
+100321877
+100302218
+100158114
+100158076
+100273002
+100280986
+100158070
+100158038
+100303454
+100158006
+100158000
+100154394
+100158238
+100153454
+100249888
+100157994
+100259034
+100328992
+100155448
+100153484
+100211236
+100191630
+100157992
+100325769
+100157990
+100157988
+100326114
+100208862
+100193674
+100019322
+100202280
+100216850
+100157978
+100153580
+100246952
+100157972
+100157970
+100153596
+100153608
+100257324
+100320079
+100153630
+100216848
+100216828
+100157950
+100173942
+100163896
+100157924
+100259928
+100289362
+100153720
+100246244
+100322766
+100280522
+100157918
+100154650
+100216826
+100257244
+100321871
+100157894
+100110376
+100319783
+100157890
+100157888
+100153792
+100281642
+100358258
+100216824
+100256922
+100231634
+100157874
+100326712
+100157848
+100153812
+100175454
+100153816
+100212850
+100202942
+100153982
+100157752
+100328928
+100157836
+100157834
+100153836
+100153830
+100153832
+100175110
+100216822
+100247130
+100328924
+100215120
+100323932
+100153858
+100201544
+100210842
+100213862
+100272854
+100210116
+100153882
+100283128
+100201446
+100202594
+100310660
+100202464
+100153928
+100192128
+100153932
+100270608
+100153940
+100230666
+100187424
+100153946
+100153950
+100272192
+100182288
+100231742
+100355637
+100300252
+100175234
+100279492
+100167564
+100175092
+100278786
+100290392
+100047440
+100157570
+100309984
+100263402
+100208922
+100157680
+100096254
+100186120
+100217712
+100201594
+100154058
+100216818
+100282676
+100154066
+100154068
+100157642
+100238740
+100206472
+100154104
+100257246
+100154118
+100253114
+100204782
+100245338
+100209990
+100210958
+100249932
+100285124
+100245944
+100178814
+100207360
+100206624
+100113088
+100154182
+100173308
+100249688
+100176158
+100045528
+100154194
+100192670
+100154200
+100154206
+100154208
+100262630
+100302648
+100301166
+100201444
+100216812
+100154220
+100157528
+100192612
+100248160
+100154236
+100157520
+100283050
+100297108
+100154250
+100176302
+100323807
+100199294
+100258414
+100325722
+100154264
+100154266
+100154270
+100209892
+100154278
+100154282
+100174120
+100327568
+100290382
+100154296
+100278174
+100175942
+100260268
+100154306
+100154308
+100297102
+100092998
+100154316
+100154322
+100281184
+100311510
+100154328
+100154330
+100304710
+100154334
+100154336
+100154338
+100154340
+100154342
+100297100
+100154346
+100154348
+100297096
+100154352
+100325678
+100321213
+100301602
+100211072
+100156498
+100243254
+100154370
+100154372
+100216804
+100156696
+100328894
+100200380
+100277434
+100289638
+100322660
+100290368
+100074144
+100154400
+100154402
+100351050
+100154412
+100262610
+100321797
+100154418
+100297046
+100154424
+100283282
+100216798
+100324274
+100157470
+100198070
+100154446
+100323535
+100253026
+100154452
+100328389
+100275922
+100327839
+100236950
+100154462
+100263404
+100190076
+100260110
+100313820
+100156868
+100154476
+100190918
+100297044
+100251306
+100154490
+100216768
+100173664
+100257534
+100154498
+100255178
+100172912
+100262592
+100154508
+100303068
+100321590
+100157410
+100318288
+100267768
+100290366
+100247628
+100172184
+100301386
+100280630
+100154542
+100202918
+100303208
+100154552
+100294088
+100154558
+100297042
+100262590
+100262588
+100154570
+100154572
+100154574
+100170760
+100292080
+100301234
+100216766
+100285512
+100154596
+100154598
+100154600
+100201442
+100232746
+100174022
+100154610
+100290480
+100154620
+100154622
+100297040
+100154626
+100154628
+100154634
+100154644
+100209940
+100291722
+100154656
+100154660
+100255326
+100154664
+100282498
+100154674
+100154682
+100154684
+100154692
+100154694
+100262586
+100154698
+100280620
+100154702
+100288216
+100168376
+100172248
+100157820
+100167358
+100311352
+100154740
+100285064
+100305154
+100249922
+100154732
+100290356
+100273632
+100237078
+100274998
+100102282
+100154754
+100157356
+100248898
+100172246
+100154762
+100258270
+100154766
+100326162
+100293112
+100154772
+100154774
+100154776
+100156880
+100258358
+100290346
+100154784
+100154786
+100154788
+100157030
+100203674
+100154796
+100154798
+100154800
+100300642
+100325218
+100326702
+100154810
+100265576
+100297008
+100192406
+100154824
+100320572
+100154828
+100172120
+100156928
+100268614
+100059566
+100249594
+100261562
+100293372
+100253854
+100154854
+100154856
+100279892
+100328862
+100154862
+100154868
+100154870
+100154872
+100175818
+100154876
+100154878
+100154880
+100154884
+100178074
+100154892
+100154894
+100154896
+100290582
+100172180
+100192222
+100296928
+100175940
+100190634
+100320865
+100320524
+100276216
+100188140
+100238634
+100292710
+100306982
+100327869
+100322824
+100154928
+100154930
+100155528
+100321568
+100154940
+100154942
+100296924
+100154946
+100154948
+100154950
+100154952
+100207460
+100321566
+100173504
+100187624
+100326624
+100260850
+100154970
+100154972
+100154976
+100154978
+100289620
+100157308
+100290340
+100157290
+100154988
+100327458
+100169764
+100154994
+100154996
+100247500
+100174454
+100155010
+100076656
+100322497
+100155018
+100321127
+100155024
+100361969
+100155028
+100155030
+100155032
+100155034
+100297516
+100155040
+100230742
+100155046
+100155048
+100270030
+100273406
+100155058
+100155060
+100319561
+100211784
+100155066
+100279710
+100281806
+100155072
+100155076
+100091006
+100155082
+100155086
+100328233
+100190920
+100155092
+100275620
+100246472
+100216752
+100157262
+100157260
+100175364
+100155106
+100262584
+100286862
+100216718
+100156890
+100322993
+100279300
+100155122
+100155124
+100155126
+100172098
+100155132
+100323007
+100155136
+100155138
+100251920
+100155142
+100325899
+100155152
+100204570
+100172096
+100155160
+100189536
+100296906
+100155170
+100155172
+100321548
+100272228
+100156932
+100174944
+100155186
+100157228
+100305418
+100155192
+100155194
+100155196
+100204304
+100170486
+100156918
+100155204
+100262582
+100157208
+100272562
+100110736
+100155216
+100155218
+100155220
+100294172
+100280896
+100205390
+100311238
+100155234
+100272560
+100281654
+100169092
+100155242
+100155244
+100211300
+100268656
+100155250
+100324583
+100155254
+100296902
+100155258
+100192858
+100270254
+100212292
+100155266
+100244528
+100326612
+100155274
+100157718
+100155276
+100155278
+100155280
+100303274
+100155284
+100310804
+100155288
+100155290
+100244916
+100155294
+100209424
+100155302
+100157198
+100288536
+100155308
+100155310
+100326556
+100155314
+100157196
+100155318
+100155320
+100155324
+100168372
+100247650
+100155332
+100325158
+100155336
+100194944
+100155340
+100203924
+100155344
+100155346
+100155350
+100326534
+100268232
+100244854
+100156982
+100326920
+100244206
+100296900
+100155368
+100168352
+100155372
+100155374
+100155376
+100155378
+100306042
+100155386
+100312596
+100325680
+100270248
+100155390
+100155392
+100269058
+100155400
+100203348
+100352845
+100155410
+100155412
+100155414
+100201364
+100240410
+100155424
+100192654
+100155428
+100279394
+100155432
+100157140
+100155436
+100288282
+100155440
+100155450
+100263354
+100129792
+100155458
+100249208
+100155464
+100313880
+100262886
+100155470
+100285248
+100284282
+100315132
+100155476
+100155480
+100155482
+100296896
+100250892
+100155486
+100175358
+100169052
+100283746
+100296894
+100175250
+100157190
+100286144
+100209414
+100175178
+100155510
+100082378
+100208954
+100256156
+100155520
+100212678
+100157186
+100300264
+100170950
+100273570
+100155532
+100262134
+100292354
+100354104
+100168492
+100258474
+100321520
+100155562
+100240276
+100155576
+100175474
+100155580
+100281182
+100155586
+100309148
+100155592
+100155594
+100155596
+100157064
+100175770
+100095696
+100287450
+100155606
+100155608
+100268052
+100155612
+100214258
+100155616
+100155618
+100155620
+100168378
+100155624
+100085664
+100275594
+100186854
+100155632
+100287174
+100155636
+100250264
+100155640
+100272924
+100155644
+100155646
+100155650
+100306948
+100155654
+100155656
+100290320
+100155660
+100321486
+100155664
+100216668
+100252912
+100321480
+100157034
+100321456
+100025798
+100155688
+100285168
+100296882
+100209826
+100282852
+100172194
+100320418
+100161552
+100157306
+100191832
+100167110
+100175012
+100161488
+100155802
+100253086
+100177866
+100156118
+100198172
+100216666
+100272006
+100175030
+100280828
+100172974
+100172468
+100216664
+100321452
+100155948
+100050202
+100173382
+100309392
+100256408
+100258002
+100303550
+100155974
+100306944
+100210156
+100201438
+100155984
+100210414
+100095122
+100186802
+100247756
+100117726
+100156010
+100323799
+100139766
+100296880
+100184954
+100157550
+100156062
+100174096
+100285384
+100248914
+100315236
+100156080
+100156088
+100173758
+100318322
+100156100
+100156120
+100350678
+100161714
+100208840
+100341038
+100163838
+100308182
+100355821
+100157816
+100216662
+100246172
+100303868
+100156184
+100216660
+100178734
+100156202
+100156204
+100295106
+100156220
+100165776
+100175836
+100100090
+100170432
+100157618
+100156234
+100157824
+100157414
+100216658
+100201704
+100323864
+100332768
+100209652
+100253838
+100156314
+100156318
+100338192
+100206954
+100211638
+100192710
+100184648
+100184650
+100184652
+100184668
+100184734
+100184736
+100184754
+100184756
+100184758
+100211982
+100206610
+100184772
+100184790
+100184796
+100276466
+100328816
+100204642
+100184862
+100184866
+100184868
+100294792
+100184956
+100184958
+100290304
+100205840
+100184984
+100184986
+100185002
+100185006
+100296826
+100185038
+100185040
+100185056
+100258918
+100185060
+100185062
+100205836
+100185084
+100185116
+100185146
+100185148
+100310406
+100185186
+100193892
+100185188
+100185190
+100198008
+100265084
+100216656
+100185294
+100185360
+100185298
+100185330
+100185332
+100283178
+100360394
+100185356
+100204776
+100185362
+100185366
+100247938
+100185370
+100204774
+100185380
+100185382
+100216652
+100321450
+100185424
+100271328
+100185460
+100185462
+100185464
+100185466
+100320554
+100256216
+100185474
+100307486
+100185492
+100285394
+100324595
+100185548
+100185550
+100185574
+100185590
+100227428
+100204764
+100239492
+100296870
+100185604
+100185608
+100290284
+100185628
+100195852
+100185632
+100262576
+100185700
+100185702
+100185734
+100185756
+100185776
+100258384
+100265090
+100185808
+100288928
+100294050
+100185818
+100185820
+100185822
+100216580
+100185870
+100185874
+100185878
+100185982
+100185942
+100185960
+100186000
+100186002
+100186004
+100186008
+100302822
+100186022
+100186052
+100186054
+100281442
+100186104
+100204638
+100186132
+100186152
+100186168
+100186170
+100296778
+100186188
+100186190
+100186196
+100186204
+100281976
+100186222
+100278894
+100191046
+100186244
+100186266
+100186268
+100186290
+100186292
+100188268
+100324368
+100186328
+100303328
+100216576
+100186354
+100216574
+100186386
+100186434
+100186454
+100186458
+100244792
+100186528
+100362355
+100186546
+100186792
+100186578
+100186622
+100186852
+100186624
+100306936
+100326512
+100186680
+100186682
+100209772
+100186746
+100187146
+100187156
+100322501
+100187176
+100187296
+100187258
+100187276
+100187294
+100187318
+100187334
+100187366
+100283634
+100216532
+100187408
+100277428
+100216514
+100187430
+100296976
+100261226
+100187512
+100187518
+100187520
+100187524
+100268998
+100187558
+100187578
+100216512
+100296868
+100190610
+100187598
+100187642
+100187646
+100207458
+100131752
+100285210
+100187664
+100206640
+100187686
+100253380
+100187690
+100187692
+100304646
+100328796
+100321448
+100187830
+100187810
+100284472
+100187834
+100187836
+100187842
+100048552
+100187876
+100283700
+100187950
+100273058
+100187958
+100272520
+100187978
+100248498
+100187996
+100187998
+100188018
+100283240
+100205826
+100321444
+100188028
+100188030
+100305188
+100205824
+100131356
+100216496
+100188054
+100296772
+100286344
+100206608
+100188060
+100188062
+100188116
+100188120
+100290270
+100283654
+100188146
+100296776
+100188168
+100188166
+100205814
+100210300
+100188218
+100193890
+100268426
+100188240
+100188242
+100324849
+100188270
+100216488
+100355315
+100321655
+100118244
+100188330
+100188356
+100207450
+100188376
+100188378
+100191326
+100188398
+100074354
+100205804
+100188404
+100188406
+100188408
+100188410
+100213850
+100188416
+100188422
+100188432
+100188448
+100188482
+100207448
+100188500
+100188502
+100205794
+100188564
+100188566
+100282096
+100324597
+100321442
+100188588
+100205788
+100188608
+100258124
+100188642
+100188674
+100189248
+100188692
+100188760
+100188762
+100188764
+100188766
+100188768
+100188770
+100262554
+100216486
+100188802
+100188804
+100188810
+100188812
+100287924
+100281028
+100284142
+100188822
+100216484
+100188880
+100188844
+100188878
+100190140
+100188910
+100274962
+100188950
+100324879
+100188958
+100278832
+100188976
+100213294
+100250176
+100188994
+100216474
+100189010
+100189092
+100191032
+100189108
+100189110
+100189160
+100264366
+100189190
+100189164
+100189170
+100276324
+100189194
+100189212
+100189214
+100189218
+100206604
+100189240
+100189242
+100210152
+100189250
+100189252
+100189254
+100189272
+100322866
+100189276
+100189306
+100296756
+100189328
+100189334
+100312878
+100189340
+100189358
+100321440
+100189362
+100189382
+100189400
+100189404
+100189434
+100205906
+100189440
+100189462
+100250746
+100282844
+100189470
+100189472
+100189518
+100247972
+100189534
+100258602
+100236764
+100189588
+100189634
+100205784
+100189684
+100321418
+100189678
+100189680
+100189682
+100189686
+100189688
+100189750
+100205758
+100189702
+100189746
+100189748
+100189784
+100189786
+100189788
+100189790
+100216464
+100189816
+100189820
+100189836
+100189858
+100189862
+100189864
+100295604
+100189872
+100189874
+100189878
+100189884
+100189904
+100189934
+100217476
+100189974
+100290730
+100275290
+100190008
+100190010
+100190026
+100190028
+100257966
+100190048
+100190064
+100190070
+100216428
+100190134
+100361385
+100190092
+100190094
+100190096
+100216426
+100216424
+100190102
+100190104
+100216420
+100190128
+100272386
+100190132
+100216448
+100190144
+100337715
+100283660
+100210278
+100321410
+100216372
+100190240
+100190242
+100192336
+100190244
+100216370
+100248936
+100190250
+100227112
+100190322
+100190324
+100248374
+100270658
+100190336
+100192134
+100190358
+100190376
+100190814
+100190408
+100190430
+100210268
+100190434
+100205748
+100222862
+100216366
+100190446
+100210552
+100190952
+100190954
+100216362
+100321400
+100190962
+100190968
+100190972
+100216360
+100326498
+100275514
+100191024
+100191026
+100191028
+100284450
+100322507
+100293470
+100205342
+100306930
+100216342
+100191076
+100191100
+100313024
+100206546
+100191084
+100191180
+100311700
+100191122
+100261156
+100323291
+100322369
+100191178
+100100534
+100326458
+100191192
+100206552
+100206530
+100192332
+100120490
+100216338
+100191246
+100192916
+100191256
+100306926
+100191258
+100191260
+100278404
+100191274
+100191320
+100201788
+100191344
+100191364
+100216374
+100191368
+100191372
+100206528
+100191390
+100231556
+100207440
+100191454
+100191460
+100191462
+100191470
+100191466
+100191472
+100191488
+100191492
+100191768
+100201562
+100191634
+100210090
+100191500
+100191502
+100207442
+100230824
+100266522
+100191510
+100206520
+100272052
+100206516
+100320913
+100305274
+100191554
+100285340
+100210676
+100290250
+100191564
+100191566
+100191584
+100281004
+100191612
+100290248
+100191618
+100191620
+100191622
+100191626
+100191658
+100206600
+100191656
+100216290
+100208920
+100239380
+100191712
+100117814
+100191718
+100191720
+100216276
+100211154
+100267954
+100191774
+100294822
+100208666
+100288106
+100216272
+100258454
+100191840
+100191856
+100211698
+100286404
+100193532
+100191868
+100191870
+100191876
+100191878
+100289258
+100278366
+100191918
+100191920
+100214126
+100191970
+100191974
+100191978
+100230314
+100191980
+100191996
+100301268
+100192000
+100323823
+100192018
+100192048
+100192054
+100304140
+100261280
+100192078
+100255058
+100192914
+100206506
+100296724
+100194178
+100192132
+100192138
+100206456
+100192144
+100192146
+100192148
+100226470
+100216256
+100216254
+100254300
+100192292
+100192408
+100192616
+100199956
+100192712
+100327060
+100192728
+100238616
+100265856
+100293338
+100192796
+100192818
+100249790
+100192824
+100275494
+100192828
+100192844
+100208266
+100207434
+100212504
+100294766
+100216218
+100216202
+100216200
+100214120
+100192878
+100192894
+100216198
+100193024
+100216194
+100211116
+100251444
+100244696
+100200394
+100207430
+100260010
+100216146
+100207428
+100323321
+100192952
+100211410
+100207424
+100192982
+100192998
+100216114
+100206380
+100193056
+100195310
+100193066
+100193068
+100193116
+100193118
+100271522
+100193124
+100193140
+100245948
+100193146
+100266336
+100321795
+100290238
+100193196
+100216000
+100301342
+100193206
+100227470
+100215998
+100323537
+100261828
+100193220
+100193224
+100193232
+100207416
+100229220
+100317245
+100204732
+100325096
+100193242
+100193262
+100246650
+100193270
+100321366
+100323271
+100311820
+100193292
+100193302
+100193306
+100193308
+100238788
+100193314
+100193318
+100193336
+100193338
+100193342
+100193344
+100293460
+100113818
+100195184
+100193424
+100193362
+100193428
+100325182
+100259002
+100215964
+100196726
+100193474
+100193480
+100193484
+100193488
+100205996
+100193526
+100193528
+100318026
+100200684
+100215960
+100208260
+100193542
+100193538
+100268940
+100193546
+100193578
+100236862
+100315232
+100247898
+100115404
+100323295
+100202528
+100193642
+100193644
+100194138
+100215940
+100194830
+100210274
+100193698
+100278772
+100246314
+100193826
+100293986
+100307044
+100193836
+100321338
+100296716
+100254568
+100193850
+100290228
+100249652
+100215876
+100208654
+100294998
+100215874
+100215866
+100193870
+100193884
+100249588
+100193878
+100193882
+100193900
+100207236
+100311816
+100215864
+100193934
+100244378
+100210564
+100193904
+100244386
+100215862
+100215858
+100193926
+100355798
+100193930
+100193932
+100237846
+100193940
+100193942
+100194116
+100210838
+100193948
+100193950
+100215854
+100193972
+100193974
+100311784
+100193980
+100265938
+100254696
+100250236
+100193996
+100270124
+100194002
+100194004
+100194008
+100194010
+100213138
+100293874
+100325641
+100194024
+100209842
+100194030
+100215850
+100321859
+100215834
+100194262
+100194042
+100248230
+100249322
+100270706
+100194054
+100245932
+100358753
+100194060
+100082776
+100208876
+100321322
+100194070
+100194072
+100194076
+100271952
+100262540
+100194082
+100194084
+100337956
+100304138
+100301966
+100194092
+100208590
+100274670
+100194102
+100194104
+100194106
+100194108
+100211066
+100215786
+100194120
+100194122
+100241718
+100194128
+100283582
+100252920
+100235706
+100241444
+100194156
+100281026
+100194152
+100273544
+100285072
+100268630
+100194210
+100194172
+100281558
+100321226
+100211314
+100248042
+100194184
+100194186
+100194202
+100306904
+100285706
+100209908
+100211062
+100210294
+100210474
+100194212
+100194214
+100235806
+100281068
+100194220
+100271948
+100057508
+100215710
+100326867
+100324211
+100194264
+100207184
+100208912
+100215676
+100194242
+100342948
+100236786
+100259012
+100319458
+100194256
+100284640
+100194270
+100194272
+100257452
+100299734
+100194278
+100194280
+100296946
+100209006
+100194286
+100194288
+100301402
+100194294
+100326452
+100194298
+100279940
+100215674
+100194306
+100194308
+100304560
+100215672
+100259650
+100194316
+100321314
+100257012
+100204900
+100278538
+100295094
+100215656
+100194330
+100271944
+100194338
+100254076
+100266548
+100315420
+100313460
+100194348
+100322343
+100194352
+100215640
+100303856
+100194358
+100194360
+100278858
+100302724
+100194368
+100242340
+100271942
+100194374
+100194376
+100194378
+100194380
+100194382
+100273698
+100266514
+100213700
+100221358
+100194400
+100194406
+100215638
+100215634
+100194412
+100194414
+100211322
+100194418
+100194420
+100194422
+100194426
+100194428
+100194430
+100194432
+100194434
+100194436
+100259754
+100194444
+100194446
+100262230
+100194450
+100194452
+100281020
+100239078
+100194462
+100194464
+100194468
+100208726
+100194472
+100353695
+100215574
+100194478
+100194480
+100194482
+100194484
+100338085
+100290212
+100292456
+100259444
+100276002
+100309622
+100194498
+100194502
+100296682
+100194506
+100194508
+100321312
+100266952
+100211002
+100197816
+100200298
+100089504
+100060242
+100235452
+100296678
+100118548
+100273124
+100281810
+100215572
+100330786
+100275854
+100326466
+100293108
+100210508
+100215510
+100194592
+100194598
+100215508
+100314874
+100194604
+100215506
+100194610
+100194612
+100194614
+100215482
+100271916
+100194622
+100296216
+100215478
+100194632
+100194634
+100194636
+100194644
+100266650
+100326436
+100215474
+100231670
+100194662
+100314968
+100194670
+100272900
+100254332
+100215456
+100333059
+100215446
+100194684
+100244664
+100215440
+100194690
+100215438
+100194698
+100277924
+100194702
+100294558
+100212820
+100194708
+100208910
+100033684
+100306716
+100355405
+100194742
+100251818
+100194746
+100194748
+100306878
+100332688
+100194754
+100194756
+100232194
+100194760
+100194762
+100212604
+100215434
+100246474
+100321306
+100321304
+100274100
+100259496
+100277376
+100246942
+100215432
+100194790
+100296268
+100194810
+100258356
+100276350
+100271912
+100260070
+100194828
+100306896
+100215428
+100215424
+100194866
+100151586
+100194872
+100316305
+100323265
+100254228
+100208040
+100215422
+100194886
+100243178
+100215420
+100245086
+100209596
+100272708
+100170976
+100194900
+100194902
+100194904
+100072584
+100264502
+100207920
+100213894
+100321805
+100238596
+100321580
+100194930
+100194932
+100195140
+100194936
+100209898
+100215412
+100307524
+100194942
+100292934
+100194948
+100194950
+100317100
+100211866
+100194960
+100194962
+100334821
+100194966
+100322177
+100110806
+100211130
+100194990
+100194992
+100195032
+100215410
+100314868
+100203920
+100195004
+100195006
+100241380
+100195016
+100262496
+100321248
+100231680
+100281448
+100215392
+100215390
+100195034
+100208918
+100354963
+100195040
+100205714
+100213966
+100195048
+100215382
+100306888
+100256098
+100195060
+100248986
+100215330
+100195096
+100215326
+100195088
+100215324
+100294760
+100326765
+100328980
+100073824
+100209966
+100195120
+100321242
+100195128
+100195130
+100195132
+100311840
+100321240
+100215320
+100195146
+100213538
+100195150
+100195154
+100195170
+100195172
+100195174
+100247718
+100195230
+100313534
+100195190
+100209306
+100195194
+100243978
+100215316
+100215292
+100195202
+100195204
+100281008
+100244354
+100195212
+100301092
+100195216
+100215288
+100195220
+100286172
+100210660
+100294854
+100195400
+100215284
+100195250
+100195252
+100195256
+100301820
+100259702
+100258146
+100195276
+100195278
+100195280
+100272868
+100212558
+100259388
+100195288
+100326202
+100195292
+100209476
+100296674
+100281808
+100253090
+100195308
+100198430
+100319585
+100110762
+100195336
+100255236
+100326424
+100195354
+100215264
+100195360
+100282220
+100195368
+100195370
+100195372
+100268038
+100195376
+100266064
+100244208
+100195384
+100273820
+100195386
+100303012
+100325222
+100195394
+100195396
+100328762
+100195404
+100074990
+100248028
+100195410
+100195412
+100195414
+100282382
+100210470
+100287180
+100195428
+100215246
+100195426
+100215242
+100195432
+100254524
+100195444
+100195446
+100195448
+100195450
+100195452
+100215192
+100195458
+100312742
+100195474
+100195464
+100195466
+100195468
+100200276
+100248534
+100247746
+100301840
+100195478
+100195482
+100215186
+100195486
+100195488
+100211848
+100296666
+100267854
+100195548
+100195550
+100195552
+100290198
+100195556
+100208942
+100195562
+100227328
+100195584
+100215152
+100195588
+100274772
+100290178
+100272804
+100331913
+100195612
+100209280
+100197178
+100195624
+100320965
+100324097
+100203160
+100240152
+100301938
+100195632
+100190432
+100215148
+100279856
+100222650
+100195656
+100195658
+100195660
+100195662
+100195664
+100195668
+100238352
+100195674
+100250730
+100342797
+100195680
+100195682
+100198552
+100195694
+100215124
+100195696
+100285032
+100321991
+100215104
+100301192
+100290176
+100296648
+100227478
+100260236
+100260364
+100311842
+100215088
+100249364
+100282910
+100195752
+100359601
+100321199
+100195758
+100195760
+100215086
+100195764
+100195766
+100195768
+100195770
+100321173
+100247916
+100240266
+100195794
+100195798
+100195800
+100275486
+100195804
+100195806
+100348825
+100195810
+100271244
+100209034
+100249486
+100323097
+100199692
+100195826
+100215132
+100342015
+100195836
+100195840
+100212422
+100195846
+100195848
+100306610
+100195854
+100290174
+100285820
+100321246
+100195876
+100271890
+100296294
+100200596
+100195886
+100302804
+100303138
+100195896
+100195902
+100195904
+100195906
+100195908
+100300454
+100269996
+100230080
+100215038
+100212724
+100195924
+100168828
+100215034
+100195938
+100195932
+100217704
+100278774
+100197710
+100321157
+100195972
+100215016
+100195976
+100321155
+100276766
+100195990
+100203812
+100313018
+100195996
+100271278
+100197076
+100196000
+100196002
+100314710
+100113588
+100196008
+100281424
+100215010
+100196044
+100300598
+100271884
+100210316
+100332618
+100215006
+100196072
+100209988
+100196068
+100267106
+100324183
+100196078
+100196080
+100304324
+100196090
+100196092
+100326420
+100250286
+100196112
+100196116
+100196118
+100196120
+100317878
+100196128
+100197060
+100196132
+100196134
+100290158
+100196144
+100302536
+100211184
+100322301
+100242738
+100196168
+100290150
+100296266
+100278856
+100211912
+100281006
+100196178
+100196180
+100227406
+100276338
+100321039
+100319929
+100268390
+100316288
+100196210
+100196212
+100196214
+100196216
+100259066
+100196222
+100196224
+100305438
+100196230
+100196232
+100196234
+100196236
+100196238
+100271888
+100049512
+100196246
+100196250
+100303198
+100053176
+100196260
+100279722
+100196264
+100214990
+100196268
+100196270
+100327733
+100314516
+100196284
+100320989
+100284586
+100270022
+100196292
+100196294
+100279728
+100214972
+100214968
+100196302
+100196304
+100256114
+100290830
+100196312
+100196316
+100196318
+100196320
+100283326
+100196328
+100196330
+100196332
+100196348
+100196350
+100196356
+100196362
+100196366
+100209634
+100196370
+100214966
+100196374
+100246726
+100196378
+100196382
+100196384
+100257352
+100196392
+100233970
+100196396
+100196398
+100196402
+100210068
+100196408
+100196410
+100196412
+100320161
+100196416
+100290138
+100196420
+100196422
+100196424
+100201298
+100210974
+100240104
+100211708
+100196434
+100327641
+100196440
+100196442
+100196444
+100249604
+100196450
+100196452
+100196454
+100353011
+100214962
+100241660
+100214958
+100196460
+100196464
+100196466
+100196470
+100212594
+100196474
+100273730
+100196480
+100241132
+100214948
+100320416
+100196488
+100271182
+100196498
+100196502
+100196504
+100196522
+100214940
+100196530
+100228108
+100196538
+100196540
+100196542
+100296644
+100196544
+100196546
+100259666
+100196552
+100196554
+100260658
+100196558
+100257978
+100320412
+100285526
+100196566
+100204356
+100214932
+100323783
+100259558
+100278626
+100214954
+100214900
+100273960
+100198654
+100196632
+100196648
+100196650
+100196652
+100290134
+100361722
+100214898
+100214896
+100047170
+100271032
+100241774
+100290132
+100196694
+100196698
+100214888
+100196704
+100214886
+100196708
+100362164
+100277062
+100277960
+100338054
+100256396
+100196730
+100282596
+100280768
+100196736
+100269288
+100271216
+100196742
+100313740
+100196746
+100196748
+100300752
+100214884
+100246922
+100196760
+100196762
+100293506
+100221642
+100197684
+100324579
+100303672
+100196792
+100320592
+100196798
+100312198
+100196804
+100250288
+100196810
+100354612
+100196814
+100265100
+100293192
+100292706
+100196824
+100196826
+100214878
+100274070
+100286964
+100257346
+100258216
+100249492
+100196872
+100196876
+100210968
+100196880
+100196882
+100276230
+100196886
+100214876
+100265578
+100196930
+100214874
+100300110
+100326412
+100196950
+100196952
+100196954
+100196956
+100238742
+100196974
+100322053
+100196978
+100196980
+100197016
+100261250
+100283998
+100197026
+100197028
+100214872
+100325533
+100197034
+100197036
+100197040
+100197046
+100197050
+100235798
+100197054
+100197056
+100255600
+100197062
+100326847
+100286382
+100271186
+100197072
+100214870
+100214866
+100262462
+100270294
+100312910
+100197098
+100197114
+100214854
+100197118
+100197120
+100197122
+100311684
+100197152
+100266856
+100295824
+100352100
+100240200
+100224358
+100197170
+100332814
+100197176
+100249584
+100197204
+100301858
+100197186
+100197188
+100260544
+100197194
+100197198
+100197248
+100197212
+100279858
+100197288
+100214172
+100197234
+100241734
+100209274
+100295686
+100197240
+100197250
+100253952
+100197254
+100197284
+100359760
+100197264
+100163840
+100324699
+100139298
+100214852
+100214836
+100197278
+100197280
+100197282
+100250338
+100197294
+100197296
+100197300
+100197302
+100212276
+100197320
+100324254
+100197328
+100197330
+100197332
+100209230
+100197336
+100249744
+100214800
+100335566
+100197344
+100207740
+100214794
+100208792
+100197354
+100197356
+100313756
+100197362
+100210270
+100360494
+100197372
+100197374
+100316807
+100197378
+100214758
+100197380
+100197396
+100210024
+100214754
+100211140
+100197412
+100209146
+100197416
+100197418
+100266364
+100197422
+100249718
+100197430
+100197432
+100199348
+100197434
+100197436
+100197438
+100197458
+100197836
+100197450
+100197454
+100197468
+100214702
+100197460
+100197464
+100323453
+100328756
+100310698
+100214700
+100293444
+100262438
+100197484
+100231632
+100197504
+100323243
+100197508
+100197510
+100197514
+100268942
+100197608
+100214696
+100197522
+100197524
+100292544
+100025852
+100273702
+100197534
+100241652
+100230340
+100359243
+100197544
+100197546
+100197548
+100197550
+100197552
+100256954
+100264152
+100298764
+100214686
+100270002
+100198414
+100197572
+100137576
+100214668
+100269140
+100197598
+100340474
+100197600
+100324492
+100197678
+100197606
+100197624
+100214666
+100320025
+100197626
+100197628
+100197630
+100197632
+100207954
+100300750
+100197642
+100197640
+100197648
+100197650
+100279656
+100197712
+100214662
+100295586
+100201580
+100213086
+100197694
+100349819
+100321715
+100244634
+100198764
+100197698
+100214648
+100292298
+100213968
+100240242
+100198894
+100253836
+100260978
+100197780
+100293382
+100283334
+100239292
+100019010
+100214644
+100197784
+100197788
+100200930
+100197792
+100197794
+100197912
+100214642
+100211616
+100197804
+100217648
+100197808
+100197810
+100197812
+100245394
+100197818
+100197820
+100214640
+100277254
+100214636
+100197842
+100197844
+100202178
+100288186
+100197864
+100197866
+100197868
+100324825
+100267020
+100198234
+100197882
+100214634
+100214632
+100199212
+100197906
+100197938
+100300728
+100259390
+100211180
+100197948
+100306854
+100197954
+100208710
+100319979
+100301576
+100250972
+100312492
+100198010
+100214622
+100198016
+100221180
+100214614
+100198036
+100046758
+100198034
+100198038
+100322599
+100198048
+100214612
+100214582
+100198064
+100198104
+100198066
+100198078
+100198080
+100296486
+100198092
+100198110
+100214580
+100319961
+100198098
+100214576
+100254462
+100323355
+100198108
+100198112
+100198114
+100198116
+100242596
+100198118
+100198124
+100269260
+100245068
+100198130
+100198132
+100332704
+100290106
+100198142
+100267520
+100198164
+100200682
+100198166
+100210246
+100274006
+100015556
+100198176
+100208806
+100065404
+100286874
+100208804
+100214572
+100296484
+100198190
+100214564
+100198194
+100198198
+100210452
+100214562
+100198238
+100087744
+100045198
+100198250
+100286854
+100198254
+100050034
+100198256
+100198276
+100198278
+100198280
+100296270
+100253524
+100306850
+100198308
+100198314
+100198316
+100214558
+100198320
+100279450
+100198324
+100201316
+100198350
+100290102
+100198482
+100319947
+100323673
+100231780
+100198492
+100301188
+100214538
+100198498
+100198502
+100279296
+100198506
+100292010
+100198510
+100198524
+100198518
+100214532
+100198522
+100211234
+100319945
+100332730
+100327719
+100198870
+100198534
+100301984
+100198538
+100299948
+100296462
+100085662
+100198548
+100301596
+100037202
+100268968
+100214524
+100214458
+100199132
+100284278
+100198564
+100198574
+100198600
+100198592
+100198596
+100302966
+100282066
+100198606
+100198608
+100275760
+100198614
+100198622
+100198626
+100323077
+100214380
+100198634
+100296442
+100048560
+100198644
+100282098
+100210946
+100353400
+100287356
+100198676
+100215040
+100198732
+100198696
+100198698
+100198798
+100198706
+100114342
+100290090
+100214378
+100210802
+100214424
+100198730
+100198740
+100283164
+100308710
+100198752
+100254226
+100198756
+100198758
+100198760
+100198800
+100198766
+100198774
+100198776
+100198778
+100328608
+100294736
+100198784
+100198786
+100293946
+100210430
+100198830
+100198810
+100279060
+100272852
+100198824
+100198826
+100282486
+100198834
+100198836
+100198838
+100324388
+100269256
+100096390
+100198860
+100214374
+100214370
+100253106
+100198878
+100209932
+100198882
+100234220
+100319911
+100241708
+100235794
+100236500
+100241062
+100258448
+100207934
+100198948
+100227392
+100237776
+100214402
+100252896
+100280440
+100214368
+100310868
+100198990
+100332709
+100198998
+100199000
+100199002
+100199006
+100281396
+100199026
+100319909
+100199030
+100209222
+100209344
+100214366
+100199064
+100199066
+100199068
+100199088
+100199104
+100214336
+100199128
+100199124
+100199136
+100199130
+100199286
+100210898
+100247640
+100267398
+100240608
+100260254
+100199500
+100271238
+100200162
+100268754
+100280988
+100199220
+100214318
+100199276
+100275672
+100199280
+100199284
+100319901
+100209250
+100258844
+100327827
+100199766
+100309388
+100319392
+100286034
+100210142
+100284940
+100199310
+100199312
+100203026
+100199316
+100199318
+100241770
+100075592
+100094894
+100304150
+100199334
+100305894
+100214286
+100199342
+100232598
+100224750
+100061582
+100199354
+100199360
+100209566
+100323203
+100199386
+100306650
+100269856
+100199392
+100323187
+100199398
+100211852
+100199402
+100199774
+100199406
+100199410
+100282042
+100262094
+100199416
+100271936
+100199422
+100242868
+100199426
+100199428
+100248128
+100091696
+100211952
+100199484
+100296398
+100199488
+100199496
+100333211
+100199502
+100199504
+100259534
+100294448
+100199512
+100199518
+100325122
+100242836
+100246818
+100263010
+100210952
+100326368
+100317118
+100199532
+100199534
+100317589
+100283300
+100199542
+100199544
+100320021
+100199562
+100260598
+100199584
+100199588
+100237528
+100199592
+100320510
+100275738
+100199596
+100290848
+100199600
+100234180
+100317104
+100225154
+100214198
+100199630
+100266858
+100199634
+100289374
+100245566
+100199676
+100199678
+100199680
+100199682
+100199690
+100356437
+100323667
+100059970
+100199716
+100199698
+100199704
+100323261
+100210126
+100199710
+100199714
+100279924
+100199740
+100280462
+100239260
+100199724
+100199748
+100231784
+100199742
+100199754
+100240684
+100272448
+100262310
+100272724
+100326362
+100199770
+100199780
+100199776
+100199790
+100199782
+100105494
+100237830
+100289346
+100199794
+100199796
+100199802
+100199820
+100199798
+100199800
+100248528
+100200020
+100227624
+100300074
+100245658
+100291988
+100199854
+100254344
+100210590
+100199860
+100319835
+100280946
+100236462
+100314802
+100292248
+100360945
+100199882
+100199918
+100199884
+100199886
+100200058
+100283960
+100242004
+100354094
+100199908
+100313216
+100199936
+100221378
+100199938
+100260568
+100087684
+100326330
+100200646
+100289080
+100214166
+100199952
+100199962
+100199958
+100199960
+100209564
+100203752
+100309836
+100199976
+100319823
+100209578
+100061402
+100248012
+100200000
+100214154
+100200002
+100200006
+100275236
+100200016
+100257320
+100277650
+100319821
+100262372
+100200026
+100271830
+100211572
+100230956
+100321699
+100200036
+100238592
+100217686
+100099030
+100200056
+100273070
+100242294
+100270390
+100272764
+100249408
+100104598
+100200076
+100284666
+100258520
+100282934
+100244414
+100200098
+100200106
+100200112
+100297630
+100312006
+100326552
+100200128
+100257358
+100294442
+100214040
+100218928
+100200218
+100202346
+100200142
+100320959
+100321687
+100200150
+100248470
+100200154
+100200156
+100200166
+100269204
+100200168
+100200172
+100200174
+100213958
+100200176
+100160910
+100141360
+100213952
+100240480
+100304878
+100234862
+100200206
+100253500
+100305644
+100200220
+100200222
+100238570
+100306586
+100253904
+100200258
+100268194
+100287140
+100200266
+100200268
+100200274
+100211716
+100290078
+100200280
+100326430
+100213924
+100200336
+100200344
+100200346
+100200352
+100200356
+100200368
+100200372
+100257072
+100211464
+100200382
+100209478
+100200398
+100200400
+100200402
+100200406
+100200408
+100268974
+100200410
+100200414
+100200416
+100200424
+100200586
+100200434
+100200436
+100237728
+100213928
+100048406
+100200444
+100200446
+100200448
+100200452
+100324773
+100305612
+100200474
+100322910
+100200498
+100200504
+100317211
+100200506
+100316634
+100302408
+100200518
+100200820
+100200514
+100213808
+100200522
+100200520
+100207002
+100200542
+100200540
+100200546
+100200548
+100317670
+100209254
+100292938
+100200560
+100200556
+100200558
+100200562
+100234298
+100200590
+100200568
+100213728
+100200578
+100303362
+100200594
+100248186
+100211054
+100293664
+100279772
+100200610
+100200612
+100068688
+100232298
+100200620
+100302950
+100254312
+100261036
+100089890
+100200636
+100328750
+100200640
+100200642
+100248298
+100213628
+100200654
+100200656
+100227370
+100314068
+100231786
+100213896
+100269960
+100200676
+100248752
+100200718
+100224438
+100200692
+100277460
+100200690
+100175010
+100213762
+100200696
+100200700
+100213868
+100200704
+100317716
+100200708
+100200710
+100328898
+100319813
+100266996
+100200722
+100213502
+100271740
+100094968
+100319793
+100200766
+100202620
+100200770
+100323173
+100200788
+100200804
+100264002
+100298368
+100260396
+100200816
+100200818
+100200830
+100200832
+100200834
+100280938
+100246886
+100249302
+100200842
+100238434
+100290068
+100210002
+100321693
+100200860
+100330192
+100241768
+100317243
+100200868
+100200870
+100200874
+100200878
+100246238
+100200884
+100266930
+100262366
+100301566
+100200900
+100013630
+100200912
+100200914
+100240218
+100250590
+100287680
+100069588
+100200936
+100200938
+100213500
+100306842
+100200950
+100363299
+100213496
+100303888
+100322055
+100323573
+100209690
+100284100
+100201002
+100290060
+100201010
+100316580
+100201014
+100201018
+100306578
+100358849
+100201028
+100332237
+100290864
+100201040
+100201042
+100213856
+100324537
+100201052
+100201070
+100268826
+100296354
+100201076
+100274770
+100290056
+100199278
+100275918
+100201124
+100201154
+100210716
+100201184
+100241518
+100202156
+100201188
+100220564
+100246642
+100201194
+100213576
+100201198
+100234226
+100201204
+100201230
+100201970
+100208696
+100201210
+100294276
+100201214
+100213466
+100247894
+100201242
+100201248
+100201254
+100201256
+100325597
+100201266
+100201268
+100201320
+100188818
+100201272
+100201274
+100338754
+100302104
+100201292
+100201296
+100201318
+100320181
+100201340
+100209480
+100046720
+100271464
+100252978
+100328714
+100235772
+100213580
+100201368
+100201366
+100320215
+100201370
+100253822
+100155398
+100201380
+100302588
+100296844
+100201978
+100328712
+100319597
+100262364
+100311374
+100201990
+100201994
+100201996
+100202000
+100321488
+100202012
+100202014
+100315366
+100202022
+100304972
+100317726
+100202040
+100213454
+100047492
+100202124
+100280866
+100202068
+100202084
+100202086
+100202088
+100202982
+100202090
+100324725
+100202098
+100217600
+100202102
+100290052
+100202108
+100202118
+100213608
+100337979
+100202122
+100202126
+100202128
+100202144
+100202146
+100202150
+100212036
+100324454
+100213452
+100206132
+100202472
+100202176
+100251308
+100357437
+100246242
+100042788
+100202206
+100202214
+100324787
+100202216
+100210428
+100202264
+100202224
+100202226
+100202228
+100202230
+100202234
+100282356
+100290964
+100321917
+100213450
+100290048
+100325961
+100335641
+100202270
+100329162
+100338114
+100213446
+100280856
+100296336
+100202284
+100242654
+100325591
+100333050
+100202948
+100355471
+100257932
+100319763
+100321280
+100065462
+100212230
+100203342
+100213546
+100312578
+100210372
+100208932
+100259622
+100202348
+100202352
+100202354
+100203088
+100338743
+100320147
+100202384
+100202386
+100212108
+100205722
+100234172
+100257712
+100325525
+100202426
+100202430
+100202484
+100352610
+100286672
+100271638
+100202440
+100204244
+100202448
+100202454
+100202456
+100202458
+100341770
+100202460
+100250986
+100268668
+100202612
+100202480
+100202502
+100323563
+100202548
+100202510
+100210210
+100239372
+100202532
+100202534
+100315424
+100255522
+100266666
+100202544
+100202546
+100202562
+100202560
+100270380
+100203780
+100202624
+100202698
+100211932
+100202626
+100202628
+100255224
+100241068
+100202648
+100292002
+100326304
+100202654
+100296334
+100202660
+100315282
+100228694
+100202666
+100203900
+100240274
+100202978
+100202678
+100202680
+100282672
+100202692
+100282888
+100202700
+100202706
+100276078
+100202732
+100202734
+100324223
+100241916
+100199526
+100202742
+100304182
+100202776
+100202778
+100202780
+100202782
+100292142
+100202786
+100202788
+100296332
+100202792
+100213396
+100340887
+100296326
+100283908
+100286014
+100202854
+100202862
+100245370
+100242760
+100337741
+100262572
+100202882
+100202884
+100253446
+100325714
+100202890
+100285338
+100202894
+100202898
+100202922
+100303164
+100250118
+100202926
+100202928
+100202930
+100257068
+100202934
+100202936
+100190808
+100207568
+100202946
+100202952
+100202954
+100281468
+100203246
+100258642
+100319332
+100271402
+100274266
+100294852
+100323399
+100235748
+100202984
+100325581
+100203784
+100202994
+100203010
+100327643
+100165424
+100203032
+100203034
+100322987
+100293916
+100203040
+100255232
+100203060
+100203064
+100203068
+100203070
+100281986
+100210752
+100203078
+100203080
+100210996
+100205610
+100203116
+100213366
+100300822
+100203126
+100203144
+100203976
+100212602
+100213202
+100268938
+100203174
+100203198
+100258214
+100203220
+100203278
+100203222
+100203242
+100203250
+100300664
+100203282
+100203260
+100338779
+100311214
+100322569
+100213360
+100260612
+100210314
+100203296
+100203298
+100203300
+100280716
+100203610
+100293412
+100293646
+100301910
+100241746
+100203380
+100203382
+100306632
+100206660
+100211720
+100203560
+100252228
+100322810
+100296174
+100279934
+100254090
+100213340
+100249874
+100212862
+100237868
+100293536
+100259708
+100245190
+100290006
+100203614
+100203618
+100319246
+100203970
+100243328
+100319238
+100203652
+100256372
+100271400
+100251258
+100210724
+100296166
+100272954
+100204088
+100285736
+100248166
+100203680
+100203688
+100320396
+100303160
+100210852
+100213328
+100203706
+100203710
+100251448
+100258604
+100203716
+100301904
+100205742
+100220686
+100213322
+100259700
+100203726
+100058030
+100296522
+100213320
+100281198
+100231220
+100203782
+100227432
+100204162
+100227410
+100260006
+100203786
+100248726
+100273168
+100203796
+100204182
+100249306
+100299546
+100203802
+100282824
+100271718
+100325623
+100326222
+100256590
+100250618
+100209508
+100203848
+100203866
+100240234
+100282822
+100203874
+100254566
+100243968
+100203880
+100203882
+100203884
+100204008
+100234182
+100203926
+100203930
+100203938
+100194488
+100203944
+100323571
+100241082
+100255386
+100319017
+100203958
+100203960
+100203966
+100262890
+100203972
+100214284
+100203974
+100203978
+100001352
+100277018
+100208126
+100203982
+100278162
+100240192
+100283972
+100249836
+100207110
+100204216
+100203996
+100203998
+100204000
+100283688
+100204006
+100207912
+100311434
+100204122
+100326204
+100213168
+100213166
+100204048
+100325400
+100204210
+100204066
+100332687
+100268234
+100204072
+100256802
+100249736
+100306536
+100204120
+100205030
+100204128
+100214046
+100257396
+100204146
+100255378
+100204150
+100272394
+100318982
+100204160
+100204208
+100204184
+100204418
+100296078
+100204188
+100204194
+100204198
+100262254
+100333007
+100092674
+100269690
+100204246
+100213158
+100282804
+100218866
+100281972
+100204228
+100204468
+100204254
+100284238
+100204260
+100282786
+100312110
+100213142
+100213140
+100284476
+100335636
+100204290
+100204292
+100283592
+100276400
+100204300
+100204306
+100322405
+100255874
+100313282
+100208718
+100083992
+100209806
+100296076
+100324376
+100204322
+100204324
+100302048
+100211320
+100211546
+100056000
+100277904
+100319532
+100253118
+100268606
+100204366
+100229402
+100204370
+100305588
+100204372
+100248246
+100318932
+100204382
+100204384
+100325563
+100204388
+100217500
+100213788
+100204390
+100231684
+100328351
+100266262
+100296018
+100204422
+100213096
+100204424
+100246284
+100264542
+100204438
+100255858
+100318976
+100204448
+100204996
+100275282
+100296016
+100204458
+100204460
+100213764
+100204620
+100204476
+100204508
+100204478
+100213094
+100210586
+100314692
+100204486
+100204490
+100204492
+100326176
+100311036
+100211318
+100236812
+100286122
+100246232
+100204510
+100210654
+100310680
+100204540
+100288566
+100204546
+100204548
+100287482
+100213090
+100204554
+100204572
+100204574
+100204576
+100342833
+100259368
+100268068
+100309038
+100355543
+100266636
+100204624
+100268024
+100164108
+100248256
+100250350
+100204632
+100272490
+100204636
+100204822
+100265144
+100204828
+100235718
+100211386
+100259176
+100213084
+100276188
+100211128
+100204842
+100204860
+100281274
+100204872
+100323073
+100338018
+100204948
+100212378
+100046090
+100204916
+100305870
+100204926
+100204960
+100204930
+100204932
+100260678
+100214012
+100325118
+100204940
+100204944
+100278332
+100213060
+100293640
+100294668
+100205452
+100204986
+100204990
+100296164
+100205000
+100204998
+100281686
+100205004
+100205010
+100289098
+100245728
+100205032
+100292206
+100318922
+100257238
+100266472
+100338763
+100333942
+100295968
+100205088
+100205076
+100213550
+100236960
+100205104
+100210986
+100118140
+100205208
+100205126
+100205128
+100205130
+100248744
+100325585
+100209552
+100205144
+100205146
+100205152
+100209656
+100205154
+100209920
+100205160
+100304100
+100205164
+100213026
+100205182
+100205184
+100205474
+100205198
+100205194
+100119296
+100205202
+100234082
+100240342
+100320472
+100213012
+100205242
+100211132
+100270280
+100205256
+100285260
+100249712
+100205280
+100285608
+100205482
+100205306
+100213300
+100205314
+100205316
+100205318
+100211406
+100306606
+100271844
+100205330
+100243242
+100207728
+100205334
+100205344
+100241512
+100205348
+100205350
+100283290
+100284568
+100205358
+100213004
+100289588
+100294604
+100330182
+100271188
+100205378
+100295950
+100205450
+100205382
+100205386
+100205388
+100209384
+100205404
+100244904
+100205410
+100246026
+100235714
+100306528
+100205418
+100205420
+100263802
+100205422
+100205424
+100205426
+100212994
+100269188
+100002898
+100271184
+100205462
+100240478
+100249902
+100239446
+100205478
+100206190
+100205480
+100329326
+100205486
+100211114
+100205488
+100205490
+100210780
+100302416
+100205496
+100249748
+100211020
+100300390
+100266050
+100205512
+100055636
+100214148
+100205520
+100154544
+100205526
+100234098
+100205532
+100205556
+100270596
+100205566
+100205568
+100211468
+100259512
+100205580
+100208508
+100278178
+100205584
+100248178
+100212616
+100319276
+100205604
+100292756
+100206394
+100205634
+100180070
+100358065
+100299658
+100151966
+100205678
+100212888
+100205960
+100303156
+100294512
+100352975
+100244100
+100205978
+100273464
+100045874
+100254726
+100269966
+100206016
+100206018
+100231494
+100325448
+100206022
+100248252
+100318916
+100303386
+100206038
+100207292
+100267618
+100212040
+100303526
+100206046
+100318282
+100214076
+100248452
+100211762
+100306514
+100206062
+100011834
+100113644
+100206070
+100211690
+100206074
+100300812
+100206078
+100206080
+100263468
+100256132
+100207306
+100243284
+100335577
+100206092
+100206094
+100291998
+100350310
+100211482
+100291708
+100295944
+100313162
+100302960
+100313814
+100305572
+100206120
+100209698
+100206124
+100206126
+100289978
+100206130
+100293984
+100239398
+100207756
+100253000
+100206148
+100314694
+100282112
+100242588
+100206184
+100206186
+100212814
+100298610
+100206192
+100236810
+100206198
+100206216
+100206218
+100313340
+100206222
+100206224
+100289962
+100207438
+100206238
+100209338
+100206236
+100240112
+100271012
+100239104
+100206246
+100206248
+100206252
+100232480
+100245268
+100318914
+100206278
+100206282
+100206286
+100212794
+100206292
+100318886
+100238546
+100311734
+100347129
+100206308
+100206310
+100295884
+100206314
+100244498
+100206318
+100242298
+100206362
+100213592
+100206366
+100206370
+100206372
+100206494
+100206376
+100206384
+100206382
+100244974
+100216384
+100212788
+100206412
+100206414
+100212790
+100206418
+100275720
+100211198
+100206432
+100284416
+100301468
+100206438
+100206440
+100206670
+100295872
+100206674
+100262202
+100206710
+100206712
+100063116
+100277500
+100206724
+100206820
+100212550
+100206746
+100117966
+100206750
+100320550
+100206758
+100309814
+100325595
+100206806
+100214568
+100255060
+100212748
+100326182
+100217524
+100318822
+100206822
+100100032
+100231614
+100206832
+100326278
+100206834
+100265922
+100208230
+100206844
+100212684
+100206850
+100243386
+100213646
+100320652
+100206860
+100206862
+100206870
+100206888
+100206902
+100206894
+100207370
+100206898
+100206900
+100206904
+100231702
+100319182
+100240974
+100207300
+100262184
+100248002
+100269080
+100245092
+100206936
+100257892
+100206940
+100236706
+100337722
+100206946
+100118494
+100338136
+100207030
+100206950
+100206982
+100206984
+100278712
+100206988
+100206990
+100206992
+100257646
+100280388
+100299302
+100294798
+100207032
+100207404
+100207038
+100277910
+100289900
+100211654
+100306502
+100358991
+100207062
+100362337
+100318854
+100244672
+100207084
+100004968
+100207088
+100212342
+100303204
+100305028
+100207116
+100207118
+100207122
+100207124
+100256692
+100268596
+100254026
+100308500
+100289944
+100175090
+100299908
+100280928
+100207270
+100207296
+100207526
+100207302
+100207304
+100275456
+100241716
+100252956
+100207312
+100114700
+100238562
+100306484
+100207316
+100211202
+100117160
+100207574
+100207572
+100207576
+100207578
+100227238
+100207596
+100255758
+100260524
+100281426
+100283246
+100207632
+100210086
+100247362
+100207638
+100207640
+100282254
+100295942
+100207646
+100207650
+100207716
+100207678
+100250708
+100241482
+100256334
+100207720
+100207726
+100207736
+100289936
+100208128
+100289946
+100260296
+100212312
+100332773
+100207764
+100272848
+100209182
+100268704
+100212358
+100244978
+100270762
+100208820
+100270270
+100217496
+100207784
+100207786
+100248010
+100299360
+100246718
+100174198
+100312772
+100207846
+100207848
+100122492
+100207852
+100209072
+100207860
+100237832
+100207864
+100207866
+100207868
+100272420
+100209482
+100266906
+100262180
+100318790
+100207956
+100287512
+100214262
+100244458
+100207910
+100269850
+100249822
+100208290
+100252292
+100258296
+100335069
+100356897
+100207942
+100208016
+100242972
+100207958
+100207964
+100207966
+100084182
+100295816
+100291994
+100287056
+100212354
+100208010
+100212288
+100332395
+100303568
+100261508
+100235702
+100325432
+100208030
+100208032
+100321101
+100112352
+100252712
+100208044
+100208056
+100269010
+100267052
+100269250
+100319995
+100208086
+100208146
+100320311
+100209038
+100208100
+100333002
+100212212
+100284488
+100356146
+100212202
+100319094
+100208162
+100208164
+100210612
+100240534
+100231674
+100208170
+100208172
+100208174
+100208176
+100208178
+100208180
+100208184
+100273842
+100295742
+100222666
+100208494
+100217490
+100320448
+100208210
+100208216
+100208222
+100208224
+100208228
+100259360
+100209026
+100209734
+100212168
+100097632
+100208264
+100258042
+100096900
+100269358
+100210444
+100095200
+100245398
+100269036
+100254872
+100208306
+100208308
+100209956
+100239444
+100217482
+100351994
+100303974
+100208332
+100318734
+100208350
+100289914
+100320390
+100320243
+100247178
+100279690
+100295740
+100263170
+100318728
+100221840
+100221842
+100318500
+100029792
+100221878
+100242594
+100221884
+100221886
+100221902
+100250710
+100221904
+100221906
+100221910
+100221912
+100263356
+100266714
+100221934
+100221928
+100300650
+100305006
+100221966
+100221990
+100250664
+100223106
+100221996
+100222026
+100222028
+100318722
+100222048
+100284968
+100268510
+100222096
+100244004
+100222100
+100222130
+100222132
+100222138
+100222140
+100247624
+100222172
+100222174
+100222190
+100257644
+100222196
+100361326
+100222218
+100222234
+100247034
+100222268
+100242528
+100271130
+100222272
+100222288
+100222294
+100260846
+100253016
+100222300
+100242556
+100222342
+100222344
+100222346
+100222348
+100262168
+100262916
+100293702
+100222356
+100252202
+100222362
+100338920
+100222368
+100222404
+100222410
+100222412
+100222428
+100222432
+100222450
+100222462
+100222464
+100222466
+100222468
+100222470
+100222472
+100222474
+100222476
+100222512
+100222514
+100222522
+100222570
+100268746
+100222574
+100222576
+100247336
+100222580
+100222596
+100320614
+100222786
+100222724
+100255372
+100222736
+100222738
+100336001
+100222756
+100240648
+100222762
+100222764
+100222766
+100222768
+100224688
+100222792
+100244248
+100222826
+100222830
+100324410
+100222854
+100222856
+100222858
+100258840
+100222934
+100222902
+100249384
+100222906
+100289118
+100222910
+100222912
+100222942
+100222944
+100222954
+100222948
+100222950
+100222970
+100319647
+100222990
+100222994
+100222996
+100270322
+100223034
+100223036
+100223204
+100274258
+100223040
+100223076
+100223108
+100267526
+100223128
+100303268
+100223264
+100223132
+100354729
+100276110
+100223174
+100223194
+100223198
+100311084
+100223202
+100223206
+100248048
+100223210
+100223214
+100258338
+100223250
+100223246
+100223260
+100289908
+100223316
+100230630
+100322886
+100223352
+100223380
+100240628
+100223356
+100223374
+100154920
+100223378
+100279384
+100299904
+100223418
+100223450
+100223520
+100329154
+100136228
+100243696
+100231316
+100223528
+100223544
+100236190
+100223548
+100246122
+100223558
+100223560
+100295736
+100223624
+100223580
+100223582
+100240626
+100284944
+100223604
+100306688
+100223720
+100241192
+100223660
+100312756
+100223694
+100223696
+100223700
+100223704
+100332779
+100318676
+100223778
+100223780
+100223810
+100223812
+100223844
+100223846
+100223862
+100223868
+100251898
+100255204
+100223898
+100223916
+100223918
+100243668
+100223922
+100273780
+100244972
+100243958
+100223990
+100223994
+100223996
+100223998
+100224010
+100224012
+100224016
+100345823
+100295688
+100224022
+100224024
+100224054
+100224060
+100253010
+100224110
+100224126
+100248834
+100352885
+100224164
+100224166
+100246662
+100224182
+100224186
+100224188
+100259958
+100240722
+100361752
+100224242
+100224244
+100300346
+100224262
+100224264
+100224270
+100224734
+100224288
+100285692
+100235292
+100224308
+100224324
+100224326
+100296218
+100277370
+100286532
+100224434
+100224440
+100224442
+100224472
+100224474
+100240326
+100240664
+100224522
+100224526
+100224530
+100224532
+100265464
+100319787
+100282734
+100224586
+100224602
+100224620
+100240294
+100224640
+100224642
+100318954
+100224650
+100224654
+100251994
+100228110
+100228408
+100224736
+100224738
+100224746
+100236122
+100322876
+100242642
+100325294
+100250778
+100240666
+100107842
+100224778
+100289904
+100224812
+100224818
+100224820
+100224822
+100224866
+100324250
+100224884
+100224906
+100240650
+100224926
+100304812
+100224946
+100255154
+100224966
+100224982
+100338189
+100224990
+100224994
+100223454
+100286860
+100251584
+100225030
+100225050
+100225070
+100258852
+100225074
+100225076
+100225080
+100241128
+100225088
+100225148
+100225150
+100225172
+100226326
+100225220
+100225222
+100250050
+100225244
+100225246
+100225250
+100355913
+100269074
+100225306
+100225322
+100225324
+100052604
+100319200
+100225358
+100225388
+100294880
+100225440
+100225444
+100225446
+100225464
+100225448
+100225494
+100305190
+100225530
+100225552
+100225582
+100225588
+100228216
+100225590
+100225594
+100225592
+100321111
+100225630
+100318672
+100225634
+100225654
+100225658
+100225662
+100225664
+100267562
+100225684
+100225686
+100225748
+100225750
+100225752
+100028992
+100225784
+100241896
+100225814
+100271986
+100225834
+100295648
+100348583
+100310838
+100301970
+100225900
+100225916
+100225918
+100324324
+100267764
+100225954
+100225958
+100225960
+100225962
+100225968
+100226000
+100226016
+100325018
+100227468
+100226118
+100226100
+100226120
+100269614
+100012620
+100317882
+100226130
+100226146
+100226148
+100240654
+100304904
+100226200
+100278946
+100323253
+100240536
+100226238
+100264916
+100226262
+100226266
+100226316
+100248378
+100226324
+100226328
+100278782
+100226346
+100311264
+100282774
+100226424
+100226374
+100226376
+100226384
+100246780
+100226388
+100226390
+100046740
+100226472
+100282728
+100240498
+100321663
+100294298
+100226540
+100226560
+100295646
+100228128
+100323163
+100226570
+100235808
+100226574
+100248472
+100226594
+100226596
+100226598
+100226648
+100226664
+100226700
+100226666
+100258570
+100226752
+100254694
+100226756
+100226758
+100333949
+100226770
+100226790
+100226792
+100310900
+100226844
+100226864
+100282670
+100051236
+100247290
+100226888
+100226928
+100226944
+100226946
+100240292
+100226998
+100227000
+100295642
+100106128
+100227024
+100227026
+100209696
+100281430
+100227104
+100227108
+100227114
+100358457
+100281230
+100227120
+100227154
+100309024
+100281312
+100318660
+100227200
+100227202
+100227220
+100227472
+100245912
+100227488
+100227482
+100262148
+100296848
+100227510
+100227550
+100252126
+100227552
+100227518
+100243544
+100227772
+100227568
+100227584
+100227588
+100227590
+100227592
+100227594
+100227598
+100228222
+100227602
+100246342
+100227626
+100230636
+100227812
+100227650
+100227652
+100227670
+100227674
+100317868
+100227694
+100227696
+100294006
+100227700
+100227722
+100227724
+100228064
+100227742
+100227814
+100245020
+100227836
+100259324
+100227816
+100227820
+100231414
+100227838
+100120800
+100326178
+100227878
+100241714
+100227854
+100175004
+100018146
+100227898
+100249094
+100227964
+100241212
+100250750
+100228018
+100228020
+100228022
+100228026
+100325352
+100228044
+100255830
+100303028
+100228066
+100229746
+100228068
+100284356
+100228074
+100249600
+100236704
+100228134
+100228136
+100268982
+100228142
+100284106
+100283508
+100305522
+100228210
+100230440
+100228214
+100242530
+100228220
+100228224
+100250754
+100228226
+100242238
+100228278
+100228294
+100314742
+100228300
+100270574
+100235858
+100228302
+100228380
+100228390
+100248276
+100301778
+100228386
+100277228
+100237834
+100228536
+100228400
+100228398
+100228402
+100228404
+100295608
+100228444
+100228446
+100228448
+100228450
+100228452
+100241164
+100122440
+100228460
+100228462
+100302816
+100228486
+100228488
+100318554
+100228492
+100228496
+100228498
+100242236
+100228504
+100228506
+100235198
+100228526
+100228528
+100283678
+100242006
+100294346
+100228556
+100317866
+100228576
+100228580
+100327326
+100297722
+100265816
+100149336
+100228664
+100283748
+100228666
+100317864
+100228724
+100228688
+100292862
+100228726
+100228736
+100228740
+100256486
+100228742
+100228758
+100228760
+100228762
+100228766
+100311026
+100228798
+100311328
+100228806
+100228822
+100246020
+100228840
+100228874
+100228876
+100228886
+100228888
+100228890
+100228906
+100228910
+100255400
+100228916
+100228948
+100228968
+100228970
+100241274
+100228974
+100319711
+100260288
+100280470
+100228992
+100228996
+100229032
+100229034
+100229036
+100229038
+100229040
+100356596
+100241154
+100229046
+100282494
+100229064
+100229098
+100229116
+100229120
+100229150
+100229166
+100229184
+100229200
+100229202
+100229222
+100230306
+100320883
+100229316
+100229348
+100229366
+100241284
+100243876
+100231794
+100242208
+100229394
+100319398
+100317858
+100269420
+100229404
+100243878
+100229406
+100241508
+100229414
+100229416
+100289880
+100229424
+100229426
+100284126
+100229436
+100241276
+100305510
+100229500
+100229504
+100229552
+100229554
+100229558
+100229560
+100229576
+100229578
+100249318
+100229610
+100229612
+100246820
+100229616
+100229622
+100229624
+100229644
+100229646
+100229664
+100229666
+100272964
+100275328
+100229700
+100241312
+100229726
+100229730
+100292066
+100229738
+100229740
+100229748
+100229750
+100229768
+100247884
+100229800
+100229824
+100229840
+100229844
+100272470
+100229852
+100229854
+100311986
+100229860
+100229900
+100260434
+100229882
+100229932
+100266384
+100280592
+100229942
+100229944
+100229946
+100229994
+100230002
+100230004
+100324949
+100230016
+100230018
+100230020
+100230036
+100230038
+100230060
+100230062
+100278370
+100262100
+100230116
+100230118
+100230120
+100230126
+100230128
+100230132
+100230136
+100235016
+100357456
+100230146
+100230148
+100230150
+100244420
+100317856
+100310724
+100230172
+100230176
+100245286
+100263028
+100230198
+100230200
+100249786
+100230232
+100317832
+100230234
+100258880
+100230268
+100230288
+100230290
+100266756
+100230310
+100255422
+100230318
+100230322
+100241272
+100296278
+100230330
+100230332
+100230334
+100362312
+100204980
+100242216
+100232944
+100230360
+100230362
+100284858
+100242996
+100230372
+100230376
+100279254
+100230382
+100333457
+100230386
+100264450
+100240800
+100230448
+100230876
+100230468
+100241220
+100244808
+100230470
+100290902
+100230472
+100230484
+100230514
+100289874
+100230496
+100249250
+100241294
+100281782
+100231432
+100230524
+100282810
+100230558
+100230556
+100070772
+100230576
+100230586
+100300466
+100230606
+100230608
+100230632
+100230638
+100234498
+100284564
+100233222
+100269162
+100313086
+100230660
+100230662
+100303220
+100299610
+100230726
+100262096
+100230748
+100242262
+100230762
+100231424
+100120370
+100230814
+100241270
+100230818
+100241230
+100302616
+100101272
+100230894
+100304136
+100230938
+100231434
+100294984
+100230962
+100230966
+100324543
+100259150
+100255316
+100231426
+100231020
+100231052
+100295606
+100324179
+100231060
+100283314
+100231064
+100231080
+100231636
+100231132
+100241298
+100231136
+100248476
+100231138
+100231140
+100231148
+100231150
+100241224
+100231158
+100231172
+100231188
+100231196
+100231204
+100256204
+100231208
+100231216
+100277226
+100247190
+100231226
+100231228
+100231230
+100231232
+100246312
+100231420
+100295596
+100231312
+100282720
+100245344
+100231324
+100231330
+100231332
+100257828
+100231338
+100231604
+100259636
+100231342
+100231418
+100251796
+100241744
+100242674
+100231440
+100231458
+100231460
+100231466
+100268920
+100246978
+100074484
+100231486
+100282046
+100233178
+100233918
+100279342
+100317808
+100231518
+100231796
+100231800
+100231802
+100231824
+100231826
+100231830
+100231832
+100231834
+100270474
+100290126
+100231846
+100231842
+100231848
+100231844
+100231852
+100231854
+100245832
+100289938
+100293468
+100231862
+100231864
+100231868
+100242462
+100320506
+100249028
+100231880
+100231900
+100360010
+100091170
+100231942
+100258834
+100242166
+100231952
+100231954
+100231970
+100232316
+100231976
+100295594
+100231978
+100231998
+100232000
+100232002
+100232014
+100299384
+100241540
+100289858
+100232036
+100232058
+100232060
+100232076
+100232240
+100232096
+100293114
+100279520
+100232118
+100232136
+100232138
+100232140
+100232142
+100232144
+100232166
+100299136
+100232186
+100281852
+100047726
+100232192
+100232196
+100244238
+100232200
+100232202
+100232204
+100285708
+100244444
+100254840
+100232228
+100268492
+100232232
+100322347
+100248484
+100232238
+100232812
+100232256
+100232458
+100232264
+100107690
+100294548
+100232276
+100232278
+100232280
+100281202
+100320608
+100107600
+100232342
+100232346
+100295592
+100268896
+100232352
+100232354
+100232404
+100232408
+100271116
+100232416
+100232420
+100232422
+100232438
+100281054
+100241882
+100257408
+100232504
+100285714
+100232524
+100232540
+100300706
+100154906
+100272770
+100232594
+100232600
+100286204
+100232612
+100232636
+100232642
+100232644
+100232646
+100266712
+100232650
+100232652
+100233914
+100242104
+100232674
+100232676
+100232750
+100232708
+100264334
+100232766
+100317291
+100232798
+100232800
+100232810
+100232808
+100301814
+100232816
+100264418
+100232822
+100130800
+100232828
+100232830
+100237908
+100232836
+100232838
+100254386
+100232870
+100232872
+100232876
+100232878
+100232884
+100232900
+100232952
+100282234
+100286592
+100232938
+100244418
+100232990
+100234048
+100243404
+100233014
+100280908
+100233018
+100233020
+100299608
+100263264
+100318528
+100233028
+100233030
+100283356
+100233152
+100242078
+100233042
+100233046
+100233062
+100233064
+100233110
+100233112
+100233114
+100233116
+100254162
+100279820
+100233138
+100233144
+100233150
+100233154
+100317343
+100233158
+100247666
+100245732
+100233186
+100233204
+100233206
+100250724
+100233232
+100361476
+100324937
+100300624
+100233318
+100251430
+100233350
+100233384
+100234534
+100293730
+100233408
+100233410
+100267218
+100233414
+100334947
+100283630
+100233436
+100321977
+100306704
+100259616
+100322587
+100233460
+100243938
+100257914
+100283886
+100233476
+100233482
+100306366
+100233488
+100233504
+100317796
+100303182
+100233510
+100233526
+100270908
+100002600
+100233538
+100233542
+100233546
+100233548
+100262494
+100233550
+100265666
+100327723
+100251510
+100233558
+100233560
+100302806
+100248828
+100042240
+100239760
+100285354
+100239708
+100317712
+100233652
+100233672
+100245890
+100267760
+100233694
+100241904
+100233714
+100291972
+100292692
+100238838
+100233744
+100233746
+100259660
+100242268
+100233780
+100233782
+100283606
+100233788
+100254370
+100240012
+100317710
+100233800
+100233806
+100233808
+100306364
+100275226
+100233850
+100233920
+100233922
+100139486
+100233926
+100272736
+100226020
+100119894
+100233964
+100233966
+100233972
+100153690
+100234008
+100285014
+100234014
+100270174
+100234020
+100234042
+100234364
+100276542
+100234386
+100234388
+100258008
+100234412
+100242048
+100241960
+100234448
+100234450
+100234482
+100285284
+100245898
+100249998
+100324933
+100234526
+100234720
+100234674
+100234530
+100325803
+100275694
+100234570
+100234670
+100234574
+100241886
+100328145
+100234702
+100234678
+100234680
+100234684
+100234694
+100285804
+100283374
+100234724
+100234742
+100234786
+100234748
+100243312
+100234820
+100300468
+100234838
+100234840
+100234844
+100035536
+100244728
+100234898
+100235006
+100234900
+100234902
+100234904
+100266210
+100357442
+100234926
+100234944
+100234962
+100235850
+100301384
+100234986
+100279736
+100235010
+100235012
+100235048
+100242040
+100235068
+100056846
+100242832
+100325288
+100235080
+100239374
+100235086
+100318384
+100004748
+100250372
+100242398
+100271344
+100242000
+100292858
+100273284
+100260100
+100235152
+100235156
+100235158
+100235164
+100235166
+100235172
+100312520
+100235190
+100235192
+100235200
+100235268
+100211556
+100289828
+100235284
+100235290
+100122062
+100235294
+100235298
+100283690
+100235302
+100236802
+100235332
+100318206
+100235326
+100235328
+100235330
+100047988
+100235368
+100235346
+100235348
+100314486
+100242842
+100089870
+100235386
+100235390
+100235398
+100082604
+100282502
+100235402
+100235450
+100273818
+100235482
+100322900
+100242270
+100235488
+100242910
+100235494
+100235530
+100235548
+100235700
+100241998
+100235604
+100293796
+100242764
+100247036
+100258088
+100235652
+100235640
+100289024
+100235660
+100270320
+100247306
+100241996
+100235664
+100282700
+100245776
+100242396
+100325328
+100235976
+100236006
+100242272
+100236012
+100242840
+100236016
+100236018
+100236020
+100236024
+100242480
+100236032
+100277638
+100236038
+100072098
+100236120
+100047020
+100325911
+100236066
+100236070
+100236230
+100236072
+100236088
+100301882
+100236092
+100236094
+100236114
+100236116
+100236420
+100295684
+100236126
+100236128
+100236144
+100239604
+100236192
+100289824
+100236212
+100236200
+100322790
+100236204
+100267664
+100236260
+100236234
+100306652
+100318524
+100236686
+100236474
+100236344
+100236346
+100236360
+100295652
+100268602
+100236378
+100236418
+100236386
+100244512
+100236442
+100247414
+100236446
+100236464
+100305528
+100236476
+100236478
+100295074
+100236504
+100236526
+100236528
+100236532
+100243962
+100254426
+100262042
+100236572
+100236578
+100236724
+100242736
+100236610
+100148814
+100318106
+100236616
+100245584
+100236638
+100236640
+100236642
+100312612
+100282218
+100236918
+100237232
+100316795
+100320394
+100236954
+100252506
+100236956
+100236982
+100236962
+100254922
+100236986
+100271594
+100268462
+100237042
+100243970
+100266668
+100237080
+100242898
+100237082
+100328507
+100237170
+100237090
+100237092
+100237096
+100237158
+100237166
+100237172
+100310366
+100047804
+100237492
+100237178
+100266782
+100237198
+100257354
+100091154
+100289818
+100237212
+100275154
+100237224
+100237226
+100284360
+100279746
+100272778
+100177696
+100286866
+100237260
+100246310
+100237266
+100237268
+100237270
+100248006
+100249128
+100237324
+100237340
+100280126
+100326968
+100237380
+100249862
+100237384
+100237402
+100237404
+100244790
+100237420
+100241940
+100237438
+100241622
+100237442
+100243546
+100333053
+100161048
+100252968
+100320949
+100237478
+100242548
+100352983
+100237476
+100237488
+100275284
+100237514
+100305456
+100237494
+100237510
+100237512
+100295488
+100277166
+100239108
+100237534
+100237552
+100241210
+100302072
+100237588
+100240358
+100242702
+100251408
+100237630
+100238444
+100237634
+100149640
+100284288
+100292068
+100267326
+100237664
+100174032
+100341687
+100237838
+100238414
+100237840
+100237870
+100281264
+100300462
+100237928
+100238024
+100237964
+100254948
+100246326
+100243030
+100281162
+100237930
+100273628
+100237938
+100237962
+100237982
+100250102
+100257774
+100246466
+100304880
+100238030
+100240386
+100238032
+100238034
+100238036
+100262036
+100085584
+100280468
+100314838
+100238086
+100247740
+100238092
+100244032
+100294300
+100238102
+100238158
+100295450
+100244754
+100238124
+100134026
+100238130
+100238132
+100238174
+100238204
+100238176
+100239110
+100089636
+100287132
+100244130
+100238222
+100268586
+100238234
+100260988
+100238238
+100238270
+100313540
+100238272
+100243180
+100238280
+100238638
+100238362
+100332271
+100238290
+100238298
+100238302
+100243362
+100238322
+100238326
+100270406
+100238472
+100300060
+100238344
+100078178
+100239678
+100277278
+100247268
+100238356
+100238354
+100238364
+100271548
+100281664
+100238420
+100238436
+100099526
+100246600
+100328686
+100238720
+100046512
+100261032
+100062366
+100238494
+100284330
+100300734
+100238500
+100238504
+100243666
+100306138
+100310692
+100238528
+100296618
+100241222
+100323849
+100253814
+100238760
+100271096
+100256168
+100274474
+100271078
+100245390
+100259416
+100250006
+100280478
+100238794
+100114832
+100238800
+100238802
+100259100
+100238806
+100238808
+100323972
+100273738
+100250012
+100280690
+100238832
+100238834
+100295428
+100239006
+100268868
+100238842
+100248274
+100312148
+100244724
+100313290
+100244454
+100238852
+100243976
+100323135
+100244034
+100238882
+100238884
+100268838
+100238894
+100253918
+100238906
+100238908
+100288506
+100271074
+100238932
+100248992
+100320408
+100253726
+100327591
+100238946
+100238962
+100238968
+100238972
+100238974
+100238998
+100238982
+100238996
+100244244
+100239014
+100239018
+100239020
+100324897
+100248546
+100257186
+100328690
+100268834
+100239084
+100300212
+100239094
+100244062
+100244060
+100166026
+100244058
+100239682
+100256598
+100273740
+100239126
+100259610
+100267308
+100267298
+100239166
+100310982
+100261852
+100239184
+100239218
+100244536
+100241612
+100239222
+100268820
+100239240
+100295386
+100244742
+100304830
+100279718
+100244228
+100239352
+100239266
+100239354
+100240540
+100318638
+100239534
+100115394
+100239540
+100281750
+100239552
+100258886
+100239556
+100239558
+100250058
+100239560
+100262024
+100239596
+100250686
+100239600
+100239610
+100247626
+100268506
+100239618
+100239620
+100239622
+100239638
+100247572
+100250770
+100278518
+100239660
+100295384
+100239664
+100355082
+100240836
+100284226
+100239900
+100279498
+100239694
+100248032
+100318116
+100263104
+100290736
+100253480
+100239706
+100246700
+100266918
+100310836
+100239764
+100260762
+100239784
+100239786
+100262022
+100279638
+100271072
+100239810
+100244350
+100246624
+100239830
+100254852
+100245186
+100239864
+100322864
+100279774
+100239926
+100248326
+100306830
+100239930
+100239940
+100239946
+100267930
+100239944
+100239948
+100240032
+100239950
+100244392
+100239954
+100243966
+100264730
+100318420
+100239988
+100239990
+100239996
+100295382
+100295380
+100240244
+100240030
+100240034
+100273268
+100240038
+100240040
+100240058
+100240268
+100240074
+100326088
+100245448
+100259036
+100183788
+100282344
+100240686
+100240698
+100263420
+100240728
+100240730
+100300730
+100306570
+100254388
+100240746
+100245906
+100240882
+100240906
+100240908
+100065738
+100240912
+100240916
+100240918
+100240922
+100260328
+100158244
+100323245
+100241302
+100248596
+100246174
+100282698
+100270422
+100241332
+100250026
+100286770
+100241336
+100271070
+100241362
+100282848
+100284526
+100241370
+100246280
+100324260
+100253416
+100244176
+100285782
+100251960
+100241398
+100244944
+100274578
+100241446
+100241448
+100241452
+100241476
+100241456
+100241460
+100256722
+100241478
+100241992
+100244180
+100310070
+100295292
+100270120
+100325336
+100242638
+100324721
+100311080
+100242656
+100271076
+100319286
+100358259
+100244002
+100243420
+100248142
+100358317
+100242940
+100242950
+100257884
+100242958
+100251802
+100247010
+100242964
+100242970
+100243402
+100242978
+100252788
+100243004
+100243306
+100244424
+100327627
+100243478
+100243442
+100317084
+100243582
+100047476
+100243462
+100243470
+100082914
+100243472
+100099804
+100289328
+100274530
+100265854
+100249690
+100243548
+100243550
+100243910
+100244056
+100303112
+100248522
+100243664
+100246686
+100303818
+100259482
+100093386
+100243600
+100243604
+100070336
+100243648
+100249798
+100318584
+100243674
+100243678
+100126318
+100274240
+100280294
+100243686
+100246636
+100295130
+100073924
+100325079
+100247294
+100243710
+100289792
+100256364
+100243720
+100317511
+100262006
+100243728
+100232742
+100243730
+100308356
+100243736
+100250868
+100317888
+100244122
+100250150
+100227698
+100243794
+100263562
+100243800
+100353539
+100243802
+100358681
+100243824
+100301842
+100243826
+100248486
+100244234
+100244722
+100269352
+100352058
+100305892
+100325360
+100247600
+100245094
+100255832
+100266608
+100330901
+100257148
+100248418
+100245148
+100317505
+100256608
+100309486
+100245162
+100270832
+100245438
+100245442
+100326648
+100295228
+100245482
+100295226
+100249582
+100360491
+100248172
+100254926
+100275222
+100245466
+100194916
+100245564
+100325048
+100268266
+100317962
+100245506
+100246740
+100291962
+100245512
+100098894
+100246388
+100245518
+100232934
+100245524
+100094688
+100245530
+100245534
+100321773
+100245536
+100283224
+100245556
+100245570
+100245580
+100245592
+100245594
+100246550
+100245596
+100301270
+100281998
+100046466
+100256992
+100324460
+100251824
+100246012
+100335650
+100271614
+100246992
+100246034
+100272430
+100260300
+100246072
+100249304
+100322203
+100257240
+100246092
+100303090
+100317768
+100317293
+100259412
+100246188
+100326154
+100246322
+100272114
+100279890
+100246330
+100246334
+100246336
+100246340
+100266798
+100246364
+100246382
+100253442
+100317289
+100246406
+100249978
+100246660
+100295172
+100119648
+100246480
+100249696
+100300518
+100271532
+100250250
+100248824
+100248892
+100301564
+100269254
+100247446
+100271490
+100108294
+100249362
+100302882
+100248530
+100295158
+100257898
+100246570
+100246574
+100246580
+100246582
+100267784
+100247080
+100247074
+100257826
+100255692
+100176306
+100247106
+100247108
+100256462
+100261942
+100247172
+100248272
+100247144
+100301900
+100247264
+100282628
+100294490
+100249810
+100013476
+100338544
+100263602
+100046710
+100267388
+100258786
+100295390
+100249802
+100247670
+100252926
+100265626
+100248478
+100255968
+100259822
+100262090
+100273234
+100116676
+100247766
+100247782
+100249752
+100247890
+100248140
+100247800
+100265280
+100249760
+100356129
+100247810
+100265820
+100248446
+100304424
+100327294
+100309954
+100282744
+100259386
+100342338
+100321476
+100250784
+100250786
+100250862
+100250788
+100252972
+100252214
+100250932
+100318494
+100272384
+100329180
+100260938
+100294486
+100328682
+100250964
+100271990
+100254650
+100258300
+100285696
+100268118
+100258298
+100314894
+100319064
+100255036
+100261232
+100294440
+100280922
+100301414
+100302204
+100294430
+100315606
+100315396
+100251310
+100255696
+100255804
+100268772
+100259900
+100255606
+100178440
+100294414
+100251356
+100251366
+100316578
+100269334
+100339915
+100251374
+100251376
+100324434
+100251386
+100251394
+100257036
+100256310
+100251494
+100258814
+100294410
+100326022
+100305392
+100328231
+100230084
+100254134
+100295008
+100259410
+100282014
+100251540
+100251546
+100294570
+100282626
+100254450
+100251602
+100047502
+100251614
+100289488
+100251618
+100256644
+100251894
+100256024
+100281854
+100251626
+100102062
+100252154
+100300458
+100257266
+100303650
+100316099
+100323968
+100301024
+100254192
+100252744
+100275490
+100257806
+100304160
+100282594
+100324817
+100280052
+100251926
+100258306
+100251828
+100254172
+100255158
+100282664
+100251838
+100324670
+100251842
+100049610
+100351411
+100235710
+100319707
+100256336
+100281580
+100328197
+100322788
+100261786
+100315868
+100285380
+100261932
+100261930
+100277842
+100251902
+100254868
+100251966
+100252846
+100299130
+100093398
+100251916
+100251922
+100255598
+100316095
+100328660
+100182490
+100281700
+100251940
+100251942
+100318614
+100254866
+100328315
+100252084
+100252108
+100302894
+100270394
+100317227
+100091370
+100328654
+100268548
+100255354
+100257778
+100252128
+100255926
+100252148
+100281648
+100316087
+100252138
+100318594
+100253394
+100317141
+100362790
+100265836
+100063484
+100270988
+100288698
+100175542
+100279162
+100294378
+100316825
+100268296
+100320684
+100328650
+100072398
+100274822
+100262072
+100270980
+100282514
+100073922
+100270538
+100254478
+100261928
+100252352
+100253316
+100269558
+100267202
+100259698
+100043866
+100261912
+100258278
+100265868
+100255590
+100270728
+100270624
+100261904
+100255056
+100252798
+100252518
+100262678
+100255862
+100338096
+100261500
+100252532
+100267444
+100294892
+100282940
+100324853
+100268416
+100258856
+100083846
+100257082
+100260208
+100296884
+100274248
+100285872
+100252606
+100328644
+100252608
+100252610
+100252614
+100254836
+100252616
+100319745
+100083930
+100264956
+100304498
+100256032
+100252640
+100299126
+100085034
+100133074
+100253902
+100281738
+100281652
+100258044
+100252664
+100254770
+100252668
+100324348
+100289592
+100252780
+100290360
+100270054
+100248662
+100252690
+100316001
+100252732
+100283684
+100261898
+100136976
+100260026
+100332705
+100258402
+100359457
+100273680
+100268994
+100358417
+100303434
+100252826
+100268740
+100315568
+100252836
+100252834
+100327967
+100252858
+100252874
+100254892
+100254890
+100265140
+100339142
+100273110
+100315380
+100287446
+100259332
+100068826
+100253886
+100273294
+100256672
+100255358
+100315168
+100322933
+100253308
+100279278
+100313816
+100255366
+100253192
+100260298
+100256426
+100285636
+100282512
+100253202
+100253434
+100255356
+100323531
+100285646
+100253262
+100327218
+100255570
+100268768
+100253242
+100324296
+100253244
+100271970
+100255546
+100253266
+100287038
+100253314
+100047662
+100361191
+100255278
+100266358
+100290612
+100263730
+100253386
+100311394
+100260592
+100261608
+100253406
+100271440
+100255646
+100253668
+100253674
+100116316
+100072374
+100332956
+100259948
+100256662
+100280190
+100315876
+100253692
+100253708
+100275460
+100253712
+100268760
+100268774
+100253770
+100257456
+100275748
+100256380
+100269436
+100270970
+100262544
+100266950
+100253978
+100258800
+100260198
+100311148
+100361684
+100280232
+100348082
+100324695
+100320576
+100326668
+100318464
+100254664
+100257822
+100254644
+100317035
+100255520
+100254666
+100259158
+100254670
+100254672
+100254682
+100271038
+100319811
+100279154
+100254998
+100255000
+100255008
+100255010
+100292302
+100169118
+100265852
+100260062
+100284354
+100275358
+100261876
+100255064
+100315848
+100270056
+100287360
+100283912
+100264528
+100256196
+100274246
+100316999
+100278064
+100287164
+100316983
+100292506
+100255260
+100318836
+100270968
+100267210
+100256478
+100270172
+100257180
+100096218
+100259240
+100255310
+100255314
+100263006
+100273660
+100257272
+100324310
+100255394
+100255404
+100259338
+100326078
+100260048
+100255416
+100271054
+100255630
+100320748
+100266348
+100258122
+100255760
+100255766
+100255768
+100057892
+100255814
+100255822
+100288648
+100327088
+100319667
+100255932
+100261978
+100316977
+100274814
+100268400
+100266530
+100294054
+100277996
+100312894
+100258282
+100255964
+100311346
+100255972
+100313722
+100259758
+100256116
+100268474
+100255990
+100315814
+100256104
+100259258
+100284600
+100256006
+100269718
+100258288
+100264336
+100287442
+100313122
+100329084
+100258508
+100278876
+100268292
+100298588
+100292812
+100323789
+100280118
+100305374
+100256236
+100315810
+100318466
+100002972
+100258686
+100323705
+100326586
+100259346
+100258862
+100200828
+100305330
+100270142
+100288780
+100258890
+100284422
+100265098
+100258894
+100259274
+100258896
+100258898
+100259378
+100258900
+100259014
+100269124
+100259106
+100260956
+100277132
+100268756
+100272796
+100338124
+100315882
+100280514
+100264070
+100281550
+100342297
+100259082
+100280834
+100259102
+100310450
+100278542
+100259140
+100273782
+100263072
+100260304
+100266212
+100262548
+100259532
+100358615
+100259540
+100282124
+100259562
+100352640
+100259586
+100267800
+100308358
+100358548
+100260314
+100335833
+100259632
+100284042
+100271590
+100261560
+100327857
+100344745
+100286734
+100272904
+100271568
+100270536
+100164872
+100087442
+100262162
+100276130
+100269986
+100300814
+100260674
+100282638
+100266914
+100334261
+100110022
+100262882
+100260490
+100285274
+100334309
+100261096
+100261552
+100272678
+100021718
+100313168
+100072756
+100324266
+100303006
+100262104
+100337945
+100332818
+100260416
+100260452
+100260454
+100317549
+100028842
+100319725
+100267766
+100260472
+100260488
+100260620
+100146692
+100319785
+100261648
+100018410
+100306250
+100268536
+100276182
+100260696
+100260666
+100276770
+100324089
+100300570
+100267626
+100325785
+100294674
+100307262
+100267464
+100276464
+100274238
+100270162
+100263168
+100315802
+100322652
+100161878
+100264918
+100261080
+100268316
+100288546
+100268594
+100271242
+100280178
+100267546
+100294296
+100291090
+100266582
+100269674
+100302274
+100260918
+100355036
+100294286
+100319166
+100261052
+100268336
+100299586
+100319462
+100261074
+100264372
+100303574
+100287260
+100319344
+100324258
+100284166
+100320516
+100261144
+100326062
+100261150
+100310272
+100273460
+100262040
+100264146
+100288324
+100034266
+100262972
+100261186
+100322541
+100317072
+100274102
+100294344
+100261292
+100262342
+100321121
+100276462
+100261320
+100277854
+100232606
+100303578
+100035900
+100261340
+100316975
+100262362
+100261348
+100275990
+100261600
+100261366
+100261694
+100294256
+100272486
+100269864
+100316969
+100266634
+100315728
+100261758
+100261384
+100274106
+100320299
+100279136
+100261424
+100261426
+100261762
+100320143
+100261432
+100261874
+100271200
+100337616
+100262470
+100261452
+100270610
+100263196
+100147708
+100263204
+100263212
+100266882
+100273194
+100263214
+100294810
+100263224
+100263244
+100266854
+100326068
+100263256
+100290170
+100283120
+100342577
+100265102
+100185814
+100318482
+100314484
+100294812
+100263274
+100278976
+100311606
+100266104
+100267360
+100117256
+100263346
+100263292
+100271878
+100263296
+100317996
+100275796
+100272588
+100283436
+100317031
+100263328
+100298564
+100160080
+100330839
+100326056
+100265390
+100270810
+100082676
+100319781
+100263412
+100270796
+100072180
+100028246
+100310474
+100263428
+100263444
+100068460
+100294246
+100263436
+100268222
+100263680
+100269464
+100316668
+100263460
+100271362
+100266928
+100267502
+100325094
+100263568
+100263472
+100268344
+100001358
+100303908
+100001042
+100306720
+100263480
+100265692
+100361296
+100263516
+100281376
+100263526
+100263558
+100311202
+100054454
+100246090
+100284484
+100306210
+100287148
+100272850
+100266848
+100263584
+100263578
+100263580
+100270326
+100268308
+100268154
+100268152
+100322319
+100268184
+100358113
+100267824
+100263608
+100274910
+100272426
+100263642
+100316949
+100263678
+100133072
+100301930
+100295044
+100267794
+100287146
+100334919
+100263644
+100299906
+100324603
+100306706
+100287030
+100280146
+100263674
+100079654
+100269680
+100263704
+100075392
+100286966
+100271384
+100270982
+100263734
+100263738
+100264478
+100317624
+100264210
+100267682
+100285784
+100270370
+100263764
+100304376
+100267512
+100279252
+100311002
+100023178
+100267018
+100267080
+100286956
+100268096
+100263812
+100317912
+100269668
+100321689
+100296204
+100263826
+100263844
+100285734
+100267714
+100267734
+100263834
+100263836
+100301940
+100233412
+100327747
+100271480
+100268568
+100263872
+100263874
+100270222
+100279542
+100306412
+100263908
+100263912
+100263932
+100263940
+100301250
+100047728
+100264076
+100263942
+100263944
+100269452
+100302972
+100263984
+100263988
+100263998
+100269328
+100272542
+100257424
+100264008
+100264004
+100266556
+100264012
+100313058
+100264020
+100306572
+100264026
+100264216
+100274918
+100264060
+100306196
+100119294
+100268414
+100286954
+100264088
+100264090
+100286602
+100319883
+100286952
+100283260
+100281662
+100264104
+100315680
+100264108
+100177638
+100271472
+100320237
+100267166
+100264144
+100264118
+100324660
+100284234
+100264178
+100278780
+100274828
+100264156
+100288438
+100269126
+100093482
+100298492
+100264186
+100271382
+100327739
+100264204
+100294220
+100264212
+100320752
+100264238
+100264240
+100267660
+100264250
+100264272
+100319777
+100264254
+100267926
+100264262
+100264264
+100350350
+100285524
+100273404
+100098910
+100265112
+100264416
+100270060
+100352642
+100270602
+100293530
+100264326
+100279578
+100264342
+100286936
+100264346
+100267858
+100282468
+100280410
+100286664
+100274480
+100264540
+100303548
+100275300
+100312884
+100287996
+100267996
+100334116
+100085234
+100303954
+100316865
+100282432
+100025096
+100274932
+100264454
+100277262
+100272396
+100264466
+100291852
+100275758
+100264500
+100303532
+100264518
+100294216
+100265450
+100034664
+100273992
+100264554
+100334875
+100284312
+100264560
+100264572
+100266558
+100322309
+100279286
+100306262
+100326234
+100315592
+100264688
+100264770
+100273894
+100264662
+100264666
+100267866
+100119696
+100264672
+100264698
+100284208
+100319270
+100264682
+100276320
+100264686
+100099180
+100019228
+100294212
+100267504
+100273456
+100267710
+100264710
+100285280
+100271390
+100270410
+100251362
+100264734
+100292932
+100294662
+100283160
+100323701
+100286926
+100271580
+100275334
+100275646
+100265408
+100294816
+100269664
+100264818
+100321703
+100267042
+100046880
+100264822
+100306616
+100274958
+100285458
+100245704
+100338273
+100272878
+100325674
+100271162
+100269588
+100264876
+100267658
+100361663
+100245790
+100264888
+100324246
+100266312
+100264962
+100264964
+100274510
+100268080
+100268078
+100279104
+100265000
+100265002
+100265004
+100265020
+100164308
+100265224
+100323759
+100265012
+100265014
+100273898
+100294178
+100265018
+100266020
+100294804
+100284806
+100113224
+100272680
+100284140
+100265066
+100271046
+100265092
+100221198
+100268666
+100265104
+100265866
+100094194
+100265114
+100269198
+100326076
+100282444
+100265516
+100099032
+100265412
+100268502
+100314890
+100267790
+100265212
+100282630
+100322736
+100265198
+100278992
+100265210
+100318318
+100265238
+100278392
+100265220
+100274886
+100267608
+100332635
+100339170
+100268396
+100120144
+100265326
+100265242
+100268180
+100265542
+100279094
+100265250
+100265284
+100304344
+100283736
+100265288
+100318504
+100046194
+100267890
+100265296
+100322686
+100272946
+100268190
+100305326
+100266132
+100304224
+100265416
+100322029
+100286132
+100267380
+100232902
+100271262
+100301236
+100265442
+100267936
+100115356
+100265456
+100267198
+100279084
+100265468
+100273594
+100265476
+100265486
+100266546
+100265678
+100265546
+100319114
+100272050
+100316284
+100274134
+100265574
+100285626
+100278620
+100328626
+100279672
+100304042
+100085228
+100265756
+100286250
+100333173
+100265620
+100266254
+100272176
+100317319
+100280566
+100265680
+100283916
+100300582
+100294890
+100265846
+100294140
+100318398
+100266362
+100299966
+100319530
+100265734
+100309294
+100028298
+100265742
+100270058
+100320879
+100265764
+100325825
+100265768
+100322353
+100265772
+100265858
+100316472
+100265882
+100286134
+100265884
+100328598
+100280162
+100274920
+100265908
+100265902
+100265912
+100320863
+100265952
+100057782
+100273416
+100302362
+100284160
+100266014
+100266010
+100266304
+100288810
+100323153
+100294138
+100268252
+100266058
+100286124
+100266070
+100266572
+100266074
+100289226
+100285638
+100266106
+100018482
+100268338
+100026844
+100266112
+100266134
+100266136
+100266146
+100320935
+100303870
+100285494
+100291198
+100320454
+100277978
+100282244
+100316837
+100278794
+100315328
+100277546
+100276986
+100288466
+100327587
+100033298
+100356237
+100280080
+100323337
+100288718
+100280306
+100146208
+100285232
+100276190
+100276606
+100120038
+100276240
+100317339
+100276246
+100355544
+100047292
+100276254
+100276412
+100322762
+100276488
+100034668
+100282158
+100310844
+100276612
+100294602
+100304802
+100284266
+100280954
+100259808
+100278516
+100321203
+100360295
+100280046
+100278124
+100280258
+100278102
+100294788
+100313152
+100013022
+100293136
+100276690
+100120134
+100294260
+100276698
+100276700
+100353686
+100276708
+100280740
+100286456
+100327605
+100278432
+100088002
+100294838
+100279894
+100294108
+100307552
+100278400
+100323151
+100276778
+100323177
+100295976
+100296042
+100093788
+100276842
+100288212
+100278032
+100285262
+100278026
+100216758
+100328580
+100336887
+100301534
+100320941
+100289182
+100278598
+100276906
+100313802
+100282862
+100282132
+100334229
+100294738
+100280042
+100278754
+100281952
+100283936
+100277010
+100048924
+100286894
+100277014
+100358902
+100277036
+100313576
+100277054
+100048408
+100284746
+100286118
+100078314
+100291824
+100277078
+100277082
+100314932
+100277192
+100294754
+100277490
+100299142
+100311438
+100299882
+100302370
+100300078
+100299188
+100299548
+100299206
+100299208
+100299218
+100299220
+100299280
+100302112
+100299226
+100299232
+100299234
+100299236
+100299238
+100299244
+100299732
+100299262
+100299282
+100299284
+100299332
+100300166
+100299336
+100299338
+100299342
+100301818
+100299362
+100299364
+100310626
+100299368
+100299386
+100299466
+100299394
+100299396
+100302324
+100309326
+100326944
+100299874
+100299408
+100299414
+100355035
+100299418
+100302142
+100317746
+100299472
+100299474
+100303048
+100303056
+100310936
+100299554
+100299574
+100321626
+100299578
+100299582
+100299584
+100300330
+100299612
+100299614
+100299616
+100299622
+100299652
+100299708
+100299736
+100303272
+100299878
+100316863
+100299772
+100300424
+100299794
+100058644
+100303136
+100302636
+100323357
+100299810
+100299826
+100303004
+100299848
+100300268
+100299916
+100284270
+100154904
+100302178
+100299924
+100303994
+100299990
+100301668
+100302368
+100300012
+100315290
+100300016
+100316743
+100342476
+100300020
+100300022
+100300024
+100304840
+100300436
+100325216
+100300034
+100302044
+100300036
+100306062
+100303504
+100332993
+100302996
+100300842
+100300122
+100300124
+100304658
+100300180
+100302356
+100299300
+100306566
+100300214
+100300200
+100300204
+100300210
+100352594
+100310504
+100300222
+100300226
+100300228
+100300230
+100300274
+100302930
+100341265
+100301356
+100300300
+100300306
+100300308
+100300386
+100301392
+100300392
+100303206
+100300546
+100309364
+100300444
+100302668
+100300480
+100300482
+100300490
+100300496
+100300514
+100300516
+100300522
+100300520
+100300524
+100302452
+100300528
+100303392
+100300532
+100302710
+100300538
+100340371
+100300548
+100300550
+100300552
+100314822
+100300558
+100317299
+100300564
+100300566
+100300568
+100300576
+100325775
+100300588
+100300590
+100302474
+100300596
+100300600
+100302480
+100302634
+100300608
+100300612
+100300614
+100302696
+100302484
+100300620
+100300626
+100300628
+100300630
+100300632
+100300634
+100300636
+100300640
+100305764
+100302298
+100306736
+100300662
+100318426
+100356705
+100315288
+100300674
+100300692
+100304666
+100300696
+100300726
+100303040
+100302638
+100353359
+100300876
+100303474
+100302640
+100300890
+100300950
+100302802
+100301782
+100301310
+100301034
+100301042
+100301038
+100306056
+100324565
+100301880
+100301096
+100301098
+100301242
+100304530
+100301312
+100302944
+100301316
+100301322
+100301344
+100302702
+100302700
+100301350
+100301352
+100301394
+100301440
+100273652
+100303054
+100301446
+100301852
+100301482
+100302022
+100301486
+100299662
+100301686
+100301712
+100301714
+100303324
+100301720
+100301722
+100194552
+100301822
+100301790
+100301794
+100301796
+100301798
+100361316
+100301948
+100321841
+100302296
+100302446
+100302470
+100318156
+100325083
+100307594
+100307598
+100307600
+100311876
+100320600
+100307614
+100311054
+100314814
+100307628
+100307632
+100307634
+100311968
+100326889
+100321023
+100326805
+100313066
+100307656
+100307658
+100324428
+100313956
+100323161
+100309398
+100307668
+100315582
+100309270
+100322957
+100307680
+100324975
+100314810
+100325664
+100324979
+100316506
+100314768
+100314870
+100323996
+100319092
+100310738
+100307714
+100314566
+100307720
+100307722
+100325759
+100353046
+100314728
+100317275
+100307734
+100316833
+100310736
+100099430
+100314642
+100314638
+100307746
+100342325
+100315519
+100308804
+100363110
+100316642
+100307766
+100307768
+100328077
+100328576
+100314568
+100214074
+100307784
+100307786
+100314414
+100325366
+100307794
+100313780
+100318582
+100310618
+100307806
+100311740
+100307822
+100314540
+100351818
+100317571
+100316596
+100307858
+100307838
+100307844
+100314506
+100307848
+100315276
+100179918
+100315104
+100331316
+100307860
+100321837
+100325849
+100307872
+100307882
+100320620
+100314478
+100318906
+100307892
+100135418
+100307902
+100349848
+100326902
+100314444
+100307910
+100307912
+100307914
+100346128
+100315006
+100314494
+100308390
+100307930
+100307932
+100321224
+100311016
+100310668
+100314420
+100333171
+100307946
+100307956
+100308842
+100087918
+100307964
+100308490
+100324229
+100359450
+100328564
+100343990
+100317477
+100316214
+100317475
+100315256
+100320085
+100312984
+100110644
+100311442
+100312524
+100320440
+100318812
+100308028
+100325735
+100308034
+100308036
+100308038
+100328930
+100308042
+100311506
+100323207
+100312256
+100308056
+100308058
+100308064
+100308070
+100323131
+100327222
+100323505
+100322848
+100323129
+100308082
+100314350
+100308090
+100314926
+100308094
+100314690
+100322037
+100348180
+100318708
+100314342
+100308110
+100321657
+100324971
+100316299
+100314470
+100325983
+100317102
+100319031
+100046608
+100315206
+100308150
+100314334
+100308154
+100308156
+100317598
+100325322
+100314488
+100324175
+100308252
+100309262
+100322712
+100104032
+100315374
+100308174
+100332700
+100314302
+100333696
+100314296
+100333052
+100314294
+100308188
+100316711
+100308192
+100308374
+100318242
+100316420
+100328812
+100308222
+100316494
+100316729
+100328538
+100123888
+100318188
+100315542
+100315372
+100308256
+100308258
+100324219
+100308266
+100361790
+100308286
+100310128
+100308274
+100327943
+100342032
+100308288
+100355451
+100308292
+100352860
+100338502
+100324125
+100308300
+100151156
+100308304
+100318000
+100324350
+100309884
+100308312
+100310522
+100326992
+100317068
+100323978
+100308340
+100327991
+100308344
+100323948
+100338402
+100308690
+100308376
+100362971
+100327853
+100322925
+100322921
+100308412
+100159992
+100323763
+100308418
+100311052
+100310018
+100308432
+100314628
+100308436
+100309176
+100197184
+100315818
+100311828
+100314240
+100351388
+100308650
+100308972
+100318514
+100316418
+100238448
+100308498
+100314138
+100324213
+100314134
+100326898
+100314124
+100311226
+100308536
+100316771
+100308534
+100322914
+100308544
+100309050
+100308542
+100308538
+100309130
+100314608
+100308552
+100308556
+100314116
+100308562
+100323359
+100310538
+100248232
+100314076
+100308590
+100308598
+100060214
+100310994
+100314072
+100335129
+100351048
+100316747
+100308696
+100311902
+100308662
+100308652
+100308654
+100324791
+100308660
+100308672
+100308664
+100308670
+100308676
+100308978
+100308920
+100308686
+100314120
+100309136
+100308698
+100308700
+100308704
+100308702
+100351287
+100326865
+100308718
+100308720
+100357737
+100314060
+100317013
+100314040
+100323241
+100335419
+100309950
+100314128
+100314024
+100173142
+100314648
+100308752
+100322961
+100313118
+100308758
+100327637
+100317363
+100308772
+100313998
+100309628
+100325973
+100323157
+100313996
+100308806
+100308814
+100308818
+100361441
+100209264
+100308824
+100309820
+100312714
+100310798
+100314448
+100317646
+100325649
+100316640
+100310064
+100323565
+100310802
+100308872
+100217246
+100315925
+100316727
+100316945
+100313468
+100314886
+100308886
+100045926
+100310386
+100320827
+100308914
+100351424
+100308922
+100310482
+100323039
+100308944
+100308930
+100360278
+100308938
+100308942
+100309142
+100320187
+100309288
+100310516
+100308980
+100308982
+100315489
+100326132
+100310946
+100332914
+100308998
+100309000
+100308992
+100314816
+100326244
+100318094
+100312024
+100309008
+100256302
+100314748
+100322247
+100309014
+100323175
+100313318
+100309082
+100313806
+100309608
+100307128
+100321115
+100337848
+100309064
+100309072
+100322754
+100309076
+100309086
+100304442
+100279770
+100313212
+100316465
+100309102
+100321324
+100317384
+100312252
+100313178
+100309116
+100332759
+100256322
+100309128
+100317058
+100326914
+100329330
+100328562
+100248836
+100309144
+100313106
+100309172
+100316374
+100313102
+100316608
+100309178
+100320740
+100326060
+100309946
+100314098
+100309366
+100309184
+100349256
+100309192
+100309200
+100312364
+100313074
+100317742
+100309204
+100326072
+100309208
+100311312
+100309212
+100312852
+100309634
+100313410
+100319056
+100312848
+100318516
+100309252
+100318468
+100309234
+100318056
+100309236
+100124916
+100309242
+100309244
+100309246
+100258396
+100251450
+100309254
+100333298
+100320544
+100312016
+100313426
+100312658
+100309282
+100310750
+100311168
+100311378
+100311502
+100309300
+100312650
+100312562
+100333620
+100309308
+100020864
+100328127
+100309394
+100321803
+100317551
+100324123
+100349216
+100309360
+100312522
+100309414
+100313972
+100309520
+100262682
+100311472
+100309746
+100315654
+100315054
+100310926
+100309434
+100320823
+100312482
+100309438
+100309744
+100309440
+100324193
+100312466
+100309444
+100332728
+100350315
+100309464
+100309474
+100309478
+100309652
+100309482
+100319671
+100309494
+100309496
+100309498
+100309502
+100309506
+100321236
+100309504
+100309516
+100314520
+100324145
+100325833
+100322003
+100318592
+100313054
+100309544
+100317972
+100310882
+100333166
+100309592
+100309560
+100309564
+100309582
+100311212
+100333801
+100309586
+100318432
+100328079
+100046652
+100317126
+100333479
+100312440
+100354917
+100328988
+100309618
+100338262
+100309664
+100309626
+100356426
+100310014
+100310102
+100262178
+100158440
+100309644
+100326252
+100312432
+100312408
+100309668
+100325749
+100309676
+100313884
+100309680
+100323854
+100311588
+100320734
+100323745
+100334945
+100315402
+100309698
+100309702
+100324575
+100317181
+100309716
+100309718
+100309720
+100318694
+100319290
+100332026
+100312388
+100319869
+100312376
+100312764
+100309734
+100313448
+100312258
+100316476
+100322505
+100325827
+100319719
+100335403
+100309770
+100313596
+100214096
+100309780
+100074860
+100309786
+100309970
+100309790
+100309800
+100325212
+100309822
+100314252
+100309824
+100311256
+100312720
+100309838
+100309842
+100317622
+100309846
+100310480
+100317064
+100312150
+100310478
+100309886
+100309888
+100309890
+100313886
+100340336
+100312558
+100290774
+100312330
+100314716
+100130186
+100309916
+100311562
+100317122
+100235484
+100309928
+100126734
+100309964
+100309934
+100309940
+100311564
+100311720
+100309968
+100310180
+100320560
+100310098
+100312604
+100333800
+100309978
+100322997
+100321228
+100356304
+100322852
+100309990
+100312114
+100310078
+100310002
+100310004
+100310006
+100317347
+100324121
+100310030
+100310024
+100310028
+100335651
+100319052
+100310036
+100233472
+100312634
+100098152
+100310050
+100312798
+100310054
+100312372
+100310058
+100310062
+100310068
+100310076
+100310080
+100270290
+100310104
+100316935
+100310106
+100310108
+100320426
+100310116
+100310130
+100311530
+100311592
+100310132
+100310138
+100320722
+100312858
+100310160
+100311528
+100312160
+100310502
+100310172
+100324701
+100320867
+100344898
+100320805
+100290972
+100310274
+100314714
+100311714
+100310216
+100310220
+100310222
+100328313
+100317045
+100310248
+100333789
+100310252
+100317376
+100310256
+100310260
+100310264
+100324674
+100310286
+100322143
+100310296
+100310300
+100310310
+100176094
+100312718
+100312322
+100313278
+100310484
+100310362
+100317569
+100328810
+100311830
+100310508
+100320452
+100310404
+100310594
+100315280
+100311452
+100320362
+100329324
+100219540
+100311682
+100310492
+100314202
+100312028
+100312706
+100324119
+100310536
+100163020
+100324366
+100323079
+100311096
+100310558
+100316470
+100317417
+100311010
+100190736
+100323155
+100311124
+100310580
+100310582
+100316404
+100312538
+100319222
+100311262
+100333466
+100353036
+100296392
+100310620
+100310990
+100310622
+100311526
+100310644
+100311192
+100318474
+100318084
+100304522
+100310652
+100311294
+100322927
+100316162
+100249494
+100291356
+100290586
+100353922
+100154922
+100332923
+100359152
+100206518
+100361365
+100115206
+100327022
+100331690
+100318964
+100203800
+100285326
+100306990
+100355826
+100224534
+100359444
+100291900
+100291558
+100329841
+100329519
+100289788
+100093628
+100330831
+100363103
+100331910
+100128604
+100338454
+100034674
+100290588
+100071142
+100341519
+100045878
+100340905
+100272242
+100281010
+100343193
+100326580
+100335785
+100325164
+100277916
+100335431
+100333162
+100313660
+100307660
+100286900
+100317187
+100329371
+100195020
+100357660
+100314246
+100354642
+100119672
+100078688
+100091028
+100102042
+100072050
+100323910
+100308624
+100301472
+100069006
+100308078
+100322563
+100285950
+100199340
+100256198
+100318028
+100281862
+100334033
+100317640
+100286408
+100354631
+100363037
+100327028
+100308048
+100327026
+100093464
+100361351
+100154454
+100341937
+100230518
+100351480
+100026586
+100309336
+100339723
+100246720
+100361050
+100343470
+100238598
+100332717
+100349290
+100351855
+100321971
+100158166
+100309518
+100327268
+100357691
+100291082
+100358484
+100216670
+100329378
+100257880
+100291398
+100354677
+100361931
+100285944
+100335887
+100339549
+100351343
+100288542
+100329390
+100250070
+100354962
+100345762
+100079130
+100330009
+100307900
+100355265
+100276244
+100087722
+100306894
+100352102
+100317453
+100168562
+100180374
+100097622
+100349767
+100351750
+100155190
+100334777
+100337398
+100065150
+100261714
+100074660
+100330458
+100354811
+100066956
+100339557
+100353456
+100329405
+100361706
+100087228
+100115892
+100330249
+100289970
+100351349
+100329957
+100330070
+100319879
+100341490
+100254578
+100338584
+100305774
+100242624
+100285110
+100216260
+100307654
+100354393
+100220682
+100290154
+100353549
+100243886
+100282212
+100307246
+100294334
+100252260
+100356410
+100281514
+100341639
+100215480
+100289816
+100329427
+100231012
+100290490
+100298872
+100334227
+100300758
+100289882
+100302518
+100362029
+100344091
+100074992
+100325991
+100329623
+100332028
+100323601
+100327258
+100081232
+100291268
+100335940
+100283716
+100144590
+100260200
+100312022
+100176908
+100339725
+100175032
+100129068
+100355060
+100271366
+100190146
+100241604
+100329436
+100329438
+100271762
+100361068
+100276772
+100330007
+100135806
+100329440
+100010832
+100332746
+100330067
+100332661
+100130342
+100332352
+100339426
+100352563
+100212612
+100289996
+100306600
+100291300
+100355481
+100355900
+100147338
+100318666
+100114722
+100250204
+100322083
+100307636
+100258682
+100290124
+100329444
+100335045
+100195134
+100332748
+100329447
+100329448
+100088978
+100236090
+100282286
+100353318
+100085452
+100243032
+100329456
+100329457
+100155512
+100195716
+100318018
+100109572
+100306624
+100360197
+100290388
+100352725
+100340577
+100290764
+100288652
+100288598
+100274214
+100333402
+100329462
+100360824
+100187802
+100353039
+100353439
+100329713
+100329489
+100096708
+100353368
+100357760
+100329494
+100329495
+100355039
+100354464
+100328411
+100325204
+100354390
+100159280
+100111158
+100355914
+100220900
+100329497
+100314740
+100162038
+100067946
+100310236
+100292788
+100002242
+100307532
+100184236
+100329500
+100144460
+100130042
+100038210
+100331111
+100329504
+100355427
+100280710
+100329514
+100329515
+100355003
+100355394
+100196692
+100329655
+100329518
+100344214
+100352481
+100355736
+100306844
+100361298
+100329521
+100342703
+100335813
+100311718
+100361682
+100338922
+100308642
+100357174
+100305874
+100362317
+100148392
+100354941
+100126354
+100304398
+100084028
+100290390
+100360937
+100276888
+100358089
+100339311
+100187374
+100356859
+100338140
+100329994
+100329993
+100329526
+100353492
+100006136
+100332251
+100303614
+100355682
+100326592
+100204224
+100355023
+100216750
+100117474
+100289998
+100329529
+100329537
+100329752
+100265108
+100321957
+100338683
+100361695
+100235648
+100338546
+100331046
+100325254
+100186920
+100287686
+100008396
+100068084
+100354127
+100138030
+100037206
+100352868
+100031810
+100307346
+100344421
+100315120
+100313342
+100329544
+100329545
+100131944
+100060854
+100360734
+100144470
+100111598
+100153680
+100148036
+100356839
+100148652
+100168778
+100339893
+100332490
+100088234
+100302202
+100267894
+100290342
+100290336
+100329551
+100337927
+100332272
+100312804
+100320029
+100329552
+100256626
+100361846
+100329555
+100329564
+100155380
+100272428
+100329573
+100315648
+100329577
+100349954
+100249068
+100335243
+100049958
+100078224
+100330396
+100148360
+100300884
+100297050
+100202664
+100343044
+100094960
+100344455
+100162230
+100282300
+100154274
+100280100
+100250336
+100290962
+100313478
+100333118
+100361659
+100013192
+100329587
+100045870
+100225614
+100329588
+100329590
+100290358
+100253776
+100035304
+100226762
+100217812
+100145460
+100315770
+100122566
+100329599
+100359681
+100355731
+100214992
+100287232
+100352819
+100329614
+100059352
+100290362
+100355130
+100053584
+100315230
+100161406
+100287788
+100204004
+100329616
+100360927
+100262144
+100341763
+100329618
+100287002
+100329620
+100281196
+100355461
+100341893
+100051814
+100307144
+100342864
+100194222
+100242634
+100121626
+100329627
+100102880
+100278236
+100007932
+100173304
+100356647
+100352269
+100338173
+100275038
+100212374
+100314784
+100344954
+100329632
+100338864
+100329634
+100062056
+100344873
+100312598
+100329637
+100314406
+100331245
+100136886
+100164468
+100203028
+100343269
+100262926
+100180786
+100291590
+100291586
+100358720
+100253908
+100181750
+100248994
+100262258
+100329641
+100293064
+100162046
+100330194
+100329644
+100331777
+100363218
+100054968
+100072866
+100271996
+100179594
+100329645
+100239182
+100362769
+100209060
+100332095
+100213424
+100213330
+100073568
+100333792
+100329665
+100334280
+100330546
+100351491
+100303242
+100092984
+100125632
+100358022
+100253142
+100012300
+100329684
+100307430
+100357597
+100307682
+100259434
+100333034
+100065308
+100345683
+100329697
+100154826
+100185602
+100300604
+100309044
+100249010
+100290946
+100329702
+100329710
+100339281
+100283986
+100355729
+100344340
+100122660
+100293428
+100280048
+100287892
+100361393
+100341398
+100342839
+100052892
+100360998
+100330387
+100329983
+100329981
+100130252
+100307184
+100291582
+100330846
+100334706
+100333165
+100329949
+100329745
+100329748
+100329750
+100330002
+100329755
+100330005
+100122526
+100329759
+100155268
+100329760
+100038516
+100176732
+100197652
+100303520
+100282318
+100209086
+100343123
+100343046
+100290884
+100329787
+100362952
+100340072
+100292242
+100354771
+100154384
+100329802
+100140944
+100351918
+100334795
+100029086
+100329987
+100318710
+100330050
+100329818
+100065082
+100330048
+100329823
+100343768
+100332713
+100308864
+100329865
+100323551
+100329830
+100353786
+100327583
+100263814
+100084440
+100337976
+100329844
+100194100
+100345977
+100084026
+100294340
+100335537
+100284912
+100256952
+100334367
+100330882
+100308442
+100298500
+100111874
+100214088
+100139758
+100321332
+100314632
+100256640
+100231878
+100329869
+100330045
+100255364
+100264370
+100084198
+100285142
+100069866
+100332277
+100031374
+100194494
+100110912
+100196812
+100221084
+100235334
+100190848
+100186908
+100213908
+100221342
+100250210
+100357464
+100329944
+100354879
+100301432
+100295388
+100302418
+100297742
+100263408
+100115644
+100356339
+100307130
+100308180
+100312990
+100329882
+100329883
+100074486
+100222572
+100221772
+100289838
+100059272
+100329890
+100329919
+100344480
+100259938
+100329899
+100107886
+100290544
+100329908
+100329910
+100355917
+100016882
+100340179
+100290666
+100329913
+100329914
+100318834
+100347624
+100332429
+100342024
+100353393
+100048592
+100206226
+100329917
+100353865
+100358577
+100246086
+100146772
+100353609
+100201212
+100245486
+100195208
+100330170
+100329929
+100230644
+100306960
+100063370
+100052886
+100032262
+100108390
+100276548
+100334987
+100291048
+100313164
+100082572
+100327707
+100359591
+100195316
+100069304
+100343395
+100200550
+100345289
+100310158
+100304332
+100329952
+100248810
+100131912
+100361643
+100329958
+100240594
+100143130
+100329978
+100339435
+100333433
+100264266
+100033098
+100333485
+100329998
+100330225
+100330000
+100253384
+100155658
+100167868
+100332901
+100201746
+100334770
+100307066
+100306852
+100330010
+100357908
+100318262
+100290026
+100271132
+100330020
+100307868
+100330022
+100290354
+100330031
+100288894
+100330034
+100290218
+100288892
+100332926
+100104936
+100063432
+100307112
+100307664
+100308656
+100311424
+100318686
+100330051
+100123362
+100330055
+100330058
+100014336
+100025608
+100194784
+100330060
+100210276
+100109706
+100249924
+100356616
+100334198
+100330072
+100155684
+100290272
+100352934
+100330291
+100277908
+100330082
+100330093
+100354718
+100330103
+100052864
+100330113
+100330114
+100330116
+100332212
+100330127
+100136160
+100333135
+100330138
+100327098
+100308338
+100149244
+100228988
+100283570
+100086002
+100212264
+100185058
+100214544
+100281318
+100216472
+100354693
+100067030
+100082064
+100183166
+100244484
+100363099
+100290186
+100176404
+100172754
+100330982
+100202016
+100342824
+100330161
+100330164
+100330165
+100201578
+100201576
+100330167
+100240748
+100219516
+100199876
+100331259
+100251372
+100330447
+100330900
+100269538
+100152000
+100355627
+100330204
+100333481
+100330206
+100316761
+100296102
+100097906
+100195442
+100124426
+100290614
+100292356
+100310530
+100330209
+100330210
+100330211
+100139864
+100330220
+100330226
+100240078
+100268534
+100330935
+100330238
+100332651
+100212832
+100057560
+100314726
+100330252
+100342615
+100351082
+100180836
+100330262
+100275216
+100262922
+100281572
+100063740
+100317704
+100330268
+100254844
+100330269
+100330272
+100117778
+100313450
+100291180
+100290690
+100290894
+100333460
+100027676
+100269446
+100343204
+100361761
+100354514
+100278796
+100330566
+100330287
+100278206
+100290280
+100291508
+100131578
+100272442
+100305904
+100290242
+100354913
+100305246
+100192624
+100314952
+100330289
+100354487
+100225584
+100347287
+100357010
+100310352
+100216482
+100330530
+100318642
+100306588
+100332789
+100187654
+100330309
+100197338
+100214062
+100320772
+100331974
+100354974
+100318634
+100117282
+100155662
+100306924
+100306946
+100345495
+100330312
+100318550
+100354632
+100154540
+100268148
+100169616
+100330324
+100361495
+100330330
+100330332
+100332889
+100169524
+100216516
+100330821
+100330352
+100289852
+100199390
+100244030
+100330357
+100085868
+100233832
+100331464
+100330377
+100338921
+100290716
+100060280
+100099150
+100330393
+100252904
+100205976
+100240910
+100353061
+100323083
+100196432
+100330398
+100330397
+100213492
+100202120
+100306918
+100330401
+100330402
+100343715
+100081476
+100029220
+100330412
+100188396
+100330413
+100330414
+100330778
+100330420
+100330421
+100252536
+100330430
+100237792
+100341488
+100290482
+100360739
+100330432
+100307790
+100310452
+100349821
+100284240
+100093236
+100330462
+100131588
+100282614
+100330636
+100330472
+100330474
+100330475
+100244746
+100306934
+100332833
+100267492
+100154708
+100076674
+100195698
+100330478
+100330480
+100161458
+100252274
+100193848
+100330485
+100172340
+100341274
+100339991
+100123320
+100206462
+100000890
+100245706
+100158802
+100069056
+100360832
+100330502
+100314360
+100313264
+100147412
+100240920
+100313380
+100155140
+100213902
+100217290
+100249938
+100243026
+100241684
+100227804
+100245184
+100040758
+100330503
+100195056
+100179324
+100311478
+100332912
+100085594
+100283060
+100330508
+100330509
+100330510
+100327374
+100168946
+100145784
+100341609
+100342044
+100331132
+100330533
+100294116
+100347256
+100291344
+100226592
+100211626
+100330543
+100147608
+100330545
+100260766
+100333055
+100308124
+100331095
+100272220
+100228162
+100338663
+100331929
+100331940
+100169028
+100290532
+100110928
+100313172
+100302358
+100332343
+100180180
+100330707
+100312338
+100342328
+100155068
+100114062
+100248714
+100266920
+100064762
+100334341
+100286660
+100289984
+100294898
+100290638
+100299406
+100293964
+100299932
+100294452
+100293350
+100299602
+100301090
+100301444
+100312012
+100316339
+100311556
+100332948
+100333274
+100255106
+100330586
+100342648
+100216754
+100161730
+100318736
+100055160
+100313328
+100059100
+100354880
+100354905
+100354659
+100354734
+100330561
+100325565
+100330645
+100331382
+100267008
+100330567
+100164262
+100043046
+100071510
+100263524
+100155262
+100153164
+100319060
+100009782
+100118064
+100080246
+100101702
+100174838
+100315532
+100330577
+100290220
+100310772
+100341416
+100330598
+100327030
+100330977
+100315900
+100347450
+100327360
+100330591
+100113838
+100271368
+100352864
+100314288
+100103006
+100279664
+100344567
+100330594
+100330836
+100335618
+100332781
+100154358
+100341629
+100339655
+100330607
+100330608
+100186858
+100310202
+100330610
+100186868
+100291360
+100328956
+100331314
+100325569
+100166340
+100330627
+100244502
+100293898
+100330646
+100177798
+100332701
+100330649
+100327046
+100261810
+100211366
+100207264
+100274046
+100310410
+100353678
+100211174
+100330654
+100155690
+100154726
+100082022
+100180790
+100155564
+100319466
+100307798
+100126308
+100249532
+100354636
+100045756
+100307114
+100306908
+100306980
+100100904
+100307326
+100307248
+100307504
+100330658
+100330659
+100349623
+100285618
+100330668
+100166478
+100330670
+100294134
+100332641
+100331054
+100114876
+100108848
+100330833
+100332303
+100330777
+100330946
+100330687
+100283014
+100316618
+100330968
+100332123
+100330689
+100330691
+100330692
+100361704
+100162892
+100351930
+100319721
+100330704
+100329172
+100089464
+100242066
+100275434
+100330716
+100330718
+100330720
+100303354
+100341847
+100111930
+100215740
+100059788
+100330725
+100355727
+100330744
+100330750
+100220184
+100327424
+100309874
+100170918
+100223208
+100238088
+100230590
+100189072
+100330754
+100262652
+100298524
+100333164
+100330762
+100135648
+100330770
+100330771
+100285560
+100290326
+100344836
+100330788
+100358267
+100330805
+100332556
+100355725
+100120206
+100330809
+100331114
+100184800
+100332120
+100331108
+100342492
+100157620
+100245872
+100322870
+100119300
+100331139
+100146754
+100139680
+100327432
+100327422
+100327416
+100327434
+100331152
+100324557
+100333061
+100350317
+100330832
+100290290
+100330841
+100330843
+100348840
+100100852
+100257218
+100331907
+100318164
+100092002
+100314652
+100335633
+100132816
+100330855
+100227084
+100361712
+100332484
+100330858
+100330860
+100049364
+100330873
+100330876
+100331275
+100332492
+100330878
+100330892
+100332488
+100276030
+100122670
+100206784
+100313656
+100330904
+100120074
+100109124
+100154516
+100317932
+100128754
+100330905
+100351221
+100330908
+100073866
+100248356
+100085364
+100255308
+100233692
+100054590
+100245762
+100330920
+100275920
+100334607
+100288558
+100190176
+100309646
+100042408
+100257638
+100350910
+100035458
+100350447
+100320782
+100203512
+100330940
+100108328
+100259790
+100358757
+100361258
+100330942
+100083304
+100149860
+100266192
+100330948
+100330957
+100330958
+100311706
+100132116
+100168868
+100108296
+100320331
+100351441
+100133684
+100332658
+100339251
+100330997
+100344110
+100164502
+100307062
+100331007
+100057624
+100359423
+100363391
+100330975
+100305630
+100319539
+100155360
+100132210
+100147470
+100080930
+100260860
+100338256
+100335100
+100310214
+100325176
+100079968
+100196150
+100333148
+100331012
+100152650
+100331021
+100342472
+100332581
+100317173
+100354272
+100113482
+100171182
+100123604
+100143460
+100169308
+100242956
+100138394
+100290244
+100249296
+100250982
+100162120
+100332955
+100285316
+100332605
+100331035
+100352786
+100335037
+100107830
+100000554
+100332619
+100052200
+100321238
+100359422
+100066954
+100253820
+100069206
+100332236
+100254394
+100334872
+100343440
+100213154
+100332737
+100355545
+100331843
+100048042
+100315503
+100258320
+100331049
+100169188
+100239252
+100332697
+100331051
+100331055
+100333850
+100344054
+100331057
+100138984
+100281754
+100331060
+100272268
+100158650
+100252514
+100095168
+100331590
+100131832
+100230724
+100331064
+100333080
+100283122
+100196976
+100331066
+100332445
+100339379
+100219518
+100130636
+100331075
+100008398
+100198452
+100290510
+100219296
+100331077
+100265040
+100106386
+100160674
+100331078
+100290746
+100067640
+100345010
+100157844
+100307616
+100333468
+100152486
+100153772
+100165692
+100291354
+100331083
+100069072
+100332295
+100249016
+100157432
+100361687
+100255458
+100335387
+100335964
+100307562
+100355268
+100070428
+100197908
+100340350
+100346855
+100331148
+100233234
+100258864
+100332142
+100228532
+100180428
+100319997
+100310288
+100288094
+100129244
+100178402
+100331105
+100270668
+100334249
+100033944
+100261118
+100071332
+100237606
+100331123
+100331124
+100291328
+100245692
+100349056
+100337790
+100357006
+100037310
+100349326
+100188310
+100331135
+100235972
+100148816
+100155634
+100067860
+100222860
+100331140
+100255018
+100108998
+100331142
+100338308
+100254880
+100196738
+100354705
+100218764
+100331146
+100331257
+100361081
+100331150
+100124844
+100331985
+100039390
+100331163
+100278200
+100195224
+100186640
+100193944
+100287300
+100196884
+100334627
+100309370
+100158544
+100331166
+100138396
+100352629
+100332707
+100198562
+100244326
+100254106
+100334339
+100331185
+100331186
+100198742
+100203804
+100173136
+100158074
+100243906
+100174172
+100323633
+100331200
+100331191
+100291304
+100185290
+100290784
+100112308
+100189326
+100312730
+100289634
+100197634
+100144784
+100307218
+100112916
+100332542
+100291190
+100314468
+100331212
+100238686
+100334659
+100331222
+100228582
+100266818
+100058150
+100362709
+100254012
+100315270
+100311926
+100318684
+100279350
+100188294
+100340822
+100309036
+100256324
+100289484
+100332715
+100298666
+100333686
+100335807
+100351788
+100331256
+100149970
+100142170
+100244540
+100192826
+100331270
+100323167
+100331278
+100331287
+100289360
+100194700
+100056956
+100196816
+100218180
+100320129
+100271102
+100196734
+100100384
+100331292
+100331293
+100234052
+100332766
+100331298
+100215924
+100331301
+100265906
+100331305
+100107514
+100354232
+100332665
+100087054
+100067558
+100331325
+100065528
+100248438
+100103630
+100333133
+100122416
+100331600
+100331327
+100212796
+100331896
+100331331
+100044412
+100331333
+100046168
+100323299
+100214330
+100174514
+100204296
+100204294
+100331338
+100339275
+100332750
+100244872
+100308096
+100331341
+100319565
+100331345
+100073374
+100331346
+100331347
+100029180
+100331352
+100215256
+100295168
+100331356
+100332999
+100178924
+100203670
+100281258
+100331361
+100227100
+100352144
+100331363
+100331364
+100331365
+100290656
+100000908
+100010368
+100331367
+100201206
+100331368
+100331369
+100291428
+100195472
+100285466
+100307204
+100037884
+100203664
+100136332
+100331379
+100338278
+100280076
+100359250
+100183570
+100066038
+100011084
+100313774
+100331385
+100054226
+100333784
+100225768
+100341926
+100291292
+100096630
+100331431
+100331398
+100331400
+100266314
+100331402
+100180018
+100331410
+100331426
+100223200
+100177998
+100054746
+100332465
+100335744
+100331451
+100197930
+100253068
+100331433
+100111228
+100273266
+100331435
+100333116
+100033404
+100247210
+100203200
+100134836
+100300660
+100152026
+100046462
+100331438
+100057988
+100333978
+100341849
+100279488
+100182952
+100078960
+100063320
+100020174
+100075532
+100085936
+100092600
+100155226
+100181862
+100196866
+100172334
+100244756
+100252282
+100236232
+100331442
+100331443
+100275410
+100119204
+100178020
+100118564
+100103654
+100125434
+100333172
+100111796
+100103058
+100348838
+100102542
+100135288
+100356507
+100331453
+100261146
+100248430
+100269620
+100155256
+100058804
+100246116
+100331916
+100201872
+100256970
+100332763
+100334152
+100095980
+100315710
+100342100
+100331492
+100331491
+100331493
+100331502
+100060428
+100331505
+100331529
+100251316
+100296272
+100060878
+100079220
+100163874
+100338352
+100303382
+100334733
+100098608
+100175892
+100331534
+100213840
+100331535
+100331536
+100154648
+100338109
+100331539
+100331548
+100319082
+100078310
+100331552
+100286958
+100147536
+100153456
+100331554
+100307388
+100361719
+100331556
+100331558
+100263034
+100331559
+100331560
+100332698
+100307944
+100331563
+100331564
+100032082
+100219110
+100331567
+100067786
+100300086
+100331569
+100263102
+100089600
+100282416
+100267686
+100166018
+100173054
+100102614
+100154608
+100064360
+100247564
+100243926
+100331571
+100331572
+100232116
+100331576
+100331578
+100331611
+100331612
+100332249
+100331581
+100182820
+100331793
+100331646
+100333262
+100331620
+100277976
+100359380
+100278278
+100331622
+100355892
+100331650
+100331648
+100047864
+100124976
+100332723
+100051590
+100331654
+100355309
+100332724
+100154504
+100331677
+100321769
+100350916
+100331676
+100331688
+100180784
+100331687
+100347068
+100331691
+100331694
+100169882
+100354978
+100308112
+100297188
+100172638
+100333867
+100331709
+100331697
+100128176
+100331704
+100275146
+100331698
+100167532
+100337667
+100116560
+100331701
+100323707
+100065934
+100269078
+100331732
+100331711
+100304290
+100210374
+100218152
+100338191
+100135966
+100149126
+100312642
+100195358
+100046672
+100331746
+100036604
+100187956
+100247232
+100275260
+100354977
+100231550
+100338825
+100337975
+100351439
+100246150
+100320430
+100331760
+100286056
+100306350
+100117076
+100282396
+100075434
+100350770
+100289924
+100322455
+100289952
+100331762
+100122216
+100123170
+100131576
+100186678
+100087138
+100201476
+100254326
+100092636
+100109788
+100332033
+100102802
+100291400
+100331766
+100291574
+100140596
+100353725
+100316721
+100039702
+100346597
+100131018
+100334464
+100361856
+100309580
+100331784
+100138680
+100331785
+100187688
+100342302
+100135362
+100245968
+100356494
+100312544
+100273692
+100331788
+100315467
+100331789
+100332003
+100291140
+100331792
+100331795
+100331797
+100342808
+100290956
+100333146
+100331984
+100331815
+100331808
+100331811
+100331813
+100128172
+100135552
+100124192
+100361143
+100332262
+100100128
+100206526
+100331817
+100201926
+100234880
+100302750
+100190284
+100246894
+100363035
+100284414
+100054346
+100190544
+100359416
+100109456
+100331818
+100332035
+100154842
+100288932
+100029386
+100322251
+100355723
+100157288
+100199856
+100205042
+100100340
+100279620
+100331825
+100246148
+100332273
+100331828
+100306892
+100331830
+100331840
+100331842
+100339002
+100301452
+100331852
+100088032
+100331867
+100162164
+100331865
+100280266
+100333453
+100360871
+100331869
+100331873
+100331883
+100162906
+100331885
+100331886
+100334824
+100259062
+100207016
+100058704
+100091012
+100049830
+100089220
+100050588
+100331905
+100331908
+100140542
+100331911
+100332341
+100339169
+100331914
+100262208
+100332068
+100331917
+100258142
+100331919
+100331921
+100282800
+100331924
+100331925
+100331927
+100331931
+100070770
+100307662
+100052412
+100264382
+100053488
+100331948
+100331950
+100332122
+100106538
+100078678
+100180402
+100334887
+100332146
+100331967
+100331968
+100331970
+100331972
+100300658
+100332757
+100341624
+100324514
+100094660
+100151710
+100099386
+100062360
+100217356
+100047012
+100331975
+100332041
+100326502
+100326821
+100046012
+100331977
+100042080
+100331979
+100252560
+100331982
+100254750
+100333122
+100059294
+100181254
+100240144
+100285152
+100165876
+100128058
+100320047
+100097930
+100156934
+100340341
+100331989
+100171208
+100246724
+100215252
+100331997
+100246118
+100332000
+100193868
+100182256
+100332013
+100073888
+100055224
+100259348
+100310140
+100246142
+100237734
+100169310
+100085620
+100133410
+100332910
+100332030
+100332042
+100326018
+100351060
+100334199
+100055294
+100275532
+100063978
+100354547
+100332048
+100333826
+100242168
+100208392
+100332065
+100332066
+100012804
+100332070
+100198128
+100332088
+100309606
+100332090
+100332752
+100342613
+100332448
+100332093
+100332096
+100155270
+100121676
+100095426
+100079672
+100099378
+100353910
+100332104
+100332890
+100281254
+100332952
+100332126
+100132418
+100363403
+100332487
+100362994
+100093232
+100333138
+100094788
+100279836
+100332169
+100332137
+100338970
+100332143
+100351823
+100245994
+100337978
+100332149
+100332158
+100332160
+100332165
+100337954
+100332180
+100332179
+100238924
+100332191
+100136006
+100186084
+100214656
+100160868
+100332201
+100333004
+100282702
+100289766
+100168690
+100363402
+100203280
+100332205
+100332968
+100332208
+100332214
+100332222
+100332224
+100339993
+100333082
+100155456
+100332232
+100332233
+100333605
+100332958
+100332255
+100332257
+100333000
+100332261
+100279838
+100168560
+100178526
+100167072
+100148974
+100339000
+100332436
+100302116
+100332296
+100333392
+100203428
+100333853
+100332300
+100332301
+100128106
+100352972
+100346083
+100333013
+100332307
+100181736
+100169652
+100338575
+100281326
+100333120
+100335039
+100332330
+100332332
+100031404
+100200004
+100334386
+100262068
+100332338
+100245960
+100084708
+100258664
+100332611
+100266878
+100332357
+100332365
+100139806
+100332368
+100068200
+100160666
+100332371
+100332372
+100361234
+100332375
+100332384
+100332385
+100332386
+100333644
+100343318
+100332397
+100332406
+100332407
+100333094
+100335836
+100332419
+100358740
+100332427
+100332430
+100332431
+100332432
+100355720
+100334109
+100279544
+100198892
+100310632
+100152194
+100161446
+100332449
+100256982
+100348273
+100332459
+100110034
+100333077
+100332461
+100177248
+100332463
+100243506
+100008748
+100363400
+100353577
+100340952
+100332468
+100341040
+100332529
+100332532
+100332544
+100332547
+100332546
+100308100
+100332549
+100332554
+100332551
+100065794
+100151996
+100332553
+100230750
+100295296
+100332950
+100354563
+100332580
+100198162
+100136130
+100281060
+100125062
+100095564
+100333248
+100323920
+100154916
+100334828
+100356677
+100332794
+100332819
+100057080
+100214556
+100108270
+100306194
+100332827
+100332830
+100363333
+100335973
+100318112
+100259836
+100332837
+100332844
+100178270
+100332845
+100332847
+100332849
+100332850
+100332851
+100084288
+100332855
+100310284
+100334574
+100143814
+100333333
+100359387
+100332859
+100333397
+100338516
+100170802
+100363325
+100088580
+100332872
+100332873
+100332874
+100334219
+100332883
+100333288
+100333127
+100315212
+100063170
+100334890
+100350637
+100067856
+100333011
+100333025
+100335934
+100318492
+100333014
+100333016
+100333039
+100333064
+100313788
+100333075
+100010048
+100333083
+100333093
+100272338
+100334389
+100341830
+100355004
+100334862
+100310394
+100334978
+100333195
+100333196
+100333198
+100360359
+100333200
+100333202
+100281696
+100333215
+100333251
+100363324
+100340581
+100154472
+100216430
+100334122
+100213160
+100358679
+100334929
+100089250
+100334416
+100333276
+100031460
+100333265
+100004292
+100275030
+100133220
+100194504
+100333278
+100333287
+100261650
+100177022
+100085106
+100339815
+100335886
+100212502
+100339805
+100333330
+100363318
+100102690
+100333302
+100078926
+100025352
+100230010
+100333303
+100333305
+100333308
+100333309
+100333311
+100333565
+100175762
+100333379
+100107372
+100358837
+100333358
+100333363
+100333364
+100339862
+100333380
+100187160
+100023430
+100333381
+100333382
+100338855
+100132412
+100363296
+100333387
+100338311
+100333395
+100333396
+100334707
+100103590
+100333403
+100333405
+100333407
+100333411
+100333416
+100334640
+100333427
+100333429
+100277526
+100041042
+100307640
+100255500
+100333796
+100333430
+100158798
+100333500
+100129346
+100334584
+100312600
+100333435
+100029514
+100363278
+100160082
+100352091
+100197802
+100204778
+100334600
+100333487
+100333488
+100173630
+100275340
+100343330
+100359285
+100144838
+100327420
+100333499
+100224928
+100333509
+100333510
+100333511
+100333512
+100333513
+100333514
+100339724
+100333518
+100333527
+100333529
+100333539
+100333541
+100254980
+100095174
+100311004
+100291342
+100334797
+100173174
+100361726
+100311614
+100333939
+100333552
+100333560
+100333561
+100333937
+100363277
+100333564
+100163276
+100333567
+100341257
+100333579
+100333569
+100333570
+100045144
+100334337
+100333571
+100334088
+100333572
+100363276
+100057818
+100333576
+100294010
+100333577
+100335814
+100340360
+100354235
+100085314
+100333581
+100333582
+100334061
+100333584
+100333586
+100333588
+100333589
+100333590
+100094364
+100161382
+100333594
+100333825
+100308260
+100333603
+100066666
+100341241
+100337991
+100363275
+100333630
+100334629
+100333632
+100333634
+100067600
+100333636
+100339842
+100333645
+100144620
+100295126
+100333783
+100053506
+100218766
+100333654
+100333655
+100154248
+100333656
+100352345
+100333657
+100333659
+100333662
+100101744
+100131222
+100333666
+100346864
+100333669
+100352075
+100333671
+100333673
+100308010
+100333682
+100333684
+100167784
+100275928
+100333687
+100355278
+100068866
+100087840
+100341263
+100333698
+100359385
+100333700
+100333702
+100334812
+100161520
+100200084
+100281926
+100275370
+100283478
+100060266
+100363266
+100232840
+100357375
+100233434
+100333712
+100333715
+100333717
+100105878
+100335920
+100344435
+100206112
+100355649
+100170892
+100282988
+100334956
+100338988
+100348598
+100333729
+100309358
+100291982
+100333733
+100333734
+100338171
+100333737
+100196800
+100333746
+100333749
+100333753
+100054938
+100333755
+100333756
+100333766
+100333774
+100334804
+100333803
+100333805
+100333807
+100355081
+100333818
+100281522
+100333809
+100338097
+100333821
+100333829
+100240454
+100028732
+100333847
+100333830
+100363262
+100333846
+100067560
+100192854
+100333854
+100333855
+100254488
+100334094
+100333944
+100195834
+100361795
+100207242
+100221042
+100338608
+100241058
+100282960
+100266342
+100333868
+100333877
+100333881
+100333882
+100333884
+100338745
+100342493
+100333893
+100333896
+100333897
+100333898
+100333906
+100333916
+100230140
+100087814
+100333922
+100355716
+100333925
+100333926
+100262166
+100116022
+100333928
+100290142
+100333933
+100333935
+100312336
+100177776
+100333936
+100334816
+100334912
+100157170
+100333946
+100248854
+100335282
+100358590
+100333957
+100175278
+100333980
+100335720
+100234318
+100363260
+100058426
+100335392
+100182830
+100348456
+100333994
+100334002
+100334010
+100334623
+100334013
+100334015
+100270648
+100296104
+100363248
+100226568
+100334027
+100253198
+100334035
+100334037
+100334485
+100334057
+100363247
+100334060
+100334073
+100166974
+100199594
+100291588
+100334075
+100334077
+100043816
+100011178
+100363245
+100334089
+100334090
+100242348
+100334096
+100334098
+100334099
+100154712
+100341627
+100361605
+100334111
+100334112
+100361751
+100334114
+100296620
+100299124
+100334118
+100265248
+100338674
+100137608
+100334120
+100334853
+100265426
+100344299
+100247854
+100182962
+100209100
+100334128
+100357144
+100334137
+100334139
+100334141
+100334142
+100334144
+100351336
+100334709
+100334980
+100334160
+100334163
+100334165
+100360752
+100334169
+100334171
+100334187
+100334202
+100334203
+100334231
+100352147
+100334234
+100349581
+100067090
+100341902
+100300998
+100334243
+100301772
+100334248
+100191780
+100334259
+100334262
+100334264
+100334981
+100334276
+100334279
+100334502
+100363244
+100030520
+100359382
+100339246
+100334285
+100286208
+100334297
+100297922
+100334301
+100306208
+100271042
+100334885
+100334312
+100334661
+100334313
+100334315
+100334316
+100334834
+100334326
+100334928
+100298574
+100298206
+100297388
+100298396
+100334401
+100334343
+100334728
+100334345
+100361309
+100334357
+100352788
+100334383
+100334760
+100080834
+100334390
+100334392
+100283586
+100334410
+100334393
+100334396
+100260366
+100334397
+100334726
+100334399
+100363242
+100334916
+100248558
+100334414
+100290952
+100044178
+100353260
+100334426
+100334435
+100334739
+100334438
+100334439
+100277140
+100322523
+100334440
+100363232
+100212386
+100334734
+100334460
+100334462
+100337779
+100074696
+100334468
+100334478
+100334479
+100161692
+100334480
+100352078
+100334494
+100231018
+100334499
+100334503
+100363231
+100158250
+100362149
+100334511
+100363229
+100334523
+100334524
+100353724
+100334527
+100334544
+100334545
+100344158
+100334954
+100334555
+100279322
+100013370
+100044318
+100334557
+100206276
+100334559
+100124274
+100334877
+100348189
+100351993
+100341804
+100334563
+100334656
+100348175
+100334570
+100195052
+100334594
+100334756
+100363213
+100342497
+100334658
+100343985
+100337757
+100334582
+100341641
+100342505
+100354976
+100334818
+100232630
+100334587
+100334588
+100158714
+100334595
+100031006
+100334597
+100336537
+100296832
+100011008
+100261708
+100188570
+100083934
+100290460
+100339599
+100334602
+100290322
+100334604
+100343207
+100334608
+100334610
+100338804
+100106308
+100334620
+100334622
+100066180
+100334632
+100341148
+100334634
+100334636
+100334637
+100340620
+100311142
+100360313
+100054416
+100353556
+100334649
+100341029
+100334652
+100334681
+100230960
+100222198
+100256014
+100296050
+100271206
+100312518
+100263760
+100334663
+100259832
+100235890
+100253004
+100334779
+100334672
+100338606
+100280516
+100334675
+100334676
+100334679
+100059300
+100321262
+100341850
+100339038
+100334684
+100334686
+100074350
+100354823
+100334692
+100334836
+100363197
+100269046
+100336474
+100334704
+100363196
+100334713
+100309098
+100334718
+100334740
+100334741
+100334962
+100334746
+100334900
+100334903
+100225632
+100336077
+100290226
+100334913
+100100306
+100112436
+100263554
+100152384
+100267590
+100248822
+100273420
+100334958
+100334952
+100334963
+100334977
+100335185
+100334991
+100334994
+100334996
+100335005
+100335563
+100335008
+100335305
+100335630
+100363193
+100153284
+100339130
+100335019
+100335022
+100335024
+100335033
+100119930
+100120798
+100142752
+100090928
+100121160
+100119744
+100114904
+100080926
+100154718
+100363192
+100335043
+100363191
+100335823
+100335718
+100212350
+100362165
+100338795
+100335073
+100291898
+100074396
+100337838
+100350069
+100341225
+100359699
+100335139
+100335315
+100338979
+100335133
+100338666
+100335594
+100335136
+100354973
+100338461
+100335149
+100335150
+100359401
+100335152
+100337916
+100335164
+100335171
+100335180
+100335183
+100312836
+100335187
+100247220
+100251870
+100354661
+100357876
+100335197
+100335199
+100335200
+100336534
+100335212
+100189586
+100146236
+100335213
+100087166
+100335876
+100250068
+100339126
+100049484
+100280094
+100278724
+100268254
+100281340
+100290230
+100051608
+100335229
+100356078
+100335242
+100335246
+100340266
+100119602
+100335614
+100146916
+100291012
+100107932
+100300886
+100064432
+100340426
+100335264
+100335267
+100335268
+100336106
+100335280
+100335639
+100335285
+100335287
+100335289
+100335291
+100335292
+100363190
+100335301
+100335302
+100335726
+100335391
+100335335
+100337610
+100335345
+100296176
+100335361
+100261684
+100335365
+100335366
+100335705
+100335369
+100339381
+100343987
+100338430
+100104630
+100335382
+100335384
+100344181
+100335693
+100335385
+100335389
+100335427
+100040868
+100335401
+100340695
+100356627
+100310146
+100362271
+100054260
+100337930
+100335412
+100341506
+100250032
+100335416
+100335420
+100335424
+100335425
+100351541
+100335761
+100335436
+100185470
+100145470
+100351615
+100335446
+100068518
+100184462
+100335461
+100335463
+100279702
+100335467
+100335468
+100335477
+100335478
+100335480
+100335481
+100335482
+100335496
+100325092
+100335922
+100335809
+100363182
+100343920
+100335724
+100335518
+100351688
+100335538
+100335540
+100335544
+100335542
+100335546
+100335547
+100338202
+100335884
+100335788
+100335743
+100354849
+100335591
+100335893
+100336562
+100363181
+100335599
+100335603
+100338620
+100336274
+100335653
+100335655
+100335664
+100335672
+100335668
+100335670
+100335674
+100335676
+100359209
+100353395
+100335684
+100335689
+100335691
+100340725
+100194730
+100335695
+100335706
+100357264
+100335708
+100335728
+100170410
+100306752
+100029218
+100335763
+100335765
+100335767
+100335769
+100335938
+100248728
+100363180
+100219440
+100359139
+100000006
+100342912
+100335868
+100337333
+100363178
+100335871
+100363169
+100335874
+100335877
+100363167
+100168266
+100335881
+100335889
+100335895
+100102998
+100335925
+100310052
+100343347
+100335942
+100335975
+100335984
+100288124
+100338652
+100336285
+100336003
+100000270
+100336005
+100336009
+100046276
+100323567
+100000212
+100337992
+100336016
+100336019
+100059400
+100336024
+100336026
+100336029
+100338315
+100336034
+100352666
+100353530
+100336038
+100336037
+100336040
+100336044
+100338260
+100336055
+100340962
+100336169
+100336064
+100134842
+100336076
+100336081
+100336082
+100336083
+100336085
+100336087
+100336088
+100359138
+100336095
+100336097
+100336101
+100336104
+100341094
+100343919
+100336123
+100336132
+100338272
+100339706
+100188148
+100340656
+100055722
+100336137
+100336139
+100337031
+100338481
+100018170
+100339349
+100336156
+100336167
+100051636
+100336186
+100336188
+100336189
+100336192
+100336194
+100336202
+100336204
+100347254
+100336205
+100336206
+100336215
+100336216
+100280760
+100352402
+100336789
+100356978
+100271394
+100207776
+100260862
+100285864
+100338020
+100336868
+100337578
+100154958
+100336283
+100336228
+100114694
+100363166
+100338361
+100336252
+100336254
+100336256
+100338420
+100339457
+100069854
+100336286
+100336288
+100336296
+100336298
+100337640
+100336312
+100336323
+100336332
+100337747
+100336342
+100336351
+100276256
+100336360
+100243680
+100147606
+100346487
+100154408
+100338629
+100337717
+100163932
+100361797
+100339951
+100340391
+100336389
+100346086
+100337817
+100036242
+100283056
+100336398
+100336401
+100336404
+100336406
+100336415
+100336417
+100336418
+100337474
+100341823
+100336422
+100336423
+100336426
+100337914
+100355979
+100336455
+100099504
+100339907
+100336464
+100136188
+100336473
+100304872
+100239058
+100336477
+100337847
+100339982
+100338368
+100159950
+100336489
+100336491
+100336493
+100336502
+100338733
+100255684
+100336512
+100336513
+100336515
+100336531
+100342651
+100338246
+100359132
+100336563
+100339232
+100216066
+100352936
+100336569
+100174168
+100299808
+100336598
+100336600
+100336610
+100339496
+100336627
+100336628
+100338714
+100336630
+100344528
+100307592
+100336640
+100203590
+100336642
+100336643
+100192590
+100336645
+100336647
+100336648
+100336658
+100336666
+100338910
+100350941
+100336685
+100336693
+100336704
+100336712
+100338661
+100341914
+100356593
+100350647
+100336718
+100336720
+100336721
+100336725
+100212898
+100336727
+100336730
+100336739
+100336740
+100339942
+100336747
+100336749
+100319384
+100336767
+100336769
+100336778
+100336780
+100336788
+100338161
+100336798
+100345120
+100336818
+100339117
+100336821
+100316897
+100309538
+100336837
+100337750
+100360091
+100336853
+100336854
+100336856
+100336867
+100338289
+100336891
+100318716
+100363358
+100359259
+100263162
+100341917
+100336907
+100336908
+100336910
+100361113
+100336912
+100336923
+100336924
+100336925
+100336926
+100336927
+100336929
+100338258
+100013660
+100339027
+100336936
+100336937
+100351962
+100356680
+100336949
+100336950
+100336951
+100336952
+100336953
+100338074
+100354395
+100336957
+100336958
+100336974
+100336976
+100339570
+100336980
+100121700
+100336988
+100336990
+100116900
+100336993
+100337964
+100347888
+100338302
+100336998
+100337006
+100337021
+100337024
+100208672
+100306994
+100337042
+100337043
+100339478
+100337055
+100287882
+100337056
+100337058
+100337059
+100344111
+100337081
+100337083
+100337084
+100337085
+100337088
+100338506
+100363156
+100344184
+100338515
+100337100
+100248938
+100337099
+100338411
+100341008
+100337123
+100337124
+100247812
+100363155
+100337773
+100337162
+100337799
+100337170
+100120040
+100337172
+100337173
+100340798
+100249404
+100338123
+100337185
+100337187
+100277436
+100338036
+100100104
+100002332
+100079730
+100000580
+100338107
+100045264
+100363154
+100348829
+100289412
+100338438
+100337215
+100337216
+100218044
+100195704
+100155134
+100270448
+100239554
+100363153
+100337221
+100337298
+100353473
+100337223
+100337297
+100356381
+100337249
+100337252
+100337260
+100337277
+100338894
+100351393
+100340100
+100337295
+100285488
+100207322
+100337315
+100337323
+100337335
+100339334
+100337346
+100337347
+100290718
+100337594
+100337357
+100337359
+100337360
+100337379
+100337380
+100239442
+100227966
+100287516
+100337409
+100353345
+100307982
+100337412
+100343346
+100337429
+100337438
+100337447
+100337448
+100337453
+100337454
+100337456
+100337457
+100337475
+100337490
+100341766
+100337494
+100337497
+100337507
+100337508
+100313422
+100337524
+100337525
+100337550
+100337558
+100323717
+100337561
+100256454
+100252634
+100337564
+100337576
+100337580
+100257156
+100337582
+100338712
+100337612
+100337618
+100337620
+100188114
+100291464
+100338017
+100288204
+100340796
+100353745
+100337657
+100356731
+100337659
+100351040
+100337670
+100337686
+100338145
+100337692
+100274934
+100337718
+100337730
+100338141
+100338147
+100316313
+100337759
+100337761
+100337762
+100340415
+100203956
+100339250
+100337785
+100339230
+100363024
+100351825
+100168342
+100149394
+100174794
+100227396
+100185008
+100216816
+100337811
+100337826
+100337835
+100291138
+100252056
+100357680
+100338526
+100337855
+100351676
+100363023
+100337869
+100340951
+100337871
+100337883
+100337884
+100337873
+100337899
+100337908
+100337910
+100290206
+100337958
+100338317
+100338328
+100338330
+100343131
+100338908
+100079024
+100338343
+100339091
+100338946
+100338364
+100363022
+100046802
+100338395
+100338398
+100342399
+100246032
+100341397
+100338439
+100339622
+100338440
+100166710
+100338445
+100338469
+100343907
+100356877
+100265958
+100053482
+100353316
+100312346
+100339233
+100339234
+100363020
+100339329
+100339359
+100339276
+100339280
+100353117
+100339283
+100339292
+100339294
+100339302
+100341901
+100339322
+100339323
+100339330
+100339332
+100357333
+100339360
+100339786
+100339424
+100350668
+100339340
+100339446
+100355712
+100351816
+100243718
+100363016
+100317481
+100351223
+100342798
+100339475
+100352866
+100339590
+100339541
+100341375
+100341191
+100339514
+100339523
+100339532
+100339558
+100339567
+100339568
+100339633
+100342578
+100340814
+100339644
+100339646
+100355879
+100341401
+100339676
+100360947
+100341948
+100339752
+100339753
+100363015
+100339765
+100339766
+100339774
+100339777
+100339788
+100339797
+100339816
+100216910
+100339832
+100358648
+100339843
+100340611
+100339872
+100339873
+100339903
+100341027
+100306920
+100339906
+100339910
+100339913
+100340671
+100341689
+100341339
+100363010
+100348198
+100340016
+100340018
+100340020
+100340022
+100340031
+100340040
+100340041
+100340044
+100340046
+100340063
+100261314
+100291196
+100340091
+100340102
+100340110
+100340112
+100343818
+100340132
+100353550
+100340131
+100340156
+100340158
+100350247
+100219544
+100253842
+100354298
+100033110
+100354761
+100221478
+100340227
+100340230
+100340247
+100340249
+100341138
+100354350
+100340302
+100340305
+100340307
+100341069
+100290702
+100340327
+100340428
+100340437
+100340438
+100340440
+100340441
+100340465
+100340466
+100340468
+100340471
+100340472
+100340485
+100357201
+100340504
+100340513
+100166580
+100360926
+100340539
+100340555
+100340557
+100340565
+100357745
+100340579
+100340622
+100340638
+100340640
+100340659
+100341597
+100090960
+100340674
+100340684
+100356082
+100341742
+100340701
+100340716
+100341664
+100340736
+100150966
+100340764
+100340780
+100035832
+100092816
+100340827
+100340836
+100340838
+100272914
+100350018
+100352423
+100357000
+100340866
+100340868
+100340907
+100340909
+100340924
+100340926
+100240864
+100354724
+100094874
+100043900
+100340984
+100340994
+100290076
+100123964
+100341019
+100341103
+100341042
+100240404
+100079674
+100280050
+100276126
+100341051
+100341053
+100341054
+100341064
+100341066
+100357347
+100161114
+100341119
+100341120
+100197904
+100341122
+100341322
+100341125
+100341133
+100341149
+100341157
+100341945
+100363377
+100341164
+100341206
+100341182
+100341200
+100341968
+100341216
+100341275
+100341277
+100341305
+100327082
+100341340
+100341356
+100341358
+100341359
+100341363
+100341555
+100100758
+100341386
+100352104
+100341403
+100341405
+100341407
+100348405
+100341418
+100146224
+100166076
+100031368
+100290088
+100341435
+100342429
+100341439
+100349646
+100341456
+100341457
+100341459
+100341475
+100341508
+100279248
+100341530
+100341531
+100343948
+100341544
+100343121
+100346616
+100341552
+100341554
+100341557
+100341558
+100341559
+100341561
+100341578
+100351935
+100341630
+100341638
+100353704
+100362999
+100341656
+100341665
+100341667
+100354553
+100089452
+100341693
+100344217
+100341697
+100341701
+100360289
+100362997
+100343309
+100344199
+100341721
+100353447
+100018716
+100341732
+100341734
+100341747
+100341769
+100346137
+100341771
+100341789
+100341801
+100281328
+100341802
+100343450
+100341824
+100344139
+100342659
+100341828
+100341840
+100341852
+100170526
+100341872
+100341958
+100359122
+100360845
+100341970
+100341980
+100341992
+100360702
+100348129
+100342047
+100361268
+100342057
+100342058
+100347225
+100344301
+100342070
+100342071
+100342072
+100344309
+100342074
+100342075
+100362995
+100342080
+100342081
+100342082
+100351747
+100342091
+100342101
+100342102
+100351046
+100342104
+100362993
+100309298
+100348261
+100342116
+100362992
+100342126
+100342128
+100362991
+100342132
+100342133
+100342134
+100342298
+100077122
+100342136
+100271338
+100359112
+100342166
+100342171
+100342172
+100342183
+100342191
+100342192
+100350089
+100361402
+100159400
+100343902
+100342196
+100362989
+100342198
+100342214
+100357755
+100308756
+100357951
+100342220
+100188820
+100342224
+100342226
+100349777
+100342229
+100342239
+100342240
+100342241
+100342242
+100342257
+100342271
+100342272
+100342273
+100342274
+100342282
+100361776
+100342286
+100342288
+100360209
+100201586
+100289988
+100342306
+100342307
+100104624
+100362988
+100193854
+100355708
+100355707
+100342341
+100342342
+100342344
+100342346
+100342355
+100342387
+100081920
+100359067
+100342397
+100348859
+100342415
+100360459
+100342418
+100342419
+100342420
+100342422
+100342424
+100349902
+100342425
+100342427
+100342431
+100342432
+100342442
+100345122
+100169434
+100342453
+100342455
+100342459
+100342461
+100342473
+100342477
+100342507
+100342508
+100342509
+100359220
+100209792
+100220838
+100343766
+100154738
+100294964
+100178876
+100031448
+100255526
+100154196
+100342517
+100342525
+100342526
+100342530
+100342532
+100342544
+100342548
+100342549
+100342550
+100342551
+100342569
+100342579
+100342588
+100342605
+100342606
+100342607
+100342611
+100342639
+100361755
+100342660
+100362660
+100342692
+100342693
+100342705
+100342721
+100362987
+100342729
+100342731
+100342747
+100342748
+100342749
+100342754
+100342763
+100342765
+100342766
+100342768
+100342784
+100352173
+100342809
+100342810
+100342811
+100205786
+100342821
+100116782
+100078538
+100189468
+100342840
+100343112
+100342898
+100362986
+100342900
+100342915
+100342917
+100342919
+100116600
+100291264
+100342935
+100342937
+100342945
+100351341
+100342953
+100316425
+100342963
+100342973
+100342974
+100342983
+100343001
+100343002
+100343004
+100343005
+100343014
+100343015
+100343033
+100202008
+100343049
+100343473
+100343051
+100351683
+100343071
+100343072
+100343080
+100024934
+100355756
+100357849
+100343134
+100343135
+100343143
+100161400
+100343145
+100343149
+100338331
+100343151
+100291770
+100343152
+100343159
+100343161
+100119180
+100343163
+100343164
+100343167
+100343168
+100361689
+100343194
+100343202
+100343209
+100348500
+100359071
+100347611
+100343270
+100361107
+100343280
+100352709
+100360335
+100343312
+100359069
+100343317
+100359217
+100343362
+100343363
+100343364
+100343372
+100314256
+100343397
+100350891
+100361615
+100343411
+100343413
+100343415
+100343426
+100343437
+100363361
+100343483
+100347536
+100347644
+100343489
+100343490
+100278528
+100058456
+100343506
+100343524
+100343525
+100350281
+100351445
+100343537
+100343551
+100343555
+100343739
+100343572
+100343575
+100034938
+100343577
+100343580
+100343588
+100343590
+100343591
+100343592
+100343600
+100343601
+100343605
+100343606
+100343610
+100343611
+100241194
+100343614
+100353482
+100343617
+100343619
+100343636
+100362969
+100343647
+100343648
+100343649
+100343664
+100343665
+100343667
+100079646
+100343676
+100359068
+100343710
+100343712
+100343713
+100343714
+100343906
+100363228
+100343719
+100343721
+100343741
+100343750
+100002450
+100343759
+100343760
+100343761
+100343762
+100343763
+100343764
+100343776
+100343777
+100343778
+100343779
+100343782
+100344165
+100343783
+100343784
+100351982
+100343995
+100343788
+100351972
+100115376
+100351964
+100343799
+100343800
+100133414
+100343802
+100357137
+100058694
+100343806
+100320800
+100343821
+100352991
+100354793
+100343831
+100343834
+100251484
+100360536
+100343857
+100343866
+100343867
+100354121
+100343878
+100343904
+100163022
+100343916
+100343917
+100315485
+100343921
+100343923
+100063776
+100343924
+100343928
+100002338
+100343931
+100343933
+100343945
+100349314
+100343993
+100343965
+100194914
+100343967
+100344718
+100343971
+100128512
+100154494
+100351980
+100343982
+100343984
+100343997
+100343999
+100344009
+100344018
+100344020
+100350352
+100344023
+100358582
+100344547
+100344040
+100064668
+100226372
+100235400
+100184838
+100344051
+100344055
+100344056
+100344058
+100344076
+100344093
+100344082
+100344083
+100167808
+100344095
+100344096
+100344097
+100344099
+100344101
+100357450
+100344107
+100344108
+100344123
+100352663
+100336533
+100276296
+100349088
+100291316
+100234722
+100287648
+100355901
+100344162
+100349795
+100344187
+100103624
+100344195
+100344193
+100344194
+100361093
+100362966
+100344200
+100344202
+100355959
+100363297
+100344204
+100344219
+100344220
+100344222
+100166804
+100348134
+100344233
+100344230
+100349185
+100344244
+100344245
+100358778
+100344248
+100344249
+100344707
+100351686
+100344260
+100344262
+100344263
+100345129
+100344265
+100344274
+100344275
+100344276
+100362965
+100344280
+100344281
+100356040
+100344292
+100344295
+100350172
+100353747
+100020632
+100344315
+100344318
+100344342
+100355440
+100344343
+100344354
+100019142
+100344352
+100344353
+100344362
+100344364
+100347496
+100356625
+100344380
+100344381
+100350400
+100308220
+100344441
+100344387
+100344895
+100344395
+100068930
+100344399
+100354580
+100344410
+100344413
+100199522
+100353418
+100344424
+100344425
+100344439
+100310134
+100344451
+100344454
+100345113
+100344456
+100344490
+100344899
+100034974
+100063312
+100101456
+100104594
+100194158
+100168204
+100194206
+100194952
+100212680
+100344462
+100352507
+100347261
+100348803
+100344476
+100345107
+100344488
+100344499
+100176846
+100344501
+100012844
+100344511
+100345209
+100344518
+100344616
+100352151
+100362962
+100346438
+100344529
+100231170
+100344532
+100344535
+100344536
+100363227
+100344548
+100344550
+100344551
+100049668
+100064444
+100344561
+100115536
+100345162
+100344565
+100344568
+100345058
+100344571
+100344597
+100345033
+100347040
+100344834
+100241364
+100344572
+100344703
+100344575
+100344576
+100344585
+100344586
+100353744
+100344588
+100344598
+100079936
+100344607
+100344618
+100344628
+100344639
+100348127
+100344649
+100344651
+100344652
+100344654
+100344655
+100326014
+100344686
+100344694
+100050186
+100344721
+100355702
+100345142
+100344747
+100346452
+100357893
+100344748
+100344759
+100344760
+100344774
+100344777
+100191998
+100344779
+100350174
+100344850
+100344831
+100147858
+100344781
+100344782
+100344786
+100355773
+100344795
+100344796
+100270626
+100194354
+100344800
+100344803
+100344812
+100354244
+100346441
+100344837
+100344838
+100344904
+100344869
+100348903
+100210150
+100344881
+100344883
+100344886
+100344896
+100344912
+100344900
+100344901
+100345012
+100344914
+100344929
+100344931
+100344933
+100282198
+100290484
+100316272
+100344936
+100352130
+100344938
+100304056
+100344952
+100286648
+100344993
+100344972
+100344973
+100344975
+100344976
+100350753
+100164212
+100357429
+100344985
+100349445
+100344986
+100344987
+100355241
+100353436
+100345060
+100347797
+100345009
+100345027
+100347935
+100345023
+100351243
+100345031
+100350223
+100346203
+100345035
+100348251
+100347972
+100345040
+100354330
+100345044
+100298498
+100226750
+100349624
+100345056
+100345071
+100345076
+100345079
+100345087
+100345095
+100345098
+100345106
+100154794
+100090728
+100288782
+100345108
+100362162
+100345116
+100007338
+100346468
+100346134
+100350711
+100345138
+100346132
+100194198
+100345140
+100345145
+100350545
+100345159
+100345160
+100345172
+100358459
+100345175
+100348370
+100304912
+100362956
+100345181
+100359064
+100362955
+100115596
+100345190
+100360240
+100345194
+100345195
+100345197
+100345205
+100345207
+100345212
+100345215
+100345216
+100322682
+100360159
+100345221
+100345222
+100361688
+100345225
+100345234
+100345236
+100345238
+100363098
+100355603
+100258548
+100345275
+100345276
+100345278
+100345290
+100345292
+100345294
+100345296
+100358195
+100345314
+100345324
+100362953
+100363111
+100363108
+100080480
+100357883
+100345351
+100345362
+100345363
+100345365
+100345367
+100345368
+100345377
+100345379
+100345382
+100345383
+100345386
+100345387
+100345390
+100345391
+100345393
+100302592
+100345409
+100345411
+100345420
+100362940
+100357154
+100345430
+100351030
+100345433
+100226822
+100345435
+100356081
+100345438
+100345441
+100349839
+100345450
+100345451
+100345471
+100000326
+100345482
+100363106
+100345486
+100345497
+100345514
+100345515
+100360214
+100345520
+100345530
+100345540
+100212540
+100361079
+100345550
+100345553
+100345555
+100345556
+100345572
+100345574
+100345583
+100345587
+100363138
+100345599
+100345601
+100360211
+100345627
+100345628
+100345643
+100346851
+100349292
+100345685
+100345689
+100278816
+100290144
+100355698
+100345694
+100345696
+100345699
+100345700
+100345701
+100345719
+100345720
+100345730
+100345731
+100345732
+100357209
+100345757
+100345759
+100345760
+100359182
+100345764
+100345766
+100345769
+100345770
+100345771
+100354904
+100345783
+100209074
+100033262
+100345786
+100345787
+100345811
+100206662
+100362121
+100360196
+100345818
+100103476
+100345819
+100345830
+100345838
+100345839
+100345841
+100345843
+100345845
+100083054
+100345847
+100359361
+100345850
+100065074
+100345873
+100345876
+100345884
+100345886
+100345887
+100345895
+100345897
+100345905
+100345921
+100362937
+100345928
+100345937
+100345939
+100345941
+100345949
+100280930
+100279218
+100345956
+100345958
+100345959
+100349450
+100345985
+100346004
+100346006
+100346008
+100346022
+100346023
+100349124
+100354780
+100346039
+100356617
+100346050
+100226534
+100285230
+100349221
+100346059
+100348722
+100346075
+100346085
+100363195
+100346088
+100346092
+100115688
+100346095
+100280078
+100104384
+100153660
+100182038
+100346097
+100346099
+100346109
+100349195
+100346120
+100355397
+100346122
+100322453
+100346324
+100226538
+100346155
+100358183
+100346159
+100351338
+100173346
+100346163
+100346239
+100346172
+100180202
+100346181
+100346182
+100346184
+100271628
+100346206
+100346207
+100346210
+100346211
+100346213
+100346230
+100346232
+100347313
+100362933
+100357851
+100346236
+100346242
+100347458
+100043704
+100346243
+100346244
+100081744
+100346602
+100346253
+100346263
+100346266
+100346267
+100346269
+100349269
+100346273
+100346275
+100127082
+100121810
+100283270
+100350802
+100362931
+100347400
+100346289
+100346291
+100346294
+100362928
+100068256
+100346311
+100346326
+100235076
+100346342
+100346344
+100348884
+100346353
+100346354
+100346356
+100346358
+100346360
+100346376
+100346378
+100346380
+100346382
+100346383
+100350279
+100346386
+100362927
+100346411
+100348897
+100346422
+100346423
+100346425
+100346426
+100346427
+100346436
+100346472
+100349812
+100346476
+100346478
+100346497
+100346500
+100346501
+100346503
+100348735
+100346504
+100346505
+100346509
+100359109
+100346512
+100346513
+100346515
+100346516
+100346518
+100362926
+100346522
+100346525
+100362925
+100346528
+100346529
+100346531
+100346533
+100346535
+100346538
+100346547
+100362916
+100282004
+100346552
+100348277
+100346556
+100355374
+100346566
+100346568
+100346570
+100346572
+100346573
+100346575
+100346576
+100346578
+100346581
+100358850
+100346584
+100363090
+100348449
+100346596
+100346598
+100346600
+100346618
+100134262
+100352209
+100346609
+100349698
+100362384
+100346927
+100346620
+100348371
+100354896
+100363394
+100362917
+100347850
+100346636
+100346646
+100346648
+100346650
+100346651
+100354915
+100361080
+100346655
+100296148
+100349552
+100346660
+100346661
+100218440
+100346664
+100261858
+100346668
+100361259
+100346673
+100346674
+100356754
+100346677
+100346681
+100346682
+100346683
+100346686
+100346687
+100346689
+100362911
+100346694
+100346697
+100346699
+100346702
+100346703
+100346704
+100346713
+100346714
+100362909
+100346717
+100346719
+100360226
+100354777
+100346728
+100346730
+100346732
+100346734
+100347876
+100346737
+100359131
+100346749
+100346751
+100346775
+100346776
+100346784
+100362889
+100346839
+100346840
+100346853
+100346867
+100346868
+100050026
+100346871
+100362888
+100346889
+100346899
+100346915
+100356746
+100346917
+100346985
+100346929
+100346931
+100306294
+100086274
+100346950
+100363094
+100346967
+100346976
+100034634
+100346980
+100347159
+100346983
+100353514
+100347453
+100276202
+100346987
+100322067
+100346990
+100346991
+100041306
+100346992
+100349679
+100346996
+100347030
+100347032
+100347034
+100347035
+100348929
+100354776
+100347045
+100347046
+100324227
+100347055
+100356797
+100347071
+100347072
+100085132
+100350656
+100350973
+100348106
+100347101
+100347110
+100085760
+100350970
+100347131
+100357679
+100347148
+100311000
+100347156
+100362886
+100349329
+100347164
+100347167
+100363120
+100347179
+100195652
+100347195
+100351741
+100347197
+100043512
+100278340
+100348173
+100347212
+100348775
+100347234
+100163794
+100347236
+100347245
+100353309
+100347257
+100347258
+100286998
+100354320
+100347298
+100353970
+100349915
+100349259
+100348323
+100349645
+100347340
+100090120
+100119624
+100347864
+100347349
+100348725
+100347360
+100347579
+100347865
+100347370
+100347380
+100347388
+100347390
+100347399
+100362884
+100347409
+100358869
+100347421
+100347437
+100351495
+100354994
+100019720
+100362846
+100348401
+100347472
+100347481
+100347482
+100362816
+100347608
+100347493
+100348939
+100347498
+100347708
+100270660
+100354083
+100362808
+100124886
+100347517
+100358984
+100347520
+100354475
+100347537
+100347541
+100347542
+100346287
+100347555
+100347563
+100354772
+100362807
+100355694
+100350399
+100351841
+100361743
+100347592
+100363342
+100347606
+100349342
+100363056
+100347615
+100349065
+100347634
+100347635
+100347638
+100347653
+100327456
+100354770
+100348588
+100363135
+100362059
+100347682
+100347683
+100348919
+100349758
+100040714
+100347698
+100349368
+100347726
+100347736
+100350256
+100347747
+100347750
+100350006
+100347752
+100260636
+100347763
+100347769
+100347770
+100353529
+100347773
+100347782
+100351320
+100347908
+100347793
+100347799
+100347824
+100347828
+100347829
+100347841
+100347842
+100348448
+100347853
+100347855
+100347886
+100347867
+100362805
+100358519
+100348948
+100347989
+100288742
+100351572
+100358766
+100347928
+100347931
+100347932
+100347937
+100347940
+100362803
+100347958
+100348843
+100355437
+100348893
+100349893
+100097218
+100350042
+100347982
+100347992
+100348001
+100362802
+100348022
+100348023
+100349271
+100348994
+100348044
+100348047
+100348051
+100348062
+100348065
+100362799
+100132192
+100155238
+100348092
+100348102
+100348104
+100348123
+100348130
+100018910
+100348150
+100348161
+100348195
+100350713
+100349997
+100348214
+100348223
+100348246
+100352899
+100348270
+100349053
+100355392
+100348302
+100348304
+100348306
+100348307
+100238860
+100348351
+100348352
+100348354
+100348363
+100348365
+100348452
+100350004
+100348382
+100348383
+100348403
+100049140
+100300006
+100356343
+100348409
+100348418
+100351958
+100348422
+100348424
+100348425
+100348464
+100348465
+100344156
+100357880
+100348492
+100348493
+100348498
+100348502
+100350001
+100348513
+100348515
+100348523
+100290712
+100348525
+100348527
+100348529
+100348538
+100348540
+100348579
+100353032
+100348599
+100348600
+100348604
+100348606
+100348608
+100348637
+100348639
+100348641
+100348643
+100351241
+100348646
+100348647
+100348649
+100348651
+100348653
+100315517
+100349305
+100360820
+100357682
+100350209
+100348738
+100357722
+100348740
+100348742
+100348745
+100348747
+100348749
+100358587
+100348753
+100348764
+100354095
+100348783
+100348785
+100348794
+100348814
+100348816
+100225356
+100348844
+100350047
+100348848
+100348849
+100348860
+100348870
+100348950
+100348952
+100348954
+100349012
+100348965
+100348967
+100348968
+100360921
+100348970
+100350021
+100349154
+100348971
+100352564
+100352691
+100357863
+100349031
+100358137
+100355822
+100128854
+100349074
+100349079
+100349097
+100349099
+100349101
+100349127
+100351502
+100349130
+100363251
+100041590
+100047850
+100017480
+100349144
+100349162
+100349500
+100350241
+100349164
+100350836
+100352762
+100349263
+100352806
+100362788
+100349635
+100349272
+100235662
+100349990
+100349283
+100349293
+100135290
+100350398
+100349331
+100349938
+100349358
+100043810
+100349360
+100354838
+100349370
+100349372
+100353600
+100293434
+100264796
+100133606
+100105308
+100349376
+100349381
+100349389
+100354960
+100359324
+100349996
+100363302
+100349412
+100349421
+100351612
+100154528
+100111232
+100357677
+100349441
+100352240
+100193188
+100349444
+100349447
+100349453
+100239710
+100350680
+100349471
+100351365
+100349483
+100046974
+100349505
+100349514
+100349525
+100349542
+100349570
+100349575
+100349576
+100349577
+100349578
+100349597
+100362786
+100351680
+100349625
+100354643
+100349628
+100349630
+100349663
+100349666
+100349668
+100350258
+100252002
+100349690
+100256300
+100362785
+100349705
+100353484
+100350267
+100349726
+100349735
+100265810
+100349737
+100352126
+100349780
+100349782
+100350533
+100350184
+100294044
+100362780
+100362779
+100272126
+100354956
+100349888
+100349818
+100349858
+100349859
+100350544
+100351471
+100015820
+100349876
+100273280
+100136686
+100352255
+100351220
+100350694
+100350834
+100362778
+100290432
+100349916
+100349923
+100349924
+100351415
+100349934
+100361412
+100349963
+100350692
+100349965
+100350008
+100184288
+100350032
+100350034
+100350889
+100350050
+100350052
+100363091
+100350080
+100290210
+100350092
+100350100
+100350466
+100351555
+100023610
+100350110
+100350124
+100351184
+100350136
+100350602
+100350168
+100363105
+100350146
+100350977
+100361115
+100350186
+100351599
+100350196
+100350200
+100290084
+100354740
+100350225
+100350437
+100362775
+100350260
+100048446
+100350262
+100350268
+100350290
+100350421
+100094356
+100350328
+100350331
+100351403
+100350337
+100350339
+100350658
+100355094
+100350346
+100362774
+100350354
+100203588
+100350415
+100350357
+100350358
+100350370
+100350381
+100350379
+100233848
+100350683
+100362773
+100350404
+100350406
+100350464
+100272652
+100350411
+100350413
+100350414
+100353553
+100350434
+100358030
+100350979
+100354353
+100351698
+100350440
+100362771
+100350451
+100350468
+100151850
+100134558
+100245758
+100182550
+100350472
+100077140
+100350481
+100077136
+100350483
+100350542
+100350485
+100351368
+100350488
+100350492
+100358748
+100350510
+100188928
+100351083
+100358566
+100353190
+100350530
+100343208
+100351360
+100350786
+100350555
+100355395
+100355684
+100320091
+100351016
+100350583
+100350593
+100253318
+100350726
+100351239
+100350757
+100350767
+100351187
+100070832
+100084724
+100117478
+100111990
+100184766
+100363133
+100350772
+100351382
+100351808
+100290628
+100350797
+100282492
+100350799
+100351018
+100351493
+100350860
+100351444
+100048520
+100352090
+100350912
+100351308
+100351255
+100310898
+100350997
+100350925
+100353535
+100350951
+100350953
+100088440
+100358583
+100087594
+100307376
+100333471
+100329116
+100351054
+100351212
+100351618
+100351055
+100358840
+100351058
+100354748
+100351185
+100351080
+100351085
+100351086
+100351100
+100178304
+100358938
+100351116
+100351227
+100351119
+100351121
+100351616
+100351362
+100351140
+100351645
+100351150
+100351152
+100362767
+100352700
+100351175
+100353116
+100351217
+100351218
+100351296
+100351310
+100216368
+100244968
+100205782
+100358216
+100351326
+100361956
+100351371
+100353110
+100351373
+100354650
+100351376
+100354647
+100362766
+100359304
+100351543
+100351547
+100363132
+100363131
+100351621
+100357359
+100351560
+100362944
+100357358
+100357357
+100351582
+100351591
+100351601
+100360498
+100351614
+100351623
+100351624
+100355692
+100307852
+100352751
+100041932
+100357696
+100351684
+100351701
+100351647
+100351704
+100351707
+100351715
+100351726
+100361715
+100351744
+100351749
+100353269
+100352719
+100351757
+100362760
+100351763
+100351764
+100351774
+100351775
+100363214
+100351786
+100351804
+100351810
+100358395
+100271962
+100272500
+100002764
+100192468
+100362759
+100351821
+100351827
+100351829
+100102326
+100351856
+100351865
+100352199
+100352393
+100351875
+100351876
+100351877
+100354765
+100351881
+100351883
+100257856
+100351887
+100224008
+100352382
+100353490
+100362755
+100105854
+100353743
+100285452
+100351992
+100351997
+100352037
+100352387
+100353076
+100352007
+100002594
+100356098
+100021124
+100103766
+100034746
+100352046
+100353489
+100233416
+100353658
+100085716
+100251406
+100363129
+100362747
+100352117
+100362745
+100352131
+100352134
+100354536
+100352155
+100352157
+100352163
+100357164
+100016086
+100016226
+100016398
+100352177
+100362744
+100356320
+100355688
+100352250
+100353258
+100352223
+100362646
+100352215
+100353427
+100352225
+100352227
+100358811
+100352752
+100293066
+100352253
+100352264
+100352265
+100352797
+100352273
+100352282
+100357463
+100352285
+100352294
+100356193
+100357439
+100355517
+100352311
+100352312
+100352313
+100352315
+100362738
+100363226
+100352340
+100352341
+100352343
+100352816
+100206290
+100352351
+100352353
+100317676
+100352365
+100352380
+100352388
+100230434
+100185784
+100314440
+100363079
+100352400
+100353030
+100353739
+100352569
+100307354
+100353872
+100353537
+100353026
+100354532
+100355877
+100352410
+100352411
+100352412
+100352413
+100352417
+100353024
+100354236
+100352420
+100358975
+100352422
+100038182
+100113152
+100081526
+100124454
+100100516
+100355680
+100363014
+100357872
+100352426
+100354257
+100362620
+100352430
+100352434
+100356827
+100282506
+100362724
+100352437
+100363261
+100352441
+100353710
+100352451
+100352452
+100352453
+100354630
+100352455
+100362761
+100352457
+100356808
+100356120
+100352461
+100352462
+100352464
+100352465
+100363075
+100352477
+100309700
+100362613
+100353315
+100222578
+100362612
+100352486
+100352487
+100362611
+100352490
+100353795
+100352492
+100352493
+100352494
+100352496
+100352498
+100353630
+100352500
+100354077
+100013628
+100352502
+100360235
+100359224
+100194562
+100352508
+100356958
+100352511
+100289546
+100354806
+100360798
+100352515
+100019144
+100352517
+100352518
+100354759
+100352522
+100352523
+100352528
+100352531
+100259860
+100363321
+100352538
+100359268
+100361710
+100352543
+100349203
+100355678
+100354738
+100157402
+100198368
+100197270
+100352582
+100361510
+100353770
+100352677
+100064284
+100363399
+100362609
+100352734
+100194392
+100357064
+100281486
+100353531
+100362314
+100352781
+100352848
+100352859
+100353142
+100282856
+100353169
+100353199
+100353301
+100363029
+100353203
+100145106
+100353405
+100353204
+100353206
+100356795
+100300534
+100353227
+100353495
+100353230
+100353231
+100353232
+100353254
+100353256
+100353303
+100353305
+100353518
+100048284
+100353312
+100291274
+100353320
+100353322
+100353408
+100354161
+100353325
+100353376
+100001652
+100288738
+100353594
+100363078
+100273062
+100353602
+100353603
+100353622
+100353625
+100353627
+100353631
+100353633
+100353649
+100102374
+100353652
+100111540
+100353668
+100020074
+100303300
+100357200
+100362674
+100362591
+100353705
+100354430
+100353756
+100353757
+100082852
+100353797
+100353989
+100354680
+100358986
+100353803
+100353800
+100358316
+100289982
+100353807
+100362590
+100353829
+100353838
+100353842
+100354529
+100311812
+100353861
+100360726
+100354388
+100354119
+100353866
+100353867
+100353869
+100353871
+100354483
+100353887
+100354249
+100353888
+100354178
+100354528
+100354523
+100362608
+100032588
+100252824
+100290166
+100353925
+100354674
+100353943
+100354011
+100361915
+100023820
+100361463
+100155224
+100360301
+100354762
+100353953
+100358965
+100275962
+100357234
+100357260
+100354435
+100353967
+100353977
+100361350
+100296714
+100292538
+100353990
+100354726
+100354009
+100354012
+100354013
+100362587
+100359241
+100362685
+100263060
+100362990
+100354041
+100362578
+100354043
+100363390
+100355623
+100301692
+100355677
+100354128
+100354130
+100178616
+100354138
+100361109
+100184420
+100354162
+100363021
+100362658
+100056124
+100134566
+100354189
+100354701
+100357657
+100354242
+100354704
+100355796
+100362543
+100355449
+100355675
+100355080
+100355672
+100362531
+100358138
+100362530
+100355104
+100355670
+100046388
+100359099
+100196630
+100355425
+100357830
+100100812
+100355132
+100324380
+100355141
+100019232
+100049362
+100355145
+100355154
+100355162
+100361436
+100171474
+100311206
+100358952
+100357140
+100355179
+100355614
+100355255
+100097872
+100253222
+100356667
+100357753
+100355605
+100355203
+100355547
+100355526
+100188814
+100360182
+100120128
+100180918
+100323309
+100355240
+100362648
+100355256
+100250098
+100362524
+100355245
+100269950
+100355246
+100355247
+100102004
+100355604
+100358900
+100363398
+100017482
+100358149
+100288518
+100130434
+100355258
+100355329
+100357431
+100355260
+100356142
+100355446
+100357085
+100355898
+100362258
+100356112
+100088088
+100362675
+100356455
+100357974
+100361264
+100065442
+100326332
+100363033
+100356028
+100356056
+100358652
+100356064
+100356065
+100356066
+100290912
+100362716
+100029500
+100363077
+100357826
+100362492
+100357434
+100291458
+100344695
+100104856
+100356242
+100359051
+100362640
+100358785
+100356156
+100171388
+100128304
+100356171
+100358947
+100264268
+100356946
+100075824
+100083130
+100357142
+100057018
+100358788
+100361459
+100245680
+100362245
+100356195
+100356782
+100048884
+100356925
+100191042
+100357433
+100362453
+100362450
+100307582
+100356252
+100356673
+100063898
+100356266
+100356274
+100055478
+100356626
+100362682
+100356377
+100356288
+100327296
+100356292
+100356295
+100356296
+100356297
+100356299
+100356300
+100358613
+100362910
+100154990
+100154438
+100359266
+100259828
+100356346
+100363395
+100356349
+100356350
+100357481
+100357426
+100356398
+100356406
+100357259
+100362446
+100356445
+100356447
+100209368
+100356460
+100360718
+100361991
+100360716
+100199718
+100356486
+100356476
+100356949
+100356508
+100075432
+100064194
+100356519
+100356553
+100116432
+100362445
+100083750
+100289840
+100357468
+100362120
+100155164
+100357408
+100356330
+100360819
+100362607
+100030054
+100356771
+100356601
+100356610
+100356611
+100297540
+100297826
+100297768
+100298652
+100360210
+100296490
+100363096
+100359244
+100356657
+100344735
+100361320
+100356997
+100358941
+100356762
+100363194
+100357487
+100359168
+100357350
+100045748
+100363032
+100362434
+100357349
+100356914
+100357362
+100266730
+100357176
+100358213
+100101628
+100362733
+100357393
+100361556
+100357045
+100357093
+100357103
+100357111
+100357126
+100361562
+100357177
+100357139
+100361078
+100357806
+100357275
+100336218
+100211668
+100357273
+100360260
+100211600
+100357216
+100357217
+100357219
+100308434
+100200022
+100357421
+100357221
+100357230
+100052750
+100358146
+100362681
+100290470
+100357241
+100357272
+100311258
+100357284
+100362426
+100357287
+100359906
+100363347
+100357305
+100358910
+100357316
+100357327
+100361569
+100358780
+100357330
+100357808
+100357337
+100357356
+100360100
+100357515
+100360814
+100362732
+100253418
+100357548
+100357665
+100358649
+100357580
+100357684
+100362784
+100360665
+100357861
+100080490
+100357608
+100357708
+100357709
+100362731
+100362490
+100358063
+100215322
+100316317
+100357891
+100267392
+100357783
+100357784
+100357787
+100103038
+100325543
+100325541
+100358028
+100363128
+100103682
+100357976
+100254016
+100357801
+100357802
+100357804
+100226204
+100357822
+100076252
+100357882
+100357894
+100358936
+100358262
+100290330
+100357910
+100118082
+100357914
+100357925
+100357926
+100358232
+100357936
+100357937
+100357939
+100357941
+100357959
+100361331
+100357968
+100357970
+100077002
+100358092
+100358000
+100092892
+100309118
+100358142
+100358162
+100358082
+100104042
+100359056
+100107306
+100196430
+100362264
+100356796
+100252566
+100358104
+100362261
+100334848
+100359796
+100285360
+100358832
+100358132
+100358150
+100358270
+100358273
+100358274
+100224780
+100038850
+100359390
+100362247
+100360828
+100041582
+100228062
+100362226
+100358923
+100358286
+100362211
+100358529
+100358995
+100130440
+100362209
+100362191
+100358547
+100358295
+100362207
+100362185
+100330295
+100358301
+100362153
+100117396
+100060824
+100362147
+100359153
+100360115
+100358312
+100360977
+100358913
+100358854
+100362413
+100362285
+100358322
+100362333
+100358325
+100358326
+100358744
+100358329
+100013098
+100251748
+100362088
+100360699
+100362416
+100363127
+100362976
+100362078
+100358616
+100358346
+100358347
+100358608
+100358358
+100358549
+100115824
+100360215
+100358362
+100262942
+100176372
+100362708
+100360618
+100362074
+100307118
+100358604
+100198420
+100358762
+100358736
+100269972
+100362066
+100362065
+100000866
+100358875
+100358565
+100358396
+100359055
+100359255
+100362063
+100360751
+100358783
+100362369
+100358414
+100267386
+100358416
+100360904
+100358422
+100358767
+100358429
+100358430
+100223472
+100358474
+100358438
+100358437
+100359263
+100358686
+100358446
+100358447
+100362266
+100362050
+100359943
+100358460
+100316486
+100358611
+100358643
+100297932
+100358483
+100358485
+100276848
+100358488
+100358518
+100358490
+100358498
+100358501
+100166334
+100362043
+100358552
+100361265
+100306428
+100358512
+100358713
+100358704
+100290632
+100358514
+100284796
+100362033
+100359498
+100362031
+100359854
+100359503
+100361989
+100067032
+100363126
+100362322
+100362636
+100359515
+100361977
+100063640
+100359519
+100359520
+100363097
+100362097
+100361124
+100121988
+100356240
+100363065
+100361403
+100361388
+100361387
+100359534
+100360992
+100361968
+100361966
+100359538
+100360239
+100354898
+100361378
+100360400
+100361945
+100361376
+100362686
+100361944
+100359547
+100362124
+100169928
+100361371
+100362365
+100362282
+100359560
+100349998
+100361367
+100361941
+100361400
+100188992
+100361364
+100361363
+100361817
+100362726
+100359573
+100363104
+100044730
+100363125
+100361916
+100362075
+100362269
+100361219
+100359584
+100359585
+100157838
+100359588
+100361910
+100359590
+100360229
+100359593
+100362192
+100360370
+100359596
+100361735
+100359599
+100357909
+100361396
+100362363
+100359613
+100359615
+100359616
+100318170
+100360342
+100362014
+100359621
+100306712
+100359624
+100362362
+100362023
+100359629
+100362022
+100361205
+100359632
+100359635
+100361204
+100359638
+100363363
+100360376
+100361203
+100361362
+100359832
+100361886
+100107510
+100362334
+100361883
+100283200
+100361132
+100360452
+100361434
+100359656
+100359659
+100363124
+100362001
+100361599
+100361881
+100126032
+100357793
+100351641
+100362725
+100361679
+100361871
+100361168
+100361831
+100359678
+100359679
+100359680
+100361044
+100359682
+100359683
+100361674
+100361019
+100360570
+100359690
+100361164
+100360516
+100361361
+100359695
+100360727
+100360511
+100362003
+100361452
+100359703
+100359704
+100359705
+100281458
+100363123
+100359712
+100359713
+100281842
+100361162
+100362026
+100359718
+100355847
+100362356
+100359721
+100361512
+100361451
+100361801
+100360969
+100361161
+100361568
+100359746
+100361255
+100361669
+100361598
+100359892
+100361152
+100359756
+100359757
+100359769
+100363122
+100361570
+100359774
+100361868
+100359782
+100361082
+100360479
+100361151
+100360125
+100361720
+100359791
+100360478
+100361842
+100359797
+100362286
+100361866
+100360551
+100202254
+100359805
+100360477
+100359822
+100061790
+100361146
+100262980
+100359829
+100052330
+100359834
+100361144
+100359836
+100362316
+100361406
+100361468
+100361105
+100359843
+100359844
+100360467
+100359847
+100361843
+100361509
+100165894
+100360464
+100363378
+100359861
+100361461
+100359864
+100359867
+100359868
+100359869
+100155070
+100360989
+100360460
+100361667
+100360449
+100359880
+100359881
+100262816
+100360439
+100362643
+100359888
+100359894
+100359895
+100360438
+100361359
+100361484
+100194738
+100360401
+100360485
+100359917
+100319526
+100359941
+100360430
+100360429
+100027584
+100359405
+100361793
+100359928
+100360169
+100359938
+100360428
+100185754
+100359949
+100360694
+100359951
+100361286
+100360451
+100359954
+100359955
+100361069
+100360424
+100359960
+100359962
+100359963
+100359964
+100359966
+100359968
+100361214
+100362974
+100360577
+100361765
+100359974
+100363252
+100359979
+100359980
+100359981
+100360294
+100254146
+100359984
+100361676
+100361308
+100362041
+100340006
+100359992
+100363393
+100361383
+100359997
+100074184
+100133216
+100360000
+100360202
+100360003
+100360004
+100360917
+100360006
+100360173
+100360008
+100363100
+100360014
+100360015
+100360016
+100360616
+100360023
+100360024
+100112046
+100360027
+100360028
+100360030
+100361768
+100360594
+100361140
+100360035
+100360876
+100360689
+100360040
+100360907
+100360042
+100360045
+100361965
+100360957
+100360062
+100361579
+100360047
+100062974
+100360051
+100360054
+100360055
+100360326
+100360058
+100361645
+100360120
+100360061
+100360063
+100360064
+100263476
+100361125
+100360325
+100360071
+100360072
+100360615
+100360705
+100360088
+100360092
+100360630
+100361123
+100335110
+100360793
+100360096
+100036202
+100360783
+100360106
+100015010
+100363076
+100361055
+100360112
+100360113
+100360116
+100360407
+100361640
+100360119
+100360775
+100045680
+100360135
+100361830
+100362892
+100361799
+100362342
+100280572
+100362343
+100360143
+100360145
+100360146
+100360403
+100363074
+100360155
+100361639
+100363216
+100362339
+100362715
+100360163
+100362274
+100360772
+100361275
+100361077
+100361458
+100360181
+100360379
+100362345
+100360559
+100035282
+100013430
+100031700
+100045698
+100017686
+100097540
+100063458
+100030442
+100053732
+100343949
+100056798
+100013738
+100022934
+100095594
+100051960
+100105684
+100155494
+100130416
+100215942
+100006298
+100035894
+100057704
+100066604
+100096436
+100033162
+100021700
+100002778
+100022870
+100029676
+100032304
+100043764
+100082120
+100246556
+100054598
+100198060
+100066488
+100033324
+100093004
+100036354
+100109060
+100067604
+100095120
+100102720
+100104038
+100101956
+100291294
+100033440
+100022054
+100085676
+100018464
+100029318
+100077506
+100088686
+100342573
+100052884
+100020396
+100031980
+100037722
+100030854
+100053324
+100063816
+100044932
+100101908
+100104848
+100261278
+100064760
+100064428
+100053812
+100010042
+100057078
+100031642
+100063366
+100035912
+100111050
+100007636
+100071066
+100020338
+100070574
+100049860
+100023536
+100054292
+100025738
+100000256
+100109150
+100078998
+100044354
+100075812
+100157828
+100043366
+100103328
+100108702
+100290512
+100068370
+100043104
+100114590
+100034310
+100008272
+100108106
+100011298
+100050712
+100019092
+100147250
+100018078
+100010272
+100106828
+100046218
+100081034
+100007806
+100036306
+100154696
+100320227
+100023930
+100132880
+100211218
+100035178
+100115322
+100054702
+100039772
+100014036
+100075846
+100016992
+100037388
+100316478
+100338524
+100051810
+100044572
+100142770
+100030264
+100053452
+100033800
+100030314
+100055076
+100100698
+100069252
+100021382
+100025724
+100029502
+100093104
+100020612
+100084558
+100120926
+100019782
+100100556
+100030838
+100066702
+100117010
+100053524
+100076522
+100061730
+100008126
+100049570
+100103992
+100080956
+100000304
+100053104
+100330853
+100103090
+100084344
+100031834
+100008834
+100062900
+100154518
+100102566
+100078686
+100009328
+100312414
+100017362
+100090342
+100060680
+100020138
+100081314
+100068830
+100119158
+100017960
+100090180
+100117378
+100088398
+100048148
+100071594
+100041772
+100272032
+100201494
+100119542
+100031236
+100120590
+100042242
+100154178
+100030522
+100090226
+100085430
+100095100
+100019714
+100002546
+100175484
+100082574
+100066210
+100016640
+100227856
+100024918
+100047860
+100066550
+100062508
+100013902
+100100814
+100075354
+100057436
+100105732
+100002912
+100222156
+100080486
+100072182
+100106444
+100109640
+100075268
+100111884
+100045966
+100154356
+100025048
+100022442
+100060546
+100058050
+100123380
+100031126
+100120852
+100302306
+100143640
+100176442
+100046822
+100098862
+100148398
+100115778
+100064200
+100133536
+100101860
+100024824
+100037230
+100293440
+100117486
+100039370
+100193530
+100125508
+100018262
+100102550
+100046830
+100138072
+100077710
+100149606
+100047812
+100136108
+100100250
+100112616
+100200318
+100140752
+100051884
+100155602
+100069320
+100134320
+100162722
+100071212
+100140606
+100073040
+100102038
+100133646
+100102866
+100131668
+100145846
+100133760
+100121494
+100127898
+100061216
+100119732
+100133376
+100130230
+100134452
+100100414
+100059756
+100104928
+100065384
+100157330
+100081356
+100155740
+100153656
+100078582
+100098456
+100133022
+100209106
+100056854
+100059512
+100036852
+100216344
+100116158
+100013972
+100130890
+100044610
+100149304
+100126786
+100307442
+100242914
+100040636
+100031444
+100154744
+100051740
+100129572
+100019234
+100132416
+100293184
+100025812
+100017620
+100043006
+100019702
+100119336
+100130634
+100140382
+100045904
+100046504
+100200616
+100105674
+100312632
+100337866
+100159008
+100112992
+100138492
+100125260
+100085954
+100125356
+100107432
+100127932
+100133858
+100142370
+100011186
+100128164
+100120320
+100139846
+100079374
+100134192
+100146192
+100122868
+100065282
+100040002
+100036326
+100106872
+100081042
+100103986
+100057252
+100342056
+100223556
+100081668
+100122386
+100087678
+100111280
+100130480
+100313508
+100181126
+100180934
+100132650
+100226386
+100123378
+100269460
+100130528
+100061986
+100075844
+100356670
+100144592
+100127574
+100142650
+100106378
+100080310
+100135438
+100154604
+100024156
+100078884
+100148416
+100025360
+100128850
+100110612
+100109226
+100120364
+100060442
+100055958
+100063716
+100110770
+100047688
+100075374
+100207696
+100030344
+100144472
+100151808
+100129940
+100029376
+100050614
+100067602
+100029482
+100142298
+100139358
+100130484
+100113040
+100121796
+100131972
+100137448
+100130144
+100088882
+100085496
+100072836
+100068110
+100079652
+100147670
+100144184
+100033164
+100272182
+100018028
+100245920
+100149384
+100127352
+100115796
+100233630
+100145584
+100128096
+100220778
+100149390
+100086612
+100130048
+100114536
+100146226
+100128160
+100134476
+100127908
+100013302
+100129704
+100252218
+100134862
+100065418
+100134078
+100080792
+100245154
+100111312
+100152676
+100160938
+100151766
+100024404
+100126810
+100133324
+100099218
+100130712
+100017688
+100131594
+100306880
+100100256
+100154026
+100148872
+100305144
+100124280
+100154210
+100154218
+100155588
+100154190
+100167866
+100239060
+100143174
+100182418
+100168248
+100155338
+100140750
+100149974
+100064644
+100126928
+100347923
+100143710
+100154804
+100261204
+100155600
+100195114
+100227540
+100165024
+100151634
+100168786
+100082068
+100101436
+100159018
+100221568
+100109826
+100044302
+100215832
+100147246
+100196574
+100145512
+100157982
+100163448
+100124730
+100125666
+100271002
+100214376
+100064320
+100201376
+100252786
+100042266
+100213704
+100172762
+100071108
+100192634
+100155214
+100055968
+100054620
+100198384
+100188568
+100162886
+100131700
+100155426
+100165218
+100037198
+100174456
+100158334
+100202874
+100101858
+100073288
+100132732
+100168870
+100103652
+100160172
+100166914
+100169766
+100178156
+100030630
+100326488
+100128558
+100164214
+100078634
+100132000
+100084784
+100310540
+100274580
+100099250
+100181956
+100173564
+100163582
+100248848
+100165020
+100168954
+100111630
+100290032
+100155198
+100060560
+100171532
+100158712
+100183336
+100104692
+100154534
+100252396
+100323051
+100131152
+100112116
+100157966
+100156818
+100164026
+100231436
+100063980
+100044940
+100154960
+100212852
+100130396
+100238366
+100171530
+100158174
+100158928
+100164974
+100170598
+100327010
+100046190
+100108140
+100174432
+100218334
+100167112
+100194680
+100161734
+100164008
+100163948
+100166156
+100166360
+100283942
+100205862
+100169358
+100102538
+100164594
+100325494
+100123236
+100079888
+100103458
+100327286
+100134644
+100232010
+100175300
+100304226
+100197292
+100190514
+100106528
+100154442
+100187580
+100291186
+100061164
+100177116
+100013678
+100159644
+100091030
+100163584
+100035838
+100182970
+100237610
+100179824
+100200650
+100060544
+100211552
+100198232
+100198186
+100155154
+100105750
+100162292
+100009046
+100103318
+100008116
+100242106
+100189274
+100165578
+100157878
+100168664
+100057442
+100159068
+100073316
+100233794
+100083572
+100133470
+100181238
+100194216
+100053052
+100169264
+100155342
+100195306
+100316783
+100025248
+100131022
+100117866
+100194954
+100171446
+100259614
+100102370
+100136802
+100154466
+100257862
+100159472
+100162726
+100192930
+100132862
+100039292
+100193748
+100065988
+100113844
+100205872
+100089328
+100163026
+100138502
+100162938
+100160942
+100190492
+100066182
+100112460
+100022448
+100195118
+100179164
+100214630
+100163680
+100199920
+100247054
+100199850
+100227156
+100201810
+100163162
+100197756
+100165828
+100194012
+100222732
+100259970
+100157332
+100190516
+100055500
+100122034
+100201756
+100194734
+100253962
+100000564
+100360413
+100162082
+100327014
+100311852
+100156320
+100194174
+100100518
+100190440
+100098044
+100219416
+100162958
+100161556
+100171360
+100194322
+100220980
+100200524
+100178524
+100177944
+100036608
+100054380
+100195484
+100250270
+100193788
+100205692
+100181778
+100167642
+100191068
+100179280
+100091626
+100093862
+100164856
+100104844
+100234234
+100174188
+100173400
+100121240
+100197570
+100134634
+100180122
+100194318
+100130958
+100228140
+100197502
+100203334
+100133830
+100228648
+100121920
+100235724
+100198392
+100148432
+100220828
+100176070
+100352396
+100155416
+100245504
+100238656
+100193142
+100246076
+100154460
+100174616
+100221330
+100209500
+100156984
+100135176
+100203376
+100039176
+100272752
+100236214
+100066972
+100255020
+100071352
+100213904
+100220840
+100356104
+100164280
+100245144
+100220404
+100080026
+100235894
+100166778
+100002220
+100148474
+100042178
+100217294
+100352406
+100174684
+100104170
+100215576
+100197052
+100218392
+100073190
+100220210
+100111190
+100211904
+100181390
+100151228
+100167094
+100230442
+100081748
+100219144
+100274160
+100265282
+100166252
+100155638
+100218278
+100076406
+100044034
+100170260
+100154314
+100248998
+100125714
+100058696
+100224948
+100166308
+100169484
+100171024
+100248526
+100243468
+100215502
+100236042
+100220608
+100145514
+100195282
+100197614
+100165672
+100193986
+100241806
+100217596
+100055078
+100227844
+100199868
+100161830
+100194522
+100246248
+100058556
+100246068
+100253312
+100248708
+100151742
+100310414
+100019758
+100307926
+100072422
+100240108
+100181206
+100248404
+100233536
+100231198
+100211790
+100064586
+100203450
+100238688
+100352495
+100250154
+100123390
+100246668
+100107736
+100224192
+100178660
+100196718
+100063998
+100246880
+100173712
+100085010
+100207468
+100232210
+100151548
+100247576
+100195082
+100242414
+100142448
+100022840
+100230308
+100202158
+100252248
+100137452
+100210436
+100246448
+100174552
+100211956
+100201758
+100242364
+100140244
+100330851
+100190330
+100234320
+100217494
+100212986
+100216466
+100359226
+100099478
+100224706
+100234924
+100248910
+100241614
+100080068
+100246088
+100187140
+100104602
+100352429
+100119200
+100177222
+100271968
+100183340
+100197686
+100082028
+100215706
+100136116
+100234522
+100140736
+100248920
+100212044
+100050720
+100199350
+100226564
+100155296
+100154758
+100224142
+100246246
+100164852
+100249910
+100118976
+100251716
+100245766
+100156426
+100237632
+100247188
+100246590
+100154728
+100170896
+100250104
+100087036
+100158232
+100245146
+100270566
+100023208
+100032926
+100247338
+100184568
+100180226
+100004806
+100136158
+100131208
+100084250
+100253196
+100117066
+100025832
+100053472
+100256698
+100248972
+100243160
+100174664
+100314454
+100028692
+100157920
+100166356
+100335240
+100154838
+100247574
+100173946
+100252220
+100260836
+100089086
+100063558
+100259058
+100259784
+100254626
+100162782
+100036088
+100243764
+100151510
+100243444
+100073292
+100175058
+100029222
+100212928
+100242618
+100107064
+100257886
+100147478
+100352404
+100254716
+100220882
+100213510
+100250500
+100178596
+100131220
+100019028
+100154214
+100056498
+100252156
+100030368
+100192076
+100251826
+100245996
+100342329
+100129862
+100269000
+100247506
+100068002
+100094458
+100205680
+100253120
+100111392
+100148882
+100293274
+100089928
+100251982
+100201676
+100057012
+100011596
+100238236
+100155420
+100173426
+100250126
+100251984
+100313654
+100250626
+100257616
+100138194
+100030160
+100196240
+100196532
+100250938
+100031554
+100257918
+100081400
+100240946
+100178194
+100253080
+100250128
+100253924
+100194618
+100161054
+100241358
+100194048
+100107448
+100140544
+100147426
+100111936
+100233650
+100307676
+100018454
+100212060
+100084442
+100253806
+100109272
+100239692
+100261536
+100062288
+100042658
+100136318
+100056694
+100078554
+100047474
+100218888
+100176296
+100228984
+100095506
+100247594
+100342216
+100221514
+100121300
+100091758
+100191080
+100248344
+100179130
+100142756
+100063164
+100269752
+100231334
+100155292
+100185600
+100195626
+100173606
+100187960
+100261868
+100182222
+100156824
+100127760
+100204890
+100252982
+100073800
+100245388
+100314354
+100216672
+100073818
+100257394
+100349128
+100222354
+100149792
+100185806
+100263448
+100165710
+100257084
+100253718
+100295522
+100032580
+100083708
+100290514
+100319551
+100050766
+100131918
+100124312
+100309198
+100267692
+100203114
+100113006
+100023764
+100179920
+100259930
+100145950
+100090118
+100175898
+100259674
+100261610
+100108536
+100029464
+100092496
+100032286
+100240118
+100167678
+100264568
+100310562
+100080436
+100290982
+100257634
+100102438
+100264728
+100195980
+100244900
+100044392
+100265552
+100268444
+100264310
+100197180
+100270454
+100274692
+100089930
+100219398
+100208018
+100127678
+100193448
+100197576
+100220566
+100270292
+100227116
+100327134
+100154926
+100327478
+100269658
+100272094
+100246224
+100146300
+100129618
+100342227
+100098032
+100266618
+100269528
+100231562
+100332959
+100267532
+100045368
+100263062
+100046026
+100228768
+100209916
+100218106
+100218158
+100142532
+100271014
+100104366
+100255756
+100201588
+100092550
+100321252
+100070382
+100044156
+100241780
+100144598
+100209228
+100155468
+100105734
+100272012
+100259880
+100019096
+100218644
+100252204
+100238938
+100105192
+100272664
+100165524
+100290100
+100262334
+100261294
+100272566
+100231054
+100271578
+100251528
+100215632
+100155246
+100265286
+100291262
+100146736
+100223168
+100090186
+100230686
+100271682
+100271100
+100065230
+100045598
+100255062
+100309180
+100161600
+100250304
+100171728
+100312622
+100044936
+100270420
+100268158
+100167438
+100188978
+100128858
+100273852
+100284222
+100230138
+100168228
+100273332
+100119248
+100086784
+100264448
+100274878
+100204098
+100101076
+100297132
+100118118
+100272000
+100275298
+100136888
+100168166
+100274916
+100124022
+100264496
+100257208
+100118752
+100310980
+100226260
+100274712
+100310970
+100143520
+100281576
+100067972
+100264590
+100313406
+100156840
+100268514
+100269432
+100308768
+100289112
+100009108
+100142602
+100091646
+100292096
+100046126
+100204634
+100276220
+100193268
+100034750
+100158550
+100247014
+100214626
+100154000
+100352009
+100244308
+100155478
+100100816
+100019974
+100272718
+100327050
+100262936
+100120540
+100048748
+100353689
+100246702
+100196228
+100074994
+100271330
+100166468
+100052204
+100095362
+100287058
+100289104
+100057626
+100308404
+100207436
+100108534
+100257290
+100166444
+100259972
+100193696
+100133342
+100135342
+100139948
+100288722
+100107596
+100274536
+100327298
+100327438
+100253340
+100192206
+100087164
+100251986
+100138416
+100258888
+100191034
+100178246
+100237706
+100252906
+100309106
+100179960
+100094606
+100157374
+100335237
+100303608
+100262920
+100177370
+100146454
+100157378
+100039816
+100197044
+100211760
+100278834
+100219520
+100276120
+100132508
+100280490
+100255574
+100278426
+100280188
+100139674
+100306846
+100302138
+100250332
+100334969
+100324022
+100110270
+100312638
+100268230
+100302258
+100258394
+100315316
+100139802
+100277590
+100023662
+100120278
+100307804
+100176664
+100135660
+100180354
+100304396
+100214238
+100245532
+100291986
+100244774
+100314588
+100274800
+100197862
+100176652
+100157984
+100292622
+100307520
+100209838
+100362621
+100194160
+100252756
+100244852
+100197148
+100282882
+100291576
+100084844
+100274956
+100313492
+100277274
+100219712
+100259306
+100109050
+100280324
+100237598
+100016578
+100016964
+100289760
+100138630
+100200626
+100159704
+100271902
+100344510
+100292904
+100316293
+100303352
+100214054
+100290934
+100207842
+100310752
+100261900
+100000118
+100294282
+100165748
+100289658
+100270716
+100270640
+100002386
+100200090
+100294250
+100176596
+100292006
+100322989
+100183440
+100015302
+100000084
+100218646
+100224584
+100216974
+100153260
+100220294
+100310312
+100303632
+100122988
+100309480
+100207342
+100108830
+100209224
+100257568
+100022356
+100289452
+100310194
+100049904
+100069698
+100277472
+100103010
+100179080
+100260804
+100178736
+100241422
+100092108
+100183306
+100047856
+100279008
+100202694
+100210662
+100127950
+100161436
+100267592
+100289460
+100049244
+100271908
+100280214
+100280526
+100327330
+100168400
+100016796
+100275002
+100248448
+100282190
+100037326
+100031762
+100279048
+100277890
+100129328
+100084098
+100168402
+100292784
+100226126
+100302242
+100308630
+100283876
+100326817
+100308888
+100282378
+100175500
+100248668
+100280290
+100280370
+100276536
+100087438
+100265592
+100077620
+100251428
+100270028
+100218514
+100277202
+100089326
+100196322
+100196308
+100187070
+100052348
+100289558
+100086426
+100109498
+100267864
+100315292
+100238490
+100283464
+100259564
+100215784
+100127896
+100222740
+100266386
+100093816
+100121694
+100269484
+100281542
+100356558
+100279050
+100277306
+100273438
+100243992
+100231516
+100206980
+100236202
+100272866
+100281094
+100251020
+100188022
+100161402
+100212860
+100229430
+100280058
+100201834
+100122260
+100083918
+100146528
+100019362
+100251886
+100256218
+100220932
+100187832
+100129988
+100241486
+100207750
+100248132
+100108240
+100198102
+100246446
+100245616
+100176794
+100265182
+100282238
+100265682
+100322027
+100279762
+100277664
+100238814
+100075068
+100292034
+100168300
+100226558
+100101946
+100265758
+100118044
+100355040
+100234484
+100124172
+100279500
+100130344
+100313876
+100134378
+100167724
+100306914
+100275168
+100154258
+100266022
+100312430
+100328890
+100295454
+100291004
+100252362
+100219936
+100319054
+100139956
+100290478
+100105284
+100330484
+100205654
+100284620
+100283738
+100136626
+100264386
+100099620
+100092556
+100290710
+100252828
+100036800
+100154592
+100184550
+100166858
+100088922
+100327244
+100285624
+100117154
+100325444
+100280774
+100318098
+100314224
+100277368
+100277852
+100245710
+100098226
+100270066
+100293062
+100327058
+100135620
+100277172
+100103004
+100116334
+100285128
+100289994
+100170002
+100291340
+100306954
+100131704
+100170460
+100070142
+100144110
+100160330
+100063902
+100302754
+100307704
+100263936
+100285542
+100283752
+100192618
+100071222
+100124652
+100327126
+100205706
+100154770
+100181732
+100204186
+100349540
+100154944
+100061538
+100290420
+100311236
+100325306
+100308550
+100208020
+100272418
+100285548
+100161990
+100269146
+100280460
+100290282
+100290876
+100307464
+100305620
+100069142
+100266552
+100285870
+100082278
+100195476
+100313330
+100292644
+100131586
+100358392
+100307966
+100002190
+100307428
+100158858
+100202916
+100047862
+100275004
+100271750
+100281498
+100009326
+100282926
+100351446
+100053574
+100104620
+100281190
+100056912
+100287298
+100256442
+100295170
+100277510
+100224290
+100276248
+100308364
+100249898
+100283078
+100155790
+100287546
+100093894
+100291794
+100292546
+100230490
+100316252
+100307260
+100281308
+100025000
+100283208
+100289040
+100137524
+100286812
+100165292
+100109872
+100317774
+100278764
+100313962
+100285324
+100291110
+100292568
+100287646
+100268540
+100199644
+100061074
+100290970
+100291250
+100008786
+100288720
+100229400
+100318990
+100280216
+100228440
+100289518
+100211514
+100318392
+100281820
+100289918
+100289986
+100182492
+100291498
+100242536
+100290116
+100312980
+100290694
+100112414
+100289158
+100291820
+100291120
+100343969
+100289456
+100289864
+100309636
+100284906
+100288236
+100304366
+100290310
+100290534
+100290162
+100303716
+100290780
+100290872
+100292224
+100290180
+100342073
+100291174
+100289966
+100289844
+100290130
+100290118
+100297182
+100232112
+100289004
+100311498
+100289172
+100290236
+100289976
+100292300
+100290066
+100290756
+100289980
+100290430
+100137566
+100034948
+100247764
+100291996
+100105282
+100295162
+100307716
+100292194
+100291184
+100294444
+100237220
+100287604
+100294082
+100292246
+100286242
+100291228
+100107970
+100310144
+100275436
+100292778
+100298032
+100291466
+100290010
+100185812
+100294900
+100294330
+100292740
+100338572
+100296396
+100295452
+100295122
+100295886
+100296842
+100295822
+100297892
+100297446
+100297256
+100296298
+100298584
+100298662
+100297378
+100295826
+100291218
+100298988
+100024154
+100298616
+100281192
+100295546
+100288494
+100297274
+100305228
+100297186
+100169442
+100163646
+100296886
+100292176
+100298772
+100290086
+100175650
+100295274
+100084030
+100297010
+100294238
+100346986
+100295150
+100007788
+100297110
+100298020
+100296782
+100291244
+100300030
+100298314
+100295948
+100297532
+100279414
+100296200
+100299176
+100303456
+100297872
+100297572
+100298744
+100297480
+100054502
+100300818
+100298888
+100203378
+100295156
+100299776
+100297114
+100299344
+100315368
+100295432
+100294688
+100301214
+100301644
+100274000
+100296570
+100294818
+100301612
+100298236
+100291256
+100296602
+100300116
+100299072
+100295548
+100295856
+100291414
+100300168
+100290416
+100300874
+100301756
+100194176
+100301458
+100031456
+100250320
+100290122
+100180740
+100296722
+100215774
+100339831
+100297912
+100300028
+100294380
+100315084
+100299510
+100289890
+100288600
+100287178
+100300112
+100295040
+100302990
+100295492
+100352408
+100099828
+100297710
+100302184
+100299476
+100195214
+100300478
+100302936
+100298704
+100304860
+100301908
+100300708
+100297414
+100293940
+100271336
+100298590
+100277068
+100342164
+100263200
+100301684
+100178248
+100292196
+100289968
+100299922
+100302136
+100302884
+100059698
+100301478
+100291194
+100297316
+100302554
+100294362
+100297738
+100291396
+100310318
+100303218
+100047358
+100299392
+100301990
+100298594
+100301074
+100289764
+100300530
+100302448
+100283150
+100038858
+100301254
+100290112
+100213206
+100179356
+100291306
+100298644
+100145114
+100057972
+100290194
+100301608
+100015910
+100299186
+100302844
+100301848
+100299416
+100299796
+100118698
+100290148
+100300456
+100291332
+100304804
+100073078
+100288290
+100065440
+100303338
+100346658
+100128282
+100095728
+100303596
+100311970
+100181288
+100273616
+100289646
+100161466
+100185180
+100307106
+100272432
+100303050
+100303922
+100290120
+100334501
+100302270
+100306856
+100277618
+100342068
+100308680
+100223874
+100324014
+100258982
+100307342
+100305796
+100111880
+100236470
+100350438
+100308362
+100301028
+100270138
+100096052
+100239548
+100308044
+100307104
+100325739
+100294902
+100165202
+100308380
+100046004
+100308890
+100079644
+100307102
+100066090
+100353696
+100178014
+100310224
+100307750
+100309190
+100308158
+100304870
+100307340
+100306788
+100313196
+100308484
+100308350
+100308164
+100056160
+100308354
+100249488
+100310568
+100239690
+100052606
+100309022
+100317772
+100308108
+100015758
+100310322
+100310122
+100155462
+100290080
+100310238
+100317305
+100077588
+100307652
+100136180
+100309162
+100310880
+100149286
+100258046
+100289294
+100309574
+100240816
+100310112
+100289136
+100307448
+100313640
+100310170
+100352425
+100230756
+100327494
+100311518
+100313920
+100244744
+100325452
+100029160
+100172738
+100348898
+100159580
+100310000
+100245662
+100133200
+100252562
+100311758
+100312290
+100129456
+100260496
+100362441
+100362017
+100325304
+100351977
+100359701
+100360368
+100359267
+100313240
+100186220
+100291554
+100360844
+100359293
+100361198
+100038864
+100357472
+100361192
+100101260
+100154324
+100360367
+100193874
+100138470
+100006140
+100314496
+100100538
+100330806
+100310418
+100358717
+100358556
+100358555
+100116202
+100228298
+100238722
+100025842
+100314338
+100117580
+100290208
+100086920
+100357859
+100268102
+100342285
+100339413
+100361176
+100314026
+100138658
+100352533
+100335216
+100099612
+100361602
+100327350
+100361975
+100317890
+100288192
+100327332
+100059744
+100362103
+100290424
+100362116
+100306318
+100315495
+100356765
+100089766
+100354834
+100362561
+100136992
+100315481
+100325434
+100362129
+100160574
+100250048
+100315304
+100362438
+100314916
+100314182
+100052626
+100160934
+100357143
+100315070
+100317415
+100248742
+100209132
+100362114
+100351370
+100197976
+100276034
+100211796
+100362060
+100312644
+100097910
+100014818
+100045620
+100310420
+100344538
+100361637
+100315704
+100352403
+100184388
+100070854
+100353676
+100046286
+100255608
+100269508
+100289400
+100123200
+100118990
+100237376
+100227774
+100314290
+100243556
+100060392
+100324127
+100362112
+100182168
+100069692
+100360461
+100352484
+100317778
+100362130
+100352454
+100361416
+100361976
+100023726
+100361154
+100361389
+100290128
+100196030
+100347519
+100075208
+100340284
+100327008
+100342822
+100319589
+100289886
+100324018
+100314262
+100290192
+100121214
+100234186
+100361215
+100361971
+100327336
+100354883
+100093742
+100312916
+100315336
+100143718
+100316753
+100313308
+100310884
+100310862
+100332791
+100317964
+100361193
+100284792
+100113216
+100050262
+100182382
+100352213
+100254850
+100083502
+100239768
+100362417
+100179480
+100179086
+100073350
+100160298
+100327178
+100151558
+100050154
+100080760
+100064578
+100288198
+100333233
+100344196
+100344197
+100361150
+100310662
+100362447
+100325003
+100362126
+100354154
+100322249
+100358143
+100362560
+100360513
+100316327
+100362128
+100131484
+100345772
+100046182
+100078472
+100352440
+100253152
+100360719
+100081908
+100122480
+100289928
+100361010
+100356637
+100360029
+100349913
+100340368
+100327174
+100361141
+100362397
+100196248
+100308738
+100338472
+100358412
+100214080
+100307432
+100360664
+100345755
+100352520
+100317650
+100156076
+100302420
+100339678
+100341946
+100250132
+100002798
+100308692
+100308882
+100154914
+100066250
+100307438
+100324478
+100048756
+100361155
+100002278
+100290312
+100183698
+100362127
+100290466
+100361160
+100284976
+100320245
+100283990
+100320081
+100362111
+100357663
+100183580
+100352483
+100307408
+100290994
+100358159
+100327142
+100018504
+100309108
+100276156
+100355432
+100222908
+100110792
+100354234
+100119252
+100308528
+100360193
+100217748
+100196596
+100291522
+100282892
+100342682
+100058034
+100246752
+100056494
+100194686
+100290536
+100307188
+100094626
+100352479
+100308924
+100326148
+100322161
+100327168
+100040908
+100226354
+100290918
+100307054
+100173138
+100360410
+100290446
+100362494
+100119000
+100055202
+100309010
+100019890
+100093692
+100291432
+100091652
+100327138
+100333232
+100362503
+100280722
+100284300
+100344078
+100210664
+100361171
+100100632
+100329979
+100130364
+100229468
+100359446
+100089916
+100000610
+100324631
+100056852
+100303702
+100345693
+100153082
+100352514
+100270308
+100302422
+100221336
+100133552
+100351372
+100358290
+100318636
+100132206
+100329553
+100323523
+100334101
+100072130
+100327464
+100002166
+100291298
+100318532
+100327100
+100105356
+100204138
+100346653
+100333992
+100254120
+100289758
+100333051
+100314162
+100286542
+100344125
+100122028
+100016106
+100163476
+100327476
+100327338
+100068828
+100155054
+100242560
+100282782
+100070628
+100356689
+100352436
+100334078
+100319280
+100242810
+100072652
+100000222
+100121404
+100046596
+100359225
+100207944
+100232268
+100196188
+100007904
+100321161
+100198610
+100043252
+100272960
+100310360
+100182306
+100194180
+100336217
+100352443
+100352540
+100085680
+100211630
+100215332
+100356635
+100320654
+100335872
+100165696
+100358934
+100362448
+100167604
+100002840
+100353009
+100312310
+100347139
+100289878
+100335592
+100357466
+100135264
+100068492
+100053930
+100316789
+100360599
+100327176
+100331187
+100212882
+100359595
+100309334
+100334688
+100289854
+100314266
+100312588
+100303356
+100362420
+100290062
+100354779
+100354720
+100290064
+100079746
+100290660
+100358537
+100226202
+100354714
+100157636
+100059676
+100342046
+100253840
+100140208
+100198418
+100349985
+100264232
+100194620
+100273222
+100228630
+100315170
+100337631
+100349701
+100362440
+100311806
+100282958
+100020838
+100328019
+100290670
+100162036
+100308202
+100362734
+100307124
+100214688
+100352435
+100307530
+100268050
+100037846
+100249464
+100136334
+100256210
+100352432
+100100628
+100237668
+100073552
+100358525
+100351520
+100354992
+100050932
+100249838
+100289888
+100281234
+100069640
+100081550
+100230588
+100353956
+100317762
+100021648
+100356003
+100351819
+100306020
+100356384
+100352398
+100101580
+100268572
+100020162
+100358264
+100352499
+100052740
+100123314
+100290576
+100347323
+100240596
+100352539
+100161378
+100258312
+100361202
+100334988
+100289934
+100181698
+100248056
+100092152
+100351890
+100352521
+100119964
+100290370
+100290384
+100358538
+100178024
+100080454
+100316258
+100211438
+100330156
+100192700
+100081962
+100154434
+100309988
+100245824
+100342316
+100204896
+100322882
+100175002
+100149462
+100125144
+100137570
+100325903
+100046730
+100291284
+100352407
+100284088
+100314396
+100227846
+100181274
+100243062
+100177378
+100342488
+100309980
+100337568
+100289884
+100309692
+100234390
+100354713
+100044352
+100327140
+100015962
+100323119
+100290686
+100211422
+100350774
+100091290
+100291320
+100349912
+100344186
+100174236
+100162328
+100238240
+100135654
+100343718
+100224992
+100273484
+100265158
+100221074
+100001586
+100112668
+100009264
+100203762
+100317678
+100020178
+100280562
+100344472
+100031740
+100280056
+100268562
+100291064
+100343487
+100314250
+100198604
+100342103
+100290550
+100230446
+100221992
+100298476
+100194118
+100351549
+100319248
+100036232
+100290850
+100167904
+100255794
+100280314
+100098844
+100341761
+100178962
+100313072
+100311432
+100154720
+100339369
+100326869
+100293480
+100240146
+100334849
+100322355
+100355897
+100352005
+100103392
+100344384
+100129708
+100343535
+100327246
+100155540
+100357315
+100186544
+100206316
+100094376
+100356385
+100214048
+100348053
+100074850
+100192702
+100138826
+100051962
+100001388
+100342443
+100307512
+100083442
+100327106
+100311854
+100335169
+100344471
+100342067
+100259536
+100176770
+100324656
+100303428
+100164696
+100290762
+100342195
+100248034
+100312068
+100178456
+100081080
+100126546
+100221444
+100307836
+100056276
+100324012
+100178432
+100324654
+100278820
+100101024
+100276262
+100108168
+100172178
+100320602
+100100158
+100163024
+100325406
+100325410
+100356758
+100090640
+100325138
+100236502
+100326938
+100247926
+100326500
+100314392
+100062886
+100309152
+100196770
+100315977
+100304784
+100065380
+100195178
+100325250
+100094390
+100141664
+100178794
+100325696
+100335217
+100330283
+100325310
+100083990
+100327120
+100313904
+100290664
+100090972
+100045930
+100329549
+100328061
+100067890
+100108736
+100354833
+100308040
+100200086
+100326432
+100327006
+100279016
+100040310
+100276972
+100174308
+100112624
+100280138
+100202024
+100059726
+100333252
+100194764
+100154396
+100330856
+100132208
+100325296
+100079930
+100339833
+100207326
+100195678
+100324444
+100139394
+100117440
+100289392
+100328732
+100257922
+100127654
+100352513
+100057736
+100034652
+100324539
+100359445
+100337287
+100330973
+100290594
+100013716
+100327342
+100268658
+100322874
+100349951
+100048528
+100280806
+100291472
+100086158
+100327238
+100307178
+100204320
+100249478
+100308684
+100310060
+100194022
+100356528
+100172072
+100154780
+100014074
+100313076
+100330275
+100326883
+100342193
+100311910
+100325943
+100289138
+100174492
+100341787
+100160152
+100278940
+100042758
+100048156
+100307282
+100341549
+100276856
+100194882
+100077546
+100125150
+100010050
+100327380
+100113488
+100288960
+100327064
+100057056
+100067018
+100008798
+100022268
+100112732
+100324382
+100098610
+100308088
+100154326
+100173164
+100153952
+100177478
+100090672
+100327352
+100210154
+100352421
+100267852
+100342444
+100273024
+100303902
+100102266
+100256122
+100318016
+100352389
+100257492
+100330556
+100225254
+100118794
+100351745
+100160752
+100327651
+100072500
+100254390
+100270732
+100209350
diff --git a/toontown/src/coderedemption/TTCodeDict.py b/toontown/src/coderedemption/TTCodeDict.py
new file mode 100644
index 0000000..d1e5793
--- /dev/null
+++ b/toontown/src/coderedemption/TTCodeDict.py
@@ -0,0 +1,235 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+import math
+import string
+
+class TTCodeDict:
+ notify = directNotify.newCategory('TTCodeDict')
+
+ # characters used for auto-generated codes
+ Characters = 'CDFGHJKLMNPQRVWX3469'
+ NumChars = len(Characters)
+
+ # characters used for manually-created codes
+ IgnoredManualCharacters = '-' + ' '
+ ManualCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + string.digits + IgnoredManualCharacters
+ # all manually-created codes must contain at least one of these characters
+ # ensures that code can never collide with an auto-generated code
+ ManualOnlyCharacters = ''
+
+ for char in ManualCharacters:
+ if char not in IgnoredManualCharacters:
+ if char not in Characters:
+ ManualOnlyCharacters += char
+
+ CharactersSet = set(Characters)
+ ManualCharactersSet = set(ManualCharacters)
+
+ _Primes = {}
+ _PrimeModuli = {}
+
+ # prevent brute-force attacks by using one in N codes
+ BruteForceFactor = 1000
+
+ @classmethod
+ def isLegalUniqueCode(cls, code):
+ chars = set(cls.getFromReadableCode(code))
+ return len(chars.difference(cls.CharactersSet)) == 0
+
+ @classmethod
+ def isLegalNonUniqueCode(cls, code):
+ for c in code:
+ if not cls.isValidManualChar(c):
+ return False
+ return True
+
+ @classmethod
+ def isLegalCode(cls, code):
+ return cls.isLegalUniqueCode(code) or cls.isLegalNonUniqueCode(code)
+
+ @classmethod
+ def isValidManualChar(cls, c):
+ if c in cls.IgnoredManualCharacters:
+ return True
+ return c.isalnum()
+
+ @classmethod
+ def isManualOnlyChar(cls, c):
+ if c.upper() in cls.CharactersSet:
+ return False
+ # any unicode alphanumeric character that is not in the auto-generated character set
+ # is a manual-only character
+ return c.isalnum()
+
+ @staticmethod
+ def _isPrime(value):
+ maxTestVal = int(math.ceil(math.sqrt(value)))
+ if (maxTestVal % 2) == 0:
+ maxTestVal -= 1
+ testVal = 3
+ while testVal <= maxTestVal:
+ if (value % testVal) == 0:
+ return False
+ testVal += 2
+ return True
+
+ @staticmethod
+ def _nextPrime(value):
+ if (value % 2) == 0:
+ value += 1
+ while not TTCodeDict._isPrime(value):
+ value += 2
+ return value
+
+ @classmethod
+ def getNumValuesInCodeSpace(cls, codeLength):
+ return pow(cls.NumChars, codeLength)
+
+ @classmethod
+ def getNumUsableValuesInCodeSpace(cls, codeLength):
+ return int(cls.getNumValuesInCodeSpace(codeLength) / cls.BruteForceFactor)
+
+ @classmethod
+ def _getPrimeModulus(cls, codeLength):
+ # get the largest prime in this code value space to use as the code value modulus
+ if codeLength in cls._PrimeModuli:
+ return cls._PrimeModuli[codeLength]
+ print ('calculating prime modulus for code length %s...' % codeLength),
+ i = cls.getNumValuesInCodeSpace(codeLength)
+ while not cls._isPrime(i):
+ i -= 1
+ if i < 0:
+ raise 'could not find prime modulus for code length %s' % codeLength
+ cls._PrimeModuli[codeLength] = i
+ print 'done.'
+ return i
+
+ @classmethod
+ def _getPrime(cls, codeLength):
+ if (codeLength in cls._Primes):
+ return cls._Primes[codeLength]
+ print ('calculating prime multiplier for code length %s...' % codeLength),
+ numValues = cls.getNumValuesInCodeSpace(codeLength)
+ if '_scatterPrime' not in cls.__dict__:
+ # longer codes will require a larger (longer/more digits/more 7's!) prime here
+ cls._scatterPrime = 677770777
+ cls._scatterPow10 = 1
+ scratch = cls._scatterPrime
+ while scratch:
+ cls._scatterPow10 *= 10
+ scratch = int(scratch / 10)
+ cls._scatterFactor = float(cls._scatterPrime) / cls._scatterPow10
+ subdivisions = cls.NumChars * cls._scatterPow10
+ primeFactor = cls._scatterFactor
+ multiplier = (subdivisions * primeFactor)
+ prime = cls._nextPrime(int((float(numValues) * multiplier) / subdivisions))
+ if prime >= numValues:
+ prime = cls._nextPrime(int(float(numValues) / subdivisions))
+ if prime >= numValues:
+ prime = cls._nextPrime(0)
+ if prime >= numValues:
+ raise 'could not find prime smaller than %s' % numValues
+ cls._Primes[codeLength] = prime
+ #print 'codeLength %s, prime=%s' % (codeLength, prime)
+ print 'done.'
+ return prime
+
+ @classmethod
+ def getObfuscatedCodeValue(cls, codeValue, codeLength):
+ prime = cls._getPrime(codeLength)
+ modulus = cls._getPrimeModulus(codeLength)
+ obfValue = (prime * codeValue) % modulus
+ return obfValue
+
+ @classmethod
+ def getCodeFromValue(cls, codeValue, codeLength):
+ codeStr = ''
+ charsLeft = codeLength
+ while charsLeft > 0:
+ index = codeValue % cls.NumChars
+ codeValue = int(codeValue / cls.NumChars)
+ codeStr = cls.Characters[index] + codeStr
+ charsLeft -= 1
+ return codeStr
+
+ @classmethod
+ def getReadableCode(cls, code):
+ """
+ 01 X
+ 02 XX
+ 03 XXX
+ 04 XXXX
+ 05 XX-XXX
+ 06 XXX-XXX
+ 07 XXX-XXXX
+ 08 XXXX-XXXX
+ 09 XXX-XXX-XXX
+ 10 XXX-XXX-XXXX # 6 + 4
+ 11 XXX-XXXX-XXXX # 7 + 4
+ 12 XXXX-XXXX-XXXX # 8 + 4
+ 13 XXX-XXX-XXX-XXXX # 9 + 4
+ 14 XXX-XXX-XXXX-XXXX # 6 + 8
+ 15 XXX-XXXX-XXXX-XXXX # 7 + 8
+ 16 XXXX-XXXX-XXXX-XXXX # 8 + 8
+ 17 XXX-XXX-XXX-XXXX-XXXX # 9 + 8
+ 18 XXX-XXX-XXXX-XXXX-XXXX # 6 + 12
+ 19 XXX-XXXX-XXXX-XXXX-XXXX # 7 + 12
+ 20 XXXX-XXXX-XXXX-XXXX-XXXX # 8 + 12
+ 21 XXX-XXX-XXX-XXXX-XXXX-XXXX # 9 + 12
+ 22 XXX-XXX-XXXX-XXXX-XXXX-XXXX # 6 + 16
+ """
+ length = len(code)
+ if length < 5:
+ return code
+ if length == 5:
+ return '%s-%s' % (code[:2], code[2:])
+ if length == 6:
+ return '%s-%s' % (code[:3], code[3:])
+ if length == 7:
+ return '%s-%s' % (code[:3], code[3:])
+ if length == 8:
+ return '%s-%s' % (code[:4], code[4:])
+ if length == 9:
+ return '%s-%s-%s' % (code[:3], code[3:6], code[6:])
+ numQuads = (len(code) - 6) / 4
+ prefixLen = len(code) - (numQuads * 4)
+ prefix = cls.getReadableCode(code[:prefixLen])
+ toQuad = code[prefixLen:]
+ rc = prefix
+ while len(toQuad):
+ rc = '%s-%s' % (rc, toQuad[:4])
+ toQuad = toQuad[4:]
+ return rc
+
+ @classmethod
+ def getFromReadableCode(cls, code):
+ cls.notify.debug('getFromReadableCode: input: %s' % code)
+ # remove dashes
+ code = ''.join(code.split('-'))
+ # remove spaces
+ code = ''.join(code.split(' '))
+ # uppercase only
+ code = code.upper()
+ cls.notify.debug('getFromReadableCode: output: %s' % code)
+ return code
+
+ @classmethod
+ def _testCodeUniqueness(cls, codeLength=None, verbose=True):
+ if not codeLength:
+ codeLength = 4
+ while 1:
+ print 'testing code uniqueness for code length: %s' % codeLength
+ codes = set()
+ maxVal = cls._getPrimeModulus(codeLength)
+ #print maxVal
+ i = 0
+ while i < maxVal:
+ x = cls.getObfuscatedCodeValue(i, codeLength)
+ code = cls.getCodeFromValue(x, codeLength)
+ if verbose:
+ print '%s %s/%s -> %s' % (cls.getReadableCode(code), i, maxVal-1, x)
+ if code in codes:
+ raise 'code %s already encountered!' % code
+ codes.add(code)
+ i += 1
+ codeLength += 1
+
diff --git a/toontown/src/coderedemption/TTCodeRedemptionConsts.py b/toontown/src/coderedemption/TTCodeRedemptionConsts.py
new file mode 100644
index 0000000..7bc1231
--- /dev/null
+++ b/toontown/src/coderedemption/TTCodeRedemptionConsts.py
@@ -0,0 +1,20 @@
+DefaultDbName = 'tt_code_redemption'
+
+RedeemErrors = Enum(
+ 'Success, CodeDoesntExist, CodeIsExpired, CodeAlreadyRedeemed, AwardCouldntBeGiven, '
+ 'TooManyAttempts, SystemUnavailable, ')
+
+# for ~code response
+RedeemErrorStrings = {
+ RedeemErrors.Success: 'Success',
+ RedeemErrors.CodeDoesntExist: 'Invalid code',
+ RedeemErrors.CodeIsExpired: 'Code is expired',
+ RedeemErrors.CodeAlreadyRedeemed: 'Code has already been redeemed',
+ RedeemErrors.AwardCouldntBeGiven: 'Award could not be given',
+ RedeemErrors.TooManyAttempts: 'Too many attempts, code ignored',
+ RedeemErrors.SystemUnavailable: 'Code redemption is currently unavailable',
+ }
+
+assert len(RedeemErrorStrings) == len(RedeemErrors)
+
+MaxCustomCodeLen = config.GetInt('tt-max-custom-code-len', 16)
diff --git a/toontown/src/coderedemption/TTCodeRedemptionDB.py b/toontown/src/coderedemption/TTCodeRedemptionDB.py
new file mode 100644
index 0000000..468f60a
--- /dev/null
+++ b/toontown/src/coderedemption/TTCodeRedemptionDB.py
@@ -0,0 +1,1708 @@
+if __name__ == '__main__':
+ # running as a MySQL communication subprocess
+
+ # redirect any output during module init to stderr
+ import sys
+ stdOut = sys.stdout
+ sys.stdout = sys.stderr
+ print 'code redemption subprocess starting...'
+
+ import direct
+ from pandac.PandaModules import *
+ from direct.showbase.ShowBase import ShowBase
+ #showbase = ShowBase(fStartDirect=False, windowType='none')
+ config = getConfigShowbase()
+
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.fsm.FSM import FSM
+from direct.fsm.StatePush import StateVar, FunctionCall
+from direct.showbase.DirectObject import DirectObject
+from direct.task import Task
+from direct.showbase.Job import Job
+from direct.stdpy import threading # MySQLdb blocks on locked table access
+from otp.uberdog.DBInterface import DBInterface
+from toontown.coderedemption.TTCodeDict import TTCodeDict
+from toontown.coderedemption import TTCodeRedemptionConsts
+from direct.directutil import DirectMySQLdb
+import _mysql_exceptions
+import random
+import datetime
+import MySQLdb
+import os
+import subprocess
+import time
+
+class MySQLErrors:
+ DbAlreadyExists = 1007
+ TableAlreadyExists = 1050
+ ServerShuttingDown = 1053
+ ServerGoneAway = 2006
+
+class TryAgainLater(Exception):
+ def __init__(self, mysqlException, address):
+ self._exception = mysqlException
+ self._address = address
+ def getMySQLException(self):
+ return self._exception
+ def __str__(self):
+ return 'problem using MySQL DB at %s, try again later (%s)' % (self._address, self._exception)
+
+class TTDBCursorBase:
+ ConnectionProblems = set([MySQLErrors.ServerShuttingDown,
+ MySQLErrors.ServerGoneAway,
+ ])
+ def _setConnection(self, connection):
+ self._connection = connection
+
+ def _doExecute(self, cursorBase, *args, **kArgs):
+ if self.notify.getDebug():
+ self.notify.debug('execute:\n%s' % u2ascii(args[0]))
+ try:
+ cursorBase.execute(self, *args, **kArgs)
+ except _mysql_exceptions.OperationalError, e:
+ if self._connection.getErrorCode(e) in TTDBCursorBase.ConnectionProblems:
+ # force a reconnect
+ TTCRDBConnection.db = None
+ raise TryAgainLater(e, '%s:%s' % (self._connection._host, self._connection._port))
+ else:
+ raise
+
+class TTDBCursor(MySQLdb.cursors.Cursor, TTDBCursorBase):
+ notify = directNotify.newCategory('TTCodeRedemptionDB')
+
+ def execute(self, *args, **kArgs):
+ self._doExecute(MySQLdb.cursors.Cursor, *args, **kArgs)
+
+class TTDBDictCursor(MySQLdb.cursors.DictCursor, TTDBCursorBase):
+ notify = directNotify.newCategory('TTCodeRedemptionDB')
+
+ def execute(self, *args, **kArgs):
+ self._doExecute(MySQLdb.cursors.DictCursor, *args, **kArgs)
+
+class TTCRDBConnection(DBInterface):
+ notify = directNotify.newCategory('TTCodeRedemptionDB')
+
+ RetryPeriod = 5.
+ TableLockRetryPeriod = 1.
+
+ Connecting = 'Connecting'
+ Initializing = 'Initializing'
+ Locking = 'Locking'
+ Connected = 'Connected'
+ Released = 'Released'
+ WaitForRetry = 'WaitForRetry'
+ WaitForRetryLocking = 'WaitForRetryLocking'
+
+ StartCodeLength = 4
+
+ READ = 'READ'
+ WRITE = 'WRITE'
+
+ LoggedConnectionInfo = False
+ ConnectedEvent = 'TTCRDBConnectionMgr-Connected-%s'
+
+ WantTableLocking = config.GetBool('want-code-redemption-db-locking', 0)
+
+ db = None
+
+ LastFailedConnectTime = None
+ ConnectRetryTimeout = 3.
+
+ def __init__(self, connectInfo, tableLocks={}):
+ # tableLocks: table name -> READ||WRITE
+ self._host = connectInfo.host
+ self._port = connectInfo.port
+ self._user = connectInfo.user
+ self._passwd = connectInfo.passwd
+ self._tableLocks = tableLocks
+ self._dbName = connectInfo.dbname
+ self._retryDoLater = None
+ self._retryLockingDoLater = None
+ self._curState = 'Off'
+ self.request(self.Connecting)
+
+ # hack FSM to allow request in enter methods
+ def request(self, state):
+ exitFuncName = 'exit%s' % self._curState
+ if hasattr(self, exitFuncName):
+ getattr(self, exitFuncName)()
+ enterFuncName = 'enter%s' % state
+ self._curState = state
+ if hasattr(self, enterFuncName):
+ getattr(self, enterFuncName)()
+
+ def getState(self):
+ return self._curState
+
+ def destroy(self):
+ self.release()
+ self.request('Off')
+
+ def getConnectedEvent(self):
+ return self.ConnectedEvent % id(self)
+
+ def isConnected(self):
+ return self._curState == self.Connected
+
+ def getDb(self):
+ # returns valid MySQLdb when in Connected state
+ return self.__class__.db
+
+ def getCursor(self):
+ cursor = TTDBCursor(self.__class__.db)
+ cursor._setConnection(self)
+ return cursor
+
+ def getDictCursor(self):
+ cursor = TTDBDictCursor(self.__class__.db)
+ cursor._setConnection(self)
+ return cursor
+
+ def commit(self):
+ self.__class__.db.commit()
+
+ def release(self):
+ self.request('Released')
+
+ def enterConnecting(self):
+ if self.__class__.LastFailedConnectTime is not None:
+ if (globalClock.getRealTime() - self.__class__.LastFailedConnectTime) < self.ConnectRetryTimeout:
+ raise TryAgainLater(None, '%s:%s' % (self._host, self._port))
+
+ if not self.__class__.db:
+ try:
+ self.__class__.db = DirectMySQLdb.connect(host=self._host,
+ port=self._port,
+ user=self._user,
+ passwd=self._passwd)
+ except _mysql_exceptions.OperationalError,e:
+ """
+ self.notify.warning("Failed to connect to MySQL at %s:%d. Retrying in %s seconds."%(
+ self._host,self._port,self.RetryPeriod))
+ self.request(self.WaitForRetry)
+ """
+ self.notify.warning(str(e))
+ self.__class__.LastFailedConnectTime = globalClock.getRealTime()
+ raise TryAgainLater(e, '%s:%s' % (self._host, self._port))
+ else:
+ self.__class__.db.set_character_set('utf8')
+
+ # spammy
+ if not self.__class__.LoggedConnectionInfo:
+ self.notify.debug("Connected to MySQL at %s:%d."%(self._host,self._port))
+ self.__class__.LoggedConnectionInfo = True
+ self.request(self.Initializing)
+ else:
+ # no DB initialization required since we're already connected
+ self.request(self.Locking)
+
+ def _createTable(self, command):
+ cursor = self.getCursor()
+ try:
+ cursor.execute(command)
+ except _mysql_exceptions.OperationalError, e:
+ if self.getErrorCode(e) == MySQLErrors.TableAlreadyExists:
+ # table already exists
+ pass
+ else:
+ raise
+
+ def enterInitializing(self):
+ # create database
+ cursor = self.getCursor()
+ initDb = config.GetBool('want-code-redemption-init-db', __dev__)
+ if initDb:
+ try:
+ cursor.execute("CREATE DATABASE %s" % self._dbName)
+ self.notify.info("database %s did not exist, created new one" % self._dbName)
+ except _mysql_exceptions.ProgrammingError, e:
+ if self.getErrorCode(e) == MySQLErrors.DbAlreadyExists:
+ # db already exists
+ pass
+ else:
+ raise
+
+ cursor.execute("USE %s" % self._dbName)
+
+ if initDb:
+ # create tables
+ self._createTable(
+ """
+ CREATE TABLE code_space (
+ code_length int(32) unsigned NOT NULL,
+ next_code_value bigint(64) unsigned NOT NULL
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ """
+ )
+ self._createTable(
+ """
+ CREATE TABLE lot (
+ lot_id int(32) unsigned NOT NULL auto_increment,
+ name text NOT NULL,
+ manual enum('F','T') NOT NULL,
+ %(rewardType)s int(32) unsigned NOT NULL,
+ %(rewardItemId)s int(32) unsigned NOT NULL,
+ size bigint(64) NOT NULL,
+ creation DATETIME,
+ expiration DATETIME,
+ PRIMARY KEY (lot_id)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ """ % {'rewardType': TTCodeRedemptionDB.RewardTypeFieldName,
+ 'rewardItemId': TTCodeRedemptionDB.RewardItemIdFieldName,
+ }
+ )
+
+ cursor = self.getDictCursor()
+
+ while 1:
+ cursor.execute(
+ """
+ SELECT code_length, next_code_value FROM code_space;
+ """
+ )
+ rows = cursor.fetchall()
+ if len(rows) == 0:
+ if self.WantTableLocking:
+ cursor.execute(
+ """
+ LOCK TABLES code_space WRITE;
+ """
+ )
+ cursor.execute(
+ """
+ INSERT INTO code_space (code_length, next_code_value) VALUES(%s, 0);
+ """ % (self.StartCodeLength)
+ )
+ if self.WantTableLocking:
+ cursor.execute(
+ """
+ UNLOCK TABLES;
+ """
+ )
+ self.commit()
+ continue
+ else:
+ assert len(rows) == 1
+ break
+
+ self.request(self.Locking)
+
+ def enterLocking(self):
+ try:
+ if self.WantTableLocking:
+ if len(self._tableLocks):
+ cmd = 'LOCK TABLES '
+ for table, lock in self._tableLocks.iteritems():
+ cmd += '%s %s, ' % (table, lock)
+ cmd = cmd[:-2] + ';'
+ self.getCursor().execute(cmd)
+ except TryAgainLater,e:
+ self.notify.warning('failed to acquire table lock(s), retrying in %s seconds') % (
+ self.TableLockRetryPeriod, )
+ self.request(self.WaitForRetryLocking)
+ else:
+ # spammy
+ #self.notify.info("tables locked")
+ self.request(self.Connected)
+
+ def enterConnected(self):
+ messenger.send(self.getConnectedEvent())
+
+ def enterDisconnected(self):
+ pass
+ def exitDisconnected(self):
+ pass
+
+ def enterWaitForRetry(self):
+ if self._retryDoLater:
+ taskMgr.remove(self._retryDoLater)
+ self._retryDoLater = taskMgr.doMethodLater(self.RetryPeriod, self._retryConnect, 'TTCRDBConnectionMgr-retryConnect-%s' % id(self))
+
+ def _retryConnect(self, task=None):
+ self.request(self.Connecting)
+ return Task.done
+
+ def exitWaitForRetry(self):
+ if self._retryDoLater:
+ taskMgr.remove(self._retryDoLater)
+ self._retryDoLater = None
+
+ def enterWaitForRetryLocking(self):
+ if self._retryLockingDoLater:
+ taskMgr.remove(self._retryLockingDoLater)
+ self._retryLockingDoLater = taskMgr.doMethodLater(self.RetryLockingPeriod, self._retryLocking,
+ 'TTCRDBConnectionMgr-retryLocking-%s' % id(self))
+
+ def _retryLockingDoLater(self, task=None):
+ self.request(self.Locking)
+ return Task.done
+
+ def exitWaitForRetryLocking(self):
+ if self._retryLockingDoLater:
+ taskMgr.remove(self._retryLockingDoLater)
+ self._retryLockingDoLater = None
+
+ def enterReleased(self):
+ if self.WantTableLocking:
+ if len(self._tableLocks):
+ self.getCursor().execute('UNLOCK TABLES;')
+
+class TTCodeRedemptionDBTester(Job):
+ notify = directNotify.newCategory('TTCodeRedemptionDBTester')
+
+ TestLotName = 'temp_auto_test_lot_'
+
+ class TestRewarder:
+ FakeAvId = 2847
+ def _giveReward(self, avId, rewardTypeId, rewardItemId, callback):
+ callback(0)
+
+ def __init__(self, db):
+ self._db = db
+ Job.__init__(self, 'TTCodeRedemptionDBTester-%s' % serialNum())
+
+ def getRandomSamples(self, callback, numSamples):
+ samples = []
+ for i in xrange(numSamples):
+ samples.append(int(random.random() * ((1L<<32)-1)))
+ callback(samples)
+
+ @classmethod
+ def isLotNameValid(cls, lotName):
+ # make sure a user doesn't create a lot that matches the test lot naming convention
+ return (cls.TestLotName not in lotName)
+
+ @classmethod
+ def cleanup(cls, db):
+ # remove any leftover data from previous tests
+ db._testing = True
+ lotNames = db.getLotNames()
+ for lotName in lotNames:
+ if cls.TestLotName in lotName:
+ db.deleteLot(lotName)
+ db._testing = False
+
+ def _handleRedeemResult(self, result, awardMgrResult):
+ self._redeemResult.append(result)
+ self._redeemResult.append(awardMgrResult)
+
+ def _getUnusedLotName(self):
+ lotNames = self._db.getLotNames()
+ while 1:
+ lotName = '%s%s' % (self.TestLotName, int(random.random() * ((1L<<32)-1)))
+ if lotName not in lotNames:
+ break
+ return lotName
+
+ def _getUnusedManualCode(self):
+ while 1:
+ code = ''
+ length = random.randrange(4, 16)
+ manualCharIndex = random.randrange(length)
+ for i in xrange(length):
+ if i == manualCharIndex:
+ charSet = TTCodeDict.ManualOnlyCharacters
+ else:
+ charSet = TTCodeDict.ManualCharacters
+ char = random.choice(charSet)
+ if char in TTCodeDict.IgnoredManualCharacters:
+ i -= 1
+ code = code + char
+ if not self._db.codeExists(code):
+ break
+ return code
+
+ def _getUnusedUtf8ManualCode(self):
+ chars = u'\u65e5\u672c\u8a9e'
+ code = unicode('', 'utf-8')
+ while 1:
+ code += random.choice(chars)
+ if not self._db.codeExists(code):
+ break
+ return code
+
+ def run(self):
+ self.notify.info('testing started')
+
+ retryStartT = None
+ retryDelay = 5
+
+ while 1:
+ try:
+ db = self._db
+ db._testing = True
+
+ lotName = self._getUnusedLotName()
+
+ # make sure there are at least one manual and one auto lot throughout the tests
+ phLots = []
+ phLots.append(self._getUnusedLotName())
+ for i in self._db.createLot(self.getRandomSamples, phLots[-1], 1, 0, 0):
+ db._testing = False
+ yield None
+ db._testing = True
+ phLots.append(self._getUnusedLotName())
+ code = self._getUnusedManualCode()
+ self._db.createManualLot(phLots[-1], code, 0, 0)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # lot creation
+ NumCodes = 3
+ RewardType = 0
+ RewardItemId = 0
+ ExpirationDate = '9999-04-01'
+ for i in self._db.createLot(self.getRandomSamples, lotName, NumCodes,
+ RewardType, RewardItemId, ExpirationDate):
+ db._testing = False
+ yield None
+ db._testing = True
+
+ lotNames = self._db.getLotNames()
+ if lotName not in lotNames:
+ self.notify.error('could not create code redemption lot \'%s\'' % lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ autoLotNames = self._db.getAutoLotNames()
+ if lotName not in autoLotNames:
+ self.notify.error('auto lot \'%s\' not found in getAutoLotNames()' % lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ manualLotNames = self._db.getManualLotNames()
+ if lotName in manualLotNames:
+ self.notify.error('auto lot \'%s\' found in getAutoLotNames()' % lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # get codes in lot
+ codes = self._db.getCodesInLot(lotName)
+ if len(codes) != NumCodes:
+ self.notify.error('incorrect number of codes from getCodesInLot (%s)' % len(codes))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # code existance query
+ exists = self._db.codeExists(codes[0])
+ if not exists:
+ self.notify.error('codeExists returned false for code %s' % codes[0])
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # number of redemptions (not yet redeemed)
+ redemptions = self._db.getRedemptions(codes[0])
+ if redemptions != 0:
+ self.notify.error(
+ 'incorrect number of redemptions (%s) for not-yet-redeemed code %s' % (
+ redemptions, codes[0], ))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # get lot name from code
+ ln = self._db.getLotNameFromCode(codes[0])
+ if ln != lotName:
+ self.notify.error('incorrect lot name (%s) from code (%s)' % (ln, codes[0]))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # get reward from code
+ rt, rid = self._db.getRewardFromCode(codes[0])
+ if rt != RewardType or rid != RewardItemId:
+ self.notify.error('incorrect reward (%s, %s) from code %s' % (rt, rid))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # redeem code
+ self._redeemResult = []
+ self._db.redeemCode(codes[0], self.TestRewarder.FakeAvId, self.TestRewarder(),
+ self._handleRedeemResult)
+ if self._redeemResult[0] or self._redeemResult[1]:
+ self.notify.error('error redeeming code %s for fake avatar %s: %s' % (
+ codes[0], self.TestRewarder.FakeAvId, self._redeemResult))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # number of redemptions (redeemed)
+ redemptions = self._db.getRedemptions(codes[0])
+ if redemptions != 1:
+ self.notify.error(
+ 'incorrect number of redemptions (%s) for already-redeemed code %s' % (
+ redemptions, codes[0], ))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # redeem code that has already been redeemed
+ self._redeemResult = []
+ self._db.redeemCode(codes[0], self.TestRewarder.FakeAvId, self.TestRewarder(),
+ self._handleRedeemResult)
+ if self._redeemResult[0] != TTCodeRedemptionConsts.RedeemErrors.CodeAlreadyRedeemed:
+ self.notify.error('able to redeem code %s twice' % (codes[0]))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # number of redemptions (redeemed)
+ redemptions = self._db.getRedemptions(codes[0])
+ if redemptions != 1:
+ self.notify.error(
+ 'incorrect number of redemptions (%s) for already-redeemed code %s' % (
+ redemptions, codes[0], ))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # lookup codes redeemed by avId
+ c = self._db.lookupCodesRedeemedByAvId(self.TestRewarder.FakeAvId)
+ if len(c) != 1:
+ self.notify.error('lookupCodesRedeemedByAvId returned wrong number of codes: %s' % c)
+ if c[0] != codes[0]:
+ self.notify.error('lookupCodesRedeemedByAvId returned wrong code: %s' % c[0])
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # get code details
+ details = self._db.getCodeDetails(codes[0])
+ if details['code'] != codes[0]:
+ self.notify.error('incorrect code (%s) returned by getCodeDetails(%s)' % (details['code'],
+ codes[0]))
+ if details['av_id'] != self.TestRewarder.FakeAvId:
+ self.notify.error('incorrect av_id (%s) returned by getCodeDetails(%s)' % (details['av_id'],
+ codes[0]))
+ if details[TTCodeRedemptionDB.RewardTypeFieldName] != RewardType:
+ self.notify.error('incorrect av_id (%s) returned by getCodeDetails(%s)' % (
+ details[TTCodeRedemptionDB.RewardTypeFieldName], codes[0]))
+ if details[TTCodeRedemptionDB.RewardItemIdFieldName] != RewardItemId:
+ self.notify.error('incorrect av_id (%s) returned by getCodeDetails(%s)' % (
+ details[TTCodeRedemptionDB.RewardItemIdFieldName], codes[0]))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # get expiration date
+ exp = self._db.getExpiration(lotName)
+ if exp != ExpirationDate:
+ self.notify.error('incorrect expiration date: %s' % exp)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # change expiration date
+ y = 1111
+ m = 4
+ d = 1
+ NewExp = '%s-%02d-%02d' % (y, m, d)
+ assert datetime.datetime.fromtimestamp(time.time()) > datetime.datetime(y, m, d)
+
+ # make sure it doesn't change the expiration date of all lots
+ controlLotName = self._getUnusedLotName()
+ controlCode = self._getUnusedManualCode()
+ controlExp = '%s-%02d-%02d' % (y, m, d+1)
+ self._db.createManualLot(controlLotName, controlCode, RewardType, RewardItemId,
+ expirationDate=controlExp)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ self._db.setExpiration(lotName, NewExp)
+ db._testing = False
+ yield None
+ db._testing = True
+ exp = self._db.getExpiration(lotName)
+ if (exp != NewExp):
+ self.notify.error('could not change expiration date for lot %s' % lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ cExp = self._db.getExpiration(controlLotName)
+ if (cExp != controlExp):
+ self.notify.error('setExpiration changed control lot expiration!')
+ db._testing = False
+ yield None
+ db._testing = True
+
+ self._db.deleteLot(controlLotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # redeem code that is expired
+ self._redeemResult = []
+ self._db.redeemCode(codes[1], self.TestRewarder.FakeAvId, self.TestRewarder(),
+ self._handleRedeemResult)
+ if self._redeemResult[0] != TTCodeRedemptionConsts.RedeemErrors.CodeIsExpired:
+ self.notify.error('expired code %s was not flagged upon redeem' % (codes[1]))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # lot deletion
+ self._db.deleteLot(lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ codes = (
+ self._getUnusedManualCode(),
+ self._getUnusedUtf8ManualCode(),
+ )
+ for code in codes:
+ # manual code lot
+ lotName = self._getUnusedLotName()
+ self.notify.info('manual code: %s' % u2ascii(code))
+ self._db.createManualLot(lotName, code, RewardType, RewardItemId)
+ if not self._db.lotExists(lotName):
+ self.notify.error('could not create manual lot %s' % lotName)
+ if not self._db.codeExists(code):
+ self.notify.error('could not create manual code %s' % code)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ autoLotNames = self._db.getAutoLotNames()
+ if lotName in autoLotNames:
+ self.notify.error('manual lot \'%s\' found in getAutoLotNames()' % lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ manualLotNames = self._db.getManualLotNames()
+ if lotName not in manualLotNames:
+ self.notify.error('manual lot \'%s\' not found in getAutoLotNames()' % lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # number of redemptions (not-yet-redeemed)
+ redemptions = self._db.getRedemptions(code)
+ if redemptions != 0:
+ self.notify.error(
+ 'incorrect number of redemptions (%s) for not-yet-redeemed code %s' % (
+ redemptions, code, ))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # redeem manually-created code
+ self._redeemResult = []
+ self._db.redeemCode(code, self.TestRewarder.FakeAvId, self.TestRewarder(),
+ self._handleRedeemResult)
+ if self._redeemResult[0] or self._redeemResult[1]:
+ self.notify.error('error redeeming code %s for fake avatar %s: %s' % (
+ code, self.TestRewarder.FakeAvId, self._redeemResult))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # number of redemptions (not-yet-redeemed)
+ self._db.commitOutstandingRedemptions()
+ redemptions = self._db.getRedemptions(code)
+ if redemptions != 1:
+ self.notify.error(
+ 'incorrect number of redemptions (%s) for redeemed code %s' % (
+ redemptions, code, ))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # redeem manually-created code again
+ self._redeemResult = []
+ self._db.redeemCode(code, self.TestRewarder.FakeAvId, self.TestRewarder(),
+ self._handleRedeemResult)
+ if self._redeemResult[0] or self._redeemResult[1]:
+ self.notify.error('error redeeming code %s again for fake avatar %s: %s' % (
+ code, self.TestRewarder.FakeAvId, self._redeemResult))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # number of redemptions (not-yet-redeemed)
+ self._db.commitOutstandingRedemptions()
+ redemptions = self._db.getRedemptions(code)
+ if redemptions != 2:
+ self.notify.error(
+ 'incorrect number of redemptions (%s) for twice-redeemed code %s' % (
+ redemptions, code, ))
+ db._testing = False
+ yield None
+ db._testing = True
+
+ self._db.deleteLot(lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ lotNames = self._db.getLotNames()
+ if lotName in lotNames:
+ self.notify.error('could not delete code redemption lot \'%s\'' % lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ # remove placeholder lots
+ for lotName in phLots:
+ self._db.deleteLot(lotName)
+ db._testing = False
+ yield None
+ db._testing = True
+
+ break
+
+ except TryAgainLater, e:
+ self.notify.warning('caught TryAgainLater exception during self-test, retrying')
+ retryStartT = globalClock.getRealTime()
+ while globalClock.getRealTime() < (retryStartT + retryDelay):
+ yield None
+ retryDelay *= 2
+
+ self.notify.info('testing done')
+ db._testing = False
+ yield Job.Done
+
+class NotFound:
+ pass
+
+class InfoCache:
+ NotFound = NotFound
+
+ def __init__(self):
+ self._cache = {}
+
+ def clear(self):
+ self._cache = {}
+
+ def cacheInfo(self, key, info):
+ self._cache[key] = info
+
+ def hasInfo(self, key):
+ return key in self._cache
+
+ def getInfo(self, key):
+ return self._cache.get(key, NotFound)
+
+class TTCodeRedemptionDB(DBInterface, DirectObject):
+ notify = directNotify.newCategory('TTCodeRedemptionDB')
+
+ TryAgainLater = TryAgainLater
+
+ READ = TTCRDBConnection.READ
+ WRITE = TTCRDBConnection.WRITE
+
+ RewardTypeFieldName = 'reward_type'
+ RewardItemIdFieldName = 'reward_item_id'
+
+ DoSelfTest = config.GetBool('code-redemption-self-test', 1)
+
+ # optimization that reads in all codes and maps them to their lot
+ # if the code set gets too large this might use up too much RAM
+ # you can disable the optimization by turning this config off
+ CacheAllCodes = config.GetBool('code-redemption-cache-all-codes', 1)
+
+ class LotFilter:
+ All = 'all'
+ Redeemable = 'redeemable'
+ NonRedeemable = 'nonRedeemable'
+ Redeemed = 'redeemed'
+ Expired = 'expired'
+
+ def __init__(self,air,host,port,user,passwd,dbname):
+ self.air = air
+ self.host = host
+ self.port = port
+ self.user = user
+ self.passwd = passwd
+ self.dbname = self.processDBName(dbname)
+
+ # lot name cache
+ self._code2lotNameCache = InfoCache()
+ self._lotName2manualCache = InfoCache()
+ self._code2rewardCache = InfoCache()
+ self.doMethodLater(5 * 60, self._cacheClearTask, uniqueName('clearLotNameCache'))
+
+ self._manualCode2outstandingRedemptions = {}
+ self.doMethodLater(1 * 60, self._updateRedemptionsTask, uniqueName('updateRedemptions'))
+
+ self._code2lotName = {}
+
+ # set to true while doing internal tests
+ self._testing = False
+ self._initializedSV = StateVar(False)
+ self._startTime = globalClock.getRealTime()
+ self._doingCleanup = False
+ self._dbInitRetryTimeout = 5
+ self._doInitialCleanup()
+
+ if config.GetBool('code-redemption-subprocess-test', 0):
+ self._testSubProc()
+
+ self._refreshCode2lotName()
+
+ def _doInitialCleanup(self, task=None):
+ if not self._initializedSV.get():
+ self._doCleanup()
+ if not self._initializedSV.get():
+ self.doMethodLater(self._dbInitRetryTimeout, self._doInitialCleanup,
+ uniqueName('codeRedemptionInitialCleanup'))
+ self._dbInitRetryTimeout *= 2
+ self.notify.warning('could not initialize MySQL db, trying again later...')
+ return Task.done
+
+ def _doCleanup(self):
+ if self._doingCleanup:
+ return
+
+ self._doingCleanup = True
+
+ if not self._initializedSV.get():
+ try:
+ TTCodeRedemptionDBTester.cleanup(self)
+ except TryAgainLater, e:
+ pass
+ else:
+ self._initializedSV.set(True)
+
+ self._doingCleanup = False
+
+ def _randFuncCallback(self, randList, randSamplesOnOrder, samples):
+ randSamplesOnOrder[0] -= len(samples)
+ randList.extend(samples)
+
+ def _refreshCode2lotName(self):
+ if not self.CacheAllCodes:
+ return
+ # update the dict of code -> lotName for all codes
+ self._code2lotName = {}
+ lotNames = self.getLotNames()
+ for lotName in lotNames:
+ codes = self.getCodesInLot(lotName)
+ for code in codes:
+ self._code2lotName[code] = lotName
+
+ @staticmethod
+ def _getExpirationString(expiration):
+ """
+ formats expiration date for MySQL
+ """
+ return '\'%s 23:59:59\'' % str(expiration)
+
+ @staticmethod
+ def _getNowString():
+ nowStr = str(datetime.datetime.fromtimestamp(time.time()))
+ # leave off the fractional seconds
+ if '.' in nowStr:
+ nowStr = nowStr[:nowStr.index('.')]
+ return nowStr
+
+ def createManualLot(self, name, code, rewardType, rewardItemId, expirationDate=None):
+ self.notify.info('creating manual code lot \'%s\', code=%s' % (name, u2ascii(code), ))
+ self._doCleanup()
+
+ code = TTCodeDict.getFromReadableCode(code)
+
+ if self.lotExists(name):
+ self.notify.error('tried to create lot %s that already exists' % name)
+
+ if self.codeExists(code):
+ self.notify.error('tried to create code %s that already exists' % u2ascii(code))
+
+ conn = TTCRDBConnection(self)
+ conn._createTable(
+ """
+ CREATE TABLE code_set_%s (
+ code text NOT NULL,
+ lot_id int(32) unsigned NOT NULL,
+ redemptions bigint(64) NOT NULL,
+ FOREIGN KEY (lot_id) REFERENCES lot (lot_id)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ """ % (name, )
+ )
+ conn.destroy()
+
+ conn = TTCRDBConnection(self, {'lot': self.WRITE,
+ 'code_set_%s' % name: self.WRITE,
+ })
+ cursor = conn.getDictCursor()
+
+ cursor.execute(
+ """
+ INSERT INTO lot (name, manual, %s, %s, size, creation%s)
+ VALUES('%s', 'T', %s, %s, 1, '%s'%s);
+ """ % (self.RewardTypeFieldName,
+ self.RewardItemIdFieldName,
+ choice(expirationDate is None, '', ', expiration'),
+ name, rewardType, rewardItemId, self._getNowString(),
+ choice(expirationDate is None, '', ', %s' % self._getExpirationString(expirationDate)),
+ )
+ )
+
+ cursor.execute(
+ """
+ SELECT lot_id FROM lot WHERE name='%s';
+ """ % (name)
+ )
+ rows = cursor.fetchall()
+ lotId = int(rows[0]['lot_id'])
+
+ cursor.execute(
+ """
+ INSERT INTO code_set_%s (code, lot_id, redemptions)
+ VALUES('%s', %s, 0);
+ """ % (name, code, lotId)
+ )
+
+ conn.release()
+ conn.commit()
+ conn.destroy()
+
+ self._refreshCode2lotName()
+
+ self.notify.info('done')
+
+ def createLot(self, randFunc, name, numCodes, rewardType, rewardItemId, expirationDate=None):
+ """
+ generator, yields None while working, yields True when finished
+ randFunc must take a callback and a number of random samples, and must call the callback
+ with a list of random 32-bit values of length equal to that specified in the call to randFunc
+ the random values must be truly random and non-repeatable (see NonRepeatableRandomSource)
+ """
+ self.notify.info('creating code lot \'%s\', %s codes' % (name, numCodes, ))
+ self._doCleanup()
+
+ if self.lotExists(name):
+ self.notify.error('tried to create lot %s that already exists' % name)
+
+ randSampleRequestSize = config.GetInt('code-redemption-rand-request-size', 50)
+ randSampleRequestThreshold = 2 * randSampleRequestSize
+ randSamples = []
+ randSamplesOnOrder = [0, ]
+
+ requestSize = min(numCodes, randSampleRequestSize)
+ randSamplesOnOrder[0] += requestSize
+ randFunc(Functor(self._randFuncCallback, randSamples, randSamplesOnOrder), requestSize)
+
+ conn = TTCRDBConnection(self)
+ conn._createTable(
+ """
+ CREATE TABLE code_set_%s (
+ code text NOT NULL,
+ lot_id int(32) unsigned NOT NULL,
+ redemptions bigint(64) NOT NULL,
+ av_id int(32) unsigned,
+ FOREIGN KEY (lot_id) REFERENCES lot (lot_id)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ """ % (name, )
+ )
+ conn.destroy()
+
+ conn = TTCRDBConnection(self, {'code_space': self.WRITE,
+ 'lot': self.WRITE,
+ 'code_set_%s' % name: self.WRITE,
+ })
+ cursor = conn.getDictCursor()
+
+ # grab the next serial number range
+ cursor.execute(
+ """
+ SELECT code_length, next_code_value FROM code_space;
+ """
+ )
+ rows = cursor.fetchall()
+ assert len(rows) == 1
+ codeLength = int(rows[0]['code_length'])
+ nextCodeValue = int(rows[0]['next_code_value'])
+
+ startSerialNum = nextCodeValue
+
+ cursor.execute(
+ """
+ INSERT INTO lot (name, manual, %s, %s, size, creation%s)
+ VALUES('%s', 'F', %s, %s, %s, '%s'%s);
+ """ % (self.RewardTypeFieldName,
+ self.RewardItemIdFieldName,
+ choice(expirationDate is None, '', ', expiration'),
+ name, rewardType, rewardItemId, numCodes, self._getNowString(),
+ choice(expirationDate is None, '', ', %s' % self._getExpirationString(expirationDate)),
+ )
+ )
+
+ cursor.execute(
+ """
+ SELECT lot_id FROM lot WHERE name='%s';
+ """ % (name)
+ )
+ rows = cursor.fetchall()
+ lotId = int(rows[0]['lot_id'])
+
+ codesLeft = numCodes
+ curSerialNum = startSerialNum
+ numCodeValues = TTCodeDict.getNumUsableValuesInCodeSpace(codeLength)
+ n = 0
+ while codesLeft:
+ #print codesLeft, len(randSamples), randSamplesOnOrder[0]
+ numCodesRequested = (len(randSamples) + randSamplesOnOrder[0])
+ if numCodesRequested < codesLeft:
+ if numCodesRequested < randSampleRequestThreshold:
+ requestSize = min(codesLeft, randSampleRequestSize)
+ randSamplesOnOrder[0] += requestSize
+ randFunc(Functor(self._randFuncCallback, randSamples, randSamplesOnOrder), requestSize)
+
+ if len(randSamples) == 0:
+ yield None
+ continue
+
+ # r in [0,1) but truly random (non-repeatable)
+ r = randSamples.pop(0) / float(1L<<32)
+ assert 0. <= r < 1.
+ # this produces the 1 in N chance of guessing a correct code
+ # each code is given a chunk of code space, of size N, and the actual value of the
+ # code is chosen from that section of code space using a true random source
+ # that means there's no way to guess a valid code based on observation of other codes
+ randScatter = int(r * TTCodeDict.BruteForceFactor)
+ assert 0 <= randScatter < TTCodeDict.BruteForceFactor
+ value = (curSerialNum * TTCodeDict.BruteForceFactor) + randScatter
+ obfValue = TTCodeDict.getObfuscatedCodeValue(value, codeLength)
+ code = TTCodeDict.getCodeFromValue(obfValue, codeLength)
+ cursor.execute(
+ """
+ INSERT INTO code_set_%s (code, lot_id, redemptions)
+ VALUES('%s', %s, 0);
+ """ % (name, code, lotId)
+ )
+
+ codesLeft -= 1
+ curSerialNum += 1
+ if curSerialNum >= numCodeValues:
+ curSerialNum = 0
+ codeLength += 1
+ numCodeValues = TTCodeDict.getNumUsableValuesInCodeSpace(codeLength)
+
+ n = n + 1
+ if (n % 100) == 0:
+ yield None
+
+ # update the code_space tracking variables
+ cursor.execute(
+ """
+ UPDATE code_space SET code_length=%s, next_code_value=%s;
+ """ % (codeLength, curSerialNum)
+ )
+
+ conn.release()
+ conn.commit()
+ conn.destroy()
+
+ self._refreshCode2lotName()
+
+ self.notify.info('done')
+ yield True
+
+ def deleteLot(self, lotName):
+ self.notify.info('deleting code lot \'%s\'' % (lotName, ))
+ self._doCleanup()
+
+ self._clearCaches()
+
+ conn = TTCRDBConnection(self)
+ cursor = conn.getDictCursor()
+
+ cursor.execute(
+ """
+ DROP TABLE IF EXISTS code_set_%s;
+ """ % lotName
+ )
+
+ if conn.WantTableLocking:
+ cursor.execute(
+ """
+ LOCK TABLES lot WRITE;
+ """
+ )
+
+ cursor.execute(
+ """
+ DELETE FROM lot WHERE name='%s';
+ """ % lotName
+ )
+
+ if conn.WantTableLocking:
+ cursor.execute(
+ """
+ UNLOCK TABLES;
+ """
+ )
+
+ conn.commit()
+ conn.destroy()
+
+ self._refreshCode2lotName()
+
+ def getLotNames(self):
+ assert self.notify.debugCall()
+ self._doCleanup()
+ lotNames = []
+ conn = TTCRDBConnection(self, {'lot': self.READ, })
+ cursor = conn.getDictCursor()
+ cursor.execute(
+ """
+ SELECT name FROM lot;
+ """
+ )
+ rows = cursor.fetchall()
+ conn.destroy()
+ for row in rows:
+ lotName = row['name']
+ if not self._testing:
+ if TTCodeRedemptionDBTester.TestLotName in lotName:
+ continue
+ lotNames.append(lotName)
+ return lotNames
+
+ def getAutoLotNames(self):
+ """
+ returns names of all code lots that were automatically generated
+ """
+ assert self.notify.debugCall()
+ self._doCleanup()
+ autoLotNames = []
+ conn = TTCRDBConnection(self, {'lot': self.READ, })
+ cursor = conn.getDictCursor()
+ cursor.execute(
+ """
+ SELECT name FROM lot WHERE manual='F';
+ """
+ )
+ rows = cursor.fetchall()
+ conn.destroy()
+ for row in rows:
+ lotName = row['name']
+ if not self._testing:
+ if TTCodeRedemptionDBTester.TestLotName in lotName:
+ continue
+ autoLotNames.append(lotName)
+ return autoLotNames
+
+ def getManualLotNames(self):
+ """
+ returns names of all code lots that were manually generated
+ """
+ assert self.notify.debugCall()
+ self._doCleanup()
+ manualLotNames = []
+ conn = TTCRDBConnection(self, {'lot': self.READ, })
+ cursor = conn.getDictCursor()
+ cursor.execute(
+ """
+ SELECT name FROM lot WHERE manual='T';
+ """
+ )
+ rows = cursor.fetchall()
+ conn.destroy()
+ for row in rows:
+ lotName = row['name']
+ if not self._testing:
+ if TTCodeRedemptionDBTester.TestLotName in lotName:
+ continue
+ manualLotNames.append(lotName)
+ return manualLotNames
+
+ def getExpirationLotNames(self):
+ """
+ returns names of all code lots that have expiration dates
+ """
+ assert self.notify.debugCall()
+ self._doCleanup()
+ lotNames = []
+ conn = TTCRDBConnection(self, {'lot': self.READ, })
+ cursor = conn.getDictCursor()
+ cursor.execute(
+ """
+ SELECT name FROM lot WHERE expiration IS NOT NULL;
+ """
+ )
+ rows = cursor.fetchall()
+ conn.destroy()
+ for row in rows:
+ lotName = row['name']
+ if not self._testing:
+ if TTCodeRedemptionDBTester.TestLotName in lotName:
+ continue
+ lotNames.append(lotName)
+ return lotNames
+
+ def getCodesInLot(self, lotName, justCode=True, filter=None):
+ # if justCode, returns list of codes
+ # if not justCode, returns list of dict of field->value
+ assert self.notify.debugCall()
+ self._doCleanup()
+ if filter is None:
+ filter = self.LotFilter.All
+
+ conn = TTCRDBConnection(self, {'code_set_%s' % lotName: self.READ,
+ 'lot': self.READ, })
+ cursor = conn.getDictCursor()
+
+ if filter == self.LotFilter.All:
+ condition = ''
+ elif filter == self.LotFilter.Redeemable:
+ condition = ('((manual=\'T\' or redemptions=0) and '
+ '((expiration IS NULL) or (CURDATE()<=expiration)))')
+ elif filter == self.LotFilter.NonRedeemable:
+ condition = ('((manual=\'F\' and redemptions>0) or '
+ '((expiration IS NOT NULL) and (CURDATE()>expiration)))')
+ elif filter == self.LotFilter.Redeemed:
+ condition = '(redemptions>0)'
+ elif filter == self.LotFilter.Expired:
+ condition = '((expiration is NOT NULL) and (CURDATE()>expiration))'
+
+ cursor.execute(
+ """
+ SELECT %s FROM code_set_%s INNER JOIN lot WHERE code_set_%s.lot_id=lot.lot_id%s%s;
+ """ % (choice(justCode, 'code', '*'), lotName, lotName,
+ choice(filter==self.LotFilter.All, '', ' AND '), condition)
+ )
+ rows = cursor.fetchall()
+ conn.destroy()
+
+ if justCode:
+ codes = []
+ for row in rows:
+ code = unicode(row['code'], 'utf-8')
+ codes.append(code)
+ result = codes
+ else:
+ for row in rows:
+ row['code'] = unicode(row['code'], 'utf-8')
+ result = rows
+ return result
+
+ def _clearCaches(self):
+ self._code2lotNameCache.clear()
+ self._lotName2manualCache.clear()
+ self._code2rewardCache.clear()
+
+ def _cacheClearTask(self, task):
+ self._clearCaches()
+ return Task.again
+
+ def commitOutstandingRedemptions(self):
+ if len(self._manualCode2outstandingRedemptions):
+ self.notify.info('committing cached manual code redemption counts to DB')
+ conn = TTCRDBConnection(self)
+ cursor = conn.getDictCursor()
+ for key in self._manualCode2outstandingRedemptions.iterkeys():
+ code, lotName = key
+ count = self._manualCode2outstandingRedemptions[key]
+ self._updateRedemptionCount(cursor, code, True, None, lotName, count)
+ self._manualCode2outstandingRedemptions = {}
+ conn.destroy()
+
+ def _updateRedemptionsTask(self, task):
+ try:
+ self.commitOutstandingRedemptions()
+ except TryAgainLater, e:
+ pass
+ return Task.again
+
+ def getLotNameFromCode(self, code):
+ assert self.notify.debugCall()
+
+ code = TTCodeDict.getFromReadableCode(code)
+ assert TTCodeDict.isLegalCode(code)
+
+ if self.CacheAllCodes:
+ return self._code2lotName.get(code, None)
+
+ cachedLotName = self._code2lotNameCache.getInfo(code)
+ if cachedLotName is not self._code2lotNameCache.NotFound:
+ return cachedLotName
+
+ assert self.notify.debug('lotNameFromCode CACHE MISS (%s)' % u2ascii(code))
+
+ self._doCleanup()
+ conn = TTCRDBConnection(self)
+ cursor = conn.getDictCursor()
+
+ lotNames = self.getLotNames()
+ result = None
+ for lotName in lotNames:
+ if conn.WantTableLocking:
+ cursor.execute(
+ """
+ LOCK TABLES code_set_%s READ;
+ """ % (lotName, )
+ )
+
+ # client hack prevention:
+ # safe; code is between quotes and can only contain letters, numbers and dashes
+ cursor.execute(
+ unicode("""
+ SELECT code FROM code_set_%s WHERE code='%s';
+ """, 'utf-8') % (lotName, code)
+ )
+ rows = cursor.fetchall()
+
+ if conn.WantTableLocking:
+ cursor.execute(
+ """
+ UNLOCK TABLES;
+ """
+ )
+
+ if len(rows) > 0:
+ result = lotName
+ break
+
+ conn.destroy()
+
+ if result is not None:
+ self._code2lotNameCache.cacheInfo(code, result)
+
+ return result
+
+ def getRewardFromCode(self, code):
+ assert self.notify.debugCall()
+
+ code = TTCodeDict.getFromReadableCode(code)
+ assert TTCodeDict.isLegalCode(code)
+
+ lotName = self.getLotNameFromCode(code)
+ assert lotName is not None
+
+ cachedReward = self._code2rewardCache.getInfo(code)
+ if cachedReward is not self._code2rewardCache.NotFound:
+ return cachedReward
+
+ assert self.notify.debug('reward from code CACHE MISS (%s)' % u2ascii(code))
+
+ self._doCleanup()
+
+ conn = TTCRDBConnection(self, {'code_set_%s' % lotName: self.READ,
+ 'lot': self.READ, })
+ cursor = conn.getDictCursor()
+
+ # client hack prevention:
+ # safe; code is between quotes and can only contain letters, numbers and dashes
+ cursor.execute(
+ unicode("""
+ SELECT %s, %s FROM code_set_%s INNER JOIN lot
+ WHERE lot.lot_id=code_set_%s.lot_id AND CODE='%s';
+ """, 'utf-8') % (self.RewardTypeFieldName, self.RewardItemIdFieldName, lotName, lotName, code)
+ )
+ rows = cursor.fetchall()
+
+ conn.destroy()
+
+ assert len(rows) == 1
+ reward = (int(rows[0][self.RewardTypeFieldName]), int(rows[0][self.RewardItemIdFieldName]))
+
+ self._code2rewardCache.cacheInfo(code, reward)
+
+ return reward
+
+ def lotExists(self, lotName):
+ return lotName in self.getLotNames()
+
+ def codeExists(self, code):
+ return self.getLotNameFromCode(code) != None
+
+ def getRedemptions(self, code):
+ assert self.notify.debugCall()
+ self._doCleanup()
+ code = TTCodeDict.getFromReadableCode(code)
+
+ lotName = self.getLotNameFromCode(code)
+
+ if lotName is None:
+ self.notify.error('getRedemptions: could not find code %s' % u2ascii(code))
+
+ conn = TTCRDBConnection(self, {'code_set_%s' % lotName: self.READ, })
+ cursor = conn.getDictCursor()
+
+ cursor.execute(
+ unicode("""
+ SELECT redemptions FROM code_set_%s WHERE code='%s';
+ """, 'utf-8') % (lotName, code)
+ )
+ rows = cursor.fetchall()
+
+ conn.destroy()
+
+ return int(rows[0]['redemptions'])
+
+ def redeemCode(self, code, avId, rewarder, callback):
+ assert self.notify.debugCall()
+ self._doCleanup()
+ # callback takes a RedeemError
+ # 'code' can come from a client, treat with care
+ origCode = code
+ code = TTCodeDict.getFromReadableCode(code)
+ assert TTCodeDict.isLegalCode(code)
+
+ lotName = self.getLotNameFromCode(code)
+ if lotName is None:
+ self.air.writeServerEvent('invalidCodeRedemption', avId, '%s' % (u2ascii(origCode), ))
+ callback(TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist, 0)
+ return
+
+ conn = TTCRDBConnection(self, {'code_set_%s' % lotName: self.READ,
+ 'lot': self.READ, })
+ cursor = conn.getDictCursor()
+
+ cachedManual = self._lotName2manualCache.getInfo(lotName)
+ if cachedManual is not self._lotName2manualCache.NotFound:
+ manualCode = cachedManual
+ else:
+ assert self.notify.debug('manualFromCode CACHE MISS (%s)' % u2ascii(code))
+
+ cursor.execute(
+ """
+ SELECT manual FROM lot WHERE name='%s';
+ """ % (lotName)
+ )
+
+ rows = cursor.fetchall()
+ assert len(rows) == 1
+
+ manualCode = (rows[0]['manual'] == 'T')
+
+ self._lotName2manualCache.cacheInfo(lotName, manualCode)
+
+ if not manualCode:
+ # client hack prevention:
+ # safe; code is between quotes and can only contain letters, numbers and dashes
+ cursor.execute(
+ unicode("""
+ SELECT redemptions FROM code_set_%s INNER JOIN lot WHERE
+ code_set_%s.lot_id=lot.lot_id AND code='%s' AND ((expiration IS NULL) OR (CURDATE()<=expiration));
+ """, 'utf-8') % (lotName, lotName, code)
+ )
+
+ rows = cursor.fetchall()
+ assert len(rows) <= 1
+
+ conn.destroy()
+
+ if not manualCode:
+ if len(rows) == 0:
+ # code is expired
+ callback(TTCodeRedemptionConsts.RedeemErrors.CodeIsExpired, 0)
+ return
+
+ redemptions = rows[0]['redemptions']
+
+ if redemptions > 0:
+ callback(TTCodeRedemptionConsts.RedeemErrors.CodeAlreadyRedeemed, 0)
+ return
+
+ rewardTypeId, rewardItemId = self.getRewardFromCode(code)
+
+ rewarder._giveReward(avId, rewardTypeId, rewardItemId, Functor(
+ self._handleRewardResult, code, manualCode, avId, lotName, rewardTypeId, rewardItemId,
+ callback))
+
+ def _updateRedemptionCount(self, cursor, code, manualCode, avId, lotName, count):
+ # client hack prevention:
+ # safe; code is between quotes and can only contain letters, numbers and dashes
+ cursor.execute(
+ unicode("""
+ UPDATE code_set_%s SET redemptions=redemptions+%s%s WHERE code='%s';
+ """, 'utf-8') % (lotName, count, choice(manualCode, '', ', av_id=%s' % avId), code)
+ )
+
+ def _handleRewardResult(self, code, manualCode, avId, lotName, rewardTypeId, rewardItemId,
+ callback, result):
+ assert self.notify.debugCall()
+ self._doCleanup()
+ assert TTCodeDict.isLegalCode(code)
+ awardMgrResult = result
+ if awardMgrResult:
+ callback(TTCodeRedemptionConsts.RedeemErrors.AwardCouldntBeGiven, awardMgrResult)
+ return
+
+ conn = TTCRDBConnection(self, {'code_set_%s' % lotName: self.WRITE, })
+ cursor = conn.getDictCursor()
+
+ if manualCode:
+ # queue up redemption count for manual code and write every N minutes
+ key = (code, lotName)
+ self._manualCode2outstandingRedemptions.setdefault(key, 0)
+ self._manualCode2outstandingRedemptions[key] += 1
+ else:
+ self._updateRedemptionCount(cursor, code, manualCode, avId, lotName, 1)
+
+ conn.release()
+ conn.commit()
+ conn.destroy()
+
+ if not self._testing:
+ self.air.writeServerEvent('codeRedeemed', avId, '%s|%s|%s|%s' % (
+ u2ascii(choice(manualCode, code, TTCodeDict.getReadableCode(code))),
+ lotName, rewardTypeId, rewardItemId, ))
+
+ callback(TTCodeRedemptionConsts.RedeemErrors.Success, awardMgrResult)
+
+ def lookupCodesRedeemedByAvId(self, avId):
+ assert self.notify.debugCall()
+ self._doCleanup()
+ conn = TTCRDBConnection(self)
+ cursor = conn.getDictCursor()
+
+ codes = []
+
+ # manual lots don't record redeemer avIds since they are single-code-multi-toon
+ for lotName in self.getAutoLotNames():
+ if conn.WantTableLocking:
+ cursor.execute(
+ """
+ LOCK TABLES code_set_%s READ;
+ """ % (lotName, )
+ )
+
+ cursor.execute(
+ """
+ SELECT code FROM code_set_%s WHERE av_id=%s;
+ """ % (lotName, avId)
+ )
+ rows = cursor.fetchall()
+
+ if conn.WantTableLocking:
+ cursor.execute(
+ """
+ UNLOCK TABLES;
+ """
+ )
+
+ for row in rows:
+ code = unicode(row['code'], 'utf-8')
+ codes.append(code)
+
+ conn.destroy()
+
+ return codes
+
+ def getExpiration(self, lotName):
+ assert self.notify.debugCall()
+ self._doCleanup()
+ conn = TTCRDBConnection(self, {'lot': self.READ, })
+ cursor = conn.getDictCursor()
+
+ cursor.execute(
+ """
+ SELECT expiration FROM lot WHERE name=\'%s\';
+ """ % (lotName, )
+ )
+
+ rows = cursor.fetchall()
+
+ conn.destroy()
+
+ # just get the date component
+ expiration = str(rows[0]['expiration'].date())
+ return expiration
+
+ def setExpiration(self, lotName, expiration):
+ assert self.notify.debugCall()
+ self._doCleanup()
+ conn = TTCRDBConnection(self, {'lot': self.WRITE, })
+ cursor = conn.getDictCursor()
+
+ cursor.execute(
+ """
+ UPDATE lot SET expiration=%s WHERE name=\'%s\';
+ """ % (self._getExpirationString(expiration), lotName, )
+ )
+
+ conn.release()
+ conn.commit()
+ conn.destroy()
+
+ def getCodeDetails(self, code):
+ assert self.notify.debugCall()
+ self._doCleanup()
+ conn = TTCRDBConnection(self)
+ cursor = conn.getDictCursor()
+
+ for lotName in self.getLotNames():
+ if conn.WantTableLocking:
+ cursor.execute(
+ """
+ LOCK TABLES lot READ, code_set_%s READ;
+ """ % (lotName, )
+ )
+
+ cursor.execute(
+ """
+ SELECT * FROM code_set_%(lotName)s INNER JOIN lot
+ WHERE code_set_%(lotName)s.lot_id=lot.lot_id
+ AND code='%(code)s';
+ """ % ({
+ 'lotName': lotName,
+ 'code': TTCodeDict.getFromReadableCode(code)
+ })
+ )
+ rows = cursor.fetchall()
+
+ if conn.WantTableLocking:
+ cursor.execute(
+ """
+ UNLOCK TABLES;
+ """
+ )
+
+ assert len(rows) <= 1
+ if len(rows):
+ conn.destroy()
+ row = rows[0]
+ row['code'] = unicode(row['code'], 'utf-8')
+ return row
+
+ self.notify.error('code \'%s\' not found' % u2ascii(code))
+
+ def _testSubProc(self):
+ self.notify.info('running subprocess test...')
+ proc = subprocess.Popen('%s -OO %s' % (choice(__dev__, 'python', os.getenv('PYTHON')), __file__),
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ proc.stdin.write('test' + '\n')
+ result = proc.stdout.readline()
+ print 'main process: %s' % repr(result)
+ while result[-1] in ('\r', '\n'):
+ result = result[:-1]
+ if (result == 'testtest'):
+ self.notify.info('subprocess test succeeded!')
+ else:
+ self.notify.info('subprocess test failed! (%s)' % repr(result))
+
+ if __debug__:
+ def runTests(self):
+ self._doRunTests(self._initializedSV.get())
+ self._runTestsFC = FunctionCall(self._doRunTests, self._initializedSV)
+
+ def _doRunTests(self, initialized):
+ if initialized and self.DoSelfTest:
+ jobMgr.add(TTCodeRedemptionDBTester(self))
+
+# this file itself is the SQL communication subprocess when run directly
+if __name__ == '__main__':
+ # restore stdout
+ sys.stdout = stdOut
+
+ l = sys.stdin.readline()
+ sys.stderr.write('subprocess: %s\n' % repr(l))
+ sys.stderr.flush()
+ while l[-1] in ('\r', '\n'):
+ l = l[:-1]
+ sys.stdout.write((l * 2) + '\n')
+ sys.stdout.flush()
diff --git a/toontown/src/coderedemption/TTCodeRedemptionMgr.py b/toontown/src/coderedemption/TTCodeRedemptionMgr.py
new file mode 100644
index 0000000..0736651
--- /dev/null
+++ b/toontown/src/coderedemption/TTCodeRedemptionMgr.py
@@ -0,0 +1,40 @@
+from direct.distributed.DistributedObject import DistributedObject
+from direct.directnotify.DirectNotifyGlobal import directNotify
+
+class TTCodeRedemptionMgr(DistributedObject):
+ neverDisable = 1
+
+ notify = directNotify.newCategory('TTCodeRedemptionMgr')
+
+ def __init__(self, cr):
+ DistributedObject.__init__(self, cr)
+
+ def announceGenerate(self):
+ DistributedObject.announceGenerate(self)
+ base.codeRedemptionMgr = self
+ self._contextGen = SerialMaskedGen(0xffffffff)
+ self._context2callback = {}
+
+ def delete(self):
+ if hasattr(base, 'codeRedemptionMgr'):
+ if base.codeRedemptionMgr is self:
+ del base.codeRedemptionMgr
+ self._context2callback = None
+ self._contextGen = None
+ DistributedObject.delete(self)
+
+ def redeemCode(self, code, callback):
+ # callback takes result, awardMgrResult
+ # if result is non-zero, there was an error (see TTCodeRedemptionConsts.RedeemErrors)
+ # if result is TTCodeRedemptionConsts.AwardCouldntBeGiven, awardMgrResult holds the error code
+ # (see AwardManagerConsts.GiveAwardErrors)
+ context = self._contextGen.next()
+ self._context2callback[context] = callback
+ self.notify.debug('redeemCode(%s, %s)' % (context, code))
+ self.sendUpdate('redeemCode', [context, code])
+
+ def redeemCodeResult(self, context, result, awardMgrResult):
+ self.notify.debug('redeemCodeResult(%s, %s, %s)' % (context, result, awardMgrResult))
+ callback = self._context2callback.pop(context)
+ callback(result, awardMgrResult)
+
diff --git a/toontown/src/coderedemption/TTCodeRedemptionMgrAI.py b/toontown/src/coderedemption/TTCodeRedemptionMgrAI.py
new file mode 100644
index 0000000..b27ff10
--- /dev/null
+++ b/toontown/src/coderedemption/TTCodeRedemptionMgrAI.py
@@ -0,0 +1,187 @@
+from pandac import PandaModules as PM
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.distributed.DistributedObjectAI import DistributedObjectAI
+from direct.showbase.DirectObject import DirectObject
+from direct.task import Task
+from otp.distributed import OtpDoGlobals
+from toontown.coderedemption import TTCodeRedemptionConsts
+import random
+import string
+
+class TTCRMAIRetryMgr(DirectObject):
+ notify = directNotify.newCategory('TTCodeRedemptionMgrAI')
+
+ MinRetryPeriod = 5
+ RetryGrowMult = 1.1
+
+ def __init__(self, air, codeRedemptionMgr):
+ self.air = air
+ self._codeRedemptionMgr = codeRedemptionMgr
+ self._serialGen = SerialNumGen()
+ self._retryPeriod = self.MinRetryPeriod
+ self._redemptions = {}
+
+ def addRedemption(self, avId, context, code):
+ assert self.notify.debugCall()
+ serial = self._serialGen.next()
+ self._redemptions[serial] = ScratchPad(avId=avId, context=context, code=code, attemptNum=0)
+ self._doRedemption(serial, True)
+
+ def resolveRedemption(self, serial, context, avId, result, awardMgrResult):
+ assert self.notify.debugCall()
+ if serial not in self._redemptions:
+ self.notify.warning('unexpected redemption resolution: %s, %s, %s, %s, %s' % (
+ serial, context, avId, result, awardMgrResult))
+ return
+ info = self._redemptions.pop(serial)
+ info.doLater.remove()
+ self._retryPeriod = self.MinRetryPeriod
+ if hasattr(self, '_stressTestInfo'):
+ if (avId, context) in self._stressTestInfo.redemptions:
+ code = self._stressTestInfo.redemptions.pop((avId, context))
+ self._stressTestInfo.numCodesResolved += 1
+ if result:
+ if not TTCodeRedemptionMgrAI.RandomizeStressTestCode:
+ self.notify.info('stress test redemption failed for %s (%s): %s, %s' % (
+ avId, code, result, awardMgrResult))
+ else:
+ self.notify.debug('stress test redemption succeeded for %s (%s)' % (
+ avId, code))
+ now = globalClock.getRealTime()
+ if (now - self._stressTestInfo.lastLogT) > 10:
+ duration = now - self._stressTestInfo.startT
+ self._stressTestInfo.lastLogT = now
+ self.notify.info('stress test progress: %s codes resolved, %s codes/sec' % (
+ self._stressTestInfo.numCodesResolved, (self._stressTestInfo.numCodesResolved / duration)))
+ self._checkCleanupStressTest()
+ self._codeRedemptionMgr.sendUpdateToAvatarId(avId, 'redeemCodeResult', [context, result, awardMgrResult])
+
+ def _doRedemption(self, serial, directCall, task=None):
+ info = self._redemptions.get(serial)
+ info.attemptNum += 1
+ if info.attemptNum > 1:
+ self._retryPeriod = max(self.MinRetryPeriod, self._retryPeriod * self.RetryGrowMult)
+ self.notify.info('code redemption retry #%s for %s: %s' % ((info.attemptNum-1), info.avId, info.code))
+ self.air.sendUpdateToDoId('TTCodeRedemptionMgr',
+ 'redeemCodeAiToUd',
+ OtpDoGlobals.OTP_DO_ID_TOONTOWN_CODE_REDEMPTION_MANAGER,
+ [serial, self._codeRedemptionMgr.doId, info.context, info.code, info.avId]
+ )
+ info.doLater = self.doMethodLater(self._retryPeriod, Functor(self._doRedemption, serial),
+ uniqueName('CodeRedemptionRetry'))
+ return Task.done
+
+ # stress test API
+ def startStressTest(self):
+ assert not hasattr(self, '_stressTestClosed')
+ self._stressTestInfo = ScratchPad()
+ self._stressTestInfo.redemptions = {}
+ self._stressTestInfo.numCodesSubmitted = 0
+ self._stressTestInfo.numCodesResolved = 0
+ self._stressTestInfo.startT = globalClock.getRealTime()
+ self._stressTestInfo.lastLogT = globalClock.getRealTime()
+ self._stressTestInfo.closed = False
+
+ def finishStressTestSubmission(self):
+ self._stressTestInfo.closed = True
+ self._checkCleanupStressTest()
+
+ def _checkCleanupStressTest(self):
+ if self._stressTestInfo.closed and (
+ self._stressTestInfo.numCodesSubmitted == self._stressTestInfo.numCodesResolved):
+ duration = globalClock.getRealTime() - self._stressTestInfo.startT
+ self.notify.info('stress test resolution completed: %s codes resolved, %s codes/sec' % (
+ self._stressTestInfo.numCodesResolved, self._stressTestInfo.numCodesResolved / duration))
+ del self._stressTestInfo
+
+ def addStressTestRedemption(self, avId, context, code):
+ self._stressTestInfo.redemptions[(avId, context)] = code
+ self._stressTestInfo.numCodesSubmitted += 1
+ self.addRedemption(avId, context, code)
+
+class TTCodeRedemptionMgrAI(DistributedObjectAI):
+ notify = directNotify.newCategory('TTCodeRedemptionMgrAI')
+
+ WantStressTest = config.GetBool('stress-test-code-redemption', 0)
+ StressTestRate = config.GetFloat('stress-test-code-redemption-rate', 3.)
+ RandomizeStressTestCode = config.GetBool('randomize-code-redemption-stress-test-code', 0)
+
+ def __init__(self, air):
+ DistributedObjectAI.__init__(self, air)
+ self._retryMgr = TTCRMAIRetryMgr(self.air, self)
+ self.air.codeRedemptionManager = self
+ if self.WantStressTest:
+ printStack()
+ taskMgr.doMethodLater(10., self._doStressTest,
+ uniqueName('codeRedemptionStartStressTest'))
+
+ def redeemCode(self, context, code):
+ assert self.notify.debugCall()
+ # pass it on to the UD w/out checking the content of the parameters; offload the
+ # CPU work to the code redemption UD
+ avId = self.air.getAvatarIdFromSender()
+ self._retryMgr.addRedemption(avId, context, code)
+
+ def redeemCodeResultUdToAi(self, serial, context, avId, result, awardMgrResult):
+ assert self.notify.debugCall()
+ # pass it back to the toon
+ self._retryMgr.resolveRedemption(serial, context, avId, result, awardMgrResult)
+
+ def _doStressTest(self, task):
+ self._stressTestCode = 'stresstest'
+ fPath = PM.Filename.expandFrom('$TOONTOWN/%scoderedemption/StressTestAvIds.txt' % (
+ choice(__dev__, 'src/', ''))).toOsSpecific()
+ self._stressTestFile = open(fPath)
+ self._stressTestLastSendT = globalClock.getFrameTime()
+ self._stressTestStartTime = globalClock.getRealTime()
+ self._stressTestAvCount = 0
+ self._stressTestLogT = globalClock.getRealTime()
+ self.notify.info('starting stress test')
+ self._retryMgr.startStressTest()
+ taskMgr.add(self._stressTest, uniqueName('codeRedemptionStressTest'))
+ return task.done
+
+ def _stressTest(self, task):
+ now = globalClock.getFrameTime()
+ dt = now - self._stressTestLastSendT
+ numCodes = max(0, int(dt * self.StressTestRate))
+ self._stressTestLastSendT += (numCodes / float(self.StressTestRate))
+ done = False
+ while numCodes:
+ try:
+ line = self._stressTestFile.readline()
+ except:
+ raise
+ done = True
+ #print line
+ if not done:
+ try:
+ avId = int(line)
+ except ValueError:
+ done = True
+ if done:
+ rate = self._stressTestAvCount / (globalClock.getRealTime() - self._stressTestStartTime)
+ self.notify.info('stress test submission complete: %s codes submitted, %s codes/sec' % (
+ self._stressTestAvCount, rate))
+ self._retryMgr.finishStressTestSubmission()
+ return task.done
+ if self.RandomizeStressTestCode:
+ len = random.randrange(1, 20)
+ code = ''
+ while len:
+ code += random.choice(string.letters)
+ len -= 1
+ else:
+ code = self._stressTestCode
+ self._retryMgr.addStressTestRedemption(avId, 0, code)
+ numCodes -= 1
+ self._stressTestAvCount += 1
+
+ now = globalClock.getRealTime()
+ if (now - self._stressTestLogT) > 10:
+ self._stressTestLogT = now
+ rate = self._stressTestAvCount / (globalClock.getRealTime() - self._stressTestStartTime)
+ self.notify.info('stress test progress: %s codes submitted, %s codes/sec' % (
+ self._stressTestAvCount, rate))
+
+ return task.cont
diff --git a/toontown/src/coderedemption/TTCodeRedemptionMgrUD.py b/toontown/src/coderedemption/TTCodeRedemptionMgrUD.py
new file mode 100644
index 0000000..72e835b
--- /dev/null
+++ b/toontown/src/coderedemption/TTCodeRedemptionMgrUD.py
@@ -0,0 +1,1715 @@
+from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.http.WebRequest import WebRequestDispatcher
+from direct.showbase import ElementTree as ET
+from direct.showbase.HTMLTree import HTMLTree
+from direct.showbase.PythonUtil import unescapeHtmlString as uhs
+from direct.showbase.PythonUtil import str2elements
+from direct.http import recaptcha
+from direct.task import Task
+from otp.distributed import OtpDoGlobals
+from toontown.catalog import CatalogItemTypes
+from toontown.coderedemption.TTCodeRedemptionDB import TTCodeRedemptionDBTester, TTCodeRedemptionDB
+from toontown.coderedemption.TTCodeDict import TTCodeDict
+from toontown.coderedemption import TTCodeRedemptionConsts
+from toontown.coderedemption import TTCodeRedemptionSpamDetector
+from toontown.rpc.AwardManagerUD import AwardManagerUD
+from toontown.rpc import AwardManagerConsts
+from toontown.uberdog import PartiesUdConfig
+from StringIO import StringIO
+import datetime
+import random
+import socket
+import string
+import re
+
+SE = ET.SubElement
+
+class FormErrors:
+ class Null:
+ pass
+
+ def __init__(self):
+ self._item2errs = {}
+
+ def isEmpty(self, item=Null):
+ if item is FormErrors.Null:
+ return len(self._item2errs) == 0
+ return len(self._item2errs.get(item, [])) == 0
+
+ def get(self, item):
+ return set(self._item2errs.get(item, []))
+
+ def add(self, item, error):
+ if item not in self._item2errs:
+ self._item2errs[item] = set()
+ self._item2errs[item].add(error)
+
+class TTCodeRedemptionMgrUD(DistributedObjectGlobalUD):
+ notify = directNotify.newCategory('TTCodeRedemptionMgrUD')
+
+ Ops = Enum('menu, create, doCreate, view, doView, modify, doModify, delete, doDelete, lookup, doLookup, redeem, doRedeem')
+
+ MaxLotSize = 10000000
+
+ ReCAPTCHADomainName = 'localhost'
+ ReCAPTCHAPublicKey = '6Ld8gwkAAAAAALtgzi9Y0q8DqflAK7DwfeiKfTGN'
+ ReCAPTCHAPrivateKey = '6Ld8gwkAAAAAAEtBhL2sblZNm4SFy3B8g-6PDIUI'
+
+ Disabled = config.GetBool('disable-code-redemption', 0)
+
+ class GenericErrors:
+ EmptyInput = 'This field is required'
+ InvalidNumber = 'Please enter a number'
+ FieldsMustMatch = 'This field must match the previous field'
+
+ class CreateErrors:
+ InvalidCharInLotName = 'Name can only contain lowercase ASCII letters, numbers, and underscores'
+ UsedLotName = 'Lot name is already in use'
+
+ class CodeErrors:
+ InvalidCharInCode = 'Code can only contain alphanumeric characters and dashes'
+ InvalidCode = 'Invalid code'
+ MustContainManualChar = ('Code must contain at least one of the following: %s' %
+ TTCodeDict.ManualOnlyCharacters)
+ CodeAlreadyExists = 'Code already exists'
+ CodeTooLong = 'Code must be %s characters or less' % TTCodeRedemptionConsts.MaxCustomCodeLen
+
+ class RedeemErrors:
+ InvalidCharInAvId = 'AvId can only contain numbers'
+ CodeIsExpired = 'Code is expired'
+ CodeAlreadyRedeemed = 'Code has already been redeemed'
+ AwardCouldntBeGiven = 'Award could not be given, code not processed'
+
+ if __dev__:
+ TestRedemptionSpamAvIdMin = 6
+ TestRedemptionSpamAvIdMax = 9999999
+ TestRedemptions = (
+ # non-ascii
+ ('\xff', (TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist, 0)),
+ # invalid characters
+ ('.', (TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist, 0)),
+ )
+ DisabledTestRedemptions = (
+ ('HWF', (TTCodeRedemptionConsts.RedeemErrors.SystemUnavailable, 0)),
+ (',', (TTCodeRedemptionConsts.RedeemErrors.SystemUnavailable, 0)),
+ )
+ # spam detection
+ TestSpamRedemptions = (([('!!!', (TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist, 0)),] * TTCodeRedemptionSpamDetector.Settings.DetectThreshold) +
+ [('!!!', (TTCodeRedemptionConsts.RedeemErrors.TooManyAttempts, 0)),]
+ )
+
+ def __init__(self, air):
+ DistributedObjectGlobalUD.__init__(self, air)
+
+ self.HTTPListenPort = uber.codeRedemptionMgrHTTPListenPort
+
+ self.webDispatcher = WebRequestDispatcher()
+ self.webDispatcher.landingPage.setTitle("TTCodeRedemptionMgr")
+ self.webDispatcher.landingPage.setDescription("TTCodeRedemptionMgr enables generation and retrieval of reward codes.")
+ self.webDispatcher.registerGETHandler(
+ "codeManagement",self.handleHTTPcodeManagement,returnsResponse=False,autoSkin=True)
+ self.webDispatcher.landingPage.addTab("CodeMgmt","/codeManagement")
+ self.webDispatcher.listenOnPort(self.HTTPListenPort)
+
+ self.DBuser = uber.config.GetString("mysql-user", PartiesUdConfig.ttDbUser)
+ self.DBpasswd = uber.config.GetString("mysql-passwd", PartiesUdConfig.ttDbPasswd)
+
+ self.DBhost = uber.config.GetString("tt-code-db-host", uber.mysqlhost)
+ self.DBport = uber.config.GetInt("tt-code-db-port", PartiesUdConfig.ttDbPort)
+ self.DBname = choice(uber.crDbName != '', uber.crDbName, TTCodeRedemptionConsts.DefaultDbName)
+
+ self._rewardSerialNumGen = SerialNumGen()
+ self._rewardContextTable = {}
+
+ self._redeemContextGen = SerialNumGen()
+ self._redeemContext2session = {}
+
+ self._db = TTCodeRedemptionDB(self.air, self.DBhost, self.DBport, self.DBuser, self.DBpasswd, self.DBname)
+
+ if __debug__:
+ self._db.runTests()
+
+ self.air.setConnectionName("TTCodeRedemptionMgr")
+ self.air.setConnectionURL("http://%s:%s/" % (socket.gethostbyname(socket.gethostname()),self.HTTPListenPort))
+
+ self._createLotSerialGen = SerialNumGen()
+ self._createLotId2task = {}
+
+ self._randSampleContext2callback = {}
+ self._randSampleContextGen = SerialMaskedGen((1L<<32)-1)
+
+ self._spamDetector = TTCodeRedemptionSpamDetector.TTCodeRedemptionSpamDetector()
+ self._wantSpamDetect = config.GetBool('want-code-redemption-spam-detect', 1)
+
+ if __dev__:
+ self._testAvId = random.randrange(self.TestRedemptionSpamAvIdMin, self.TestRedemptionSpamAvIdMax)
+ self._avId2table = {self._testAvId: self.TestRedemptions,
+ }
+ self._disabledAvId2table = {self._testAvId: self.DisabledTestRedemptions,
+ }
+ if self._wantSpamDetect:
+ self._spamAvId = random.randrange(self.TestRedemptionSpamAvIdMin, self.TestRedemptionSpamAvIdMax)
+ self._avId2table[self._spamAvId] = self.TestSpamRedemptions
+
+ if __dev__:
+ def _sendTestRedemptions(self):
+ for avId in self._avId2table.iterkeys():
+ redemptions = self._avId2table[avId]
+ for i in xrange(len(redemptions)):
+ redemption = redemptions[i]
+ code, results = redemption
+ self.redeemCodeAiToUd(0, 0, i, code, avId, self._resolveTestRedemption)
+
+ def _sendDisabledTestRedemptions(self):
+ saved = TTCodeRedemptionMgrUD.Disabled
+ TTCodeRedemptionMgrUD.Disabled = True
+ for avId in self._disabledAvId2table.iterkeys():
+ redemptions = self._disabledAvId2table[avId]
+ for i in xrange(len(redemptions)):
+ redemption = redemptions[i]
+ code, results = redemption
+ self.redeemCodeAiToUd(0, 0, i, code, avId, self._resolveDisabledTestRedemption)
+ TTCodeRedemptionMgrUD.Disabled = saved
+
+ def _resolveTestRedemption(self, serial, context, avId, result, awardMgrResult):
+ if avId in self._avId2table:
+ redemptions = self._avId2table.get(avId)
+ redemption = redemptions[context]
+ code, results = redemption
+ assert result == results[0]
+ assert awardMgrResult == results[1]
+
+ def _resolveDisabledTestRedemption(self, serial, context, avId, result, awardMgrResult):
+ if avId in self._disabledAvId2table:
+ redemptions = self._disabledAvId2table.get(avId)
+ redemption = redemptions[context]
+ code, results = redemption
+ assert result == results[0]
+ assert awardMgrResult == results[1]
+
+ def announceGenerate(self):
+ """Start accepting http requests."""
+ assert self.notify.debugCall()
+ DistributedObjectGlobalUD.announceGenerate(self)
+ self.webDispatcher.startCheckingIncomingHTTP()
+ if __dev__ and TTCodeRedemptionDB.DoSelfTest:
+ if not self.Disabled:
+ self._sendTestRedemptions()
+ self._sendDisabledTestRedemptions()
+
+ def delete(self):
+ for task in self._createLotId2task.values():
+ self.removeTask(task)
+ self._createLotId2task = {}
+
+ def _reply(self, page, replyTo):
+ # everything is already under the landing page's body tag
+ """
+ fileStr = StringIO()
+ page.write(fileStr)
+ replyTo.respond(fileStr.getvalue())
+ """
+ replyTo.respond('')
+
+ def _addErrorsCell(self, row, errors, fieldName):
+ errorSet = errors.get(fieldName)
+ if len(errorSet):
+ errorCell = SE(row, 'td')
+ errorsTable = SE(errorCell, 'table')
+ for error in errorSet:
+ row = SE(errorsTable, 'tr')
+ data = SE(row, 'td')
+ data.set('style', 'color:red')
+ dataBold = SE(data, 'b')
+ # allow __str__ overload
+ dataBold.text = str(error)
+
+ def _addRecaptcha(self, form, errors):
+ rError = None
+ rErrors = errors.get('recaptcha')
+ if len(rErrors):
+ rError = list(rErrors)[0]
+ ieNotice = ET.Element('b')
+ ieNotice.text = 'NOTE: doesn\'t work with IE. Please use FireFox/Google Chrome/Safari/etc.'
+ form.append(ieNotice)
+ cError = None
+ cErrors = errors.get('recaptchaCustom')
+ if len(cErrors):
+ cError = list(cErrors)[0]
+ if cError:
+ brk = ET.Element('br')
+ form.append(brk)
+ crNotice = ET.Element('b')
+ crNotice.set('style', 'color:red')
+ crNotice.text = '\n' + cError
+ form.append(crNotice)
+ recaptchaHTML = recaptcha.displayhtml(self.ReCAPTCHAPublicKey, use_ssl=True, error=rError)
+ for el in str2elements(recaptchaHTML):
+ form.append(el)
+ SE(form, 'br')
+
+ def _addExpirationControls(self, table, values, yearName, monthName, dayName):
+ expDateRow = SE(table, 'tr')
+
+ edDescCell = SE(expDateRow, 'td')
+ edDescCell.text = 'Expiration date'
+
+ edInputCell = SE(expDateRow, 'td')
+ edYearInput = SE(edInputCell, 'select', name=yearName)
+ edMonthInput = SE(edInputCell, 'select', name=monthName)
+ edDayInput = SE(edInputCell, 'select', name=dayName)
+
+ thisYear = datetime.date.today().year
+ for i in xrange(thisYear, thisYear+100):
+ option = SE(edYearInput, 'option', value=str(i))
+ option.text = str(i)
+ if values.get('expYear') == str(i):
+ option.set('selected', 'selected')
+ for i, name in ((1, 'Jan'), (2, 'Feb'), (3, 'Mar'), (4, 'Apr'), (5, 'May'), (6, 'Jun'),
+ (7, 'Jul'), (8, 'Aug'), (9, 'Sep'), (10, 'Oct'), (11, 'Nov'), (12, 'Dec'), ):
+ option = SE(edMonthInput, 'option', value=str(i))
+ option.text = '%02i: %s' % (i, name)
+ if values.get('expMonth') == str(i):
+ option.set('selected', 'selected')
+ for i in xrange(1, 31+1):
+ option = SE(edDayInput, 'option', value=str(i))
+ option.text = str(i)
+ if values.get('expDay') == str(i):
+ option.set('selected', 'selected')
+
+ return expDateRow
+
+ def _addSubmitButtonDisable(self, headTag, bodyTag, parentTag, submitButton, formName, ):
+ buttonName = submitButton.get('name')
+ assert buttonName is not None
+ normalText = submitButton.get('value')
+ assert normalText is not None
+ submitScript = ET.Element('script', type='text/javascript')
+ enableSubmitFuncName = 'enableSubmitButton'
+ submitScript.text = """
+ function %(funcName)s(enable) {
+ var submitButton = document.%(formName)s.%(buttonName)s;
+ if (enable) {
+ submitButton.disabled = false;
+ submitButton.value = '%(normalText)s';
+ } else {
+ submitButton.disabled = true;
+ submitButton.value = 'Please wait...';
+ }
+ }
+ function resetSubmitButton() {
+ %(funcName)s(true);
+ }
+ """ % ({
+ 'funcName': enableSubmitFuncName,
+ 'formName': formName,
+ 'buttonName': buttonName,
+ 'normalText': normalText,
+ })
+ # put the javascript in the HEAD tag
+ headTag.append(submitScript)
+ # disable the submit button on click
+ submitButton.set('onclick',
+ '%(funcName)s(false);' % ({
+ 'funcName': enableSubmitFuncName,
+ }))
+ # enable the submit button on page load
+ SE(parentTag, 'script', type='text/javascript', text='%s(true);' % (enableSubmitFuncName, ))
+ bodyTag.set('onUnload', 'resetSubmitButton();');
+
+ def _isValidManualCodeRewardType(self, rewardType):
+ isPermanent = rewardType not in CatalogItemTypes.NonPermanentItemTypes
+ multipleAllowed = CatalogItemTypes.CatalogItemType2multipleAllowed[rewardType]
+ return (isPermanent and (not multipleAllowed))
+
+ def _doCreateForm(self, parent, body, replyTo, values=None, errors=None):
+ # values is dict of element->string
+ if values is None:
+ values = {}
+ # errors is sparse dict of sets of FormErrors
+ if errors is None:
+ errors = FormErrors()
+
+ formName = 'createForm'
+ mainForm = SE(parent, 'form', name=formName, action='codeManagement', method='GET')
+ hiddenOp = SE(mainForm, 'input', type='hidden', name='op', value='doCreate')
+
+ formTable = SE(mainForm, 'table')
+
+ # code lot name
+ lotNameRow = SE(formTable, 'tr')
+
+ lnDescCell = SE(lotNameRow, 'td')
+ lnDescCell.text = 'Name of code lot'
+
+ lnInputCell = SE(lotNameRow, 'td')
+
+ lotName = SE(lnInputCell, 'input', type='text', name='lotName')
+ if 'lotName' in values:
+ lotName.set('value', values['lotName'])
+
+ self._addErrorsCell(lotNameRow, errors, 'lotName')
+
+ # code type
+ codeTypeRow = SE(formTable, 'tr')
+
+ ctDescCell = SE(codeTypeRow, 'td')
+ ctDescCell.text = 'Code type'
+
+ codeTypeDropdownCell = SE(codeTypeRow, 'td')
+
+ codeTypeName = 'codeType'
+ codeTypeDropdown = SE(codeTypeDropdownCell, 'select', name=codeTypeName)
+
+ codeTypes = [('manual', 'Manually-created code, many toons use same code'), ]
+ if config.GetBool('want-unique-code-generation', 0):
+ codeTypes.append(('auto', 'Auto-generated codes, one redemption per code'))
+
+ for formVal, desc in codeTypes:
+ option = SE(codeTypeDropdown, 'option', value=formVal)
+ option.text = desc
+ if values.get('codeType') == formVal:
+ option.set('selected', 'selected')
+
+ self._addErrorsCell(codeTypeRow, errors, 'codeType')
+
+ # number of codes
+ numCodesRow = SE(formTable, 'tr')
+
+ ncDescCell = SE(numCodesRow, 'td')
+ ncDescCell.text = 'Number of codes'
+
+ ncInputCell = SE(numCodesRow, 'td')
+
+ numCodesName = 'numCodes'
+ numCodes = SE(ncInputCell, 'input', type='text', name=numCodesName)
+ if 'numCodes' in values:
+ numCodes.set('value', values['numCodes'])
+
+ self._addErrorsCell(numCodesRow, errors, 'numCodes')
+
+ # number of codes 2 (verify)
+ numCodes2Row = SE(formTable, 'tr')
+
+ nc2DescCell = SE(numCodes2Row, 'td')
+ nc2DescCell.text = 'Number of codes (again)'
+
+ nc2InputCell = SE(numCodes2Row, 'td')
+
+ numCodes2Name = 'numCodes2'
+ numCodes2 = SE(nc2InputCell, 'input', type='text', name=numCodes2Name)
+ if 'numCodes2' in values:
+ numCodes2.set('value', values['numCodes2'])
+
+ self._addErrorsCell(numCodes2Row, errors, 'numCodes2')
+
+ # manual code
+ manualCodeRow = SE(formTable, 'tr')
+
+ mcDescCell = SE(manualCodeRow, 'td')
+ mcDescCell.text = 'Manual code'
+
+ mcInputCell = SE(manualCodeRow, 'td')
+
+ manualCodeName = 'manualCode'
+ manualCode = SE(mcInputCell, 'input', type='text', name=manualCodeName)
+ if 'manualCode' in values:
+ manualCode.set('value', values['manualCode'])
+
+ self._addErrorsCell(manualCodeRow, errors, 'manualCode')
+
+ # manual code 2
+ manualCode2Row = SE(formTable, 'tr')
+
+ mc2DescCell = SE(manualCode2Row, 'td')
+ mc2DescCell.text = 'Manual code (again)'
+
+ mc2InputCell = SE(manualCode2Row, 'td')
+
+ manualCode2Name = 'manualCode2'
+ manualCode2 = SE(mc2InputCell, 'input', type='text', name=manualCode2Name)
+ if 'manualCode2' in values:
+ manualCode2.set('value', values['manualCode2'])
+
+ self._addErrorsCell(manualCode2Row, errors, 'manualCode2')
+
+ # reward type
+ awardChoices = AwardManagerUD.getReversedAwardChoices()
+
+ rewardTypeRow = SE(formTable, 'tr')
+
+ rtDescCell = SE(rewardTypeRow, 'td')
+ rtDescCell.text = 'Reward type'
+
+ rtChoiceCell = SE(rewardTypeRow, 'td')
+
+ rtSelectName = 'rewardType'
+ rtChoice = SE(rtChoiceCell, 'select', name=rtSelectName)
+
+ rewardTypes = awardChoices.keys()
+ rewardTypes.sort()
+ manualRewardTypes = []
+ for rewardType in rewardTypes:
+ if self._isValidManualCodeRewardType(rewardType):
+ manualRewardTypes.append(rewardType)
+
+ """ this is done by the javascript method defined below that handles code type changes
+ for rewardType in rewardTypes:
+ option = SE(rtChoice, 'option', value=str(rewardType))
+ option.text = AwardManagerUD.getAwardTypeName(rewardType)
+ if values.get('rewardType') == str(rewardType):
+ option.set('selected', 'selected')
+ """
+
+ self._addErrorsCell(rewardTypeRow, errors, 'rewardType')
+
+ # add javascript to change the interface based on code generation method
+ # (number of codes or manual code string)
+ genMethodScript = ET.Element('script', type='text/javascript')
+ genMethodChangeFuncName = 'handleGenerationMethodChange'
+ setRewardTypesCode = ''
+ for codeType in ('auto', 'manual', ):
+ setRewardTypesCode += 'if (genMethod.value == "%s") {' % codeType
+ if (codeType == 'auto'):
+ rts = rewardTypes
+ else:
+ rts = manualRewardTypes
+ for rewardType in rts:
+ value = str(rewardType)
+ text = AwardManagerUD.getAwardTypeName(rewardType)
+ setRewardTypesCode += ('rewardType.options[rewardType.options.length] = new Option('
+ '"%s", "%s");' % (text, value))
+ setRewardTypesCode += '}'
+ genMethodScript.text = """
+ function %(genMethodChangeFuncName)s() {
+ var genMethod = document.%(formName)s.%(genMethodName)s;
+ var numCodes = document.%(formName)s.%(numCodesName)s;
+ var numCodes2 = document.%(formName)s.%(numCodes2Name)s;
+ var manualCode = document.%(formName)s.%(manualCodeName)s;
+ var manualCode2 = document.%(formName)s.%(manualCode2Name)s;
+ var rewardType = document.%(formName)s.%(rewardTypeName)s;
+ rewardType.options.length = 0;
+ if (genMethod.value == 'auto') {
+ numCodes.disabled = false;
+ numCodes2.disabled = false;
+ manualCode.disabled = true;
+ manualCode2.disabled = true;
+ } else {
+ numCodes.disabled = true;
+ numCodes2.disabled = true;
+ manualCode.disabled = false;
+ manualCode2.disabled = false;
+ }
+ %(setRewardTypes)s
+ handleRewardTypeChange();
+ }
+ """ % ({
+ 'genMethodChangeFuncName': genMethodChangeFuncName,
+ 'formName': formName,
+ 'genMethodName': codeTypeName,
+ 'numCodesName': numCodesName,
+ 'numCodes2Name': numCodes2Name,
+ 'manualCodeName': manualCodeName,
+ 'manualCode2Name': manualCode2Name,
+ 'rewardTypeName': rtSelectName,
+ 'setRewardTypes': setRewardTypesCode,
+ })
+ # put the javascript inside the HEAD tag
+ replyTo.getHeadTag().append(genMethodScript)
+ # hook up the onchange method so that the interface changes when a generation
+ # method is selected
+ codeTypeDropdown.set('onchange', '%s();' % genMethodChangeFuncName)
+ # adjust the interface properly on page load
+ rewardTypeSelectIndex = 0
+ if 'codeType' in values and 'rewardType' in values:
+ if (values.get('codeType') == 'auto'):
+ rts = rewardTypes
+ else:
+ rts = manualRewardTypes
+ rewardTypeSelectIndex = rts.index(int(values.get('rewardType')))
+ initGenMethodOptions = SE(parent, 'script', type='text/javascript')
+ initGenMethodOptions.text = '%(codeTypeChangeFunc)s(); document.%(formName)s.%(rewardTypeSelectName)s.selectedIndex = %(index)s;' % {
+ 'codeTypeChangeFunc': genMethodChangeFuncName,
+ 'formName': formName,
+ 'rewardTypeSelectName': rtSelectName,
+ 'index': rewardTypeSelectIndex,
+ }
+
+ # reward itemId
+ rewardItemRow = SE(formTable, 'tr')
+
+ riDescCell = SE(rewardItemRow, 'td')
+ riDescCell.text = 'Reward item'
+
+ riChoiceCell = SE(rewardItemRow, 'td')
+
+ riSelectName = 'rewardItemId'
+ riChoice = SE(riChoiceCell, 'select', name=riSelectName)
+
+ # this selection is filled in automatically based on the reward type selection
+ """
+ id2item = awardChoices[1]
+ itemIds = id2item.keys()
+ itemIds.sort()
+ for itemId in itemIds:
+ option = SE(riChoice, 'option', value=str(itemId))
+ option.text = id2item[itemId]
+ if values.get('rewardItemId') == str(itemId):
+ option.set('selected', 'selected')
+ """
+
+ self._addErrorsCell(rewardItemRow, errors, 'rewardItemId')
+
+ # add javascript to change the reward item list based on the reward type selection
+ rewardTypeScript = ET.Element('script', type='text/javascript')
+ rewardTypeChangeFuncName = 'handleRewardTypeChange'
+ setRewardItemsCode = ''
+ for rewardType in rewardTypes:
+ setRewardItemsCode += 'if (typeValue == "%s") {' % rewardType
+ id2item = awardChoices[rewardType]
+ itemIds = id2item.keys()
+ itemIds.sort()
+ for itemId in itemIds:
+ value = str(itemId)
+ text = id2item[itemId]
+ setRewardItemsCode += ('itemSel.options[itemSel.options.length] = new Option('
+ '"%s", "%s");' % (text, value))
+ setRewardItemsCode += '}'
+ rewardTypeScript.text = """
+ function %(rewardChangeFuncName)s() {
+ var typeSel = document.%(formName)s.%(rewardTypeSelectName)s;
+ var itemSel = document.%(formName)s.%(rewardItemSelectName)s;
+ var typeValue = typeSel[typeSel.selectedIndex].value;
+ itemSel.options.length = 0;
+ %(setRewardItems)s
+ }
+ """ % ({
+ 'rewardChangeFuncName': rewardTypeChangeFuncName,
+ 'formName': formName,
+ 'rewardTypeSelectName': rtSelectName,
+ 'rewardItemSelectName': riSelectName,
+ 'setRewardItems': setRewardItemsCode,
+ })
+ # put the javascript inside the HEAD tag
+ replyTo.getHeadTag().append(rewardTypeScript)
+ # load the correct items into the reward item selection when the reward type
+ # selection is changed
+ rtChoice.set('onchange',
+ '%(rewardChangeFuncName)s();' % ({
+ 'rewardChangeFuncName': rewardTypeChangeFuncName,
+ }))
+ # load the correct items into the reward item selection on page load
+ rewardItemSelectIndex = 0
+ if 'rewardType' in values and 'rewardItemId' in values:
+ id2item = awardChoices[int(values.rewardType)]
+ itemIds = id2item.keys()
+ itemIds.sort()
+ rewardItemSelectIndex = itemIds.index(int(values.rewardItemId))
+ initRewardItems = SE(parent, 'script', type='text/javascript')
+ initRewardItems.text = '%(rewardChangeFuncName)s(); document.%(formName)s.%(rewardItemSelectName)s.selectedIndex = %(index)s;' % (
+ {'rewardChangeFuncName': rewardTypeChangeFuncName,
+ 'formName': formName,
+ 'rewardItemSelectName': riSelectName,
+ 'index': rewardItemSelectIndex,
+ })
+
+ # has expiration date
+ hasExpRow = SE(formTable, 'tr')
+
+ heDescCell = SE(hasExpRow, 'td')
+ heDescCell.text = 'Has expiration date'
+
+ heInputCell = SE(hasExpRow, 'td')
+ hasExpirationName = 'hasExpiration'
+ heInput = SE(heInputCell, 'select', name=hasExpirationName)
+
+ for formVal, desc in (('yes', 'Yes'),
+ ('no', 'No'),
+ ):
+ option = SE(heInput, 'option', value=formVal)
+ option.text = desc
+ if 'hasExpiration' not in values:
+ if formVal == 'no':
+ option.set('selected', 'selected')
+ elif values.get('hasExpiration') == formVal:
+ option.set('selected', 'selected')
+
+ # expiration date
+ yearName = 'expYear'
+ monthName = 'expMonth'
+ dayName = 'expDay'
+ expDateRow = self._addExpirationControls(formTable, values, yearName, monthName, dayName)
+ self._addErrorsCell(expDateRow, errors, 'expiration')
+
+ # add javascript to disable the date entry if expiration date is turned off
+ dateScript = ET.Element('script', type='text/javascript')
+ dateEnableFuncName = 'enableDateEntry'
+ dateScript.text = """
+ function %(funcName)s() {
+ var disabled = false;
+ if (document.%(formName)s.%(hasExpName)s.value == 'no') {
+ disabled = true;
+ }
+ document.%(formName)s.%(yearName)s.disabled = disabled;
+ document.%(formName)s.%(monthName)s.disabled = disabled;
+ document.%(formName)s.%(dayName)s.disabled = disabled;
+ }
+ """ % ({
+ 'funcName': dateEnableFuncName,
+ 'formName': formName,
+ 'hasExpName': hasExpirationName,
+ 'yearName': yearName,
+ 'monthName': monthName,
+ 'dayName': dayName,
+ })
+ # put the javascript in the HEAD tag
+ replyTo.getHeadTag().append(dateScript)
+ # handle changes to the expiration enable field
+ heInput.set('onchange',
+ '%(funcName)s();' % ({
+ 'funcName': dateEnableFuncName,
+ }))
+ # set the correct date enable state on page load
+ initDate = SE(parent, 'script', type='text/javascript')
+ initDate.text = '%(funcName)s();' % ({
+ 'funcName': dateEnableFuncName,
+ })
+
+ SE(mainForm, 'br')
+
+ self._addRecaptcha(mainForm, errors)
+
+ buttonName = 'submitButton'
+ submitText = 'Create Code Lot'
+ submitButton = SE(mainForm, 'input', name=buttonName, type='submit', value=submitText)
+ self._addSubmitButtonDisable(replyTo.getHeadTag(), replyTo.getBodyTag(), parent,
+ submitButton, formName, )
+
+ def _startCreateLotTask(self, replyTo, page, body, values, manualCode, numCodes, manualCodeStr, expDate):
+ assert self.notify.debugCall()
+ if manualCode:
+ self._db.createManualLot(values.lotName, manualCodeStr, values.rewardType, values.rewardItemId,
+ expirationDate=expDate)
+ self._showCreateLotResults(replyTo, page, body, values)
+ else:
+ createLotId = self._createLotSerialGen.next()
+ gen = self._db.createLot(self._requestRandomSamples, values.lotName, numCodes,
+ values.rewardType, values.rewardItemId,
+ expirationDate=expDate)
+ t = self.addTask(self._createLotTask, '%s-createLot-%s' % (self.__class__.__name__, createLotId))
+ t.createLotId = createLotId
+ t.gen = gen
+ t.replyTo = replyTo
+ t.page = page
+ t.body = body
+ t.values = values
+ self._createLotId2task[createLotId] = t
+
+ def _createLotTask(self, task):
+ for result in task.gen:
+ break
+
+ if result is True:
+ self._showCreateLotResults(task.replyTo, task.page, task.body, task.values)
+ del self._createLotId2task[task.createLotId]
+ return Task.done
+
+ return Task.cont
+
+ def _showCreateLotResults(self, replyTo, page, body, values):
+ assert self.notify.debugCall()
+ self._doViewLot(values.lotName, body)
+ self._reply(page, replyTo)
+
+ def _requestRandomSamples(self, callback, numSamples):
+ assert self.notify.debugCall()
+ context = self._randSampleContextGen.next()
+ self._randSampleContext2callback[context] = callback
+ self.air.dispatchUpdateToGlobalDoId(
+ "NonRepeatableRandomSourceUD", "getRandomSamples",
+ OtpDoGlobals.OTP_DO_ID_TOONTOWN_NON_REPEATABLE_RANDOM_SOURCE,
+ [self.doId, 'TTCodeRedemptionMgr', context, numSamples])
+
+ def getRandomSamplesReply(self, context, samples):
+ assert self.notify.debugCall()
+ callback = self._randSampleContext2callback.pop(context)
+ callback(samples)
+
+ def _createCodeTable(self, parent, fieldRows, justCode=False, manual=False):
+ internalFields = ('name','lot.lot_id','lot_id','size',
+ 'reward.reward_id','reward_id',)
+ nameTransform = {
+ 'av_id': 'redeemed.av_id',
+ TTCodeRedemptionDB.RewardTypeFieldName: 'reward.category',
+ TTCodeRedemptionDB.RewardItemIdFieldName: 'reward.item',
+ }
+ transAndField = []
+ if justCode:
+ transAndField.append(['code','code'])
+ else:
+ fieldSet = set()
+ for row in fieldRows:
+ for field in row:
+ if field not in fieldSet:
+ if field not in internalFields:
+ transAndField.append([nameTransform.get(field, field), field])
+ fieldSet.add(field)
+
+ # sort by transformed name, keep track of original field name
+ transAndField.sort()
+
+ table = SE(parent, 'table')
+
+ titleRow = SE(table, 'tr')
+ for trans, field in transAndField:
+ fieldTitle = SE(titleRow, 'th')
+ fieldTitle.text = trans.upper()
+
+ for row in fieldRows:
+ tableRow = SE(table, 'tr')
+ for trans, field in transAndField:
+ if justCode:
+ value = row
+ else:
+ value = row[field]
+ tableData = SE(tableRow, 'td')
+ if field == 'code':
+ # if the code is manually-entered, don't modify it to make it readable
+ # if the code row has a manual field, go by that,
+ # otherwise use the keyword arg to this method
+ if 'manual' in row:
+ isManual = row['manual'] == 'T'
+ else:
+ isManual = manual
+ if not isManual:
+ value = TTCodeDict.getReadableCode(value)
+ else:
+ if trans == 'reward.category':
+ value = AwardManagerUD.getAwardTypeName(row[field])
+ if trans == 'reward.item':
+ typeId = int(row[TTCodeRedemptionDB.RewardTypeFieldName])
+ itemId = int(row[field])
+ value = AwardManagerUD.getAwardText(typeId, itemId);
+ if field in ('manual_code', 'redeemed'):
+ value = {'T': 'Yes',
+ 'F': 'No',
+ }[row[field]]
+ value = str(value)
+ tableData.text = value
+
+ def _doViewForm(self, parent, replyTo, values=None, errors=None):
+ formName = 'viewForm'
+ mainForm = SE(parent, 'form', name=formName)
+ mainForm.set('action', 'codeManagement')
+ mainForm.set('method', 'GET')
+
+ hiddenOp = SE(mainForm, 'input')
+ hiddenOp.set('type', 'hidden')
+ hiddenOp.set('name', 'op')
+ hiddenOp.set('value', 'doView')
+
+ lotNames = self._db.getLotNames()
+
+ viewTable = SE(mainForm, 'table')
+
+ lotNameRow = SE(viewTable, 'tr')
+
+ lnDesc = SE(lotNameRow, 'td')
+ lnDesc.text = 'Code lot'
+
+ lnSelect = SE(lotNameRow, 'td')
+ lotNameDropdown = SE(lnSelect, 'select')
+ lotNameDropdown.set('name', 'lotName')
+ for name in lotNames:
+ lnOnePer = SE(lotNameDropdown, 'option')
+ lnOnePer.set('value', name)
+ lnOnePer.text = name
+
+ filterRow = SE(viewTable, 'tr')
+
+ frDesc = SE(filterRow, 'td')
+ frDesc.text = 'Filter by'
+
+ fSelect = SE(filterRow, 'td')
+ filterDropdown = SE(fSelect, 'select')
+ filterDropdown.set('name', 'filter')
+ fElements = ((self._db.LotFilter.All, 'all codes'),
+ (self._db.LotFilter.Redeemable, 'redeemable codes'),
+ (self._db.LotFilter.NonRedeemable, 'non-redeemable codes'),
+ (self._db.LotFilter.Redeemed, 'redeemed codes'),
+ (self._db.LotFilter.Expired, 'expired codes'),
+ )
+ for name, desc in fElements:
+ fOption = SE(filterDropdown, 'option')
+ fOption.set('value', name)
+ fOption.text = desc
+
+ showFieldsRow = SE(viewTable, 'tr')
+
+ sfDesc = SE(showFieldsRow, 'td')
+ sfDesc.text = 'Show fields'
+
+ sfSelect = SE(showFieldsRow, 'td')
+ sfDropdown = SE(sfSelect, 'select')
+ sfDropdown.set('name', 'showFields')
+ sfElements = (('codeOnly', 'code only'),
+ ('all', 'all fields'),
+ )
+ for name, desc in sfElements:
+ sfOption = SE(sfDropdown, 'option')
+ sfOption.set('value', name)
+ sfOption.text = desc
+
+ SE(mainForm, 'br')
+
+ submitButton = SE(mainForm, 'input', name='submitButton')
+ submitButton.set('type', 'submit')
+ submitButton.set('value', 'View Code Lot')
+ self._addSubmitButtonDisable(replyTo.getHeadTag(), replyTo.getBodyTag(), parent,
+ submitButton, formName, )
+
+ def _doViewLot(self, lotName, body, justCode=None, filter=None):
+ if justCode is None:
+ justCode = True
+ if filter is None:
+ filter = self._db.LotFilter.All
+
+ results = self._db.getCodesInLot(lotName, justCode, filter)
+
+ manual = (lotName in self._db.getManualLotNames())
+
+ tableTitle = SE(body, 'h1')
+ tableTitle.text = 'Code Lot: %s%s, %s results' % (
+ lotName, choice(filter == self._db.LotFilter.All, '', ' (%s)' % filter), len(results))
+
+ self._createCodeTable(body, results, justCode=justCode, manual=manual)
+
+ def _doModifyForm(self, parent, replyTo, values=None, errors=None):
+ # values is dict of element->string
+ if values is None:
+ values = {}
+ # errors is sparse dict of sets of FormErrors
+ if errors is None:
+ errors = FormErrors()
+
+ formName = 'modifyForm'
+ mainForm = SE(parent, 'form', name=formName)
+ mainForm.set('action', 'codeManagement')
+ mainForm.set('method', 'GET')
+
+ hiddenOp = SE(mainForm, 'input')
+ hiddenOp.set('type', 'hidden')
+ hiddenOp.set('name', 'op')
+ hiddenOp.set('value', 'doModify')
+
+ modifyTable = SE(mainForm, 'table')
+
+ modificationRow = SE(modifyTable, 'tr')
+
+ mrDesc = SE(modificationRow, 'td')
+ mrDesc.text = 'Modification'
+
+ mSelect = SE(modificationRow, 'td')
+ modificationDropdown = SE(mSelect, 'select')
+ modificationDropdown.set('name', 'modification')
+ fElements = (('expiration', 'Change expiration date'),
+ )
+ for name, desc in fElements:
+ fOption = SE(modificationDropdown, 'option')
+ fOption.set('value', name)
+ fOption.text = desc
+ if 'modification' in values:
+ if values.modification == name:
+ fOption.set('selected', 'selected')
+
+ self._addErrorsCell(modificationRow, errors, 'modification')
+
+ lotNameRow = SE(modifyTable, 'tr')
+
+ lnDesc = SE(lotNameRow, 'td')
+ lnDesc.text = 'Code lot'
+
+ # TODO: change the lot filtering based on the modification selected
+ lotNames = self._db.getExpirationLotNames()
+
+ lnSelect = SE(lotNameRow, 'td')
+ lotNameDropdown = SE(lnSelect, 'select')
+ lotNameDropdown.set('name', 'lotName')
+ for name in lotNames:
+ lnOnePer = SE(lotNameDropdown, 'option')
+ lnOnePer.set('value', name)
+ lnOnePer.text = name
+ if 'lotName' in values:
+ if name == values.lotName:
+ lnOnePer.set('selected', 'selected')
+
+ self._addErrorsCell(lotNameRow, errors, 'lotName')
+
+ yearName = 'expYear'
+ monthName = 'expMonth'
+ dayName = 'expDay'
+ expDateRow = self._addExpirationControls(modifyTable, values, yearName, monthName, dayName)
+
+ self._addErrorsCell(expDateRow, errors, 'expiration')
+
+ SE(mainForm, 'br')
+
+ self._addRecaptcha(mainForm, errors)
+
+ submitButton = SE(mainForm, 'input', name='submitButton')
+ submitButton.set('type', 'submit')
+ submitButton.set('value', 'Modify Code Lot')
+ self._addSubmitButtonDisable(replyTo.getHeadTag(), replyTo.getBodyTag(), parent,
+ submitButton, formName, )
+
+ def _doModifyLot(self, parent, replyTo, page, values):
+ if values.modification == 'expiration':
+ exp = '%s-%02d-%02d' % (values.expYear, int(values.expMonth), int(values.expDay), )
+ self._db.setExpiration(values.lotName, exp)
+ resultHeading = SE(parent, 'h2')
+ resultHeading.text = 'expiration date set to %s' % (exp, )
+
+ self._doViewLot(values.lotName, parent, justCode=False)
+
+ def _doDeleteForm(self, parent, replyTo, values=None, errors=None):
+ if values is None:
+ values = {}
+ if errors is None:
+ errors = FormErrors()
+
+ formName = 'deleteForm'
+ mainForm = SE(parent, 'form', name=formName)
+ mainForm.set('action', 'codeManagement')
+ mainForm.set('method', 'GET')
+
+ hiddenOp = SE(mainForm, 'input')
+ hiddenOp.set('type', 'hidden')
+ hiddenOp.set('name', 'op')
+ hiddenOp.set('value', 'doDelete')
+
+ deleteTable = SE(mainForm, 'table')
+
+ lotNames = self._db.getLotNames()
+
+ lotNameRow = SE(deleteTable, 'tr')
+
+ lnDesc = SE(lotNameRow, 'td')
+ lnDesc.text = 'Code lot'
+
+ lnSelect = SE(lotNameRow, 'td')
+ lotNameDropdown = SE(lnSelect, 'select')
+ lotNameDropdown.set('name', 'lotName')
+ for name in lotNames:
+ lnOnePer = SE(lotNameDropdown, 'option')
+ lnOnePer.set('value', name)
+ lnOnePer.text = name
+ if 'lotName' in values:
+ if name == values.lotName:
+ lnOnePer.set('selected', 'selected')
+
+ self._addErrorsCell(lotNameRow, errors, 'lotName')
+
+ lotName2Row = SE(deleteTable, 'tr')
+
+ ln2Desc = SE(lotName2Row, 'td')
+ ln2Desc.text = 'Code lot (again)'
+
+ ln2Select = SE(lotName2Row, 'td')
+ lotName2Dropdown = SE(ln2Select, 'select')
+ lotName2Dropdown.set('name', 'lotName2')
+ for name in (['',] + lotNames):
+ lnOnePer = SE(lotName2Dropdown, 'option')
+ lnOnePer.set('value', name)
+ lnOnePer.text = name
+
+ self._addErrorsCell(lotName2Row, errors, 'lotName2')
+
+ SE(mainForm, 'br')
+
+ self._addRecaptcha(mainForm, errors)
+
+ submitButton = SE(mainForm, 'input', name='submitButton')
+ submitButton.set('type', 'submit')
+ submitButton.set('value', 'Delete Lot')
+ self._addSubmitButtonDisable(replyTo.getHeadTag(), replyTo.getBodyTag(), parent,
+ submitButton, formName, )
+
+ def _doDelete(self, parent, replyTo, page, values):
+ success = False
+ preLotNames = self._db.getLotNames()
+ if values.lotName in preLotNames:
+ self._db.deleteLot(values.lotName)
+ postLotNames = self._db.getLotNames()
+ if values.lotName not in postLotNames:
+ success = True
+
+ resultHeading = SE(parent, 'h2')
+ resultHeading.text = choice(success,
+ 'code lot %s deleted' % (values.lotName, ),
+ 'could not delete lot %s' % (values.lotName, ))
+
+ SE(parent, 'br')
+
+ backToMenu = SE(parent, 'a')
+ backToMenu.set('href', '/codeManagement')
+ backToMenu.text = 'Back to Menu'
+
+ def _doLookupForm(self, parent, replyTo, values=None, errors=None):
+ if values is None:
+ values = {}
+ if errors is None:
+ errors = FormErrors()
+
+ formName = 'lookupForm'
+ mainForm = SE(parent, 'form', name=formName, action='codeManagement', method='GET')
+ hiddenOp = SE(mainForm, 'input', type='hidden', name='op', value='doLookup')
+
+ formTable = SE(mainForm, 'table')
+
+ # mode selection (look up by X)
+ modeRow = SE(formTable, 'tr')
+
+ modeDescCell = SE(modeRow, 'td')
+ modeDescCell.text = 'Lookup by'
+
+ modeSelectCell = SE(modeRow, 'td')
+ modeSelectName = 'mode'
+ modeSelect = SE(modeSelectCell, 'select', name=modeSelectName)
+ for name, value in (('Code', 'Code'), ('Redeemer AvId', 'AvId'), ):
+ msOption = SE(modeSelect, 'option', value=value)
+ msOption.text = name
+
+ # code
+ codeRow = SE(formTable, 'tr')
+
+ codeDescCell = SE(codeRow, 'td')
+ codeDescCell.text = 'Code'
+
+ codeInputCell = SE(codeRow, 'td')
+ codeInputName = 'code'
+ codeInput = SE(codeInputCell, 'input', type='text', name=codeInputName)
+ if 'code' in values:
+ codeInput.set('value', values[codeInputName])
+
+ self._addErrorsCell(codeRow, errors, codeInputName)
+
+ # avId
+ avIdRow = SE(formTable, 'tr')
+
+ avIdDescCell = SE(avIdRow, 'td')
+ avIdDescCell.text = 'Redeemer AvId'
+
+ avIdInputCell = SE(avIdRow, 'td')
+ avIdInputName = 'avId'
+ avIdInput = SE(avIdInputCell, 'input', type='text', name=avIdInputName)
+ if 'avId' in values:
+ avIdInput.set('value', values[avIdInputName])
+
+ self._addErrorsCell(avIdRow, errors, avIdInputName)
+
+ # add javascript to disable/enable fields as appropriate
+ enableScript = ET.Element('script', type='text/javascript')
+ enableFuncName = 'enableEntries'
+ enableScript.text = """
+ function %(funcName)s() {
+ var codeDisabled = false;
+ var avIdDisabled = false;
+ if (document.%(formName)s.%(modeSelectName)s.value == 'AvId') {
+ codeDisabled = true;
+ } else {
+ avIdDisabled = true;
+ }
+ document.%(formName)s.%(codeInputName)s.disabled = codeDisabled;
+ document.%(formName)s.%(avIdInputName)s.disabled = avIdDisabled;
+ }
+ """ % ({
+ 'funcName': enableFuncName,
+ 'formName': formName,
+ 'modeSelectName': modeSelectName,
+ 'codeInputName': codeInputName,
+ 'avIdInputName': avIdInputName,
+ })
+ # put the javascript in the HEAD tag
+ replyTo.getHeadTag().append(enableScript)
+ # handle changes to the mode selection
+ modeSelect.set('onchange',
+ '%(funcName)s();' % ({
+ 'funcName': enableFuncName,
+ }))
+ # set the correct enable state on page load
+ initMode = SE(parent, 'script', type='text/javascript')
+ initMode.text = '%(funcName)s();' % ({
+ 'funcName': enableFuncName,
+ })
+
+ SE(mainForm, 'br')
+
+ submitButton = SE(mainForm, 'input', name='submitButton')
+ submitButton.set('type', 'submit')
+ submitButton.set('value', 'Look Up Code(s)')
+ self._addSubmitButtonDisable(replyTo.getHeadTag(), replyTo.getBodyTag(), parent,
+ submitButton, formName, )
+
+ def _doLookup(self, parent, avId=None, code=None):
+ if avId is not None:
+ codes = self._db.lookupCodesRedeemedByAvId(avId)
+ else:
+ codes = [code,]
+ codeFields = []
+ for cd in codes:
+ codeFields.append(self._db.getCodeDetails(cd))
+
+ if avId is not None:
+ queryType = 'avId=%s' % avId
+ else:
+ queryType = 'code=%s' % code
+ tableTitle = SE(parent, 'h1')
+ tableTitle.text = 'Code Lookup: %s, %s results' % (queryType, len(codeFields))
+
+ self._createCodeTable(parent, codeFields)
+
+ def _doRedeemForm(self, parent, replyTo, values=None, errors=None):
+ if values is None:
+ values = {}
+ if errors is None:
+ errors = FormErrors()
+
+ formName = 'redeemForm'
+ mainForm = SE(parent, 'form', name=formName)
+ mainForm.set('action', 'codeManagement')
+ mainForm.set('method', 'GET')
+
+ hiddenOp = SE(mainForm, 'input')
+ hiddenOp.set('type', 'hidden')
+ hiddenOp.set('name', 'op')
+ hiddenOp.set('value', 'doRedeem')
+
+ formTable = SE(mainForm, 'table')
+
+ # code
+ codeRow = SE(formTable, 'tr')
+
+ codeDescCell = SE(codeRow, 'td')
+ codeDescCell.text = 'Code'
+
+ codeInputCell = SE(codeRow, 'td')
+ codeInput = SE(codeInputCell, 'input')
+ codeInput.set('type', 'text')
+ codeInput.set('name', 'code')
+ if 'code' in values:
+ codeInput.set('value', values['code'])
+
+ self._addErrorsCell(codeRow, errors, 'code')
+
+ # avId
+ avIdRow = SE(formTable, 'tr')
+
+ avIdDescCell = SE(avIdRow, 'td')
+ avIdDescCell.text = 'AvId'
+
+ avIdInputCell = SE(avIdRow, 'td')
+ avIdInput = SE(avIdInputCell, 'input')
+ avIdInput.set('type', 'text')
+ avIdInput.set('name', 'avId')
+ if 'avId' in values:
+ avIdInput.set('value', values['avId'])
+
+ self._addErrorsCell(avIdRow, errors, 'avId')
+
+ SE(mainForm, 'br')
+
+ self._addRecaptcha(mainForm, errors)
+
+ submitButton = SE(mainForm, 'input', name='submitButton')
+ submitButton.set('type', 'submit')
+ submitButton.set('value', 'Redeem Code')
+ self._addSubmitButtonDisable(replyTo.getHeadTag(), replyTo.getBodyTag(), parent,
+ submitButton, formName, )
+
+ def _doRedeemResult(self, body, replyTo, avId, result, awardMgrResult, values, errors):
+ RE = TTCodeRedemptionConsts.RedeemErrors
+ errMap = {RE.CodeDoesntExist: self.CodeErrors.InvalidCode,
+ RE.CodeIsExpired: self.RedeemErrors.CodeIsExpired,
+ RE.CodeAlreadyRedeemed: self.RedeemErrors.CodeAlreadyRedeemed,
+ RE.AwardCouldntBeGiven: self.RedeemErrors.AwardCouldntBeGiven,
+ }
+ if result in (errMap):
+ errStr = errMap[result]
+ if result == RE.AwardCouldntBeGiven:
+ errStr += ': %s' % AwardManagerConsts.GiveAwardErrors.getString(awardMgrResult)
+ errors.add('code', errStr)
+ self._doRedeemForm(body, replyTo, values, errors)
+ else:
+ resultTable = SE(body, 'table')
+
+ headingRow = SE(resultTable, 'tr')
+ headingData = SE(headingRow, 'th')
+ headingCenter = SE(headingData, 'center')
+ headingCenter.text = 'Success!'
+
+ rewardType, rewardId = self._db.getRewardFromCode(values.code)
+
+ resultRow = SE(resultTable, 'tr')
+ resultData = SE(resultRow, 'td')
+ resultCenter = SE(resultData, 'center')
+ resultCenter.text = ('Redeemed code %s for avId %s, awarded [%s | %s].' % (
+ values.code, avId,
+ AwardManagerUD.getAwardTypeName(rewardType),
+ AwardManagerUD.getAwardText(rewardType, rewardId)))
+
+ delayRow = SE(resultTable, 'tr')
+ delayData = SE(delayRow, 'td')
+ delayCenter = SE(delayData, 'center')
+ delayCenter.text = 'Reward will arrive in mailbox in a few minutes.'
+
+ SE(body, 'br')
+
+ backToMenu = SE(body, 'a')
+ backToMenu.set('href', '/codeManagement')
+ backToMenu.text = 'Back to Menu'
+
+ def _codeHasInvalidChars(self, code):
+ return not TTCodeDict.isLegalCode(code)
+
+ def _errorCheckCode(self, errors, code, fieldName='code'):
+ if len(code.strip()) == 0:
+ errors.add(fieldName, self.GenericErrors.EmptyInput)
+
+ if self._codeHasInvalidChars(code):
+ errors.add(fieldName, self.CodeErrors.InvalidCharInCode)
+
+ def _errorCheckAvId(self, errors, avId, fieldName='avId'):
+ if len(avId.strip()) == 0:
+ errors.add(fieldName, self.GenericErrors.EmptyInput)
+
+ for char in avId:
+ if (char not in string.digits):
+ errors.add(fieldName, self.RedeemErrors.InvalidCharInAvId)
+
+ def _doRecaptcha(self, replyTo, values, errors):
+ """
+ rResponse = recaptcha.submit(values.recaptchaChallenge, values.recaptchaResponse,
+ self.ReCAPTCHAPrivateKey, replyTo.getSourceAddress())
+ if not rResponse.is_valid:
+ errors.add('recaptcha', rResponse.error_code)
+ """
+ # make sure it's two words
+ valid = re.match(r'[ ]*[\w]+[ ]+[\w]+[ ]*', values.recaptchaResponse)
+ if not valid:
+ errors.add('recaptchaCustom', 'Invalid entry')
+
+ def _doSystemUnavailablePage(self, page):
+ page.text = 'System is unavailable, please try again later.'
+
+ def handleHTTPcodeManagement(self, replyTo=None, **kw):
+ replyNow = True
+
+ #page = HTMLTree('Toontown Code Management')
+ body = replyTo.getBodyTag()
+ # we're using the landing page so the body 'is' the page
+ page = ET.ElementTree(body)
+
+ try:
+ op = None
+
+ if 'op' in kw:
+ opStr = kw['op']
+ if self.Ops.hasString(opStr):
+ op = self.Ops.fromString(opStr)
+ if op is None:
+ op = self.Ops.menu
+
+ if op == self.Ops.menu:
+ newLot = SE(body, 'a', href='/codeManagement?op=create')
+ newLot.text = 'Create a new code lot'
+ SE(body, 'br')
+
+ if len(self._db.getLotNames()):
+ viewLot = SE(body, 'a', href='/codeManagement?op=view')
+ viewLot.text = 'View an existing code lot'
+ SE(body, 'br')
+
+ modifyLot = SE(body, 'a', href='/codeManagement?op=modify')
+ modifyLot.text = 'Modify an existing code lot'
+ SE(body, 'br')
+
+ deleteLot = SE(body, 'a', href='/codeManagement?op=delete')
+ deleteLot.text = 'Delete an existing code lot'
+ SE(body, 'br')
+
+ viewLot = SE(body, 'a', href='/codeManagement?op=lookup')
+ viewLot.text = 'Look up existing codes'
+ SE(body, 'br')
+
+ redeemCode = SE(body, 'a', href='/codeManagement?op=redeem')
+ redeemCode.text = 'Redeem a code'
+ SE(body, 'br')
+
+ SE(body, 'br')
+ SE(body, 'br')
+
+ img = SE(body, 'img', title='relevant this is',
+ src='http://icanhascheezburger.files.wordpress.com/2007/01/2000455272489756911_rs.jpg')
+
+ elif op == self.Ops.create:
+ self._doCreateForm(body, body, replyTo)
+
+ elif op == self.Ops.doCreate:
+ values = ScratchPad(
+ lotName = uhs(kw['lotName']),
+ codeType = uhs(kw['codeType']),
+ rewardType = uhs(kw['rewardType']),
+ rewardItemId = uhs(kw['rewardItemId']),
+ hasExpiration = uhs(kw['hasExpiration']),
+ recaptchaChallenge = uhs(kw['recaptcha_challenge_field']),
+ recaptchaResponse = uhs(kw['recaptcha_response_field']),
+ )
+ if values.codeType == 'auto':
+ values.add(numCodes = uhs(kw['numCodes']))
+ values.add(numCodes2 = uhs(kw['numCodes2']))
+ else:
+ values.add(manualCode = uhs(kw['manualCode']))
+ values.add(manualCode2 = uhs(kw['manualCode2']))
+ values.manualCode = unicode(values.manualCode, 'utf-8')
+ values.manualCode2 = unicode(values.manualCode2, 'utf-8')
+ if values.hasExpiration == 'yes':
+ values.add(
+ expYear = uhs(kw['expYear']),
+ expMonth = uhs(kw['expMonth']),
+ expDay = uhs(kw['expDay']),
+ )
+
+ errors = FormErrors()
+
+ if len(values.lotName.strip()) == 0:
+ errors.add('lotName', self.GenericErrors.EmptyInput)
+
+ for char in values.lotName:
+ # lot names can only contain lowercase ASCII letters, numbers, and underscores
+ if ((char not in (string.letters + string.digits + '_')) or
+ ((char in string.letters) and (string.upper(char) == char))):
+ errors.add('lotName', self.CreateErrors.InvalidCharInLotName)
+
+ if values.lotName in self._db.getLotNames():
+ errors.add('lotName', self.CreateErrors.UsedLotName)
+
+ if not TTCodeRedemptionDBTester.isLotNameValid(values.lotName):
+ errors.add('lotName', self.CreateErrors.UsedLotName)
+
+ manualCode = (values.codeType == 'manual')
+
+ if not manualCode:
+ manualCodeStr = None
+ try:
+ numCodes = int(values.numCodes)
+ except ValueError:
+ errors.add('numCodes', self.GenericErrors.InvalidNumber)
+ else:
+ if numCodes <= 0:
+ errors.add('numCodes', 'Number must be 1 or greater')
+ if numCodes > self.MaxLotSize:
+ errors.add('numCodes', 'Number cannot be larger than %s' % self.MaxLotSize)
+ if values.numCodes != values.numCodes2:
+ errors.add('numCodes2', self.GenericErrors.FieldsMustMatch)
+ else:
+ numCodes = 1
+ manualCodeStr = values.manualCode
+ # manual codes can only contain ManualCharacters and must contain at least one
+ # ManualOnlyCharacter
+ foundManualOnlyChar = False
+ foundInvalidChar = False
+ for char in values.manualCode:
+ char = char.upper()
+ if not TTCodeDict.isValidManualChar(char):
+ foundInvalidChar = True
+ errors.add('manualCode', self.CodeErrors.InvalidCharInCode)
+ if TTCodeDict.isManualOnlyChar(char):
+ foundManualOnlyChar = True
+ # only show this error if all chars are valid (too confusing otherwise)
+ if (not foundInvalidChar) and (not foundManualOnlyChar):
+ errors.add('manualCode', self.CodeErrors.MustContainManualChar)
+ # check if the code is too long
+ if len(values.manualCode) > TTCodeRedemptionConsts.MaxCustomCodeLen:
+ errors.add('manualCode', self.CodeErrors.CodeTooLong)
+ # check if the code already exists
+ if (not foundInvalidChar) and (foundManualOnlyChar):
+ if self._db.codeExists(values.manualCode):
+ errors.add('manualCode', self.CodeErrors.CodeAlreadyExists)
+ if values.manualCode != values.manualCode2:
+ errors.add('manualCode2', self.GenericErrors.FieldsMustMatch)
+
+ expDate = None
+ if values.hasExpiration == 'yes':
+ try:
+ expDate = datetime.date(int(values.expYear), int(values.expMonth), int(values.expDay))
+ except ValueError, e:
+ errors.add('expiration', str(e).capitalize())
+
+ # disable this check until we have 'active' flag or activation date
+ """
+ if expDate is not None:
+ if expDate < datetime.date.today():
+ errors.add('expiration', 'Expiration date must be in the future')
+ """
+
+ self._doRecaptcha(replyTo, values, errors)
+
+ if not errors.isEmpty():
+ self._doCreateForm(body, body, replyTo, values, errors)
+ else:
+ self._startCreateLotTask(replyTo, page, body, values,
+ manualCode, numCodes, manualCodeStr, expDate)
+ replyNow = False
+
+ elif op == self.Ops.view:
+ self._doViewForm(body, replyTo)
+
+ elif op == self.Ops.doView:
+ lotName = kw['lotName']
+ filter = kw['filter']
+ showFields = kw['showFields']
+
+ justCode = (showFields != 'all')
+
+ self._doViewLot(lotName, body, justCode, filter)
+
+ elif op == self.Ops.modify:
+ self._doModifyForm(body, replyTo)
+
+ elif op == self.Ops.doModify:
+ values = ScratchPad(
+ modification = uhs(kw['modification']),
+ recaptchaChallenge = uhs(kw['recaptcha_challenge_field']),
+ recaptchaResponse = uhs(kw['recaptcha_response_field']),
+ )
+ if 'lotName' in kw:
+ values.add(
+ lotName = kw['lotName'],
+ )
+ if values.modification == 'expiration':
+ values.add(
+ expYear = kw['expYear'],
+ expMonth = kw['expMonth'],
+ expDay = kw['expDay'],
+ )
+
+ errors = FormErrors()
+
+ if 'lotName' not in values:
+ errors.add('lotName', 'Invalid lot')
+
+ self._doRecaptcha(replyTo, values, errors)
+
+ if not errors.isEmpty():
+ self._doModifyForm(body, replyTo, values, errors)
+ else:
+ self._doModifyLot(body, replyTo, page, values)
+
+ elif op == self.Ops.delete:
+ self._doDeleteForm(body, replyTo)
+
+ elif op == self.Ops.doDelete:
+ values = ScratchPad(
+ lotName = kw['lotName'],
+ lotName2 = kw['lotName2'],
+ recaptchaChallenge = uhs(kw['recaptcha_challenge_field']),
+ recaptchaResponse = uhs(kw['recaptcha_response_field']),
+ )
+ errors = FormErrors()
+
+ if values.lotName != values.lotName2:
+ errors.add('lotName2', self.GenericErrors.FieldsMustMatch)
+
+ self._doRecaptcha(replyTo, values, errors)
+
+ if not errors.isEmpty():
+ self._doDeleteForm(body, replyTo, values, errors)
+ else:
+ self._doDelete(body, replyTo, page, values)
+
+ elif op == self.Ops.lookup:
+ self._doLookupForm(body, replyTo)
+
+ elif op == self.Ops.doLookup:
+ values = ScratchPad(
+ mode = uhs(kw['mode']),
+ )
+ avIdMode = (values.mode == 'AvId')
+ if avIdMode:
+ values.add(avId = uhs(kw['avId']))
+ else:
+ values.add(code = uhs(kw['code']))
+ values.code = unicode(values.code, 'utf-8')
+
+ errors = FormErrors()
+ if avIdMode:
+ self._errorCheckAvId(errors, values.avId)
+ else:
+ self._errorCheckCode(errors, values.code)
+ if not self._db.codeExists(values.code):
+ errors.add('code', self.CodeErrors.InvalidCode)
+
+ if not errors.isEmpty():
+ self._doLookupForm(body, replyTo, values, errors)
+ else:
+ if avIdMode:
+ self._doLookup(body, avId=values.avId)
+ else:
+ self._doLookup(body, code=values.code)
+
+ elif op == self.Ops.redeem:
+ self._doRedeemForm(body, replyTo)
+
+ elif op == self.Ops.doRedeem:
+ values = ScratchPad(
+ code = uhs(kw['code']),
+ avId = uhs(kw['avId']),
+ recaptchaChallenge = uhs(kw['recaptcha_challenge_field']),
+ recaptchaResponse = uhs(kw['recaptcha_response_field']),
+ )
+ values.code = unicode(values.code, 'utf-8')
+
+ errors = FormErrors()
+ self._errorCheckCode(errors, values.code)
+ self._errorCheckAvId(errors, values.avId)
+
+ self._doRecaptcha(replyTo, values, errors)
+
+ if not errors.isEmpty():
+ self._doRedeemForm(body, replyTo, values, errors)
+ else:
+ avId = int(values.avId)
+ context = self._redeemContextGen.next()
+ self._redeemContext2session[context] = ScratchPad(
+ result = None,
+ avId = avId,
+ values = values,
+ errors = errors,
+ )
+ result = self.redeemCode(values.code, avId, Functor(
+ self._handleRedeemResult, context, page, body, replyTo, ))
+ if result is None:
+ replyNow = False
+ else:
+ error = {
+ TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist: self.CodeErrors.InvalidCode,
+ TTCodeRedemptionConsts.RedeemErrors.CodeIsExpired: self.RedeemErrors.CodeIsExpired,
+ TTCodeRedemptionConsts.RedeemErrors.CodeAlreadyRedeemed: self.RedeemErrors.CodeAlreadyRedeemed,
+ TTCodeRedemptionConsts.RedeemErrors.AwardCouldntBeGiven: self.RedeemErrors.AwardCouldntBeGiven,
+ }[result]
+ errors.add('code', error)
+ self._doRedeemForm(body, replyTo, values, errors)
+
+ if replyNow:
+ self._reply(page, replyTo)
+
+ except TTCodeRedemptionDB.TryAgainLater, e:
+ self._warnTryAgainLater(e)
+ body.clear()
+ self._doSystemUnavailablePage(body)
+ self._reply(page, replyTo)
+
+ def _handleRedeemResult(self, context, page, body, replyTo, result, awardMgrResult):
+ assert self.notify.debugCall()
+ session = self._redeemContext2session.pop(context)
+ session.result = result
+ session.awardMgrResult = awardMgrResult
+ self._doRedeemResult(body, replyTo, session.avId, session.result, session.awardMgrResult,
+ session.values, session.errors)
+ self._reply(page, replyTo)
+
+ def redeemCodeAiToUd(self, serial, rmDoId, context, code, senderId, callback=None):
+ assert self.notify.debugCall()
+ avId = senderId
+
+ # context is supplied by the client and there are no invalid values for it
+ # code comes from the client and could be any string
+
+ try:
+ result = None
+
+ if self.Disabled:
+ result = TTCodeRedemptionConsts.RedeemErrors.SystemUnavailable
+ else:
+ while 1:
+ try:
+ code = unicode(code, 'utf-8')
+ except UnicodeDecodeError, e:
+ # code is not utf-8-able
+ self.air.writeServerEvent('suspicious', avId, 'non-utf-8 code redemption: %s' % repr(code))
+ result = TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist
+ break
+
+ if self._codeHasInvalidChars(code):
+ # code has non-letter/digit/dash characters
+ result = TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist
+ break
+
+ break
+
+ if (result or (not self._db.codeExists(code))):
+ # check to make sure this avatar isn't submitting incorrect codes too often
+ self._spamDetector.codeSubmitted(senderId)
+
+ if self._wantSpamDetect and self._spamDetector.avIsBlocked(senderId):
+ self.air.writeServerEvent('suspicious', avId,
+ 'too many invalid code redemption attempts, '
+ 'submission rejected: %s' % u2ascii(code))
+ result = TTCodeRedemptionConsts.RedeemErrors.TooManyAttempts
+
+ if result is not None:
+ awardMgrResult = 0
+ self._handleRedeemCodeAiToUdResult(callback, serial, rmDoId, context, avId, result, awardMgrResult)
+ else:
+ """
+ 'code' came from a client and therefore should be considered to be any potential string
+ (apart from any checks that have already been done), in particular strings intended
+ to cause trouble
+ """
+ self._db.redeemCode(code, avId, self, Functor(
+ self._handleRedeemCodeAiToUdResult, callback, serial, rmDoId, context, avId, ))
+
+ except TTCodeRedemptionDB.TryAgainLater, e:
+ self._warnTryAgainLater(e)
+
+ def _handleRedeemCodeAiToUdResult(self, callback, serial, rmDoId, context, avId, result, awardMgrResult):
+ assert self.notify.debugCall()
+ if callback:
+ callback(serial, context, avId, result, awardMgrResult)
+ else:
+ self.air.sendUpdateToDoId('TTCodeRedemptionMgr',
+ 'redeemCodeResultUdToAi',
+ rmDoId,
+ [serial, context, avId, result, awardMgrResult]
+ )
+
+ def redeemCode(self, code, avId, callback):
+ assert self.notify.debugCall()
+ # callback takes TTCodeRedemptionConsts.RedeemErrors value
+ return self._db.redeemCode(code, avId, self, callback)
+
+ def _giveReward(self, avId, rewardType, rewardItemId, callback):
+ assert self.notify.debugCall()
+ # callback takes result
+ context = self._rewardSerialNumGen.next()
+ self._rewardContextTable[context] = callback
+ self.air.dispatchUpdateToGlobalDoId(
+ "AwardManagerUD", "giveAwardToToon",
+ OtpDoGlobals.OTP_DO_ID_TOONTOWN_AWARD_MANAGER,
+ [context, self.doId, "TTCodeRedemptionMgrUD", avId, rewardType, rewardItemId, ])
+
+ def giveAwardToToonResult(self, context, result):
+ assert self.notify.debugCall()
+ callback = self._rewardContextTable.pop(context)
+ try:
+ callback(result)
+ except TTCodeRedemptionDB.TryAgainLater, e:
+ self._warnTryAgainLater(e)
+
+ def _warnTryAgainLater(self, exception):
+ # if we catch a TryAgainLater, drop this code submission on the floor. The AI
+ # will resubmit the code shortly
+ self.notify.warning('%s' % exception)
+ self.notify.warning(
+ 'caught TryAgainLater exception from TTCodeRedemptionDB. Dropping request')
diff --git a/toontown/src/coderedemption/TTCodeRedemptionSpamDetector.py b/toontown/src/coderedemption/TTCodeRedemptionSpamDetector.py
new file mode 100644
index 0000000..bb1ad4d
--- /dev/null
+++ b/toontown/src/coderedemption/TTCodeRedemptionSpamDetector.py
@@ -0,0 +1,177 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.showbase.DirectObject import DirectObject
+from direct.showbase.PythonUtil import formatTimeExact
+
+Settings = ScratchPad(
+ DetectWindow = config.GetFloat('code-redemption-spam-detect-window', 30.), # minutes
+ DetectThreshold = config.GetInt('code-redemption-spam-detect-threshold', 10),
+ FirstPenalty = config.GetFloat('code-redemption-spam-first-penalty', .5), # minutes
+ PenaltyMultiplier = config.GetFloat('code-redemption-spam-penalty-multiplier', 2.),
+ MaxPenaltyDays = config.GetFloat('code-redemption-spam-max-penalty-days', 2.),
+ PenaltyResetDays = config.GetFloat('code-redemption-penalty-reset-days', 7.),
+ )
+
+class TTCodeRedemptionSpamDetector:
+ notify = directNotify.newCategory('TTCodeRedemptionSpamDetector')
+
+ def __init__(self):
+ self._avId2tracker = {}
+ if __dev__:
+ #self._tester = TTCRSDTester(self)
+ pass
+ self._cullTask = taskMgr.doMethodLater(10 * 60, self._cullTrackers, uniqueName('cullCodeSpamTrackers'))
+
+ def destroy(self):
+ if __dev__:
+ #self._tester.destroy()
+ self._tester = None
+
+ def codeSubmitted(self, avId):
+ if avId not in self._avId2tracker:
+ self._avId2tracker[avId] = TTCRSDTracker(avId)
+ self._avId2tracker[avId].codeSubmitted()
+
+ def avIsBlocked(self, avId):
+ tracker = self._avId2tracker.get(avId)
+ if tracker:
+ return tracker.avIsBlocked()
+ return False
+
+ def _cullTrackers(self, task=None):
+ # remove records for avIds that have gone long enough without spamming
+ avIds = self._avId2tracker.keys()
+ for avId in avIds:
+ tracker = self._avId2tracker.get(avId)
+ if tracker.isExpired():
+ self.notify.debug('culling code redemption spam tracker for %s' % avId)
+ self._avId2tracker.pop(avId)
+ return task.again
+
+class TTCRSDTracker:
+ notify = directNotify.newCategory('TTCodeRedemptionSpamDetector')
+
+ def __init__(self, avId):
+ self._avId = avId
+ self._timestamps = []
+ self._lastTimestamp = None
+ self._penaltyDuration = 0
+ self._penaltyUntil = 0
+
+ def codeSubmitted(self):
+ now = globalClock.getRealTime()
+ self.notify.debug('codeSubmitted by %s @ %s' % (self._avId, now))
+ if self._penaltyActive():
+ return
+ self._timestamps.append(now)
+ self._lastTimestamp = now
+ self.update()
+
+ def isExpired(self):
+ if self._lastTimestamp is None:
+ return True
+ now = globalClock.getRealTime()
+ # if they've gone for X days without spamming, we can wipe that toon's record
+ amnestyDelay = Settings.PenaltyResetDays * 24 * 60 * 60
+ return now > (self._lastTimestamp + amnestyDelay)
+
+ def update(self):
+ self._trimTimestamps()
+ if (not self._penaltyActive()) and self._overThreshold():
+ if self._penaltyDuration == 0:
+ self._penaltyDuration = Settings.FirstPenalty * 60 # seconds/min
+ else:
+ self._penaltyDuration = self._penaltyDuration * Settings.PenaltyMultiplier
+ MaxPenaltySecs = Settings.MaxPenaltyDays * 24 * 60 * 60
+ if self._penaltyDuration > MaxPenaltySecs:
+ self._penaltyDuration = MaxPenaltySecs
+ self._penaltyUntil = globalClock.getRealTime() + self._penaltyDuration
+ self._timestamps = self._timestamps[Settings.DetectThreshold:]
+ durationStr = formatTimeExact(self._penaltyDuration)
+ self.notify.info('time penalty for %s: %s' % (self._avId, durationStr))
+
+ def avIsBlocked(self):
+ self.update()
+ return self._penaltyActive()
+
+ def _trimTimestamps(self):
+ now = globalClock.getRealTime()
+ cutoff = now - (Settings.DetectWindow * 60) # seconds/min
+ while len(self._timestamps):
+ if self._timestamps[0] < cutoff:
+ self._timestamps = self._timestamps[1:]
+ else:
+ break
+
+ def _penaltyActive(self):
+ return globalClock.getRealTime() < self._penaltyUntil
+
+ def _overThreshold(self):
+ return len(self._timestamps) > Settings.DetectThreshold
+
+if __dev__:
+ class TTCRSDTester(DirectObject):
+ notify = directNotify.newCategory('TTCodeRedemptionSpamDetector')
+
+ def __init__(self, detector):
+ self._detector = detector
+ self._idGen = SerialNumGen()
+ self.notify.info('starting tests...')
+ self._thresholdTest()
+ self._timeoutTest()
+
+ def destroy(self):
+ self._detector = None
+
+ def _thresholdTest(self):
+ avId = self._idGen.next()
+ for i in xrange(Settings.DetectThreshold+1):
+ self._detector.codeSubmitted(avId)
+ if i < Settings.DetectThreshold:
+ assert not self._detector.avIsBlocked(avId)
+ else:
+ assert self._detector.avIsBlocked(avId)
+ self.notify.info('threshold test passed.')
+
+ def _timeoutTest(self):
+ avId = self._idGen.next()
+ for i in xrange(Settings.DetectThreshold+1):
+ self._detector.codeSubmitted(avId)
+ assert self._detector.avIsBlocked(avId)
+ self._timeoutTestStartT = globalClock.getRealTime()
+ penaltyDuration = Settings.FirstPenalty * 60
+ self._timeoutTestEventT = penaltyDuration
+ self.doMethodLater(Settings.FirstPenalty * 60 * .5, Functor(self._timeoutEarlyTest, avId),
+ uniqueName('timeoutEarlyTest'))
+ self.doMethodLater(Settings.FirstPenalty * 60 * 10, Functor(self._timeoutLateTest, avId),
+ uniqueName('timeoutLateTest'))
+
+ def _timeoutEarlyTest(self, avId, task=None):
+ # only do this test if we didn't chug
+ if (globalClock.getRealTime() - self._timeoutTestStartT) < (self._timeoutTestEventT * .9):
+ assert self._detector.avIsBlocked(avId)
+ return task.done
+
+ def _timeoutLateTest(self, avId, task=None):
+ assert not self._detector.avIsBlocked(avId)
+ for i in xrange(Settings.DetectThreshold+1):
+ self._detector.codeSubmitted(avId)
+ assert self._detector.avIsBlocked(avId)
+ self._timeoutLateTestStartT = globalClock.getRealTime()
+ penaltyDuration = Settings.PenaltyMultiplier * Settings.FirstPenalty * 60
+ self._timeoutLateTestEventT = penaltyDuration
+ self.doMethodLater(penaltyDuration * .5, Functor(self._timeoutSecondEarlyTest, avId),
+ uniqueName('timeoutSecondEarlyTest'))
+ self.doMethodLater(penaltyDuration * 1.5, Functor(self._timeoutSecondLateTest, avId),
+ uniqueName('timeoutSecondLateTest'))
+ return task.done
+
+ def _timeoutSecondEarlyTest(self, avId, task=None):
+ # only do this test if we didn't chug
+ if (globalClock.getRealTime() - self._timeoutLateTestStartT) < (self._timeoutLateTestEventT * .9):
+ assert self._detector.avIsBlocked(avId)
+ return task.done
+
+ def _timeoutSecondLateTest(self, avId, task=None):
+ assert not self._detector.avIsBlocked(avId)
+ self.notify.info('timeout test passed.')
+ return task.done
diff --git a/toontown/src/coderedemption/__init__.py b/toontown/src/coderedemption/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/cogdominium/CogdoCraneGameBase.py b/toontown/src/cogdominium/CogdoCraneGameBase.py
new file mode 100644
index 0000000..58f46d1
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoCraneGameBase.py
@@ -0,0 +1,35 @@
+from otp.level.LevelSpec import LevelSpec
+from toontown.cogdominium import CogdoCraneGameSpec
+from toontown.cogdominium import CogdoCraneGameConsts as Consts
+from direct.fsm.StatePush import FunctionCall
+
+class CogdoCraneGameBase:
+ def startHandleEdits(self):
+ if __dev__:
+ fcs = []
+ # each attribute in the game settings entity can have a handler, e.g.
+ # def _handleGameDurationChanged(self, gameDuration): ...
+ for attribName in Consts.Settings._getAttributeNames():
+ handler = getattr(self, '_handle%sChanged' % attribName, None)
+ if handler:
+ stateVar = getattr(Consts.Settings, attribName)
+ fcs.append(FunctionCall(handler, stateVar))
+ self._functionCalls = fcs
+
+ def stopHandleEdits(self):
+ if __dev__:
+ for fc in self._functionCalls:
+ fc.destroy()
+ self._functionCalls = None
+
+ def getLevelSpec(self):
+ return LevelSpec(CogdoCraneGameSpec)
+
+ if __dev__:
+ def getEntityTypeReg(self):
+ # return an EntityTypeRegistry with information about the
+ # entity types that the crane game uses
+ import CogdoEntityTypes
+ from otp.level import EntityTypeRegistry
+ typeReg = EntityTypeRegistry.EntityTypeRegistry(CogdoEntityTypes)
+ return typeReg
diff --git a/toontown/src/cogdominium/CogdoCraneGameConsts.py b/toontown/src/cogdominium/CogdoCraneGameConsts.py
new file mode 100644
index 0000000..31b91b6
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoCraneGameConsts.py
@@ -0,0 +1,13 @@
+from direct.fsm.StatePush import StateVar
+from otp.level.EntityStateVarSet import EntityStateVarSet
+from toontown.cogdominium.CogdoEntityTypes import CogdoCraneGameSettings
+
+# constants that can be modified by the IGE (~edit)
+Settings = EntityStateVarSet(CogdoCraneGameSettings)
+
+CranePosHprs = [
+ (13.4, -136.6, 6, -45, 0, 0),
+ (13.4, -91.4, 6, -135, 0, 0),
+ (58.6, -91.4, 6, 135, 0, 0),
+ (58.6, -136.6, 6, 45, 0, 0),
+ ]
diff --git a/toontown/src/cogdominium/CogdoCraneGameSpec.py b/toontown/src/cogdominium/CogdoCraneGameSpec.py
new file mode 100644
index 0000000..416c816
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoCraneGameSpec.py
@@ -0,0 +1,50 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'modelFilename': 'phase_10/models/cogHQ/EndVault.bam',
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # COGDOCRANEGAMESETTINGS
+ 10000: {
+ 'type': 'cogdoCraneGameSettings',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'GameDuration': 180.0,
+ }, # end entity 10000
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/cogdominium/CogdoEntityCreator.py b/toontown/src/cogdominium/CogdoEntityCreator.py
new file mode 100644
index 0000000..02e8b05
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoEntityCreator.py
@@ -0,0 +1,22 @@
+from otp.level import EntityCreator
+from toontown.cogdominium import CogdoCraneGameConsts
+from toontown.cogdominium.CogdoLevelMgr import CogdoLevelMgr
+from toontown.cogdominium import CogdoCraneGameConsts
+
+class CogdoEntityCreator(EntityCreator.EntityCreator):
+ def __init__(self, level):
+ EntityCreator.EntityCreator.__init__(self, level)
+
+ # create short aliases for EntityCreator create funcs
+ nothing = EntityCreator.nothing
+ nonlocal = EntityCreator.nonlocal
+
+ self.privRegisterTypes({
+ 'levelMgr': CogdoLevelMgr,
+ 'cogdoCraneGameSettings': self._createCogdoSettings,
+ })
+
+ def _createCogdoSettings(self, level, entId):
+ CogdoCraneGameConsts.Settings.initializeEntity(level, entId)
+ return CogdoCraneGameConsts.Settings
+
diff --git a/toontown/src/cogdominium/CogdoEntityCreatorAI.py b/toontown/src/cogdominium/CogdoEntityCreatorAI.py
new file mode 100644
index 0000000..e646f3e
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoEntityCreatorAI.py
@@ -0,0 +1,22 @@
+from direct.showbase.PythonUtil import Functor
+from otp.level import EntityCreatorAI
+from toontown.cogdominium.CogdoLevelMgrAI import CogdoLevelMgrAI
+from toontown.cogdominium import CogdoCraneGameConsts
+
+class CogdoEntityCreatorAI(EntityCreatorAI.EntityCreatorAI):
+ def __init__(self, level):
+ EntityCreatorAI.EntityCreatorAI.__init__(self, level)
+
+ # create short aliases for EntityCreatorAI create funcs
+ cDE = EntityCreatorAI.createDistributedEntity
+ cLE = EntityCreatorAI.createLocalEntity
+ nothing = EntityCreatorAI.nothing
+
+ self.privRegisterTypes({
+ 'levelMgr': Functor(cLE, CogdoLevelMgrAI),
+ 'cogdoCraneGameSettings': Functor(cLE, self._createCogdoSettings),
+ })
+
+ def _createCogdoSettings(self, level, entId):
+ CogdoCraneGameConsts.Settings.initializeEntity(level, entId)
+ return CogdoCraneGameConsts.Settings
diff --git a/toontown/src/cogdominium/CogdoEntityTypes.py b/toontown/src/cogdominium/CogdoEntityTypes.py
new file mode 100644
index 0000000..8ebbe7a
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoEntityTypes.py
@@ -0,0 +1,10 @@
+from otp.level.EntityTypes import *
+
+class CogdoLevelMgr(LevelMgr):
+ type = 'levelMgr'
+
+class CogdoCraneGameSettings(Entity):
+ type = 'cogdoCraneGameSettings'
+ attribs = (
+ ('GameDuration', 180., 'float'),
+ )
diff --git a/toontown/src/cogdominium/CogdoFlyingGameGlobals.py b/toontown/src/cogdominium/CogdoFlyingGameGlobals.py
new file mode 100644
index 0000000..f847873
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoFlyingGameGlobals.py
@@ -0,0 +1,40 @@
+"""RingGameGlobals: contains values shared by server and client ring games"""
+
+from toontown.toonbase import TTLocalizer
+from toontown.toonbase import ToontownGlobals
+
+class VariableContainer:
+ def __init__(self):
+ pass
+
+FlyingGame = VariableContainer()
+
+#The effect how fast you get up to max speed
+FlyingGame.TOON_ACCELERATION = {
+ "forward" : 20.0,
+ "backward": 20.0,
+ "turning" : 50.0,
+ "vertical" : 10.0
+}
+# This effects how quickly the toon movement is dampened
+FlyingGame.TOON_DECELERATION = {
+ "forward" : 30.0,
+ "backward": 30.0,
+ "turning" : 40.0,
+ "vertical" : 10.0
+}
+# This effects the max velocity in each direction
+FlyingGame.TOON_VEL_MAX = {
+ "forward" : 20.0,
+ "backward": 10.0,
+ "turning" : 8.0,
+ "vertical" : 8.0
+}
+
+FlyingGame.DISABLE_DEATH = False
+FlyingGame.MULTIPLE_REFUELS_PER_STATION = False
+# This sets the player's fuel to max and then doesn't deplete it
+FlyingGame.INFINITE_FUEL = False
+
+FlyingGame.FUEL_BURN_RATE = 0.02
+FlyingGame.FUEL_START_AMT = 1.0
diff --git a/toontown/src/cogdominium/CogdoInterior.py b/toontown/src/cogdominium/CogdoInterior.py
new file mode 100644
index 0000000..4504fee
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoInterior.py
@@ -0,0 +1,385 @@
+from pandac.PandaModules import *
+from toontown.toonbase.ToonBaseGlobal import *
+
+from direct.directnotify import DirectNotifyGlobal
+from toontown.hood import Place
+from direct.showbase import DirectObject
+from direct.fsm import StateData
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.town import TownBattle
+from toontown.suit import Suit
+from toontown.building import Elevator
+from direct.task.Task import Task
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import ToontownBattleGlobals
+
+class CogdoInterior(Place.Place):
+ """CogdoInterior class"""
+
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("CogdoInterior")
+
+ # special methods
+
+ def __init__(self, loader, parentFSM, doneEvent):
+ """
+ CogdoInterior constructor: create a play game ClassicFSM
+ """
+ Place.Place.__init__(self, loader, doneEvent)
+
+ self.fsm = ClassicFSM.ClassicFSM('CogdoInterior',
+ [State.State('entrance',
+ self.enterEntrance,
+ self.exitEntrance,
+ ['Game', 'walk']),
+ State.State('Elevator',
+ self.enterElevator,
+ self.exitElevator,
+ ['Game', 'battle', 'walk', 'crane', ]),
+ State.State('Game',
+ self.enterGame,
+ self.exitGame,
+ ['battle', 'died', 'crane', ]),
+ State.State('battle',
+ self.enterBattle,
+ self.exitBattle,
+ ['walk', 'died']),
+ State.State('crane',
+ self.enterCrane,
+ self.exitCrane,
+ ['walk', 'battle', 'finalBattle',
+ 'died', 'ouch', 'squished']),
+ State.State('walk',
+ self.enterWalk,
+ self.exitWalk,
+ ['stickerBook', 'stopped',
+ 'sit', 'died',
+ 'teleportOut',
+ 'Elevator',
+ 'crane',
+ 'DFA', 'trialerFA',]),
+ State.State('sit',
+ self.enterSit,
+ self.exitSit,
+ ['walk',]),
+ State.State('stickerBook',
+ self.enterStickerBook,
+ self.exitStickerBook,
+ ['walk', 'stopped', 'sit', 'died',
+ 'DFA', 'trialerFA',
+ 'teleportOut', 'Elevator',]),
+ # Trialer Force Acknowledge:
+ State.State('trialerFA',
+ self.enterTrialerFA,
+ self.exitTrialerFA,
+ ['trialerFAReject', 'DFA']),
+ State.State('trialerFAReject',
+ self.enterTrialerFAReject,
+ self.exitTrialerFAReject,
+ ['walk']),
+ State.State('DFA',
+ self.enterDFA,
+ self.exitDFA,
+ ['DFAReject', 'teleportOut']),
+ State.State('DFAReject',
+ self.enterDFAReject,
+ self.exitDFAReject,
+ ['walk']),
+ State.State('teleportIn',
+ self.enterTeleportIn,
+ self.exitTeleportIn,
+ ['walk']),
+ State.State('teleportOut',
+ self.enterTeleportOut,
+ self.exitTeleportOut,
+ ['teleportIn']),
+ State.State('stopped',
+ self.enterStopped,
+ self.exitStopped,
+ ['walk', 'elevatorOut']),
+ State.State('died',
+ self.enterDied,
+ self.exitDied,
+ []),
+ State.State('elevatorOut',
+ self.enterElevatorOut,
+ self.exitElevatorOut,
+ [])],
+ # Initial State
+ 'entrance',
+ # Final State
+ 'elevatorOut',
+ )
+ self.parentFSM = parentFSM
+ self.elevatorDoneEvent = "elevatorDoneSI"
+
+ # This is updated each floor by the DistributedCogdoInterior.
+ self.currentFloor = 0
+
+ def enter(self, requestStatus):
+ assert(self.notify.debug("enter(requestStatus="+str(requestStatus)+")"))
+ self.fsm.enterInitialState()
+ # Let the safe zone manager know that we are here.
+ #messenger.send("enterToonInterior")
+
+ #self.geom.reparentTo(render)
+
+ self.zoneId = requestStatus['zoneId']
+ self.accept("DSIDoneEvent", self.handleDSIDoneEvent)
+
+ def exit(self):
+ assert(self.notify.debug("exit()"))
+ self.ignoreAll()
+ # Let the safe zone manager know that we are leaving
+ #messenger.send("exitToonInterior")
+ #self.geom.reparentTo(hidden)
+
+ # Turn off the little red arrows.
+ #NametagGlobals.setMasterArrowsOn(0)
+
+ def load(self):
+ assert(self.notify.debug("load()"))
+ # Call up the chain
+ Place.Place.load(self)
+ self.parentFSM.getStateNamed("cogdoInterior").addChild(self.fsm)
+ self.townBattle = TownBattle.TownBattle('town-battle-done')
+ self.townBattle.load()
+ for i in range(1, 3):
+ Suit.loadSuits(i)
+
+ def unload(self):
+ assert(self.notify.debug("unload()"))
+ # Call up the chain
+ Place.Place.unload(self)
+
+ self.parentFSM.getStateNamed("cogdoInterior").removeChild(self.fsm)
+ del self.parentFSM
+ del self.fsm
+ #self.geom.removeNode()
+ #del self.geom
+ self.ignoreAll()
+ # Get rid of any references to models or textures from this safe zone
+ ModelPool.garbageCollect()
+ TexturePool.garbageCollect()
+ self.townBattle.unload()
+ self.townBattle.cleanup()
+ del self.townBattle
+
+ for i in range(1, 3):
+ Suit.unloadSuits(i)
+
+ def setState(self, state, battleEvent=None):
+ assert(self.notify.debug("setState(state="+str(state)
+ +", battleEvent="+str(battleEvent)+")"))
+ if (battleEvent):
+ self.fsm.request(state, [battleEvent])
+ else:
+ self.fsm.request(state)
+
+ def getZoneId(self):
+ """
+ Returns the current zone ID.
+ """
+ return self.zoneId
+
+ def enterZone(self, zoneId):
+ assert(self.notify.debug('enterZone() - %d' % zoneId))
+ pass
+
+ def isPeriodTimerEffective(self):
+ """
+ Returns true if the period timer will be honored if it expires
+ in this kind of Place (and we're also in a suitable mode).
+ Generally, CogdoInterior returns false, and other kinds of
+ Place return true.
+ """
+ return 0
+
+ def handleDSIDoneEvent(self, requestStatus):
+ self.doneStatus = requestStatus
+ messenger.send(self.doneEvent)
+
+ def doRequestLeave(self, requestStatus):
+ # when it's time to leave, check their trialer status first
+ self.fsm.request('trialerFA', [requestStatus])
+
+ # Elevator state
+
+ def enterEntrance(self):
+ return
+
+ def exitEntrance(self):
+ return
+
+ def enterElevator(self, distElevator):
+ assert(self.notify.debug('enterElevator()'))
+ self.accept(self.elevatorDoneEvent, self.handleElevatorDone)
+ self.elevator = Elevator.Elevator(self.fsm.getStateNamed("Elevator"),
+ self.elevatorDoneEvent,
+ distElevator)
+ self.elevator.load()
+ self.elevator.enter()
+ # Disable leave to pay / set parent password
+ base.localAvatar.cantLeaveGame = 1
+ return
+
+ def exitElevator(self):
+ base.localAvatar.cantLeaveGame = 0
+ self.ignore(self.elevatorDoneEvent)
+ self.elevator.unload()
+ self.elevator.exit()
+ del self.elevator
+ return None
+
+ def detectedElevatorCollision(self, distElevator):
+ assert(self.notify.debug("detectedElevatorCollision()"))
+ self.fsm.request("Elevator", [distElevator])
+ return None
+
+ def handleElevatorDone(self, doneStatus):
+ assert(self.notify.debug("handleElevatorDone()"))
+ self.notify.debug("handling elevator done event")
+ where = doneStatus['where']
+ if (where == 'reject'):
+ # If there has been a reject the Elevator should show an
+ # elevatorNotifier message and put the toon in the stopped state.
+ # Don't request the walk state here. Let the the toon be stuck in the
+ # stopped state till the player removes that message from his screen.
+ # Removing the message will automatically put him in the walk state there.
+ # Put the player in the walk state only if there is no elevator message.
+ if hasattr(base.localAvatar, "elevatorNotifier") and base.localAvatar.elevatorNotifier.isNotifierOpen():
+ pass
+ else:
+ self.fsm.request("walk")
+ elif (where == 'exit'):
+ self.fsm.request("walk")
+ elif (where == 'cogdoInterior'):
+ pass
+ else:
+ self.notify.error("Unknown mode: " + where +
+ " in handleElevatorDone")
+
+ # Game state
+
+ def enterGame(self):
+ pass
+ def exitGame(self):
+ pass
+
+ # Battle state
+
+ def enterBattle(self, event):
+ assert(self.notify.debug("enterBattle()"))
+
+ # Get the floor multiplier
+ mult = ToontownBattleGlobals.getCreditMultiplier(self.currentFloor)
+ self.townBattle.enter(event, self.fsm.getStateNamed("battle"),
+ bldg=1, creditMultiplier=mult)
+
+ # Make sure the toon's anim state gets reset
+ base.localAvatar.b_setAnimState('off', 1)
+
+ # Disable leave to pay / set parent password
+ base.localAvatar.cantLeaveGame = 1
+
+ def exitBattle(self):
+ assert(self.notify.debug("exitBattle()"))
+ self.townBattle.exit()
+ base.localAvatar.cantLeaveGame = 0
+
+ def enterCrane(self):
+ assert(self.notify.debug("enterCrane()"))
+ base.localAvatar.setTeleportAvailable(0)
+ base.localAvatar.laffMeter.start()
+ base.localAvatar.collisionsOn()
+
+ def exitCrane(self):
+ assert(self.notify.debug("exitCrane()"))
+ base.localAvatar.collisionsOff()
+ base.localAvatar.laffMeter.stop()
+
+ # walk state inherited from Place.py
+ def enterWalk(self, teleportIn=0):
+ Place.Place.enterWalk(self, teleportIn)
+ self.ignore('teleportQuery')
+ base.localAvatar.setTeleportAvailable(0)
+
+ # sticker book state inherited from Place.py
+ def enterStickerBook(self, page = None):
+ Place.Place.enterStickerBook(self, page)
+ self.ignore('teleportQuery')
+ base.localAvatar.setTeleportAvailable(0)
+
+ # sit state inherited from Place.py
+ def enterSit(self):
+ Place.Place.enterSit(self)
+ self.ignore('teleportQuery')
+ base.localAvatar.setTeleportAvailable(0)
+
+ # teleport in state
+
+ def enterTeleportIn(self, requestStatus):
+ # We can only teleport in if our goHome or teleport to toon
+ # request failed.
+ # Set localToon to the starting position within the
+ # interior
+ base.localAvatar.setPosHpr(2.5, 11.5, ToontownGlobals.FloorOffset,
+ 45.0, 0.0, 0.0)
+
+ Place.Place.enterTeleportIn(self, requestStatus)
+
+ # teleport out state
+
+ def enterTeleportOut(self, requestStatus):
+ assert(self.notify.debug('enterTeleportOut()'))
+ Place.Place.enterTeleportOut(self, requestStatus,
+ self.__teleportOutDone)
+
+ def __teleportOutDone(self, requestStatus):
+ # Get out of here.
+ hoodId = requestStatus["hoodId"]
+ if hoodId == ToontownGlobals.MyEstate:
+ # We are trying to go to an estate. This request might fail
+ # if we are going to a toon's estate that we are not friends with.
+ # So we don't want to tell the AI that we are leaving right away.
+ # We will rely on the Place.Place.goHome function to do that if
+ # the teleport to estate request is successful.
+ self.getEstateZoneAndGoHome(requestStatus)
+ else:
+ # Let the DSI know that we are leaving.
+ messenger.send("localToonLeft")
+ self.doneStatus = requestStatus
+ messenger.send(self.doneEvent)
+
+ def exitTeleportOut(self):
+ Place.Place.exitTeleportOut(self)
+
+ def goHomeFailed(self, task):
+ # it took too long to hear back from the server,
+ # or we tried going to a non-friends house
+ self.notifyUserGoHomeFailed()
+ # ignore the setLocalEstateZone message
+ self.ignore("setLocalEstateZone")
+ self.doneStatus["avId"] = -1
+ self.doneStatus["zoneId"] = self.getZoneId()
+ self.fsm.request("teleportIn", [self.doneStatus])
+ return Task.done
+
+
+ # elevatorOut state
+
+ def enterElevatorOut(self):
+ assert(self.notify.debug('enterElevatorOut()'))
+ # TODO: Eventually, we will have a sequence here (like iris out)
+ # and when it is done, it should find a way to call __elevatorOutDone.
+ # for now, we'll just call it directly.
+ #self.__elevatorOutDone(
+ return None
+
+ def __elevatorOutDone(self, requestStatus):
+ self.doneStatus = requestStatus
+ messenger.send(self.doneEvent)
+
+ def exitElevatorOut(self):
+ return None
diff --git a/toontown/src/cogdominium/CogdoLayout.py b/toontown/src/cogdominium/CogdoLayout.py
new file mode 100644
index 0000000..500e928
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoLayout.py
@@ -0,0 +1,24 @@
+from direct.directnotify import DirectNotifyGlobal
+
+class CogdoLayout:
+ notify = DirectNotifyGlobal.directNotify.newCategory('CogdoLayout')
+
+ def __init__(self, numFloors):
+ self._numFloors = numFloors
+
+ def getNumGameFloors(self):
+ return self._numFloors
+
+ def hasBossBattle(self):
+ return self._numFloors >= 3
+
+ def getNumFloors(self):
+ if self.hasBossBattle():
+ return self._numFloors + 1
+ else:
+ return self._numFloors
+
+ def getBossBattleFloor(self):
+ if not self.hasBossBattle():
+ self.notify.error('getBossBattleFloor(): cogdo has no boss battle')
+ return self.getNumFloors()-1
diff --git a/toontown/src/cogdominium/CogdoLevelMgr.py b/toontown/src/cogdominium/CogdoLevelMgr.py
new file mode 100644
index 0000000..17758cd
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoLevelMgr.py
@@ -0,0 +1,7 @@
+from otp.level import LevelMgr
+from direct.showbase.PythonUtil import Functor
+from toontown.toonbase import ToontownGlobals
+
+class CogdoLevelMgr(LevelMgr.LevelMgr):
+ """This class manages editable cogdo game attributes"""
+ pass
diff --git a/toontown/src/cogdominium/CogdoLevelMgrAI.py b/toontown/src/cogdominium/CogdoLevelMgrAI.py
new file mode 100644
index 0000000..0e75c84
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoLevelMgrAI.py
@@ -0,0 +1,5 @@
+from otp.level import LevelMgrAI
+
+class CogdoLevelMgrAI(LevelMgrAI.LevelMgrAI):
+ """This class manages editable cogdo attributes"""
+ pass
diff --git a/toontown/src/cogdominium/CogdoMazeGameGlobals.py b/toontown/src/cogdominium/CogdoMazeGameGlobals.py
new file mode 100644
index 0000000..ad1f9e9
--- /dev/null
+++ b/toontown/src/cogdominium/CogdoMazeGameGlobals.py
@@ -0,0 +1,69 @@
+"""
+@author: Schell Games
+3-10-2010
+"""
+import math
+
+from direct.showbase import PythonUtil
+
+from pandac.PandaModules import VBase4
+
+from toontown.minigame import MazeData
+
+TempMazeFile = "phase_4/models/minigames/maze_3player"
+TempMazeData = MazeData.mazeData[TempMazeFile]
+
+GameActions = PythonUtil.Enum((
+ "Unlock",
+ "EnterDoor",
+ "RevealLock",
+ "RevealDoor",
+ "GameOver",
+ ))
+
+GameDuration = 180.0
+
+ToonRunSpeed = 9.778
+
+OverheadCameraAngle = math.radians(60)
+OverheadCameraDistance = 30
+
+LockColors = (
+ VBase4(1, 1, 1, 1),
+ VBase4(0, 0, 1, 1),
+ VBase4(1, 1, 0, 1),
+ VBase4(1, 0, 0, 1),
+ )
+
+LockNames = (
+ "White",
+ "Blue",
+ "Yellow",
+ "Red"
+ )
+
+#===============================================================================
+# AUDIO
+#===============================================================================
+
+MusicFiles = {
+ "normal" : "phase_9/audio/bgm/CHQ_FACT_bg.mid",
+ "suspense": "phase_7/audio/bgm/encntr_general_bg_indoor.mid",
+ "timeRunningOut": "phase_7/audio/bgm/encntr_suit_winning_indoor.mid",
+ "end" : "phase_4/audio/bgm/FF_safezone.mid"
+ }
+
+SfxFiles = {
+ "doorOpen": "phase_5/audio/sfx/elevator_door_open.mp3",
+ "fusePlaced": "phase_11/audio/sfx/LB_laser_beam_on_2.mp3",
+ "notification": "phase_3.5/audio/sfx/GUI_whisper_3.mp3",
+ }
+
+# TEMP Placed here!
+
+class CogdoMazeLockInfo:
+ def __init__(self, toonId, tileX, tileY, locked=True):
+ self.toonId = toonId
+ self.locked = locked
+ self.tileX = tileX
+ self.tileY = tileY
diff --git a/toontown/src/cogdominium/DistBoardroomGame.py b/toontown/src/cogdominium/DistBoardroomGame.py
new file mode 100644
index 0000000..c6cb943
--- /dev/null
+++ b/toontown/src/cogdominium/DistBoardroomGame.py
@@ -0,0 +1,44 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from toontown.cogdominium.DistCogdoGame import DistCogdoGame
+from toontown.toonbase import ToontownTimer
+from toontown.toonbase import TTLocalizer as TTL
+
+class DistBoardroomGame(DistCogdoGame):
+ notify = directNotify.newCategory("DistBoardroomGame")
+
+ def __init__(self, cr):
+ DistCogdoGame.__init__(self, cr)
+
+ def getTitle(self):
+ return TTL.BoardroomGameTitle
+
+ def getInstructions(self):
+ return TTL.BoardroomGameInstructions
+
+ def announceGenerate(self):
+ DistCogdoGame.announceGenerate(self)
+ self.timer = ToontownTimer.ToontownTimer()
+ self.timer.stash()
+
+ def disable(self):
+ self.timer.destroy()
+ self.timer = None
+ DistCogdoGame.disable(self)
+
+ def enterGame(self):
+ DistCogdoGame.enterGame(self)
+ #self.timer.posInTopRightCorner()
+ timeLeft = 15. - (globalClock.getRealTime() - self.getStartTime())
+ self.timer.setTime(timeLeft)
+ self.timer.countdown(timeLeft, self.timerExpired)
+ self.timer.unstash()
+
+ def enterFinish(self):
+ DistCogdoGame.enterFinish(self)
+ timeLeft = 10 - (globalClock.getRealTime() - self.getFinishTime())
+ self.timer.setTime(timeLeft)
+ self.timer.countdown(timeLeft, self.timerExpired)
+ self.timer.unstash()
+
+ def timerExpired(self):
+ pass
diff --git a/toontown/src/cogdominium/DistBoardroomGameAI.py b/toontown/src/cogdominium/DistBoardroomGameAI.py
new file mode 100644
index 0000000..ef92da5
--- /dev/null
+++ b/toontown/src/cogdominium/DistBoardroomGameAI.py
@@ -0,0 +1,36 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from toontown.cogdominium.DistCogdoGameAI import DistCogdoGameAI
+
+class DistBoardroomGameAI(DistCogdoGameAI):
+ notify = directNotify.newCategory("DistBoardroomGameAI")
+
+ def __init__(self, air, interior):
+ DistCogdoGameAI.__init__(self, air, interior)
+
+ def enterGame(self):
+ DistCogdoGameAI.enterGame(self)
+ # start the game up. Or wait for a while, that's fun too
+ self._gameDoneEvent = taskMgr.doMethodLater(
+ 15., self._gameDoneDL, self.uniqueName('boardroomGameDone'))
+
+ def exitGame(self):
+ taskMgr.remove(self._gameDoneEvent)
+ self._gameDoneEvent = None
+
+ def _gameDoneDL(self, task):
+ self._handleGameFinished()
+ return task.done
+
+ def enterFinish(self):
+ DistCogdoGameAI.enterFinish(self)
+ self._finishDoneEvent = taskMgr.doMethodLater(
+ 10., self._finishDoneDL, self.uniqueName('boardroomFinishDone'))
+
+ def exitFinish(self):
+ taskMgr.remove(self._finishDoneEvent)
+ self._finishDoneEvent = None
+
+ def _finishDoneDL(self, task):
+ self.announceGameDone()
+ return task.done
+
diff --git a/toontown/src/cogdominium/DistCogdoCrane.py b/toontown/src/cogdominium/DistCogdoCrane.py
new file mode 100644
index 0000000..d3bf6e2
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoCrane.py
@@ -0,0 +1,1260 @@
+from direct.gui.DirectGui import *
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+from direct.fsm import FSM
+from direct.distributed import DistributedObject
+from direct.showutil import Rope
+from direct.showbase import PythonUtil
+from direct.task import Task
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import TTLocalizer
+from otp.otpbase import OTPGlobals
+from toontown.cogdominium import CogdoCraneGameConsts as GameConsts
+import random
+
+class DistCogdoCrane(DistributedObject.DistributedObject, FSM.FSM):
+ notify = DirectNotifyGlobal.directNotify.newCategory('DistCogdoCrane')
+
+ firstMagnetBit = 21
+
+ craneMinY = 8
+ craneMaxY = 25
+
+ armMinH = -45
+ armMaxH = 45
+
+ # How high to place the shadows. We can put these pretty high
+ # because we play that trick with the bins to make them render
+ # after other stuff.
+ shadowOffset = 7
+
+ # The properties when the magnet is unencumbered.
+ emptyFrictionCoef = 0.1
+ emptySlideSpeed = 10 # feet per second
+ emptyRotateSpeed = 20 # degrees per second
+
+ # These points will be useful for sticking the control stick into
+ # the toon's hands.
+ lookAtPoint = Point3(0.3, 0, 0.1)
+ lookAtUp = Vec3(0, -1, 0)
+
+ neutralStickHinge = VBase3(0, 90, 0)
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+ FSM.FSM.__init__(self, 'DistCogdoCrane')
+
+ self.craneGame = None
+ self.index = None
+ self.avId = 0
+
+ self.cableLength = 20
+ self.numLinks = 3
+ self.initialArmPosition = (0, 20, 0)
+
+ self.slideSpeed = self.emptySlideSpeed
+ self.rotateSpeed = self.emptyRotateSpeed
+
+ # This number increments each time we change direction on the
+ # crane controls. It's used to update the animation
+ # appropriately.
+ self.changeSeq = 0
+ self.lastChangeSeq = 0
+
+ # This is the sound effect currently looping for the crane
+ # controls.
+ self.moveSound = None
+
+ self.links = []
+ self.activeLinks = []
+ self.collisions = NodePathCollection()
+ self.physicsActivated = 0
+ self.snifferActivated = 0
+ self.magnetOn = 0
+ self.root = NodePath('root')
+ self.hinge = self.root.attachNewNode('hinge')
+ self.hinge.setPos(0, -17.6, 38.5)
+ self.controls = self.root.attachNewNode('controls')
+ self.controls.setPos(0, -4.9, 0)
+ self.arm = self.hinge.attachNewNode('arm')
+ self.crane = self.arm.attachNewNode('crane')
+ self.cable = self.hinge.attachNewNode('cable')
+ self.topLink = self.crane.attachNewNode('topLink')
+ self.topLink.setPos(0, 0, -1)
+ self.shadow = None
+
+ # These are goot to pre-compute for __rotateMagnet().
+ self.p0 = Point3(0, 0, 0)
+ self.v1 = Vec3(1, 1, 1)
+
+ # Smoothers.
+ self.armSmoother = SmoothMover()
+ self.armSmoother.setSmoothMode(SmoothMover.SMOn)
+ self.linkSmoothers = []
+ self.smoothStarted = 0
+ self.__broadcastPeriod = 0.2
+
+ # Since the cable might not calculate its bounding volume
+ # correctly, let's say that anything that passes the outer
+ # bounding volume passes everything.
+ self.cable.node().setFinal(1)
+
+ self.crane.setPos(*self.initialArmPosition)
+ self.heldObject = None
+
+ self.craneAdviceLabel = None
+ self.magnetAdviceLabel = None
+
+ self.atLimitSfx = base.loadSfx(
+ "phase_4/audio/sfx/MG_cannon_adjust.mp3")
+
+ self.magnetOnSfx = base.loadSfx(
+ "phase_10/audio/sfx/CBHQ_CFO_magnet_on.mp3")
+
+ # We prefer a wav file for this looping sound effect, since
+ # looping an mp3 always introduces some stutter.
+ self.magnetLoopSfx = base.loadSfx(
+ "phase_10/audio/sfx/CBHQ_CFO_magnet_loop.wav")
+
+ # Make these overlap just a bit.
+ self.magnetSoundInterval = Parallel(
+ SoundInterval(self.magnetOnSfx),
+ Sequence(Wait(0.5),
+ Func(base.playSfx, self.magnetLoopSfx, looping=1)))
+
+ self.craneMoveSfx = base.loadSfx(
+ "phase_9/audio/sfx/CHQ_FACT_elevator_up_down.mp3")
+
+ self.fadeTrack = None
+
+ def announceGenerate(self):
+ DistributedObject.DistributedObject.announceGenerate(self)
+ self.name = 'crane-%s' % (self.doId)
+ self.root.setName(self.name)
+
+ self.root.setPosHpr(*GameConsts.CranePosHprs[self.index])
+
+ self.rotateLinkName = self.uniqueName('rotateLink')
+ self.snifferEvent = self.uniqueName('sniffer')
+ self.triggerName = self.uniqueName('trigger')
+ self.triggerEvent = 'enter%s' % (self.triggerName)
+ self.shadowName = self.uniqueName('shadow')
+ self.flickerName = self.uniqueName('flicker')
+
+ self.smoothName = self.uniqueName('craneSmooth')
+ self.posHprBroadcastName = self.uniqueName('craneBroadcast')
+
+ self.craneAdviceName = self.uniqueName('craneAdvice')
+ self.magnetAdviceName = self.uniqueName('magnetAdvice')
+
+ # Load up the control model and the stick. We have to do some
+ # reparenting so we can set things up to slide and scale
+ # pieces around to accomodate toons of various sizes.
+ self.controlModel = self.craneGame.controls.copyTo(self.controls)
+ self.cc = NodePath('cc')
+ column = self.controlModel.find('**/column')
+ column.getChildren().reparentTo(self.cc)
+ self.cc.reparentTo(column)
+ self.stickHinge = self.cc.attachNewNode('stickHinge')
+ self.stick = self.craneGame.stick.copyTo(self.stickHinge)
+ self.stickHinge.setHpr(self.neutralStickHinge)
+ self.stick.setHpr(0, -90, 0)
+ self.stick.flattenLight()
+ self.bottom = self.controlModel.find('**/bottom')
+ self.bottom.wrtReparentTo(self.cc)
+ self.bottomPos = self.bottom.getPos()
+
+ # Make a trigger sphere so we can detect when the local avatar
+ # runs up to the controls. We bury the sphere mostly under
+ # the floor to minimize accidental collisions.
+ cs = CollisionSphere(0, -5, -2, 3)
+ cs.setTangible(0)
+ cn = CollisionNode(self.triggerName)
+ cn.addSolid(cs)
+ cn.setIntoCollideMask(OTPGlobals.WallBitmask)
+ self.trigger = self.root.attachNewNode(cn)
+ self.trigger.stash()
+
+ # Also, a solid tube to keep us from running through the
+ # control stick itself. This one scales with the control
+ # model.
+ cs = CollisionTube(0, 2.7, 0, 0, 2.7, 3, 1.2)
+ cn = CollisionNode('tube')
+ cn.addSolid(cs)
+ cn.setIntoCollideMask(OTPGlobals.WallBitmask)
+ self.tube = self.controlModel.attachNewNode(cn)
+
+ # And finally, a safe-proof bubble we put over the whole thing
+ # to keep safes from falling on us while we're on the controls
+ # (or from occupying the spot when the controls are vacant).
+ cs = CollisionSphere(0, 0, 2, 3)
+ cn = CollisionNode('safetyBubble')
+ cn.addSolid(cs)
+ cn.setIntoCollideMask(ToontownGlobals.PieBitmask)
+ self.controls.attachNewNode(cn)
+
+ arm = self.craneGame.craneArm.copyTo(self.crane)
+
+ assert(not self.craneGame.cranes.has_key(self.index))
+ self.craneGame.cranes[self.index] = self
+
+ def disable(self):
+ DistributedObject.DistributedObject.disable(self)
+ assert(self.craneGame.cranes.get(self.index) == self)
+ del self.craneGame.cranes[self.index]
+ self.cleanup()
+
+ def cleanup(self):
+ if self.state != 'Off':
+ self.demand('Off')
+ self.craneGame = None
+
+ def accomodateToon(self, toon):
+ # This method has two effects:
+
+ # (1) It computes and returns an interval to scale and slide
+ # the crane controls to suit the indicated toon.
+
+ # (2) As a side effect, when it returns, the crane controls are
+ # *already* scaled and slid to accomodate the toon, and the toon
+ # has been positioned in place to operate the controls.
+
+ # Thus, you can use it either by calling it and playing the
+ # interval that it returns to get a smooth lerp, or simply by
+ # calling it and ignoring the return value, to jump to
+ # position.
+
+
+ # We start by figuring out where we are going by setting the
+ # scale and position appropriately, and then we generate a
+ # lerp interval to take us there.
+ origScale = self.controlModel.getSz()
+ origCcPos = self.cc.getPos()
+ origBottomPos = self.bottom.getPos()
+ origStickHingeHpr = self.stickHinge.getHpr()
+
+ # First, scale the thing overall to match the toon's scale,
+ # including cheesy effect scales.
+ scale = toon.getGeomNode().getChild(0).getSz(render)
+ self.controlModel.setScale(scale)
+
+ # Then get the position of the toon's right hand when he's
+ # standing at the controls in a leverNeutral pose.
+ self.cc.setPos(0, 0, 0)
+ toon.setPosHpr(self.controls, 0, 0, 0, 0, 0, 0)
+ toon.pose('leverNeutral', 0)
+ toon.update()
+ pos = toon.rightHand.getPos(self.cc)
+
+ # Now set the control column to the right height and position
+ # to put the top of the stick approximately in his hand.
+ self.cc.setPos(pos[0], pos[1], pos[2] - 1)
+
+ # And put the bottom piece back on the floor, wherever that
+ # is from here.
+ self.bottom.setZ(toon, 0.0)
+ self.bottom.setPos(self.bottomPos[0], self.bottomPos[1], self.bottom.getZ())
+
+ # Also put the joystick in his hand.
+ self.stickHinge.lookAt(toon.rightHand, self.lookAtPoint, self.lookAtUp)
+
+ # Ok, now we can generate the lerp.
+ lerpTime = 0.5
+ return Parallel(
+ self.controlModel.scaleInterval(lerpTime, scale, origScale, blendType = 'easeInOut'),
+ self.cc.posInterval(lerpTime, self.cc.getPos(), origCcPos, blendType = 'easeInOut'),
+ self.bottom.posInterval(lerpTime, self.bottom.getPos(), origBottomPos, blendType = 'easeInOut'),
+ self.stickHinge.quatInterval(lerpTime, self.stickHinge.getHpr(), origStickHingeHpr, blendType = 'easeInOut'),
+ )
+
+ def getRestoreScaleInterval(self):
+ # This undoes the effect of accomodateToon(), to restore the
+ # controls' scale to neutral position. Unlike
+ # accomodateToon(), it has no side effects; you must play (or
+ # immediately finish) the interval to restore the scale.
+
+ lerpTime = 1
+ return Parallel(
+ self.controlModel.scaleInterval(lerpTime, 1, blendType = 'easeInOut'),
+ self.cc.posInterval(lerpTime, Point3(0, 0, 0), blendType = 'easeInOut'),
+ self.bottom.posInterval(lerpTime, self.bottomPos, blendType = 'easeInOut'),
+ self.stickHinge.quatInterval(lerpTime, self.neutralStickHinge, blendType = 'easeInOut'),
+ )
+
+ def makeToonGrabInterval(self, toon):
+ # Generates an interval showing the crane controls scaling to
+ # match the toon and the toon simultaneously reaching to grab
+ # the controls. Thenceforth, the toon will animate with the
+ # controls.
+ origPos = toon.getPos()
+ origHpr = toon.getHpr()
+ a = self.accomodateToon(toon)
+ newPos = toon.getPos()
+ newHpr = toon.getHpr()
+ origHpr.setX(PythonUtil.fitSrcAngle2Dest(origHpr[0], newHpr[0]))
+ toon.setPosHpr(origPos, origHpr)
+
+ walkTime = 0.2
+ reach = ActorInterval(toon, 'leverReach')
+ if reach.getDuration() < walkTime:
+ reach = Sequence(ActorInterval(toon, 'walk', loop = 1,
+ duration = walkTime - reach.getDuration()),
+ reach)
+
+ i = Sequence(
+ Parallel(toon.posInterval(walkTime, newPos, origPos),
+ toon.hprInterval(walkTime, newHpr, origHpr),
+ reach),
+ Func(self.startWatchJoystick, toon))
+ i = Parallel(i, a)
+
+ return i
+
+ def __toonPlayWithCallback(self, animName, numFrames):
+ # Plays the indicated animation on self.toon, and after the
+ # indicated time has elapsed calls __toonPlayCallback().
+
+ duration = numFrames / 24.
+ self.toon.play(animName)
+ taskMgr.doMethodLater(duration, self.__toonPlayCallback,
+ self.uniqueName('toonPlay'))
+
+ def __toonPlayCallback(self, task):
+ # The animation has finished playing; play the next one.
+ if self.changeSeq == self.lastChangeSeq:
+ self.__toonPlayWithCallback('leverNeutral', 40)
+ else:
+ self.__toonPlayWithCallback('leverPull', 40)
+ self.lastChangeSeq = self.changeSeq
+
+ def startWatchJoystick(self, toon):
+ self.toon = toon
+ taskMgr.add(self.__watchJoystick, self.uniqueName('watchJoystick'))
+ self.__toonPlayWithCallback('leverNeutral', 40)
+
+ self.accept(toon.uniqueName('disable'),
+ self.__handleUnexpectedExit, extraArgs = [toon.doId])
+
+ def stopWatchJoystick(self):
+ taskMgr.remove(self.uniqueName('toonPlay'))
+ taskMgr.remove(self.uniqueName('watchJoystick'))
+
+ if self.toon:
+ self.ignore(self.toon.uniqueName('disable'))
+ self.toon = None
+
+ def __watchJoystick(self, task):
+ # Ensure the toon is still standing at the controls.
+ self.toon.setPosHpr(self.controls, 0, 0, 0, 0, 0, 0)
+ self.toon.update()
+ self.stickHinge.lookAt(self.toon.rightHand, self.lookAtPoint,
+ self.lookAtUp)
+ return Task.cont
+
+ def __handleUnexpectedExit(self, toonId):
+ self.notify.warning('%s: unexpected exit for %s' % (self.doId, toonId))
+ if self.toon and self.toon.doId == toonId:
+ self.stopWatchJoystick()
+
+
+ def __activatePhysics(self):
+ if not self.physicsActivated:
+ for an, anp, cnp in self.activeLinks:
+ self.craneGame.physicsMgr.attachPhysicalNode(an)
+ base.cTrav.addCollider(cnp, self.handler)
+ self.collisions.unstash()
+ self.physicsActivated = 1
+
+ def __deactivatePhysics(self):
+ if self.physicsActivated:
+ for an, anp, cnp in self.activeLinks:
+ self.craneGame.physicsMgr.removePhysicalNode(an)
+ base.cTrav.removeCollider(cnp)
+ self.collisions.stash()
+ self.physicsActivated = 0
+
+ def __straightenCable(self):
+ # Arbitrarily drops the cable right where it stands.
+ for linkNum in range(self.numLinks):
+ an, anp, cnp = self.activeLinks[linkNum]
+
+ an.getPhysicsObject().setVelocity(0, 0, 0)
+ z = float(linkNum + 1) / float(self.numLinks) * self.cableLength
+ anp.setPos(self.crane.getPos(self.cable))
+ anp.setZ(-z)
+
+ def setCableLength(self, length):
+ self.cableLength = length
+ linkWidth = float(length) / float(self.numLinks)
+ self.shell.setRadius(linkWidth + 1)
+
+ def setupCable(self):
+ activated = self.physicsActivated
+ self.clearCable()
+
+ self.handler = PhysicsCollisionHandler()
+ self.handler.setStaticFrictionCoef(0.1)
+ self.handler.setDynamicFrictionCoef(self.emptyFrictionCoef)
+
+ linkWidth = float(self.cableLength) / float(self.numLinks)
+ self.shell = CollisionInvSphere(0, 0, 0, linkWidth + 1)
+
+ # The list of links is built up to pass to the Rope class, to
+ # make a renderable spline for the cable.
+ self.links = []
+ self.links.append((self.topLink, Point3(0, 0, 0)))
+
+ anchor = self.topLink
+ for linkNum in range(self.numLinks):
+ anchor = self.__makeLink(anchor, linkNum)
+
+ # Now that we've made a bunch of collisions, stash 'em all
+ # (we're initially deactivated).
+ self.collisions.stash()
+
+ # Make the magnet swing naturally on the end of the cable.
+ self.bottomLink = self.links[-1][0]
+ self.middleLink = self.links[-2][0]
+ self.magnet = self.bottomLink.attachNewNode('magnet')
+ self.wiggleMagnet = self.magnet.attachNewNode('wiggleMagnet')
+ taskMgr.add(self.__rotateMagnet, self.rotateLinkName)
+
+ magnetModel = self.craneGame.magnet.copyTo(self.wiggleMagnet)
+ magnetModel.setHpr(90, 45, 90)
+
+ # And a node to hold stuff.
+ self.gripper = magnetModel.attachNewNode('gripper')
+ self.gripper.setPos(0, 0, -4)
+
+ # Not to mention a bubble to detect stuff to grab.
+ cn = CollisionNode('sniffer')
+ self.sniffer = magnetModel.attachNewNode(cn)
+ self.sniffer.stash()
+ cs = CollisionSphere(0, 0, -10, 6)
+ cs.setTangible(0)
+ cn.addSolid(cs)
+ cn.setIntoCollideMask(BitMask32(0))
+ cn.setFromCollideMask(ToontownGlobals.CashbotBossObjectBitmask)
+ self.snifferHandler = CollisionHandlerEvent()
+ self.snifferHandler.addInPattern(self.snifferEvent)
+ self.snifferHandler.addAgainPattern(self.snifferEvent)
+
+ rope = self.makeSpline()
+ rope.reparentTo(self.cable)
+ rope.setTexture(self.craneGame.cableTex)
+
+ # Texture coordinates on the cable should be in the range
+ # (0.83, 0.01) - (0.98, 0.14).
+ ts = TextureStage.getDefault()
+ rope.setTexScale(ts, 0.15, 0.13)
+ rope.setTexOffset(ts, 0.83, 0.01)
+
+ if activated:
+ self.__activatePhysics()
+
+ def clearCable(self):
+ self.__deactivatePhysics()
+ taskMgr.remove(self.rotateLinkName)
+ self.links = []
+ self.activeLinks = []
+ self.linkSmoothers = []
+ self.collisions.clear()
+ self.cable.getChildren().detach()
+ self.topLink.getChildren().detach()
+ self.gripper = None
+
+ def makeSpline(self):
+ # Use the Rope class to draw a spline between the joints of
+ # the cable.
+
+ rope = Rope.Rope()
+ rope.setup(min(len(self.links), 4), self.links)
+ rope.curve.normalizeKnots()
+
+ rn = rope.ropeNode
+ rn.setRenderMode(RopeNode.RMTube)
+ rn.setNumSlices(3)
+ rn.setTubeUp(Vec3(0, -1, 0))
+ rn.setUvMode(RopeNode.UVParametric)
+ rn.setUvDirection(1)
+ rn.setThickness(0.5)
+
+ return rope
+
+ def startShadow(self):
+ self.shadow = self.craneGame.geomRoot.attachNewNode('%s-shadow' % (self.name))
+ self.shadow.setColor(1, 1, 1, 0.3)
+ self.shadow.setDepthWrite(0)
+ self.shadow.setTransparency(1)
+ self.shadow.setBin('shadow', 0)
+
+ # Hack to fix the bounding volume on the cable. If it got
+ # within the shadow node, render it.
+ self.shadow.node().setFinal(1)
+
+ self.magnetShadow = loader.loadModel("phase_3/models/props/drop_shadow")
+ self.magnetShadow.reparentTo(self.shadow)
+
+ self.craneShadow = loader.loadModel("phase_3/models/props/square_drop_shadow")
+ self.craneShadow.setScale(0.5, 4, 1)
+ self.craneShadow.setPos(0, -12, 0)
+ self.craneShadow.flattenLight()
+ self.craneShadow.reparentTo(self.shadow)
+
+ taskMgr.add(self.__followShadow, self.shadowName)
+
+ rope = self.makeSpline()
+ rope.reparentTo(self.shadow)
+ rope.setColor(1, 1, 1, 0.2)
+
+ tex = self.craneShadow.findTexture('*')
+ rope.setTexture(tex)
+
+ rn = rope.ropeNode
+ rn.setRenderMode(RopeNode.RMTape)
+ rn.setNumSubdiv(6)
+ rn.setThickness(0.8)
+ rn.setTubeUp(Vec3(0, 0, 1))
+ rn.setMatrix(Mat4.translateMat(0, 0, self.shadowOffset) * Mat4.scaleMat(1, 1, 0.01))
+
+
+ def stopShadow(self):
+ if self.shadow:
+ self.shadow.removeNode()
+ self.shadow = None
+ self.magnetShadow = None
+ self.craneShadow = None
+
+ taskMgr.remove(self.shadowName)
+
+ def __followShadow(self, task):
+ p = self.magnet.getPos(self.craneGame.geomRoot)
+ self.magnetShadow.setPos(p[0], p[1], self.shadowOffset)
+
+ self.craneShadow.setPosHpr(self.crane, 0, 0, 0, 0, 0, 0)
+ self.craneShadow.setZ(self.shadowOffset)
+
+ return Task.cont
+
+ def __makeLink(self, anchor, linkNum):
+ an = ActorNode('link%s' % (linkNum))
+ anp = NodePath(an)
+
+ cn = CollisionNode('cn')
+ sphere = CollisionSphere(0, 0, 0, 1)
+ cn.addSolid(sphere)
+ cnp = anp.attachNewNode(cn)
+
+ self.handler.addCollider(cnp, anp)
+
+ self.activeLinks.append((an, anp, cnp))
+ self.linkSmoothers.append(SmoothMover())
+
+ anp.reparentTo(self.cable)
+ z = float(linkNum + 1) / float(self.numLinks) * self.cableLength
+ anp.setPos(self.crane.getPos())
+ anp.setZ(-z)
+
+ mask = BitMask32.bit(self.firstMagnetBit + linkNum)
+ cn.setFromCollideMask(mask)
+ cn.setIntoCollideMask(BitMask32(0))
+
+ shellNode = CollisionNode('shell%s' % (linkNum))
+ shellNode.addSolid(self.shell)
+ shellNP = anchor.attachNewNode(shellNode)
+ shellNode.setIntoCollideMask(mask)
+
+ self.collisions.addPath(shellNP)
+ self.collisions.addPath(cnp)
+
+ self.links.append((anp, Point3(0, 0, 0)))
+
+ return anp
+
+ def __rotateMagnet(self, task):
+ # Rotate the magnet to the penultimate link, so that the
+ # magnet seems to swing realistically (instead of always
+ # hanging straight down).
+
+ self.magnet.lookAt(self.middleLink, self.p0, self.v1)
+ return Task.cont
+
+
+ def __enableControlInterface(self):
+ gui = loader.loadModel("phase_3.5/models/gui/avatar_panel_gui")
+
+ self.accept('control', self.__controlPressed)
+ self.accept('control-up', self.__controlReleased)
+ self.accept('InputState-forward', self.__upArrow)
+ self.accept('InputState-reverse', self.__downArrow)
+ self.accept('InputState-turnLeft', self.__leftArrow)
+ self.accept('InputState-turnRight', self.__rightArrow)
+
+ taskMgr.add(self.__watchControls, 'watchCraneControls')
+
+ # In case they don't figure it out, hit them over the head
+ # with it after a few seconds.
+ taskMgr.doMethodLater(5, self.__displayCraneAdvice,
+ self.craneAdviceName)
+ taskMgr.doMethodLater(10, self.__displayMagnetAdvice,
+ self.magnetAdviceName)
+
+ # Up in the sky, it's hard to read what people are saying.
+ NametagGlobals.setOnscreenChatForced(1)
+
+ self.arrowVert = 0
+ self.arrowHorz = 0
+
+ def __disableControlInterface(self):
+ self.__turnOffMagnet()
+
+ self.__cleanupCraneAdvice()
+ self.__cleanupMagnetAdvice()
+
+ self.ignore('escape')
+ self.ignore('control')
+ self.ignore('control-up')
+ self.ignore('InputState-forward')
+ self.ignore('InputState-reverse')
+ self.ignore('InputState-turnLeft')
+ self.ignore('InputState-turnRight')
+
+ self.arrowVert = 0
+ self.arrowHorz = 0
+
+ NametagGlobals.setOnscreenChatForced(0)
+
+ taskMgr.remove('watchCraneControls')
+ self.__setMoveSound(None)
+
+ def __displayCraneAdvice(self, task):
+ if self.craneAdviceLabel == None:
+ self.craneAdviceLabel = DirectLabel(
+ text = TTLocalizer.CashbotCraneAdvice,
+ text_fg = VBase4(1,1,1,1),
+ text_align = TextNode.ACenter,
+ relief = None,
+ pos = (0, 0, 0.69),
+ scale = 0.1)
+
+ def __cleanupCraneAdvice(self):
+ if self.craneAdviceLabel:
+ self.craneAdviceLabel.destroy()
+ self.craneAdviceLabel = None
+ taskMgr.remove(self.craneAdviceName)
+
+ def __displayMagnetAdvice(self, task):
+ if self.magnetAdviceLabel == None:
+ self.magnetAdviceLabel = DirectLabel(
+ text = TTLocalizer.CashbotMagnetAdvice,
+ text_fg = VBase4(1,1,1,1),
+ text_align = TextNode.ACenter,
+ relief = None,
+ pos = (0, 0, 0.55),
+ scale = 0.1)
+
+ def __cleanupMagnetAdvice(self):
+ if self.magnetAdviceLabel:
+ self.magnetAdviceLabel.destroy()
+ self.magnetAdviceLabel = None
+ taskMgr.remove(self.magnetAdviceName)
+
+ def __watchControls(self, task):
+ if self.arrowHorz or self.arrowVert:
+ self.__moveCraneArcHinge(self.arrowHorz, self.arrowVert)
+ else:
+ self.__setMoveSound(None)
+ return Task.cont
+
+ def __incrementChangeSeq(self):
+ self.changeSeq = (self.changeSeq + 1) & 0xff
+
+ def __controlPressed(self):
+ self.__cleanupMagnetAdvice()
+ self.__turnOnMagnet()
+
+ def __controlReleased(self):
+ self.__turnOffMagnet()
+
+ def __turnOnMagnet(self):
+ if not self.magnetOn:
+ self.__incrementChangeSeq()
+ self.magnetOn = 1
+ if not self.heldObject:
+ self.__activateSniffer()
+
+ def __turnOffMagnet(self):
+ if self.magnetOn:
+ self.magnetOn = 0
+ self.__deactivateSniffer()
+ self.releaseObject()
+
+ def __upArrow(self, pressed):
+ self.__incrementChangeSeq()
+ self.__cleanupCraneAdvice()
+ if pressed:
+ self.arrowVert = 1
+ elif self.arrowVert > 0:
+ self.arrowVert = 0
+
+ def __downArrow(self, pressed):
+ self.__incrementChangeSeq()
+ self.__cleanupCraneAdvice()
+ if pressed:
+ self.arrowVert = -1
+ elif self.arrowVert < 0:
+ self.arrowVert = 0
+
+ def __rightArrow(self, pressed):
+ self.__incrementChangeSeq()
+ self.__cleanupCraneAdvice()
+ if pressed:
+ self.arrowHorz = 1
+ elif self.arrowHorz > 0:
+ self.arrowHorz = 0
+
+ def __leftArrow(self, pressed):
+ self.__incrementChangeSeq()
+ self.__cleanupCraneAdvice()
+ if pressed:
+ self.arrowHorz = -1
+ elif self.arrowHorz < 0:
+ self.arrowHorz = 0
+
+ def __moveCraneArcHinge(self, xd, yd):
+ dt = globalClock.getDt()
+
+ h = self.arm.getH() - xd * self.rotateSpeed * dt
+ limitH = max(min(h, self.armMaxH), self.armMinH)
+ self.arm.setH(limitH)
+
+ y = self.crane.getY() + yd * self.slideSpeed * dt
+ limitY = max(min(y, self.craneMaxY), self.craneMinY)
+
+ atLimit = (limitH != h) or (limitY != y)
+
+ if atLimit:
+ # Wiggle the crane up and down and left and right to show
+ # that it is struggling against its limits of motion.
+ now = globalClock.getFrameTime()
+ x = math.sin(now * 79) * 0.05
+ z = math.sin(now * 70) * 0.02
+ self.crane.setPos(x, limitY, z)
+ self.__setMoveSound(self.atLimitSfx)
+
+ else:
+ self.crane.setPos(0, limitY, 0)
+ self.__setMoveSound(self.craneMoveSfx)
+
+ def __setMoveSound(self, sfx):
+ # Starts looping the indicated sound effect, or stops it.
+ if sfx != self.moveSound:
+ if self.moveSound:
+ self.moveSound.stop()
+ self.moveSound = sfx
+ if self.moveSound:
+ base.playSfx(self.moveSound, looping=1, volume = 0.5)
+
+ def __activateSniffer(self):
+ # Turns on the sniffer on the end of the magnet, looking for
+ # something to grab.
+ if not self.snifferActivated:
+ self.sniffer.unstash()
+ base.cTrav.addCollider(self.sniffer, self.snifferHandler)
+ self.accept(self.snifferEvent, self.__sniffedSomething)
+ self.startFlicker()
+ self.snifferActivated = 1
+
+ def __deactivateSniffer(self):
+ if self.snifferActivated:
+ base.cTrav.removeCollider(self.sniffer)
+ self.sniffer.stash()
+ self.ignore(self.snifferEvent)
+ self.stopFlicker()
+ self.snifferActivated = 0
+
+ def startFlicker(self):
+ # Starts the lightning bolt effect flashing.
+
+ self.magnetSoundInterval.start()
+
+ self.lightning = []
+ for i in range(4):
+ t = (float(i) / 3.0 - 0.5)
+ l = self.craneGame.lightning.copyTo(self.gripper)
+ l.setScale(random.choice([1, -1]), 1, 5)
+ l.setZ(random.uniform(-5, -5.5))
+ l.flattenLight()
+ l.setTwoSided(1)
+ l.setBillboardAxis()
+ l.setScale(random.uniform(0.5, 1.0))
+ if t < 0:
+ l.setX(t - 0.7)
+ else:
+ l.setX(t + 0.7)
+ l.setR(-20 * t)
+ l.setP(random.uniform(-20, 20))
+ self.lightning.append(l)
+
+ taskMgr.add(self.__flickerLightning, self.flickerName)
+
+ def stopFlicker(self):
+ # Stops the lightning bolt effect flashing. This must pair
+ # with a previous call to startFlicker().
+
+ self.magnetSoundInterval.finish()
+ self.magnetLoopSfx.stop()
+
+ taskMgr.remove(self.flickerName)
+ for l in self.lightning:
+ l.detachNode()
+ self.lightning = None
+
+ def __flickerLightning(self, task):
+ for l in self.lightning:
+ if random.random() < 0.5:
+ l.hide()
+ else:
+ l.show()
+ return Task.cont
+
+ def __sniffedSomething(self, entry):
+ # Something was sniffed as grabbable.
+ np = entry.getIntoNodePath()
+ doId = int(np.getNetTag('object'))
+
+ obj = base.cr.doId2do.get(doId)
+ if obj and obj.state != 'LocalDropped' and \
+ (obj.state != 'Dropped' or obj.craneId != self.doId):
+ obj.d_requestGrab()
+ obj.demand('LocalGrabbed', localAvatar.doId, self.doId)
+
+ def grabObject(self, obj):
+ # This is only called by DistributedCashbotBossObject.enterGrabbed().
+ assert(self.notify.debug('%s.grabObject(%s)' % (self.doId, obj.doId)))
+ assert(self.heldObject == None)
+
+ if self.state == 'Off':
+ return
+
+ # This condition is just for sake of the publish, in case we
+ # have gotten into some screwy state. In the dev environment,
+ # we should have verified this already with the above
+ # assertion.
+ if self.heldObject != None:
+ self.releaseObject()
+
+ self.__deactivateSniffer()
+
+ obj.wrtReparentTo(self.gripper)
+
+ if obj.lerpInterval:
+ obj.lerpInterval.finish()
+
+ obj.lerpInterval = Parallel(
+ obj.posInterval(ToontownGlobals.CashbotBossToMagnetTime, Point3(*obj.grabPos)),
+ obj.quatInterval(ToontownGlobals.CashbotBossToMagnetTime, VBase3(obj.getH(), 0, 0)),
+ obj.toMagnetSoundInterval)
+ obj.lerpInterval.start()
+
+ self.heldObject = obj
+ self.handler.setDynamicFrictionCoef(obj.craneFrictionCoef)
+ self.slideSpeed = obj.craneSlideSpeed
+ self.rotateSpeed = obj.craneRotateSpeed
+
+ if self.avId == localAvatar.doId and not self.magnetOn:
+ # We got a late grab. Grab it, then immediately drop it.
+ self.releaseObject()
+
+ def dropObject(self, obj):
+ # This is only called by DistributedCashbotBossObject.exitGrabbed().
+ assert(self.notify.debug('%s.dropObject(%s)' % (self.doId, obj.doId)))
+ assert(self.heldObject == obj)
+ assert(not self.snifferActivated)
+
+ if obj.lerpInterval:
+ obj.lerpInterval.finish()
+
+ obj.wrtReparentTo(render)
+
+ obj.lerpInterval = Parallel(
+ obj.quatInterval(ToontownGlobals.CashbotBossFromMagnetTime, VBase3(obj.getH(), 0, 0), blendType = 'easeOut'),
+ )
+ obj.lerpInterval.start()
+
+ p1 = self.bottomLink.node().getPhysicsObject()
+ v = render.getRelativeVector(self.bottomLink, p1.getVelocity())
+ obj.physicsObject.setVelocity(v * 1.5)
+
+ # This condition is just for sake of the publish, in case we
+ # have gotten into some screwy state. In the dev environment,
+ # we should have verified this already with the above
+ # assertion.
+ if self.heldObject == obj:
+ self.heldObject = None
+ self.handler.setDynamicFrictionCoef(self.emptyFrictionCoef)
+ self.slideSpeed = self.emptySlideSpeed
+ self.rotateSpeed = self.emptyRotateSpeed
+
+ def releaseObject(self):
+ # Don't confuse this method with dropObject. That method
+ # implements the object's request to move out of the Grabbed
+ # state, and is called only by the object itself, while
+ # releaseObject() is called by the crane and asks the object
+ # to drop itself, so that the object will set its state
+ # appropriately. A side-effect of this call will be an
+ # eventual call to dropObject() by the newly-released object.
+
+ assert(self.avId == localAvatar.doId)
+
+ if self.heldObject:
+ obj = self.heldObject
+ obj.d_requestDrop()
+
+ if obj.state == 'Grabbed':
+ # Go ahead and move the local object instance into the
+ # 'LocalDropped' state--presumably the AI will grant our
+ # request shortly anyway, and we can avoid a hitch by
+ # not waiting around for it. However, we can't do
+ # this if the object is just in 'LocalGrabbed' state,
+ # because we can't start broadcasting updates on the
+ # object's position until we *know* we're the object's
+ # owner.
+ obj.demand('LocalDropped', localAvatar.doId, self.doId)
+
+ def __hitTrigger(self, event):
+ #self.d_requestControl()
+ pass
+
+
+
+ ##### Messages To/From The Server #####
+
+ def setCraneGameId(self, craneGameId):
+ self.craneGameId = craneGameId
+
+ # This would be risky if we had toons entering the zone during
+ # a battle--but since all the toons are always there from the
+ # beginning, we can be confident that the BossCog has already
+ # been generated by the time we receive the generate for its
+ # associated battles.
+ self.craneGame = base.cr.doId2do[craneGameId]
+
+ def setIndex(self, index):
+ self.index = index
+
+ def setState(self, state, avId):
+ if state == 'C':
+ self.demand('Controlled', avId)
+ elif state == 'F':
+ self.demand('Free')
+ else:
+ self.notify.error("Invalid state from AI: %s" % (state))
+
+ def d_requestControl(self):
+ self.sendUpdate('requestControl')
+
+ def d_requestFree(self):
+ self.sendUpdate('requestFree')
+
+ ### Handle smoothing of distributed updates. This is similar to
+ ### code in DistributedSmoothNode, but streamlined for our
+ ### purposes.
+
+ def b_clearSmoothing(self):
+ self.d_clearSmoothing()
+ self.clearSmoothing()
+ def d_clearSmoothing(self):
+ self.sendUpdate("clearSmoothing", [0])
+
+ def clearSmoothing(self, bogus = None):
+ # Call this to invalidate all the old position reports
+ # (e.g. just before popping to a new position).
+ self.armSmoother.clearPositions(1)
+ for smoother in self.linkSmoothers:
+ smoother.clearPositions(1)
+
+ def reloadPosition(self):
+ """reloadPosition(self)
+
+ This function re-reads the position from the node itself and
+ clears any old position reports for the node. This should be
+ used whenever show code bangs on the node position and expects
+ it to stick.
+
+ """
+ self.armSmoother.clearPositions(0)
+ self.armSmoother.setPos(self.crane.getPos())
+ self.armSmoother.setHpr(self.arm.getHpr())
+ self.armSmoother.setPhonyTimestamp()
+
+ for linkNum in range(self.numLinks):
+ smoother = self.linkSmoothers[linkNum]
+ an, anp, cnp = self.activeLinks[linkNum]
+
+ smoother.clearPositions(0)
+ smoother.setPos(anp.getPos())
+ smoother.setPhonyTimestamp()
+
+ def doSmoothTask(self, task):
+ """
+ This function updates the position of the node to its computed
+ smoothed position. This may be overridden by a derived class
+ to specialize the behavior.
+ """
+ self.armSmoother.computeAndApplySmoothPosHpr(self.crane, self.arm)
+
+ for linkNum in range(self.numLinks):
+ smoother = self.linkSmoothers[linkNum]
+ anp = self.activeLinks[linkNum][1]
+ smoother.computeAndApplySmoothPos(anp)
+
+ return Task.cont
+
+ def startSmooth(self):
+ """
+ This function starts the task that ensures the node is
+ positioned correctly every frame. However, while the task is
+ running, you won't be able to lerp the node or directly
+ position it.
+ """
+ if not self.smoothStarted:
+ taskName = self.smoothName
+ taskMgr.remove(taskName)
+ self.reloadPosition()
+ taskMgr.add(self.doSmoothTask, taskName)
+ self.smoothStarted = 1
+
+ def stopSmooth(self):
+ """
+ This function stops the task spawned by startSmooth(), and
+ allows show code to move the node around directly.
+ """
+ if self.smoothStarted:
+ taskName = self.smoothName
+ taskMgr.remove(taskName)
+ self.forceToTruePosition()
+ self.smoothStarted = 0
+
+
+ def forceToTruePosition(self):
+ """forceToTruePosition(self)
+
+ This forces the node to reposition itself to its latest known
+ position. This may result in a pop as the node skips the last
+ of its lerp points.
+
+ """
+ if self.armSmoother.getLatestPosition():
+ self.armSmoother.applySmoothPos(self.crane)
+ self.armSmoother.applySmoothHpr(self.arm)
+ self.armSmoother.clearPositions(1)
+
+ for linkNum in range(self.numLinks):
+ smoother = self.linkSmoothers[linkNum]
+ an, anp, cnp = self.activeLinks[linkNum]
+
+ if smoother.getLatestPosition():
+ smoother.applySmoothPos(anp)
+ smoother.clearPositions(1)
+
+
+ def setCablePos(self, changeSeq, y, h, links, timestamp):
+ self.changeSeq = changeSeq
+ if self.smoothStarted:
+ now = globalClock.getFrameTime()
+ local = globalClockDelta.networkToLocalTime(timestamp, now)
+
+ self.armSmoother.setY(y)
+ self.armSmoother.setH(h)
+ self.armSmoother.setTimestamp(local)
+ self.armSmoother.markPosition()
+
+ for linkNum in range(self.numLinks):
+ smoother = self.linkSmoothers[linkNum]
+ lp = links[linkNum]
+
+ smoother.setPos(*lp)
+ smoother.setTimestamp(local)
+ smoother.markPosition()
+
+ else:
+ self.crane.setY(y)
+ self.arm.setH(h)
+
+ def d_sendCablePos(self):
+ timestamp = globalClockDelta.getFrameNetworkTime()
+
+ links = []
+ for linkNum in range(self.numLinks):
+ an, anp, cnp = self.activeLinks[linkNum]
+
+ p = anp.getPos()
+ links.append((p[0], p[1], p[2]))
+
+ self.sendUpdate('setCablePos', [
+ self.changeSeq, self.crane.getY(), self.arm.getH(), links, timestamp])
+
+ def stopPosHprBroadcast(self):
+ taskName = self.posHprBroadcastName
+ taskMgr.remove(taskName)
+
+ def startPosHprBroadcast(self):
+ taskName = self.posHprBroadcastName
+
+ # Broadcast our initial position
+ self.b_clearSmoothing()
+ self.d_sendCablePos()
+
+ # remove any old tasks
+ taskMgr.remove(taskName)
+ taskMgr.doMethodLater(self.__broadcastPeriod,
+ self.__posHprBroadcast, taskName)
+
+ def __posHprBroadcast(self, task):
+ self.d_sendCablePos()
+ taskName = self.posHprBroadcastName
+ taskMgr.doMethodLater(self.__broadcastPeriod,
+ self.__posHprBroadcast, taskName)
+ return Task.done
+
+ ### FSM States ###
+
+ def enterOff(self):
+ self.clearCable()
+ self.root.detachNode()
+
+ def exitOff(self):
+ if self.craneGame:
+ self.setupCable()
+ self.root.reparentTo(render)
+
+ def enterControlled(self, avId):
+ self.avId = avId
+ toon = base.cr.doId2do.get(avId)
+ if not toon:
+ return
+
+ self.grabTrack = self.makeToonGrabInterval(toon)
+
+ if avId == localAvatar.doId:
+ # The local toon is beginning to control the crane.
+
+ self.craneGame.toCraneMode()
+
+ camera.reparentTo(self.hinge)
+ camera.setPosHpr(0, -20, -5, 0, -20, 0)
+ self.tube.stash()
+
+ localAvatar.setPosHpr(self.controls, 0, 0, 0, 0, 0, 0)
+ localAvatar.sendCurrentPosition()
+
+ self.__activatePhysics()
+ self.__enableControlInterface()
+ self.startPosHprBroadcast()
+ self.startShadow()
+
+ else:
+ self.startSmooth()
+ toon.stopSmooth()
+ self.grabTrack = Sequence(self.grabTrack,
+ Func(toon.startSmooth))
+
+ self.grabTrack.start()
+
+ def exitControlled(self):
+ self.grabTrack.finish()
+ del self.grabTrack
+
+ if self.toon and not self.toon.isDisabled():
+ self.toon.loop('neutral')
+ self.toon.startSmooth()
+ self.stopWatchJoystick()
+
+ self.stopPosHprBroadcast()
+ self.stopShadow()
+ self.stopSmooth()
+
+ if self.avId == localAvatar.doId:
+ # The local toon is no longer in control of the crane.
+
+ self.__disableControlInterface()
+ self.__deactivatePhysics()
+ self.tube.unstash()
+
+ camera.reparentTo(base.localAvatar)
+ camera.setPos(base.localAvatar.cameraPositions[0][0])
+ camera.setHpr(0, 0, 0)
+
+ self.__straightenCable()
+
+ def enterFree(self):
+ if self.fadeTrack:
+ self.fadeTrack.finish()
+ self.fadeTrack = None
+
+ # Wait a few seconds before neutralizing the scale; maybe the
+ # same avatar wants to come right back (after his 5-second
+ # timeout).
+ self.restoreScaleTrack = Sequence(Wait(6),
+ self.getRestoreScaleInterval())
+ self.restoreScaleTrack.start()
+
+ if self.avId == localAvatar.doId:
+ # Five second timeout on grabbing the same crane again. Go
+ # get a different crane!
+ self.controlModel.setAlphaScale(0.3)
+ self.controlModel.setTransparency(1)
+ taskMgr.doMethodLater(5, self.__allowDetect, self.triggerName)
+
+ self.fadeTrack = Sequence(
+ Func(self.controlModel.setTransparency, 1),
+ self.controlModel.colorScaleInterval(0.2, VBase4(1,1,1,0.3)))
+ self.fadeTrack.start()
+
+ else:
+ # Other players can grab this crane immediately.
+ self.trigger.unstash()
+ self.accept(self.triggerEvent, self.__hitTrigger)
+
+ self.avId = 0
+
+ def __allowDetect(self, task):
+ if self.fadeTrack:
+ self.fadeTrack.finish()
+ self.fadeTrack = Sequence(
+ self.controlModel.colorScaleInterval(0.2, VBase4(1,1,1,1)),
+ Func(self.controlModel.clearColorScale),
+ Func(self.controlModel.clearTransparency))
+ self.fadeTrack.start()
+
+ self.trigger.unstash()
+ self.accept(self.triggerEvent, self.__hitTrigger)
+
+ def exitFree(self):
+ if self.fadeTrack:
+ self.fadeTrack.finish()
+ self.fadeTrack = None
+
+ self.restoreScaleTrack.pause() # We just pause, to leave it where it is.
+ del self.restoreScaleTrack
+
+ taskMgr.remove(self.triggerName)
+ self.controlModel.clearColorScale()
+ self.controlModel.clearTransparency()
+
+ self.trigger.stash()
+ self.ignore(self.triggerEvent)
+
+ def enterMovie(self):
+ # This is used to enable a movie mode (particularly for
+ # playing the cutscene showing the resistance toon using the
+ # crane). In this mode, lerps on the crane will apply physics
+ # to the cable in the expected way.
+
+ self.__activatePhysics()
+
+ def exitMovie(self):
+ self.__deactivatePhysics()
+ self.__straightenCable()
diff --git a/toontown/src/cogdominium/DistCogdoCraneAI.py b/toontown/src/cogdominium/DistCogdoCraneAI.py
new file mode 100644
index 0000000..6e70899
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoCraneAI.py
@@ -0,0 +1,55 @@
+from pandac.PandaModules import *
+from direct.distributed import DistributedObjectAI
+from toontown.toonbase import ToontownGlobals
+from otp.otpbase import OTPGlobals
+from direct.fsm import FSM
+
+class DistCogdoCraneAI(DistributedObjectAI.DistributedObjectAI, FSM.FSM):
+ def __init__(self, air, craneGame, index):
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ FSM.FSM.__init__(self, 'DistCogdoCraneAI')
+
+ self.craneGame = craneGame
+ self.index = index
+
+ self.avId = 0
+ self.objectId = 0
+
+ def getCraneGameId(self):
+ return self.craneGame.doId
+
+ def getIndex(self):
+ return self.index
+
+ def generate(self):
+ DistributedObjectAI.DistributedObjectAI.generate(self)
+ self.request('Free')
+
+ def d_setState(self, state, avId):
+ self.sendUpdate('setState', [state, avId])
+
+ ### FSM States ###
+
+ def enterOff(self):
+ pass
+
+ def exitOff(self):
+ pass
+
+ def enterControlled(self, avId):
+ self.avId = avId
+ self.d_setState('C', avId)
+
+ def exitControlled(self):
+ if self.objectId:
+ # This will be filled in if an object has requested a
+ # grab. In this case, drop the object.
+ obj = self.air.doId2do[self.objectId]
+ obj.request('Dropped', self.avId, self.doId)
+
+ def enterFree(self):
+ self.avId = 0
+ self.d_setState('F', 0)
+
+ def exitFree(self):
+ pass
diff --git a/toontown/src/cogdominium/DistCogdoCraneGame.py b/toontown/src/cogdominium/DistCogdoCraneGame.py
new file mode 100644
index 0000000..78c7726
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoCraneGame.py
@@ -0,0 +1,219 @@
+from pandac import PandaModules as PM
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.task.Task import Task
+from otp.level import LevelConstants
+from otp.otpbase import OTPGlobals
+from toontown.cogdominium.DistCogdoLevelGame import DistCogdoLevelGame
+from toontown.cogdominium import CogdoCraneGameConsts as GameConsts
+from toontown.cogdominium.CogdoCraneGameBase import CogdoCraneGameBase
+from toontown.toonbase import ToontownTimer
+from toontown.toonbase import TTLocalizer as TTL
+from toontown.toonbase import ToontownGlobals
+
+class DistCogdoCraneGame(DistCogdoLevelGame, CogdoCraneGameBase):
+ notify = directNotify.newCategory("DistCogdoCraneGame")
+
+ def __init__(self, cr):
+ DistCogdoLevelGame.__init__(self, cr)
+ self.cranes = {}
+
+ def getTitle(self):
+ return TTL.CogdoCraneGameTitle
+
+ def getInstructions(self):
+ return TTL.CogdoCraneGameInstructions
+
+ def announceGenerate(self):
+ DistCogdoLevelGame.announceGenerate(self)
+ self.timer = ToontownTimer.ToontownTimer()
+ self.timer.stash()
+ if __dev__:
+ self._durationChangedEvent = self.uniqueName('durationChanged')
+
+ def disable(self):
+ self.timer.destroy()
+ self.timer = None
+ DistCogdoLevelGame.disable(self)
+
+ def enterLoaded(self):
+ DistCogdoLevelGame.enterLoaded(self)
+
+ self.lightning = loader.loadModel('phase_10/models/cogHQ/CBLightning.bam')
+ self.magnet = loader.loadModel('phase_10/models/cogHQ/CBMagnet.bam')
+ self.craneArm = loader.loadModel('phase_10/models/cogHQ/CBCraneArm.bam')
+ self.controls = loader.loadModel('phase_10/models/cogHQ/CBCraneControls.bam')
+ self.stick = loader.loadModel('phase_10/models/cogHQ/CBCraneStick.bam')
+ self.cableTex = self.craneArm.findTexture('MagnetControl')
+
+ self.geomRoot = PM.NodePath('geom')
+
+ # Set up a physics manager for the cables and the objects
+ # falling around in the room.
+
+ self.physicsMgr = PM.PhysicsManager()
+ integrator = PM.LinearEulerIntegrator()
+ self.physicsMgr.attachLinearIntegrator(integrator)
+
+ fn = PM.ForceNode('gravity')
+ self.fnp = self.geomRoot.attachNewNode(fn)
+ gravity = PM.LinearVectorForce(0, 0, -32)
+ fn.addForce(gravity)
+ self.physicsMgr.addLinearForce(gravity)
+
+ def privGotSpec(self, levelSpec):
+ DistCogdoLevelGame.privGotSpec(self, levelSpec)
+
+ levelMgr = self.getEntity(LevelConstants.LevelMgrEntId)
+ self.endVault = levelMgr.geom
+ self.endVault.reparentTo(self.geomRoot)
+
+ # Clear out unneeded backstage models from the EndVault, if
+ # they're in the file.
+ self.endVault.findAllMatches('**/MagnetArms').detach()
+ self.endVault.findAllMatches('**/Safes').detach()
+ self.endVault.findAllMatches('**/MagnetControlsAll').detach()
+
+ # Flag the collisions in the end vault so safes and magnets
+ # don't try to go through the wall.
+ cn = self.endVault.find('**/wallsCollision').node()
+ cn.setIntoCollideMask(OTPGlobals.WallBitmask | ToontownGlobals.PieBitmask |
+ (PM.BitMask32.lowerOn(3) << 21))
+
+ # Find all the wall polygons and replace them with planes,
+ # which are solid, so there will be zero chance of safes or
+ # toons slipping through a wall.
+ walls = self.endVault.find('**/RollUpFrameCillison')
+ walls.detachNode()
+ self.evWalls = self.replaceCollisionPolysWithPlanes(walls)
+ self.evWalls.reparentTo(self.endVault)
+
+ # Initially, these new planar walls are stashed, so they don't
+ # cause us trouble in the intro movie or in battle one. We
+ # will unstash them when we move to battle three.
+ self.evWalls.stash()
+
+
+ # Also replace the floor polygon with a plane, and rename it
+ # so we can detect a collision with it.
+ floor = self.endVault.find('**/EndVaultFloorCollision')
+ floor.detachNode()
+ self.evFloor = self.replaceCollisionPolysWithPlanes(floor)
+ self.evFloor.reparentTo(self.endVault)
+ self.evFloor.setName('floor')
+
+ # Also, put a big plane across the universe a few feet below
+ # the floor, to catch things that fall out of the world.
+ plane = PM.CollisionPlane(PM.Plane(PM.Vec3(0, 0, 1), PM.Point3(0, 0, -50)))
+ planeNode = PM.CollisionNode('dropPlane')
+ planeNode.addSolid(plane)
+ planeNode.setCollideMask(ToontownGlobals.PieBitmask)
+ self.geomRoot.attachNewNode(planeNode)
+
+ def replaceCollisionPolysWithPlanes(self, model):
+ newCollisionNode = PM.CollisionNode('collisions')
+ newCollideMask = PM.BitMask32(0)
+ planes = []
+
+ collList = model.findAllMatches('**/+CollisionNode')
+ if not collList:
+ collList = [model]
+
+ for cnp in collList:
+ cn = cnp.node()
+ if not isinstance(cn, PM.CollisionNode):
+ self.notify.warning("Not a collision node: %s" % (repr(cnp)))
+ break
+
+ newCollideMask = newCollideMask | cn.getIntoCollideMask()
+ for i in range(cn.getNumSolids()):
+ solid = cn.getSolid(i)
+ if isinstance(solid, PM.CollisionPolygon):
+ # Save the plane defined by this polygon
+ plane = PM.Plane(solid.getPlane())
+ planes.append(plane)
+ else:
+ self.notify.warning("Unexpected collision solid: %s" % (repr(solid)))
+ newCollisionNode.addSolid(plane)
+
+ newCollisionNode.setIntoCollideMask(newCollideMask)
+
+ # Now sort all of the planes and remove the nonunique ones.
+ # We can't use traditional dictionary-based tricks, because we
+ # want to use Plane.compareTo(), not Plane.__hash__(), to make
+ # the comparison.
+ threshold = 0.1
+ planes.sort(lambda p1, p2: p1.compareTo(p2, threshold))
+ lastPlane = None
+ for plane in planes:
+ if lastPlane == None or plane.compareTo(lastPlane, threshold) != 0:
+ cp = PM.CollisionPlane(plane)
+ newCollisionNode.addSolid(cp)
+ lastPlane = plane
+
+ return PM.NodePath(newCollisionNode)
+
+ def exitLoaded(self):
+ self.fnp.removeNode()
+ self.physicsMgr.clearLinearForces()
+
+ self.geomRoot.removeNode()
+
+ DistCogdoLevelGame.exitLoaded(self)
+
+ def toCraneMode(self):
+ # Move the localToon to 'crane' mode: we're not walking the
+ # avatar around, but we're still controlling something
+ # in-game, e.g. to move the cranes in the CFO battle.
+ # Collisions are still active.
+ if self.cr:
+ place = self.cr.playGame.getPlace()
+ if place and hasattr(place, 'fsm'):
+ place.setState('crane')
+
+ def enterIntro(self):
+ DistCogdoLevelGame.enterIntro(self)
+ self.geomRoot.reparentTo(render)
+
+ def enterGame(self):
+ DistCogdoLevelGame.enterGame(self)
+ self._physicsTask = taskMgr.add(self._doPhysics, self.uniqueName('physics'), priority=25)
+
+ self.evWalls.stash()
+
+ self._startTimer()
+
+ if __dev__:
+ self.accept(self._durationChangedEvent, self._startTimer)
+
+ def _startTimer(self):
+ timeLeft = GameConsts.Settings.GameDuration.get() - (globalClock.getRealTime() - self.getStartTime())
+ self.timer.posInTopRightCorner()
+ self.timer.setTime(timeLeft)
+ self.timer.countdown(timeLeft, self.timerExpired)
+ self.timer.unstash()
+
+ def _doPhysics(self, task):
+ dt = globalClock.getDt()
+ self.physicsMgr.doPhysics(dt)
+ return Task.cont
+
+ def exitGame(self):
+ if __dev__:
+ self.ignore(self._durationChangedEvent)
+ DistCogdoLevelGame.exitGame(self)
+ self._physicsTask.remove()
+
+ def enterFinish(self):
+ DistCogdoLevelGame.enterFinish(self)
+ timeLeft = 10 - (globalClock.getRealTime() - self.getFinishTime())
+ self.timer.setTime(timeLeft)
+ self.timer.countdown(timeLeft, self.timerExpired)
+ self.timer.unstash()
+
+ def timerExpired(self):
+ pass
+
+ if __dev__:
+ def _handleGameDurationChanged(self, gameDuration):
+ messenger.send(self._durationChangedEvent)
+
diff --git a/toontown/src/cogdominium/DistCogdoCraneGameAI.py b/toontown/src/cogdominium/DistCogdoCraneGameAI.py
new file mode 100644
index 0000000..fc7dfe0
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoCraneGameAI.py
@@ -0,0 +1,73 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from toontown.cogdominium.DistCogdoLevelGameAI import DistCogdoLevelGameAI
+from toontown.cogdominium.DistCogdoCraneAI import DistCogdoCraneAI
+from toontown.cogdominium import CogdoCraneGameConsts as GameConsts
+from toontown.cogdominium.CogdoCraneGameBase import CogdoCraneGameBase
+
+class DistCogdoCraneGameAI(DistCogdoLevelGameAI, CogdoCraneGameBase):
+ notify = directNotify.newCategory("DistCogdoCraneGameAI")
+
+ def __init__(self, air, interior):
+ DistCogdoLevelGameAI.__init__(self, air, interior)
+ self._cranes = [None,] * self.MaxPlayers
+
+ def enterLoaded(self):
+ DistCogdoLevelGameAI.enterLoaded(self)
+ # create the cranes
+ for i in xrange(self.MaxPlayers):
+ crane = DistCogdoCraneAI(self.air, self, i)
+ crane.generateWithRequired(self.zoneId)
+ self._cranes[i] = crane
+
+ def exitLoaded(self):
+ # destroy the cranes
+ for i in xrange(self.MaxPlayers):
+ if self._cranes[i]:
+ self._cranes[i].requestDelete()
+ self._cranes[i] = None
+ DistCogdoLevelGameAI.exitLoaded(self)
+
+ def enterGame(self):
+ DistCogdoLevelGameAI.enterGame(self)
+
+ # put the players on the cranes
+ for i in xrange(self.getNumPlayers()):
+ self._cranes[i].request('Controlled', self.getToonIds()[i])
+
+ # start the game up. Or wait for a while, that's fun too
+ self._scheduleGameDone()
+
+ def _scheduleGameDone(self):
+ timeLeft = GameConsts.Settings.GameDuration.get() - (globalClock.getRealTime() - self.getStartTime())
+ if timeLeft > 0:
+ self._gameDoneEvent = taskMgr.doMethodLater(
+ timeLeft, self._gameDoneDL, self.uniqueName('boardroomGameDone'))
+ else:
+ self._gameDoneDL()
+
+ def exitGame(self):
+ taskMgr.remove(self._gameDoneEvent)
+ self._gameDoneEvent = None
+
+ def _gameDoneDL(self, task=None):
+ self._handleGameFinished()
+ return task.done
+
+ def enterFinish(self):
+ DistCogdoLevelGameAI.enterFinish(self)
+ self._finishDoneEvent = taskMgr.doMethodLater(
+ 10., self._finishDoneDL, self.uniqueName('boardroomFinishDone'))
+
+ def exitFinish(self):
+ taskMgr.remove(self._finishDoneEvent)
+ self._finishDoneEvent = None
+
+ def _finishDoneDL(self, task):
+ self.announceGameDone()
+ return task.done
+
+ if __dev__:
+ def _handleGameDurationChanged(self, gameDuration):
+ if hasattr(self, '_gameDoneEvent') and self._gameDoneEvent != None:
+ taskMgr.remove(self._gameDoneEvent)
+ self._scheduleGameDone()
diff --git a/toontown/src/cogdominium/DistCogdoFlyingGame.py b/toontown/src/cogdominium/DistCogdoFlyingGame.py
new file mode 100644
index 0000000..e880811
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoFlyingGame.py
@@ -0,0 +1,974 @@
+#-------------------------------------------------------------------------------
+# Contact: Sam Polglase (Schell Games)
+# Created: March 2010
+#
+# Purpose: This module currently contains all the classes necessary to run the Congdo
+# minigame. Soon all these classes will get split into their own modules
+#------------------------------------------------------------------------------
+
+
+#------------------------------------------------------------------------------
+# Config Overrides:
+# cogdo-flying-game-disable-death bool
+# Allows the dev to run the flying game without the death state
+#------------------------------------------------------------------------------
+
+from direct.showbase.DirectObject import DirectObject
+from direct.showbase.PythonUtil import bound as clamp
+from direct.fsm.FSM import FSM
+
+from pandac.PandaModules import NodePath, VBase3, Vec3, VBase4, Fog
+
+from toontown.minigame.DistributedMinigame import DistributedMinigame
+from toontown.minigame import ArrowKeys
+from toontown.toonbase import ToontownTimer
+
+import CogdoFlyingGameGlobals
+
+def loadMockup(fileName, dmodelsAlt="coffin"):
+ try:
+ model = loader.loadModel(fileName)
+ except IOError:
+ model = loader.loadModel("phase_4/models/props/%s" % dmodelsAlt)
+
+ return model
+
+class DistCogdoFlyingGame(DistributedMinigame):
+ """
+ Flying Cogdominium Minigame client Distributed Object!
+ """
+ notify = directNotify.newCategory("DistCogdoFlyingGame")
+
+ def __init__(self, cr):
+ DistributedMinigame.__init__(self, cr)
+ #print "adding onCodeReload"
+ self.accept("onCodeReload", self.codeReload)
+
+ #print "FLYING COGDO GAME CREATED!"
+ self.game = CogdoFlyingGame(self)
+
+ def codeReload(self):
+ reload(CogdoFlyingGameGlobals)
+
+ def load(self):
+ DistributedMinigame.load(self)
+ self.game.load()
+
+ # This isn't get called, that seems bad!
+ def unload(self):
+ self.game.unload()
+ del self.game
+ self.ignore("onCodeReload")
+
+ print "Unload Distributed Game"
+
+ DistributedMinigame.unload(self)
+
+ def onstage(self):
+ self.game.onstage()
+ DistributedMinigame.onstage(self)
+
+ # This isn't get called, that seems bad!
+ def offstage(self):
+ self.game.offstage()
+ DistributedMinigame.offstage(self)
+
+ def announceGenerate(self):
+ DistributedMinigame.announceGenerate(self)
+
+ # broadcast
+ def setGameStart(self, timestamp):
+ DistributedMinigame.setGameStart(self, timestamp)
+ self.acceptOnce("escape", self.d_requestExit)
+ self.game.enable()
+ #self.fsm.request('Game')
+ #print "setGameStart"
+
+ def delete(self):
+ pass
+
+
+class CogdoFlyingGame(DirectObject):
+ UPDATE_TASK_NAME = "CogdoFlyingGameUpdate"
+ GAME_COMPLETE_TASK_NAME = "CogdoFlyingGameCompleteTask"
+
+ def __init__(self, distGame):
+ self.distGame = distGame
+
+ # Unused currently
+ self.players = {}
+ self.localPlayer = CogdoFlyingLocalPlayer(self, base.localAvatar)
+
+ # Unused currently
+ self.toonDropShadows = []
+
+ self.startPlatform = None
+# self.currentPlatform = None
+ self.endPlatform = None
+ self.fuelPlatforms = {}
+ self.isGameComplete = False
+
+ self.upLimit = 0.0
+ self.downLimit = 0.0
+ self.leftLimit = 0.0
+ self.rightLimit = 0.0
+
+ def load(self):
+ self.root = NodePath('root')
+ self.root.reparentTo(render)
+ self.root.stash()
+
+ self.world = loadMockup("cogdominium/mockup.egg")
+ self.world.reparentTo(self.root)
+ self.world.stash()
+
+ # Setup and placement of starting platform
+ self.startPlatform = loadMockup("cogdominium/start_platform.egg")
+ startPlatformLoc = self.world.find("**/start_platform_loc")
+ self.startPlatform.reparentTo(startPlatformLoc)
+ colModel = self.startPlatform.find("**/col_floor")
+ colModel.setTag('start_platform', '%s' % base.localAvatar.doId)
+
+ # Here we set the current platform for the local player
+ self.localPlayer.setCheckpointPlatform(self.startPlatform)
+
+ # Setup and placement of the end platform
+ self.endPlatform = loadMockup("cogdominium/end_platform.egg")
+ endPlatformLoc = self.world.find("**/end_platform_loc")
+ self.endPlatform.reparentTo(endPlatformLoc)
+ colModel = self.endPlatform.find("**/col_floor")
+ colModel.setTag('end_platform', '%s' % base.localAvatar.doId)
+
+ # Setup and placement for all the fuel platforms
+ fuelPlatformModel = loadMockup("cogdominium/fuel_platform.egg")
+ fuelIndex = 1
+ fuelLoc = self.world.find('**/fuel_platform_loc_%d' % fuelIndex)
+ while not fuelLoc.isEmpty():
+ fuelModel = NodePath("fuel_platform_%d" % fuelIndex)
+ fuelPlatformModel.copyTo(fuelModel)
+ fuelModel.reparentTo(fuelLoc)
+ colModel = fuelModel.find("**/col_floor")
+ colModel.setTag('fuel_platform', '%s' % base.localAvatar.doId)
+ colModel.setTag('isUsed', '%s' % 0)
+ self.fuelPlatforms[fuelModel.getName()] = fuelModel
+ fuelIndex += 1
+ fuelLoc = self.world.find('**/fuel_platform_loc_%d' % fuelIndex)
+
+
+ self.accept("entercol_floor", self.handleCollision)
+
+ self.skybox = self.world.find("**/skybox")
+ self.upLimit = self.world.find("**/limit_up").getPos(render).getZ()
+ self.downLimit = self.world.find("**/limit_down").getPos(render).getZ()
+ self.leftLimit = self.world.find("**/limit_left").getPos(render).getX()
+ self.rightLimit = self.world.find("**/limit_right").getPos(render).getX()
+
+ del fuelPlatformModel
+
+ self._initFog()
+
+
+ def unload(self):
+ self.__stopUpdateTask()
+ self.__stopGameCompleteTask()
+ self._destroyFog()
+# print "Unload Flying CogdoGame"
+ self.localPlayer.unload()
+ del self.localPlayer
+
+ self.fuelPlatforms.clear()
+ self.endPlatform = None
+
+ self.world.detachNode()
+ del self.world
+
+ self.root.detachNode()
+ del self.root
+
+ self.ignore("entercol_floor")
+
+ def handleCollision(self, collEntry):
+ fromNodePath = collEntry.getFromNodePath()
+ intoNodePath = collEntry.getIntoNodePath()
+ intoName = intoNodePath.getName()
+ fromName = fromNodePath.getName()
+
+ if intoNodePath.getTag('fuel_platform') != "":
+ if not int(intoNodePath.getTag('isUsed')) or CogdoFlyingGameGlobals.FlyingGame.MULTIPLE_REFUELS_PER_STATION:
+ intoNodePath.setTag('isUsed','%s' % 1)
+ self.localPlayer.setCheckpointPlatform(intoNodePath.getParent())
+ self.localPlayer.request("Refuel")
+ if intoNodePath.getTag('end_platform') != "":
+ self.localPlayer.request("WaitingForWin")
+
+ def enable(self):
+ self.localPlayer.request("FreeFly")
+ self.__startUpdateTask()
+ self.isGameComplete = False
+
+ def disable(self):
+ self.__stopUpdateTask()
+ self.__stopGameCompleteTask()
+ self.localPlayer.request("Inactive")
+
+ def _initFog(self):
+ self.fog = Fog("FlyingFog")
+ self.fog.setColor(VBase4(0.8, 0.8, 0.8, 1.0))
+ self.fog.setLinearRange(100.0, 400.0)
+
+ self._renderFog = render.getFog()
+ render.setFog(self.fog)
+
+ def _destroyFog(self):
+ render.clearFog()
+ del self.fog
+ del self._renderFog
+
+ def onstage(self):
+ self.root.unstash()
+ self.world.unstash()
+
+ self.localPlayer.onstage()
+
+ def offstage(self):
+ self.__stopUpdateTask()
+ self.world.stash()
+ self.root.stash()
+ self.localPlayer.offstage()
+
+ #TODO: Temp solution, this is supposed to come from the minigame
+ # Which means the minigame isn't getting cleaned up properly look into this
+ self.unload()
+
+ def handleToonJoined(self, toon):
+ # Not used, no multiplayer support in yet
+ if toon == base.localAvatar:
+ player = CogdoFlyingLocalPlayer(toon)
+ player.entersActivity()
+
+ self.localPlayer = player
+ else:
+ player = CogdoFlyingPlayer(toon)
+ player.entersActivity()
+
+ self.players[toon.doId] = player
+
+ def __startUpdateTask(self):
+ self.__stopUpdateTask()
+ taskMgr.add(self.__updateTask, CogdoFlyingGame.UPDATE_TASK_NAME, 45)
+
+ def __stopUpdateTask(self):
+ taskMgr.remove(CogdoFlyingGame.UPDATE_TASK_NAME)
+
+ def __stopGameCompleteTask(self):
+ taskMgr.remove(CogdoFlyingGame.GAME_COMPLETE_TASK_NAME)
+
+ def gameComplete(self):
+ self.localPlayer.request("Win")
+
+ def __updateTask(self, task):
+ self.localPlayer.update()
+
+ #TODO:flying: make this win condition stuff work for multiple toons
+ if self.localPlayer.state == "WaitingForWin" and not self.isGameComplete:
+ self.isGameComplete = True
+ taskMgr.doMethodLater(6.0, self.gameComplete, CogdoFlyingGame.GAME_COMPLETE_TASK_NAME, extraArgs=[])
+
+ self.skybox.setPos(self.skybox.getPos().getX(),
+ self.localPlayer.toon.getPos().getY(),
+ self.skybox.getPos().getZ())
+ return Task.cont
+
+from direct.directnotify import DirectNotifyGlobal
+
+class CogdoFlyingPlayer(FSM):
+ notify = DirectNotifyGlobal.directNotify.newCategory( "CogdoFlyingPlayer" )
+
+ def __init__(self, toon):
+ FSM.__init__(self, "CogdoFlyingPlayer")
+
+ self.defaultTransitions = {
+ "Inactive" : ["FreeFly", "Inactive"],
+ "FreeFly" : ["Inactive", "FlyingUp", "Death", "Refuel", "WaitingForWin"],
+ "FlyingUp" : ["Inactive", "FreeFly", "Death", "Refuel", "WaitingForWin"],
+ "Death" : ["FreeFly", "Inactive"],
+ "Refuel" : ["FreeFly", "Inactive"],
+ "WaitingForWin" : ["Win", "Inactive"],
+ "Win" : ["Inactive"],
+ }
+
+ self.toon = toon
+ self.toon.setActiveShadow(True)
+
+ def enable(self):
+ pass
+
+ def unload(self):
+ del self.toon
+
+ def onstage(self):
+ self.request("Inactive")
+
+ def offstage(self):
+ self.request("Inactive")
+
+ def enterInactive(self):
+ CogdoFlyingPlayer.notify.info( "enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState) )
+
+ def exitInactive(self):
+ CogdoFlyingPlayer.notify.debug( "exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState) )
+
+ def enterFreeFly(self):
+ CogdoFlyingPlayer.notify.info( "enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState) )
+
+ def exitFreeFly(self):
+ CogdoFlyingPlayer.notify.debug( "exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState) )
+
+ def enterFlyingUp(self):
+ CogdoFlyingPlayer.notify.info( "enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState) )
+
+ def exitFlyingUp(self):
+ CogdoFlyingPlayer.notify.debug( "exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState) )
+
+ def enterDeath(self):
+ CogdoFlyingPlayer.notify.info( "enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState) )
+
+ def exitDeath(self):
+ CogdoFlyingPlayer.notify.debug( "exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState) )
+
+ def enterRefuel(self):
+ CogdoFlyingPlayer.notify.info( "enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState) )
+
+ def exitRefuel(self):
+ CogdoFlyingPlayer.notify.debug( "exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState) )
+
+ def enterWaitingForWin(self):
+ CogdoFlyingPlayer.notify.info( "enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState) )
+
+ def exitWaitingForWin(self):
+ CogdoFlyingPlayer.notify.debug( "exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState) )
+
+ def enterWin(self):
+ CogdoFlyingPlayer.notify.info( "enter%s: '%s' -> '%s'" % (self.newState, self.oldState, self.newState) )
+
+ def exitWin(self):
+ CogdoFlyingPlayer.notify.debug( "exit%s: '%s' -> '%s'" % (self.oldState, self.oldState, self.newState) )
+
+
+from direct.task.Task import Task
+from direct.interval.FunctionInterval import Wait
+from direct.interval.IntervalGlobal import Func, LerpHprInterval, LerpFunctionInterval, ActorInterval
+from direct.interval.MetaInterval import Sequence, Parallel
+
+from toontown.battle import BattleProps
+
+class CogdoFlyingLocalPlayer(CogdoFlyingPlayer):
+
+ def __init__(self, game, toon):
+ CogdoFlyingPlayer.__init__(self, toon)
+ self.velocity = Vec3(0.0,0.0,0.0)
+ self.lastVelocity = Vec3(0.0,0.0,0.0)
+ self.instantaneousVelocity = Vec3(0.0,0.0,0.0)
+ self.oldPos = Vec3(0.0,0.0,0.0)
+ self.game = game
+ self.inputMgr = CogdoFlyingInputManager()
+ self.cameraMgr = CogdoFlyingCameraManager(self, camera, render)
+ self.guiMgr = CogdoFlyingGuiManager(self)
+
+ self.checkpointPlatform = None
+
+ self.fuel = 0.0
+ self.props = {}
+
+ self.initModels()
+ self.initIntervals()
+
+ self.propSound = base.loadSfx('phase_4/audio/sfx/TB_propeller.wav')
+
+
+ def initModels(self):
+ # We place the propeller prop on all 3 lod's
+ self.placePropeller('1000')
+ self.placePropeller('500')
+ self.placePropeller('250')
+
+ def placePropeller(self, lod):
+ prop = BattleProps.globalPropPool.getProp('propeller')
+ prop.setScale(1.1)
+
+ self.props[lod] = prop
+
+ head = base.localAvatar.getPart('head', lod)
+
+ if head.isEmpty():
+ return
+
+ prop.reparentTo(head)
+ animal = base.localAvatar.style.getAnimal()
+ if (animal == 'dog') or (animal == 'bear') or (animal == 'horse'):
+ torso = base.localAvatar.style.torso
+ legs = base.localAvatar.style.legs
+ if ((torso == 'ls') or (torso == 'ld')) and (legs == 'l'):
+ prop.setZ(-1.3)
+ else:
+ prop.setZ(-0.7)
+ elif (animal == 'mouse') or (animal == 'duck'):
+ prop.setZ(0.5)
+ elif (animal == 'cat'):
+ prop.setZ(-0.3)
+ elif (animal == 'rabbit'):
+ prop.setZ(-0.5)
+ elif (animal == 'monkey'):
+ prop.setZ(0.3)
+ elif (animal == 'pig'):
+ prop.setZ(-0.7)
+
+ def initIntervals(self):
+ self.deathInterval = Sequence(
+ Func(self.inputMgr.disable),
+ Parallel(
+ LerpHprInterval(self.toon, 1.0, Vec3(720,0,0)),
+ LerpFunctionInterval(self.toon.setScale, fromData=1.0, toData=0.0, duration=1.0)
+ ),
+ Func(self.resetToon),
+ Wait(0.5), # Added this because with no pause here the FreeFly prop sound was attached to the old toon pos
+ Func(self.request, "FreeFly"),
+ name="%s.deathInterval" % (self.__class__.__name__)
+ )
+
+ self.refuelInterval = Sequence(
+ Func(self.guiMgr.setRefuelLerpFromData),
+ Func(self.guiMgr.messageLabel.unstash),
+ Parallel(
+ self.guiMgr.refuelLerp,
+ Sequence(
+ Func(self.guiMgr.setMessageLabelText, "Refueling"),
+ Wait(0.5),
+ Func(self.guiMgr.setMessageLabelText, "Refueling."),
+ Wait(0.5),
+ Func(self.guiMgr.setMessageLabelText, "Refueling.."),
+ Wait(0.5),
+ Func(self.guiMgr.setMessageLabelText, "Refueling..."),
+ Wait(0.5),
+ ),
+ ),
+ Func(self.resetFuel),
+ Func(self.guiMgr.messageLabel.stash),
+ Func(self.request, "FreeFly"),
+ name="%s.refuelInterval" % (self.__class__.__name__)
+ )
+
+ self.waitingForWinInterval = Sequence(
+ Func(self.guiMgr.setMessageLabelText, "Waiting for other players"),
+ Wait(1.5),
+ Func(self.guiMgr.setMessageLabelText, "Waiting for other players."),
+ Wait(1.5),
+ Func(self.guiMgr.setMessageLabelText, "Waiting for other players.."),
+ Wait(1.5),
+ Func(self.guiMgr.setMessageLabelText, "Waiting for other players..."),
+ Wait(1.5),
+ name="%s.waitingForWinInterval" % (self.__class__.__name__)
+ )
+
+ self.winInterval = Sequence(
+ Func(self.guiMgr.setMessageLabelText, ""),
+ Wait(1.0),
+ Func(self.guiMgr.winLabel.unstash),
+ Wait(2.0),
+ Func(self.guiMgr.winLabel.stash),
+ Wait(0.5),
+ Func(messenger.send, "escape"),
+ name="%s.waitingForWinInterval" % (self.__class__.__name__)
+ )
+
+ self.slowPropTrack = Parallel(
+ ActorInterval(self.props['1000'], 'propeller', startFrame = 8, endFrame = 23, playRate = 1.0),
+ ActorInterval(self.props['500'], 'propeller', startFrame = 8, endFrame = 23, playRate = 1.0),
+ ActorInterval(self.props['250'], 'propeller', startFrame = 8, endFrame = 23, playRate = 1.0),
+ )
+ self.fastPropTrack = Parallel(
+ ActorInterval(self.props['1000'], 'propeller', startFrame = 8, endFrame = 23, playRate = 2.0),
+ ActorInterval(self.props['500'], 'propeller', startFrame = 8, endFrame = 23, playRate = 2.0),
+ ActorInterval(self.props['250'], 'propeller', startFrame = 8, endFrame = 23, playRate = 2.0),
+ )
+
+ def onstage(self):
+# print "local player onstage"
+ CogdoFlyingPlayer.onstage(self)
+ self.toon.reparentTo(render)
+ self.toon.hideName()
+ self.toon.setSpeed(0, 0)
+# self.toon.setAnimState('Happy',1.0)
+ self.toon.loop('jump-idle')
+ # When we had the toon in anim state swim we needed this so that the
+ # player wasn't getting stuck to floors
+ self.toon.controlManager.currentControls.setGravity(0)
+# print "setting gravity to 0"
+ self.resetToon()
+ self.cameraMgr.enable()
+ self.cameraMgr.update()
+
+ def offstage(self):
+ #print "local player offstage"
+ CogdoFlyingPlayer.offstage(self)
+ self.cameraMgr.disable()
+ base.localAvatar.controlManager.currentControls.setGravity(32.174*2.0)
+ self.toon.showName()
+
+ #self.unload()
+
+ def unload(self):
+# print "unloading CogdoFlyingLocalPlayer"
+ self.toon.showName()
+ CogdoFlyingPlayer.unload(self)
+
+ self.checkpointPlatform = None
+
+ self.cameraMgr.disable()
+ del self.cameraMgr
+
+ base.localAvatar.controlManager.currentControls.setGravity(32.174*2.0)
+ del self.game
+
+ self.inputMgr.destroy()
+ del self.inputMgr
+
+ self.props['1000'].detachNode()
+ self.props['500'].detachNode()
+ self.props['250'].detachNode()
+ self.props.clear()
+
+ self.slowPropTrack.clearToInitial()
+ del self.slowPropTrack
+
+ self.fastPropTrack.clearToInitial()
+ del self.fastPropTrack
+
+ del self.propSound
+
+ self.deathInterval.clearToInitial()
+ del self.deathInterval
+
+ self.refuelInterval.clearToInitial()
+ del self.refuelInterval
+
+ self.guiMgr.destroy()
+ self.guiMgr = None
+
+ def setCheckpointPlatform(self, platform):
+ self.checkpointPlatform = platform
+
+ def resetToon(self):
+# print "Reset toon"
+# print "------------------------"
+# self.checkpointPlatform.ls()
+ self.toon.setPos(render, self.checkpointPlatform.find("**/start_p1").getPos(render))
+ self.toon.setHpr(render, 0, 0, 0)
+ self.toon.setScale(1.0)
+# self.toon.loop('jump-idle')
+ self.toon.collisionsOn()
+# self.toon.stopBobSwimTask()
+# self.toon.getGeomNode().setZ(1.0)
+ self.resetFuel()
+
+ def resetFuel(self):
+ self.fuel = CogdoFlyingGameGlobals.FlyingGame.FUEL_START_AMT
+
+ def isFuelLeft(self):
+ return self.fuel > 0.0
+
+ def __updateToonMovement(self):
+ # move the local toon
+ dt = globalClock.getDt()
+ leftPressed = self.inputMgr.arrowKeys.leftPressed()
+ rightPressed = self.inputMgr.arrowKeys.rightPressed()
+ upPressed = self.inputMgr.arrowKeys.upPressed()
+ downPressed = self.inputMgr.arrowKeys.downPressed()
+ jumpPressed = self.inputMgr.arrowKeys.jumpPressed()
+
+# print leftPressed,rightPressed,upPressed,downPressed,jumpPressed
+ self.instantaneousVelocity = (self.toon.getPos() - self.oldPos)/dt
+ self.oldPos = self.toon.getPos()
+ toonPos = self.toon.getPos()
+
+ self.lastVelocity = Vec3(self.velocity)
+
+ # Adds boost to velocity values and calculates toon pos changes
+ if leftPressed:
+ self.velocity[0] -= CogdoFlyingGameGlobals.FlyingGame.TOON_ACCELERATION["turning"]*dt
+ if rightPressed:
+ self.velocity[0] += CogdoFlyingGameGlobals.FlyingGame.TOON_ACCELERATION["turning"]*dt
+ if upPressed:
+ self.velocity[1] += CogdoFlyingGameGlobals.FlyingGame.TOON_ACCELERATION["forward"]*dt
+ if downPressed:
+ self.velocity[1] -= CogdoFlyingGameGlobals.FlyingGame.TOON_ACCELERATION["backward"]*dt
+
+
+# print jumpPressed
+ if jumpPressed and self.isFuelLeft():
+ self.velocity[2] += CogdoFlyingGameGlobals.FlyingGame.TOON_ACCELERATION["vertical"]*dt
+ if self.state == "FreeFly" and self.isInTransition() == False:
+ #print "Going to flying up"
+ self.request("FlyingUp")
+ else:
+ if self.state == "FlyingUp" and self.isInTransition() == False:
+ #print "Going to free fly"
+ self.request("FreeFly")
+
+ toonPos += self.velocity*dt
+
+ # TODO:flying: death probably needs to happen on the server...
+ if (CogdoFlyingGameGlobals.FlyingGame.DISABLE_DEATH) or \
+ (base.config.GetBool('cogdo-flying-game-disable-death', 0)):
+ pass
+ else:
+ # Tests to see whether the toon has dropped low enough to die
+ if toonPos[2] < 0.0 and self.state in ["FreeFly","FlyingUp"]:
+ self.request("Death")
+
+ toonPos[2] = clamp(toonPos[2], self.game.downLimit, self.game.upLimit)
+ toonPos[0] = clamp(toonPos[0], self.game.leftLimit, self.game.rightLimit)
+
+ # Sets toon position based on velocity
+ self.toon.setPos(toonPos)
+
+ #print "Before degrades:",self.velocity
+
+ # Degrades left/right velocity values back to normal
+ minVal = -CogdoFlyingGameGlobals.FlyingGame.TOON_VEL_MAX["turning"]
+ maxVal = CogdoFlyingGameGlobals.FlyingGame.TOON_VEL_MAX["turning"]
+ if (not leftPressed and not rightPressed) or (self.velocity[0] > maxVal or self.velocity[0] < minVal):
+ if self.velocity[0] > 0.0:
+ self.velocity[0] -= CogdoFlyingGameGlobals.FlyingGame.TOON_DECELERATION["turning"] * dt
+ self.velocity[0] = clamp(self.velocity[0], 0.0, maxVal)
+ elif self.velocity[0] < 0.0:
+ self.velocity[0] += CogdoFlyingGameGlobals.FlyingGame.TOON_DECELERATION["turning"] * dt
+ self.velocity[0] = clamp(self.velocity[0], minVal, 0.0)
+
+ # Degrades forward/backward velocity values back to normal
+ minVal = -CogdoFlyingGameGlobals.FlyingGame.TOON_VEL_MAX["backward"]
+ maxVal = CogdoFlyingGameGlobals.FlyingGame.TOON_VEL_MAX["forward"]
+ if (not upPressed and not downPressed) or (self.velocity[1] > maxVal or self.velocity[1] < minVal):
+ if self.velocity[1] > 0.0:
+ self.velocity[1] -= CogdoFlyingGameGlobals.FlyingGame.TOON_DECELERATION["forward"] * dt
+ self.velocity[1] = clamp(self.velocity[1], 0.0, maxVal)
+ elif self.velocity[1] < 0.0:
+ self.velocity[1] += CogdoFlyingGameGlobals.FlyingGame.TOON_DECELERATION["backward"] * dt
+ self.velocity[1] = clamp(self.velocity[1], minVal, 0.0)
+
+ # Degrades boost/fall velocity values back to normal
+ minVal = -CogdoFlyingGameGlobals.FlyingGame.TOON_VEL_MAX["vertical"]
+ maxVal = CogdoFlyingGameGlobals.FlyingGame.TOON_VEL_MAX["vertical"]
+ if self.velocity[2] > minVal:
+ if not self.inputMgr.arrowKeys.jumpPressed():
+ self.velocity[2] -= CogdoFlyingGameGlobals.FlyingGame.TOON_DECELERATION["vertical"] * dt
+
+ self.velocity[2] = clamp(self.velocity[2], minVal, maxVal)
+
+ #print self.lastVelocity, self.velocity
+# if self.lastVelocity != self.velocity:
+# print "Velocity:",self.velocity
+
+ def __updateFuel(self):
+ dt = globalClock.getDt()
+
+ if CogdoFlyingGameGlobals.FlyingGame.INFINITE_FUEL:
+ self.fuel = CogdoFlyingGameGlobals.FlyingGame.FUEL_START_AMT
+ else:
+ if self.fuel > 0.0:
+ self.fuel -= CogdoFlyingGameGlobals.FlyingGame.FUEL_BURN_RATE * dt
+ elif self.fuel < 0.0:
+ self.fuel = 0.0
+
+
+ def update(self):
+ if self.state not in ["Inactive","Refuel","WaitingForWin","Win"]:
+ self.__updateToonMovement()
+ self.__updateFuel()
+ self.cameraMgr.update()
+ self.guiMgr.update()
+
+ def enterInactive(self):
+ CogdoFlyingPlayer.enterInactive(self)
+ self.inputMgr.disable()
+
+ def exitInactive(self):
+ CogdoFlyingPlayer.exitInactive(self)
+ self.inputMgr.enable()
+
+ def enterRefuel(self):
+ CogdoFlyingPlayer.enterInactive(self)
+ self.inputMgr.disable()
+ self.refuelInterval.start()
+
+ def exitRefuel(self):
+ CogdoFlyingPlayer.exitInactive(self)
+ self.inputMgr.enable()
+
+ def enterFreeFly(self):
+ CogdoFlyingPlayer.enterFreeFly(self)
+ self.slowPropTrack.loop()
+ base.playSfx(self.propSound, node = self.toon, looping = 1)
+ self.propSound.setPlayRate(0.9)
+
+ def exitFreeFly(self):
+ CogdoFlyingPlayer.exitFreeFly(self)
+ self.slowPropTrack.clearToInitial()
+ self.propSound.stop()
+
+ def enterFlyingUp(self):
+ CogdoFlyingPlayer.enterFlyingUp(self)
+ self.fastPropTrack.loop()
+ base.playSfx(self.propSound, node = self.toon, looping = 1)
+ self.propSound.setPlayRate(1.1)
+
+ def exitFlyingUp(self):
+ CogdoFlyingPlayer.exitFlyingUp(self)
+ self.fastPropTrack.clearToInitial()
+ self.propSound.stop()
+
+ def enterDeath(self):
+ CogdoFlyingPlayer.enterDeath(self)
+ self.inputMgr.disable()
+ self.deathInterval.start()
+
+ def exitDeath(self):
+ CogdoFlyingPlayer.exitDeath(self)
+ self.inputMgr.enable()
+
+ def enterWaitingForWin(self):
+ CogdoFlyingPlayer.enterDeath(self)
+ self.inputMgr.disable()
+ self.guiMgr.messageLabel.unstash()
+ self.waitingForWinInterval.loop()
+
+ def exitWaitingForWin(self):
+ CogdoFlyingPlayer.exitDeath(self)
+ self.waitingForWinInterval.clearToInitial()
+ self.guiMgr.messageLabel.stash()
+ self.inputMgr.enable()
+
+ def enterWin(self):
+ CogdoFlyingPlayer.enterDeath(self)
+ self.inputMgr.disable()
+ self.winInterval.start()
+
+ def exitWin(self):
+ CogdoFlyingPlayer.exitDeath(self)
+ self.inputMgr.enable()
+
+
+class CogdoFlyingInputManager:
+ def __init__(self):
+ self.arrowKeys = ArrowKeys.ArrowKeys()
+ self.arrowKeys.disable()
+
+ def enable(self):
+ #print "CogdoFlyingInputManager.enable()"
+ self.arrowKeys.setPressHandlers([
+ self.__upArrowPressed,
+ self.__downArrowPressed,
+ self.__leftArrowPressed,
+ self.__rightArrowPressed,
+ self.__controlPressed])
+ self.arrowKeys.enable()
+
+ def disable(self):
+ self.arrowKeys.clearPressHandlers()
+ self.arrowKeys.disable()
+
+ def destroy(self):
+ print "Destroying CogdoFlyingInputManager"
+ self.disable()
+ self.arrowKeys.destroy()
+ self.arrowKeys = None
+
+ self.refuelLerp = None
+
+ def __upArrowPressed(self):
+ """Handle up arrow being pressed."""
+ pass
+ #print "__upArrowPressed"
+
+ def __downArrowPressed(self):
+ """Handle down arrow being pressed."""
+ pass
+# print "__downArrowPressed"
+
+ def __leftArrowPressed(self):
+ """Handle left arrow being pressed."""
+ pass
+# print "__leftArrowPressed"
+
+ def __rightArrowPressed(self):
+ """Handle right arrow being pressed."""
+ pass
+# print "__rightArrowPressed"
+
+ def __controlPressed(self):
+ """Handle control key being pressed."""
+ pass
+# print "__controlPressed"
+
+import math
+from toontown.parties.PartyCogUtils import CameraManager
+
+inverse_e = 1.0/math.e
+
+class FlyingCamera(CameraManager):
+ def __init__(self, cameraNP):
+ CameraManager.__init__(self,cameraNP)
+ self.vecRate = Vec3(self.rate, self.rate, self.rate)
+
+ def rateInterpolate(self, currentPos, targetPos):
+ dt = globalClock.getDt()
+ vec = currentPos - targetPos
+ return Vec3(targetPos[0] + vec[0]*(inverse_e**(dt*self.vecRate[0])),
+ targetPos[1] + vec[1]*(inverse_e**(dt*self.vecRate[1])),
+ targetPos[2] + vec[2]*(inverse_e**(dt*self.vecRate[2]))
+ )
+
+class CogdoFlyingCameraManager:
+ def __init__(self, player, cam, root):
+ self.player = player
+ self.toon = player.toon
+ self.camera = cam
+ self.root = root
+
+ self.camOffset = VBase3(0, -12, 5)
+ self.cameraManager = FlyingCamera(self.camera)
+ self.cameraManager.vecRate = Vec3(3.0, 2.0, 1.8)
+ self.cameraManager.otherNP = self.root
+
+ def enable(self):
+ self.camera.reparentTo(self.cameraManager.otherNP)
+ self.cameraManager.setPos(self.toon.getPos() + self.camOffset)
+ self.cameraManager.setLookAtPos(self.toon.getPos())
+ self.cameraManager.setEnabled(True)
+
+ def setCameraOffset(self, offset):
+ self.camera.setPos(offset)
+ self.camera.lookAt(self.toon)
+
+ def disable(self):
+ self.camera.wrtReparentTo(render)
+ self.cameraManager.setEnabled(False)
+
+
+ def update(self):
+ toonPos = self.toon.getPos()
+ newOffset = Vec3(self.player.instantaneousVelocity.getX()*0.1,self.player.instantaneousVelocity.getY()*2.0,self.player.velocity.getZ())
+ targetPos = toonPos + self.camOffset + Vec3(0,0,-newOffset.getZ()*0.3)
+ targetLookAt = toonPos + Vec3(0,35,0) + newOffset
+
+ if self.player.instantaneousVelocity.getY() < 0.0 or (self.player.instantaneousVelocity.getY() <= 0.0 and self.player.instantaneousVelocity.getZ() < 0.0):
+ targetPos = targetPos + Vec3(0, +2.0, abs(self.player.instantaneousVelocity.getZ()*0.4))
+ targetLookAt = targetLookAt + Vec3(0,0,-abs(self.player.instantaneousVelocity.getZ()*0.2))
+ targetLookAt[1] = toonPos[1]
+
+ p = 0.90
+ targetPos[0] = clamp(targetPos[0], self.player.game.leftLimit*p, self.player.game.rightLimit*p)
+ targetLookAt[0] = clamp(targetLookAt[0], self.player.game.leftLimit*p, self.player.game.rightLimit*p)
+
+ #targetPos[2] = clamp(targetPos[2], self.player.game.downLimit*p, self.player.game.upLimit*p)
+ p = 0.95
+ targetLookAt[2] = clamp(targetLookAt[2], self.player.game.downLimit*p, self.player.game.upLimit*p)
+
+ self.cameraManager.setTargetPos(targetPos)
+ self.cameraManager.setTargetLookAtPos(targetLookAt)
+
+
+from pandac.PandaModules import CardMaker, TextNode
+from direct.gui.DirectGui import DirectLabel
+from toontown.toonbase import ToontownGlobals
+
+class CogdoFlyingGuiManager:
+ def __init__(self, player):
+
+ self.player = player
+
+ self.root = NodePath("CogdoFlyingGui")
+ self.root.reparentTo(aspect2d)
+
+ self.fuelMeter = NodePath("scrubMeter")
+ self.fuelMeter.reparentTo(self.root)
+ self.fuelMeter.setPos(1.1, 0.0, -0.7)
+ self.fuelMeter.setSz(2.0)
+
+ cm = CardMaker('card')
+ cm.setFrame( -0.07, 0.07, 0.0, 0.75 )
+ self.fuelMeterBar = self.fuelMeter.attachNewNode(cm.generate())
+ self.fuelMeterBar.setColor(0.95, 0.95, 0.0, 1.0)
+
+
+ self.fuelLabel = DirectLabel(
+ parent = self.root,
+ relief = None,
+ pos = (1.1,0,-0.8),
+ scale = 0.075,
+ text = "Fuel",
+ text_fg = (0.95, 0.95, 0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getInterfaceFont(),
+ )
+
+ self.messageLabel = DirectLabel(
+ parent = self.root,
+ relief = None,
+ pos = (0.0,0.0,-0.9),
+ scale = 0.1,
+ text = " ",
+ text_align = TextNode.ACenter,
+ text_fg = (0.95, 0.95, 0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getInterfaceFont(),
+ textMayChange = 1,
+ )
+ self.messageLabel.stash()
+
+ self.winLabel = DirectLabel(
+ parent = self.root,
+ relief = None,
+ pos = (0.0,0.0,0.0),
+ scale = 0.25,
+ text = "You win!",
+ text_align = TextNode.ACenter,
+ text_fg = (0.95, 0.95, 0, 1),
+ text_shadow = (0, 0, 0, 1),
+ text_font = ToontownGlobals.getInterfaceFont(),
+ )
+ self.winLabel.stash()
+
+ self.refuelLerp = LerpFunctionInterval(self.fuelMeterBar.setSz, fromData=0.0, toData=1.0, duration=2.0)
+
+ def setRefuelLerpFromData(self):
+ startScale = self.fuelMeterBar.getSz()
+ self.refuelLerp.fromData = startScale
+
+ def setMessageLabelText(self,text):
+ self.messageLabel["text"] = text
+ self.messageLabel.setText()
+
+ def update(self):
+ self.fuelMeterBar.setSz(self.player.fuel)
+
+ def destroy(self):
+# print "Destroying GUI"
+ self.fuelMeterBar.detachNode()
+ self.fuelMeterBar = None
+
+ self.fuelLabel.detachNode()
+ self.fuelLabel = None
+
+ self.fuelMeter.detachNode()
+ self.fuelMeter = None
+
+ self.winLabel.detachNode()
+ self.winLabel = None
+
+ self.root.detachNode()
+ self.root = None
+
+ self.player = None
+
+
diff --git a/toontown/src/cogdominium/DistCogdoFlyingGameAI.py b/toontown/src/cogdominium/DistCogdoFlyingGameAI.py
new file mode 100644
index 0000000..1dfb6e1
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoFlyingGameAI.py
@@ -0,0 +1,32 @@
+"""
+@author: Schell Games
+3-16-2010
+"""
+from toontown.minigame.DistributedMinigameAI import DistributedMinigameAI
+from toontown.minigame.DistributedMinigameAI import EXITED, EXPECTED, JOINED, READY
+
+class DistCogdoFlyingGameAI(DistributedMinigameAI):
+ """
+ Flying Cogdominium Minigame AI Distributed Object!
+ """
+ notify = directNotify.newCategory("DistCogdoFlyingGameAI")
+
+ def __init__(self, air, id):
+ try:
+ self.DistCogdoFlyingGameAI_initialized
+ except:
+ self.DistCogdoFlyingGameAI_initialized = 1
+ DistributedMinigameAI.__init__(self, air, id)
+
+ print "FLYING COGDO GAME AI CREATED!"
+
+ def areAllPlayersReady(self):
+ ready = True
+
+ for avId in self.avIdList:
+ ready = ready and (self.stateDict[avId] == READY)
+
+ return ready
+
+ def setAvatarReady(self):
+ DistributedMinigameAI.setAvatarReady(self)
diff --git a/toontown/src/cogdominium/DistCogdoGame.py b/toontown/src/cogdominium/DistCogdoGame.py
new file mode 100644
index 0000000..1137ed4
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoGame.py
@@ -0,0 +1,190 @@
+from pandac.PandaModules import VBase4
+from direct.gui.DirectGui import DirectLabel
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.distributed.ClockDelta import globalClockDelta
+from direct.distributed.DistributedObject import DistributedObject
+from direct.fsm import ClassicFSM, State
+from toontown.minigame.MinigameRulesPanel import MinigameRulesPanel
+from toontown.toonbase import TTLocalizer as TTL
+
+class DistCogdoGame(DistributedObject):
+ notify = directNotify.newCategory("DistCogdoGame")
+
+ def __init__(self, cr):
+ DistributedObject.__init__(self, cr)
+
+ self._waitingStartLabel = DirectLabel(
+ text = TTL.MinigameWaitingForOtherPlayers,
+ text_fg = VBase4(1,1,1,1),
+ relief = None,
+ pos = (-0.6, 0, -0.75),
+ scale = 0.075)
+ self._waitingStartLabel.hide()
+
+ self.loadFSM = ClassicFSM.ClassicFSM(
+ 'DistCogdoGame.loaded',
+ [State.State('NotLoaded',
+ self.enterNotLoaded,
+ self.exitNotLoaded,
+ ['Loaded']),
+ State.State('Loaded',
+ self.enterLoaded,
+ self.exitLoaded,
+ ['NotLoaded'])],
+ # Initial state
+ 'NotLoaded',
+ # Final state
+ 'NotLoaded')
+
+ self.fsm = ClassicFSM.ClassicFSM(
+ 'DistCogdoGame',
+ [State.State('Intro',
+ self.enterIntro,
+ self.exitIntro,
+ ['WaitServerStart']),
+ State.State('WaitServerStart',
+ self.enterWaitServerStart,
+ self.exitWaitServerStart,
+ ['Game']),
+ State.State('Game',
+ self.enterGame,
+ self.exitGame,
+ ['Finish']),
+ State.State('Finish',
+ self.enterFinish,
+ self.exitFinish,
+ ['Off']),
+ State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Intro'])],
+ # Initial state
+ 'Off',
+ # Final state
+ 'Off')
+ self.fsm.enterInitialState()
+
+ def getTitle(self):
+ pass # override and return title
+
+ def getInstructions(self):
+ pass # override and return instructions
+
+ def setInteriorId(self, interiorId):
+ self._interiorId = interiorId
+
+ def getInterior(self):
+ return self.cr.getDo(self._interiorId)
+
+ def getToonIds(self):
+ toonIds = []
+ interior = self.getInterior()
+ for toonId in interior.getToons()[0]:
+ if toonId:
+ toonIds.append(toonId)
+ return toonIds
+
+ def getNumPlayers(self):
+ return len(self.getToonIds())
+
+ def announceGenerate(self):
+ DistributedObject.announceGenerate(self)
+ self.loadFSM.request('Loaded')
+
+ def disable(self):
+ self.fsm.requestFinalState()
+ self.loadFSM.requestFinalState()
+ self.fsm = None
+ self.loadFSM = None
+ DistributedObject.disable(self)
+
+ def delete(self):
+ self._waitingStartLabel.destroy()
+ self._waitingStartLabel = None
+ DistributedObject.delete(self)
+
+
+ def enterNotLoaded(self):
+ pass
+ def exitNotLoaded(self):
+ pass
+
+ def enterLoaded(self):
+ pass
+ def exitLoaded(self):
+ pass
+
+
+ def enterOff(self):
+ pass
+ def exitOff(self):
+ pass
+
+ def setIntroStart(self):
+ self.fsm.request('Intro')
+
+ def enterIntro(self):
+ assert self.notify.debugCall()
+ base.cr.playGame.getPlace().fsm.request('Game')
+ self._rulesDoneEvent = uniqueName('cogdoGameRulesDone')
+ self.accept(self._rulesDoneEvent, self._handleRulesDone)
+ self._rulesPanel = MinigameRulesPanel(
+ "MinigameRulesPanel",
+ self.getTitle(),
+ self.getInstructions(),
+ self._rulesDoneEvent)
+ self._rulesPanel.load()
+ self._rulesPanel.enter()
+
+ def exitIntro(self):
+ self.ignore(self._rulesDoneEvent)
+ if self._rulesPanel:
+ self._rulesPanel.exit()
+ self._rulesPanel.unload()
+ self._rulesPanel = None
+
+ def _handleRulesDone(self):
+ self.ignore(self._rulesDoneEvent)
+ self._rulesPanel.exit()
+ self._rulesPanel.unload()
+ self._rulesPanel = None
+ self.sendUpdate('setAvatarReady', [])
+ self.fsm.request('WaitServerStart')
+
+ def enterWaitServerStart(self):
+ numToons = 1
+ interior = self.getInterior()
+ if interior:
+ numToons = len(interior.getToonIds())
+ if numToons > 1:
+ msg = TTL.MinigameWaitingForOtherPlayers
+ else:
+ msg = TTL.MinigamePleaseWait
+ self._waitingStartLabel['text'] = msg
+ self._waitingStartLabel.show()
+ def exitWaitServerStart(self):
+ self._waitingStartLabel.hide()
+
+ def setGameStart(self, timestamp):
+ self._startTime = globalClockDelta.networkToLocalTime(timestamp)
+ self.fsm.request('Game')
+
+ def getStartTime(self):
+ return self._startTime
+
+ def enterGame(self):
+ assert self.notify.debugCall()
+ def exitGame(self):
+ pass
+
+ def setGameFinish(self, timestamp):
+ self._finishTime = globalClockDelta.networkToLocalTime(timestamp)
+ self.fsm.request('Finish')
+
+ def getFinishTime(self):
+ return self._finishTime
+
+ def enterFinish(self):
+ assert self.notify.debugCall()
+ def exitFinish(self):
+ pass
diff --git a/toontown/src/cogdominium/DistCogdoGameAI.py b/toontown/src/cogdominium/DistCogdoGameAI.py
new file mode 100644
index 0000000..40f2d44
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoGameAI.py
@@ -0,0 +1,201 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from direct.distributed.ClockDelta import globalClockDelta
+from direct.distributed.DistributedObjectAI import DistributedObjectAI
+from direct.fsm import ClassicFSM, State
+from otp.ai.Barrier import Barrier
+
+class SadCallbackToken:
+ pass
+
+class DistCogdoGameAI(DistributedObjectAI):
+ notify = directNotify.newCategory("DistCogdoGameAI")
+
+ MaxPlayers = 4
+
+ def __init__(self, air, interior):
+ DistributedObjectAI.__init__(self, air)
+ self._interior = interior
+
+ self.loadFSM = ClassicFSM.ClassicFSM(
+ 'DistCogdoGameAI.loaded',
+ [State.State('NotLoaded',
+ self.enterNotLoaded,
+ self.exitNotLoaded,
+ ['Loaded']),
+ State.State('Loaded',
+ self.enterLoaded,
+ self.exitLoaded,
+ ['NotLoaded'])],
+ # Initial state
+ 'NotLoaded',
+ # Final state
+ 'NotLoaded')
+
+ self.fsm = ClassicFSM.ClassicFSM(
+ 'DistCogdoGameAI',
+ [State.State('Intro',
+ self.enterIntro,
+ self.exitIntro,
+ ['Game']),
+ State.State('Game',
+ self.enterGame,
+ self.exitGame,
+ ['Finish']),
+ State.State('Finish',
+ self.enterFinish,
+ self.exitFinish,
+ ['Off']),
+ State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Intro'])],
+ # Initial state
+ 'Off',
+ # Final state
+ 'Off')
+
+ def generate(self):
+ DistributedObjectAI.generate(self)
+ self._sadToken2callback = {}
+ self._interior.addGameFSM(self.fsm)
+
+ def getInteriorId(self):
+ return self._interior.doId
+
+ def getToonIds(self):
+ toonIds = []
+ for toonId in self._interior.getToons()[0]:
+ if toonId:
+ toonIds.append(toonId)
+ return toonIds
+
+ def getNumPlayers(self):
+ return len(self.getToonIds())
+
+ def requestDelete(self):
+ self.fsm.requestFinalState()
+ self.loadFSM.requestFinalState()
+ self._sadToken2callback = None
+ DistributedObjectAI.requestDelete(self)
+
+ def delete(self):
+ self._interior.removeGameFSM(self.fsm)
+ self._interior = None
+ self.fsm = None
+ self.loadFSM = None
+ DistributedObjectAI.delete(self)
+
+ def start(self):
+ self.loadFSM.enterInitialState()
+ self.fsm.enterInitialState()
+
+ self.loadFSM.request('Loaded')
+ self.fsm.request('Intro')
+
+ def markStartTime(self):
+ self._startTime = globalClock.getRealTime()
+
+ def getStartTime(self):
+ return self._startTime
+
+ def markFinishTime(self):
+ self._finishTime = globalClock.getRealTime()
+
+ def getFinishTime(self):
+ return self._finishTime
+
+ ########################
+
+ # game class must override these methods if anything needs to be done when a toon
+ # leaves the game. game class should call down
+
+ def handleToonDisconnected(self, toonId):
+ self.notify.debug('handleToonDisconnected: %s' % toonId)
+
+ def handleToonWentSad(self, toonId):
+ self.notify.debug('handleToonWentSad: %s' % toonId)
+ callbacks = self._sadToken2callback.values()
+ for callback in callbacks:
+ callback(toonId)
+
+ ########################
+
+ # use these methods to get notification when a toon goes sad
+ def _registerSadCallback(self, callback):
+ token = SadCallbackToken()
+ self._sadToken2callback[token] = callback
+ return token
+
+ def _unregisterSadCallback(self, token):
+ self._sadToken2callback.pop(token)
+
+
+ def enterNotLoaded(self):
+ pass
+ def exitNotLoaded(self):
+ pass
+
+ def enterLoaded(self):
+ pass
+ def exitLoaded(self):
+ pass
+
+
+ def enterOff(self):
+ pass
+ def exitOff(self):
+ pass
+
+ def enterSetup(self):
+ pass
+ def exitSetup(self):
+ pass
+
+ def enterIntro(self):
+ self.sendUpdate('setIntroStart', [])
+ self._introBarrier = Barrier('intro', uniqueName('intro'), self.getToonIds(), 1<<20,
+ doneFunc=self._handleIntroBarrierDone)
+ self._sadToken = self._registerSadCallback(self._handleSadToonDuringIntro)
+ def exitIntro(self):
+ self._unregisterSadCallback(self._sadToken)
+ self._sadToken = None
+ self._introBarrier.cleanup()
+ self._introBarrier = None
+
+ def _handleSadToonDuringIntro(self, toonId):
+ # shouldn't be possible unless someone hacks their client or we add some
+ # sort of DOT
+ self._introBarrier.clear(toonId)
+
+ def setAvatarReady(self):
+ senderId = self.air.getAvatarIdFromSender()
+ if senderId not in self.getToonIds():
+ self.air.writeServerEvent('suspicious', senderId, 'CogdoGameAI.setAvatarReady: unknown avatar')
+ return
+ if self._introBarrier:
+ self._introBarrier.clear(senderId)
+
+ def _handleIntroBarrierDone(self, avIds):
+ self.fsm.request('Game')
+
+ def enterGame(self):
+ self.markStartTime()
+ self.sendUpdate('setGameStart', [
+ globalClockDelta.localToNetworkTime(self.getStartTime())])
+ def exitGame(self):
+ pass
+
+ def _handleGameFinished(self):
+ # call this from subclass when Game state is completed
+ self.fsm.request('Finish')
+
+ def enterFinish(self):
+ self.markFinishTime()
+ self.sendUpdate('setGameFinish', [
+ globalClockDelta.localToNetworkTime(self.getFinishTime())])
+ def exitFinish(self):
+ pass
+
+ def announceGameDone(self):
+ # call this from subclass when Finish state is completed
+ self._interior._gameDone()
diff --git a/toontown/src/cogdominium/DistCogdoLevelGame.py b/toontown/src/cogdominium/DistCogdoLevelGame.py
new file mode 100644
index 0000000..80fee2e
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoLevelGame.py
@@ -0,0 +1,82 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from otp.level.DistributedLevel import DistributedLevel
+from otp.level import LevelConstants
+from otp.level import EditorGlobals
+from toontown.cogdominium.DistCogdoGame import DistCogdoGame
+from toontown.cogdominium.CogdoEntityCreator import CogdoEntityCreator
+
+class DistCogdoLevelGame(DistributedLevel, DistCogdoGame):
+ notify = directNotify.newCategory("DistCogdoLevelGame")
+
+ def __init__(self, cr):
+ DistributedLevel.__init__(self, cr)
+ DistCogdoGame.__init__(self, cr)
+
+ def createEntityCreator(self):
+ return CogdoEntityCreator(level=self)
+
+ def generate(self):
+ DistributedLevel.generate(self)
+ DistCogdoGame.generate(self)
+ if __dev__:
+ bboard.post(EditorGlobals.EditTargetPostName, self)
+
+ def announceGenerate(self):
+ DistributedLevel.announceGenerate(self)
+ DistCogdoGame.announceGenerate(self)
+ self.startHandleEdits()
+
+ def levelAnnounceGenerate(self):
+ self.notify.debug('levelAnnounceGenerate')
+ DistributedLevel.levelAnnounceGenerate(self)
+
+ # create our spec
+ # NOTE: in dev, the AI will probably send us another spec to use
+ spec = self.getLevelSpec()
+ if __dev__:
+ # give the spec an EntityTypeRegistry.
+ typeReg = self.getEntityTypeReg()
+ spec.setEntityTypeReg(typeReg)
+
+ DistributedLevel.initializeLevel(self, spec)
+
+ # if the AI is sending us a spec, we won't have it yet and the
+ # level isn't really initialized yet. So we can't assume that we
+ # can start doing stuff here. Much of what used to be here
+ # has been moved to FactoryLevelMgr, where it really belongs, but...
+ # this could be cleaner.
+
+ def privGotSpec(self, levelSpec):
+ # OK, we've got the spec that we're going to use, either the one
+ # we provided or the one from the AI. When we call down, the level
+ # is going to be initialized, and all the local entities will be
+ # created.
+ if __dev__:
+ # First, give the spec a factory EntityTypeRegistry if it doesn't
+ # have one.
+ if not levelSpec.hasEntityTypeReg():
+ typeReg = self.getEntityTypeReg()
+ levelSpec.setEntityTypeReg(typeReg)
+
+ DistributedLevel.privGotSpec(self, levelSpec)
+
+ def initVisibility(self):
+ # prevent crash in initVisibility
+ levelMgr = self.getEntity(LevelConstants.LevelMgrEntId)
+ levelMgr.geom.reparentTo(render)
+ DistributedLevel.initVisibility(self)
+
+ def placeLocalToon(self):
+ # don't let the level move the local toon at init
+ DistributedLevel.placeLocalToon(self, moveLocalAvatar=False)
+
+ def disable(self):
+ self.stopHandleEdits()
+ DistCogdoGame.disable(self)
+ DistributedLevel.disable(self)
+
+ def delete(self):
+ DistCogdoGame.delete(self)
+ DistributedLevel.delete(self)
+ if __dev__:
+ bboard.removeIfEqual(EditorGlobals.EditTargetPostName, self)
diff --git a/toontown/src/cogdominium/DistCogdoLevelGameAI.py b/toontown/src/cogdominium/DistCogdoLevelGameAI.py
new file mode 100644
index 0000000..6aaed78
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoLevelGameAI.py
@@ -0,0 +1,37 @@
+from direct.directnotify.DirectNotifyGlobal import directNotify
+from otp.level.DistributedLevelAI import DistributedLevelAI
+from toontown.cogdominium.DistCogdoGameAI import DistCogdoGameAI
+from toontown.cogdominium.CogdoEntityCreatorAI import CogdoEntityCreatorAI
+
+class DistCogdoLevelGameAI(DistributedLevelAI, DistCogdoGameAI):
+ notify = directNotify.newCategory("DistCogdoLevelGameAI")
+
+ def __init__(self, air, interior):
+ DistCogdoGameAI.__init__(self, air, interior)
+ DistributedLevelAI.__init__(self, air, self.zoneId, 0, self.getToonIds())
+
+ def createEntityCreator(self):
+ return CogdoEntityCreatorAI(level=self)
+
+ def generate(self):
+ # create our spec
+ self.notify.info('loading spec')
+ spec = self.getLevelSpec()
+ if __dev__:
+ # create an EntityTypeRegistry and hand it to the spec
+ self.notify.info('creating entity type registry')
+ typeReg = self.getEntityTypeReg()
+ spec.setEntityTypeReg(typeReg)
+
+ DistributedLevelAI.generate(self, spec)
+ DistCogdoGameAI.generate(self)
+ self.startHandleEdits()
+
+ def requestDelete(self):
+ DistCogdoGameAI.requestDelete(self)
+
+ def delete(self):
+ self.stopHandleEdits()
+ DistCogdoGameAI.delete(self)
+ DistributedLevelAI.delete(self, deAllocZone=False)
+
diff --git a/toontown/src/cogdominium/DistCogdoMazeGame.py b/toontown/src/cogdominium/DistCogdoMazeGame.py
new file mode 100644
index 0000000..73507f8
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoMazeGame.py
@@ -0,0 +1,1029 @@
+"""
+@author: Schell Games
+3-16-2010
+"""
+from direct.showbase.DirectObject import DirectObject
+
+from pandac.PandaModules import NodePath, VBase4, Fog
+
+from toontown.minigame.DistributedMinigame import DistributedMinigame
+from toontown.minigame.MazeMapGui import MazeMapGui
+
+import CogdoMazeGameGlobals
+from CogdoMazeGameGlobals import CogdoMazeLockInfo
+
+class DistCogdoMazeGame(DistributedMinigame):
+ """
+ Maze Cogdominium Minigame client Distributed Object!
+ Class is primarily a controller for the networking/game events.
+ Visuals are handled by CogdoMazeGame instead.
+ """
+ notify = directNotify.newCategory("DistCogdoMazeGame")
+
+ def __init__(self, cr):
+ DistributedMinigame.__init__(self, cr)
+ self.game = CogdoMazeGame(self)
+
+ def load(self):
+ DistributedMinigame.load(self)
+ self._remoteActionEventName = self.uniqueName("doAction")
+ self.game.load()
+
+ def unload(self):
+ self.game.unload()
+ DistributedMinigame.unload(self)
+
+ def onstage(self):
+ DistributedMinigame.onstage(self)
+ self.game.onstage()
+
+ def offstage(self):
+ self.game.offstage()
+ DistributedMinigame.offstage(self)
+
+ # broadcast
+ def setGameReady(self):
+ if DistributedMinigame.setGameReady(self):
+ return
+
+ self.game.ready()
+
+ # broadcast
+ def setGameStart(self, timestamp):
+ DistributedMinigame.setGameStart(self, timestamp)
+
+ self.game.start()
+
+ # broadcast
+ def setGameExit(self):
+ DistributedMinigame.setGameExit(self)
+
+ self.game.exit()
+
+ # required broadcast ram
+ def setLocks(self, toonIds, spawnPointsX, spawnPointsY):
+ self.lockInfoList = []
+ self.locksInfo = {}
+
+ for i in range(len(toonIds)):
+ lockInfo = CogdoMazeLockInfo(toonIds[i], spawnPointsX[i], spawnPointsY[i])
+ self.lockInfoList.append(lockInfo)
+ self.locksInfo[lockInfo.toonId] = lockInfo
+
+ def d_sendRequestAction(self, action, data):
+ self.sendUpdate("requestAction", [action, data])
+
+
+ # broadcast
+ def doAction(self, action, data):
+ self.notify.debugCall()
+
+ assert action in CogdoMazeGameGlobals.GameActions
+ if data == base.localAvatar.doId: return
+
+ messenger.send(self._remoteActionEventName, [action, data])
+
+
+ def getTitle(self):
+ return "Cogdominium Maze Game"
+
+ def getInstructions(self):
+ return ""
+
+ def getRemoteActionEventName(self):
+ return self._remoteActionEventName
+
+from toontown.minigame.Maze import Maze
+
+class CogdoMazeGame(DirectObject):
+ """
+ Handles the visuals/looks of the cogdo maze game
+ """
+ notify = directNotify.newCategory("CogdoMazeGame")
+
+ UPDATE_TASK_NAME = "CogdoMazeGameUpdate"
+
+ def __init__(self, distGame):
+ self.distGame = distGame
+
+ def load(self):
+ self.maze = Maze(CogdoMazeGameGlobals.TempMazeFile)
+ #self.maze.maze.setColorScale(0.0, 0, 5.0, 1.0)
+ self.maze.setScale(2, 1.75)
+
+ self.guiMgr = CogdoMazeGuiManager(self.maze)
+ self.audioMgr = CogdoMazeAudioManager()
+
+ self.toonId2Door = {}
+ self.keyIdToKey = {}
+ self.players = []
+ self.toonId2Player = {}
+ self.lockId2Lock = {}
+
+ self.localPlayer = CogdoMazeLocalPlayer(len(self.players), base.localAvatar, self, self.guiMgr)
+ self._addPlayer(self.localPlayer)
+
+ # TEMP...
+ self.sprites = loader.loadModel("cogdominium/mazeSprites.egg")
+
+ # Create door
+ pos = self.maze.tile2world(int(self.maze.width / 2), self.maze.height - 1)
+ gridPos = self.maze.world2tile(pos[0], pos[1])
+
+ # TEMP
+ openDoorModel = self.sprites.find("**/door_open")
+ openDoorModel.setScale(8)
+ openDoorModel.setZ(0.25)
+ closedDoorModel = self.sprites.find("**/door_closed")
+ closedDoorModel.setScale(8)
+ closedDoorModel.setZ(0.25)
+
+ self.door = CogdoMazeDoor(closedDoorModel, openDoorModel)
+ self.door.setPosition(pos[0], pos[1] - 0.05)
+ self.door.offstage()
+ self.guiMgr.mazeMapGui.addDoor(gridPos[0], gridPos[1], VBase4(1, 1, 1, 1))
+
+ # load key model, keys will be placed when everyone's there
+ self.fuseModels = (
+ self.sprites.find("**/fuse_white"),
+ self.sprites.find("**/fuse_blue"),
+ self.sprites.find("**/fuse_yellow"),
+ self.sprites.find("**/fuse_red"),
+ )
+ for fuse in self.fuseModels:
+ fuse.setScale(2)
+ fuse.setBillboardPointEye()
+
+ self.fuseBoxModels = (
+ (self.sprites.find("**/fusebox_white"), self.sprites.find("**/fusebox_white_plugged")),
+ (self.sprites.find("**/fusebox_blue"), self.sprites.find("**/fusebox_blue_plugged")),
+ (self.sprites.find("**/fusebox_yellow"), self.sprites.find("**/fusebox_yellow_plugged")),
+ (self.sprites.find("**/fusebox_red"), self.sprites.find("**/fusebox_red_plugged")),
+ )
+ for fuseBox in self.fuseBoxModels:
+ fuseBox[0].setScale(4)
+ fuseBox[1].setScale(4)
+
+ #self._initFog()
+ self.accept(self.distGame.getRemoteActionEventName(), self.handleRemoteAction)
+
+ def unload(self):
+ self.__stopUpdateTask()
+ self.ignoreAll()
+ #self._destroyFog()
+
+ self.maze.destroy()
+ del self.maze
+
+ self.door.destroy()
+ del self.door
+
+ for key in self.keyIdToKey.values():
+ key.destroy()
+ del self.keyIdToKey
+
+ self.audioMgr.destroy()
+ del self.audioMgr
+
+ self.guiMgr.destroy()
+ del self.guiMgr
+
+ self.sprites.removeNode()
+ del self.sprites
+
+ del self.players
+ del self.toonId2Player
+ del self.localPlayer
+
+ def _initFog(self):
+ self.fog = Fog("MazeFog")
+ self.fog.setColor(VBase4(0.3, 0.3, 0.3, 1.0))
+ self.fog.setLinearRange(0.0, 100.0)
+
+ self._renderFog = render.getFog()
+ render.setFog(self.fog)
+
+ def _destroyFog(self):
+ render.clearFog()
+ del self.fog
+ del self._renderFog
+
+ def onstage(self):
+ self.door.onstage()
+ self.maze.onstage()
+
+ self.placePlayer(self.localPlayer)
+ self.localPlayer.ready()
+ self.localPlayer.onstage()
+
+ def offstage(self):
+ self.maze.offstage()
+ self.door.offstage()
+ self.localPlayer.offstage()
+
+ def ready(self):
+ # Initialize remote players
+ for avId in self.distGame.remoteAvIdList:
+ toon = self.distGame.getAvatar(avId)
+ if toon is not None:
+ player = CogdoMazePlayer(len(self.players), toon)
+ self._addPlayer(player)
+
+ self.placePlayer(player)
+ player.ready()
+
+ assert len(self.distGame.lockInfoList) <= CogdoMazeGameGlobals.LockColors
+
+ # Initialize locks and keys
+ for i in range(len(self.distGame.lockInfoList)):
+ lockInfo = self.distGame.lockInfoList[i]
+ toon = base.cr.doId2do[lockInfo.toonId]
+ color = CogdoMazeGameGlobals.LockColors[i] #toon.style.getHeadColor()
+ pos = self.maze.tile2world(lockInfo.tileX, lockInfo.tileY)
+ player = self.toonId2Player[lockInfo.toonId]
+
+ # Create and place locks
+ lock = CogdoMazeLock(i, lockInfo.toonId, self.fuseBoxModels[i][0], self.fuseBoxModels[i][1], color, pos[0], pos[1] + 1)
+ self.door.addLock(lock)
+ self.guiMgr.mazeMapGui.addLock(lockInfo.tileX, lockInfo.tileY, color)
+
+ # Create and place keys
+ key = CogdoMazeKey(lockInfo.toonId, self.fuseModels[i], color)
+ self.keyIdToKey[key.id] = key
+
+ lock.setKey(key)
+ player.holdKey(key)
+
+ if toon == self.localPlayer.toon:
+ self.lockColorIndex = i
+
+ def start(self):
+ self.accept(self.door.enterCollisionEventName, self.handleDoorCollision)
+
+ for lock in self.door.getLocks():
+ self.accept(lock.enterCollisionEventName, self.handleLockCollision)
+
+ self.__startUpdateTask()
+ self.localPlayer.enable()
+
+ if __debug__:
+ self.acceptOnce("escape", self.distGame.d_requestExit)
+ self.acceptOnce("home", self.guiMgr.revealMazeMap)
+
+ self.guiMgr.displayNotification("Find the %s fusebox!" % CogdoMazeGameGlobals.LockNames[self.lockColorIndex])
+
+ self.audioMgr.playMusic("normal")
+
+ def exit(self):
+ if __debug__:
+ self.ignore("escape")
+ self.ignore("home")
+
+ self.ignore(self.door.enterCollisionEventName)
+
+ for lock in self.door.getLocks():
+ self.ignore(lock.enterCollisionEventName)
+
+ self.__stopUpdateTask()
+ self.localPlayer.disable()
+
+ def _addPlayer(self, player):
+ self.players.append(player)
+ self.toonId2Player[player.toon.doId] = player
+ self.guiMgr.mazeMapGui.addPlayer(0, 0, player.toon.style.getHeadColor())
+
+ def _removePlayer(self, player):
+ self.players.remove(player)
+ self.players.remove(player.toon.doId)
+
+ def __startUpdateTask(self):
+ self.__stopUpdateTask()
+ taskMgr.add(self.__updateTask, CogdoMazeGame.UPDATE_TASK_NAME, 45)
+
+ def __stopUpdateTask(self):
+ taskMgr.remove(CogdoMazeGame.UPDATE_TASK_NAME)
+
+ def __updateTask(self, task):
+ self.localPlayer.update()
+
+ for player in self.players:
+ curTX, curTY = self.maze.world2tile(player.toon.getX(), player.toon.getY())
+ self.guiMgr.updateMazeMapPlayer(player.id, curTX, curTY)
+
+ return Task.cont
+
+ def placePlayer(self, player):
+ i = self.distGame.avIdList.index(player.toon.doId)
+ pos = self.maze.tile2world(int(self.maze.width / 2), 2)
+ player.toon.setPos(
+ pos[0] + i * self.maze.cellWidth,
+ pos[1],
+ 0
+ )
+
+ def handleRemoteAction(self, action, data):
+ if action == CogdoMazeGameGlobals.GameActions.Unlock:
+ self.toonUnlocks(data)
+
+ if action == CogdoMazeGameGlobals.GameActions.RevealDoor:
+ self.toonRevealsDoor(data)
+
+ if action == CogdoMazeGameGlobals.GameActions.RevealLock:
+ self.toonRevealsLock(data)
+
+ elif action == CogdoMazeGameGlobals.GameActions.GameOver:
+ self.distGame.gameOver()
+
+ def toonRevealsDoor(self, toonId):
+ self.door.revealed = True
+
+ message = None
+ nextMessage = None
+ if toonId == self.localPlayer.toon.doId:
+ message = "You found the elevator door!"
+ if self.localPlayer.hasKey():
+ nextMessage = "Find the %s fusebox\nto help open the elevator." % CogdoMazeGameGlobals.LockNames[self.lockColorIndex]
+ else:
+ toon = self.distGame.getAvatar(toonId)
+ message = "%s found the elevator door!" % toon.getName()
+
+ if message is not None:
+ self.guiMgr.displayNotification(message, nextMessage)
+
+
+ def toonRevealsLock(self, toonId):
+ if toonId == self.localPlayer.toon.doId:
+ self.guiMgr.displayNotification("Someone found your fusebox!", "Go to the %s fusebox!" % CogdoMazeGameGlobals.LockNames[self.lockColorIndex])
+
+
+ def toonEntersDoor(self, toonId):
+ player = self.toonId2Player[toonId]
+ player.disable()
+
+ self.door.playerEntersDoor(player)
+
+ message = None
+ if self.door.getPlayerCount() == len(self.players):
+ message = ""
+ else:
+ message = "Waiting for %d toons to get to elevator door..." % (len(self.players) - self.door.getPlayerCount())
+
+ if message is not None:
+ self.guiMgr.displayNotification(message)
+
+ def toonUnlocks(self, toonId):
+ player = self.toonId2Player[toonId]
+ self.door.unlock(player.getKey().lock.id)
+ key = self.toonId2Player[toonId].dropKey()
+ key.offstage()
+
+ if player == self.localPlayer:
+ self.audioMgr.playSfx("fusePlaced")
+
+ if not self.door.isLocked():
+ self.door.open()
+ self.audioMgr.playSfx("doorOpen")
+
+ message = None
+ nextMessage = None
+ if not self.door.isLocked():
+ message = "All the fuseboxes are fixed!"
+ nextMessage = "Find the elevator door!"
+ elif self.door.isLocked() and player == self.localPlayer:
+ message = "Help your friends find the other fuseboxes!"
+ else:
+ if player == self.localPlayer:
+ message = "Good job! Now, find the exit!"
+ #elif self.localPlayer.getKey() == key:
+ #message = "[name] found your fusebox! Go to the [color] fusebox!"
+
+ if message is not None:
+ self.guiMgr.displayNotification(message, nextMessage)
+
+ def handleLockCollision(self, collEntry):
+ assert self.notify.debugCall()
+
+ intoNodePath = collEntry.getIntoNodePath()
+ intoName = intoNodePath.getName()
+
+ nameParts = intoName.split("-")
+ assert len(nameParts) > 1
+ id = int(nameParts[1])
+ lock = self.door.getLock(id)
+
+ if self.localPlayer.hasKey():
+ key = self.localPlayer.getKey()
+ if lock == key.lock:
+ self.toonUnlocks(self.localPlayer.toon.doId)
+ self.distGame.d_sendRequestAction(CogdoMazeGameGlobals.GameActions.Unlock, id)
+ else:
+ self.toonRevealsLock(lock.toonId)
+ self.distGame.d_sendRequestAction(CogdoMazeGameGlobals.GameActions.RevealLock, lock.toonId)
+
+ def handleDoorCollision(self, collEntry):
+ assert self.notify.debugCall()
+
+ if not self.door.revealed:
+ if self.localPlayer.hasKey():
+ self.toonRevealsDoor(self.localPlayer.toon.doId)
+ self.distGame.d_sendRequestAction(CogdoMazeGameGlobals.GameActions.RevealDoor, 0)
+
+ if not self.localPlayer.hasKey():
+ self.toonEntersDoor(self.localPlayer.toon.doId)
+ self.distGame.d_sendRequestAction(CogdoMazeGameGlobals.GameActions.EnterDoor, 0)
+
+
+ def doMazeCollisions(self, oldPos, newPos):
+ # we will calculate an offset vector that
+ # keeps the toon out of the walls
+ offset = newPos - oldPos
+
+ # toons can only get this close to walls
+ WALL_OFFSET = 1.
+
+ # make sure we're not in a wall already
+ curX = oldPos[0]; curY = oldPos[1]
+ curTX, curTY = self.maze.world2tile(curX, curY)
+ assert(not self.maze.collisionTable[curTY][curTX])
+
+ def calcFlushCoord(curTile, newTile, centerTile):
+ # calculates resulting one-dimensional coordinate,
+ # given that the object is moving from curTile to
+ # newTile, where newTile is a wall
+ EPSILON = 0.01
+ if newTile > curTile:
+ return ((newTile-centerTile)*self.maze.cellWidth)\
+ -EPSILON-WALL_OFFSET
+ else:
+ return ((curTile-centerTile)*self.maze.cellWidth)+WALL_OFFSET
+
+ offsetX = offset[0]; offsetY = offset[1]
+
+ WALL_OFFSET_X = WALL_OFFSET
+ if offsetX < 0:
+ WALL_OFFSET_X = -WALL_OFFSET_X
+ WALL_OFFSET_Y = WALL_OFFSET
+ if offsetY < 0:
+ WALL_OFFSET_Y = -WALL_OFFSET_Y
+
+ # check movement in X direction
+ newX = curX + offsetX + WALL_OFFSET_X; newY = curY
+ newTX, newTY = self.maze.world2tile(newX, newY)
+ if newTX != curTX:
+ # we've crossed a tile boundary
+ if self.maze.collisionTable[newTY][newTX]:
+ # there's a wall
+ # adjust the X offset so that the toon
+ # hits the wall exactly
+ offset.setX(calcFlushCoord(curTX, newTX,
+ self.maze.originTX)-curX)
+
+ newX = curX; newY = curY + offsetY + WALL_OFFSET_Y
+ newTX, newTY = self.maze.world2tile(newX, newY)
+ if newTY != curTY:
+ # we've crossed a tile boundary
+ if self.maze.collisionTable[newTY][newTX]:
+ # there's a wall
+ # adjust the Y offset so that the toon
+ # hits the wall exactly
+ offset.setY(calcFlushCoord(curTY, newTY,
+ self.maze.originTY)-curY)
+
+ # at this point, if our new position is in a wall, we're
+ # running right into a protruding corner:
+ #
+ # \
+ # ###
+ # ###
+ # ###
+ #
+ offsetX = offset[0]; offsetY = offset[1]
+
+ newX = curX + offsetX + WALL_OFFSET_X
+ newY = curY + offsetY + WALL_OFFSET_Y
+ newTX, newTY = self.maze.world2tile(newX, newY)
+ if self.maze.collisionTable[newTY][newTX]:
+ # collide in only one of the dimensions
+ cX = calcFlushCoord(curTX, newTX, self.maze.originTX)
+ cY = calcFlushCoord(curTY, newTY, self.maze.originTY)
+ if (abs(cX - curX) < abs(cY - curY)):
+ offset.setX(cX - curX)
+ else:
+ offset.setY(cY - curY)
+
+ return oldPos + offset
+
+
+class CogdoMazeDoor:
+ def __init__(self, closedDoorModel, openDoorModel):
+ self.model = NodePath("CogdoMazeDoor")
+ self.model.setPos(0, 0, 0)
+ self.model.reparentTo(render)
+
+ self.closedDoorModel = closedDoorModel
+ self.closedDoorModel.reparentTo(self.model)
+
+ self.openDoorModel = openDoorModel
+ self.openDoorModel.reparentTo(self.model)
+ self.openDoorModel.stash()
+
+ self.lockId2lock = {}
+ self._open = False
+ self.revealed = False
+ self.players = []
+
+ self._initCollisions()
+
+ def setPosition(self, x, y):
+ self.model.setPos(x, y, 2.5)
+
+ def _initCollisions(self):
+ name = "CogdoMazeDoor"
+ collSphere = CollisionSphere(0, 0, 0.0, 0.25)
+ collSphere.setTangible(0)
+ collNode = CollisionNode(name)
+ collNode.setFromCollideMask(ToontownGlobals.CatchGameBitmask)
+ collNode.addSolid(collSphere)
+ self.collNP = self.model.attachNewNode(collNode)
+
+ self.enterCollisionEventName = "enter" + name
+
+ def destroy(self):
+ self.model.removeNode()
+ del self.model
+ del self.openDoorModel
+ del self.closedDoorModel
+
+ for lock in self.lockId2lock.values():
+ lock.destroy()
+ del self.lockId2lock
+
+ def onstage(self):
+ self.model.unstash()
+
+ def offstage(self):
+ self.model.stash()
+
+ def open(self):
+ self._open = True
+ self.closedDoorModel.stash()
+ self.openDoorModel.unstash()
+
+ def close(self):
+ self._open = False
+ self.closedDoorModel.unstash()
+ self.openDoorModel.stash()
+
+ def addLock(self, lock):
+ self.lockId2lock[lock.id] = lock
+
+ def unlock(self, lockId):
+ lock = self.lockId2lock.get(lockId)
+
+ if lock is not None and lock.isLocked():
+ lock.unlock()
+ return True
+
+ return False
+
+ def getLocks(self):
+ return self.lockId2lock.values()
+
+ def getLock(self, lockId):
+ return self.lockId2lock.get(lockId)
+
+ def isLocked(self):
+ return True in [lock.isLocked() for lock in self.lockId2lock.values()]
+
+ def playerEntersDoor(self, player):
+ self.players.append(player)
+
+ def getPlayerCount(self):
+ return len(self.players)
+
+
+class CogdoMazeLock:
+ def __init__(self, id, toonId, model, pluggedModel, color, x=0, y=0):
+ self.id = id
+ self.toonId = toonId
+ self.model = model
+ self.pluggedModel = pluggedModel
+
+ self.model.reparentTo(render)
+ self.pluggedModel.reparentTo(render)
+ self.pluggedModel.stash()
+
+ self.setPosition(x, y)
+
+ self._locked = True
+ self.key = None
+ self.revealed = False
+
+ self._initCollisions()
+
+ def destroy(self):
+ self.model.removeNode()
+ del self.model
+
+ self.pluggedModel.removeNode()
+ del self.pluggedModel
+
+ def onstage(self):
+ if self._locked:
+ self.model.unstash()
+ else:
+ self.pluggedModel.unstash()
+
+ def offstage(self):
+ self.model.stash()
+ self.pluggedModel.stash()
+
+ def _initCollisions(self):
+ name = "CogdoMazeLock-%d" % self.id
+ collSphere = CollisionSphere(0, 0, 0.0, 0.25)
+ collSphere.setTangible(0)
+ collNode = CollisionNode(name)
+ collNode.setFromCollideMask(ToontownGlobals.CatchGameBitmask)
+ collNode.addSolid(collSphere)
+ self.model.attachNewNode(collNode)
+
+ self.enterCollisionEventName = "enter" + name
+
+ def unlock(self):
+ self.pluggedModel.unstash()
+ self.model.stash()
+ self._locked = False
+
+ def isLocked(self):
+ return self._locked
+
+ def setPosition(self, x, y, z=2.5):
+ self.model.setPos(x, y-0.1, z)
+ self.pluggedModel.setPos(self.model, 0, 0, 0)
+
+ def setKey(self, key):
+ self.key = key
+ self.key.setLock(self)
+
+from toontown.toonbase import ToontownGlobals
+
+from pandac.PandaModules import CollisionSphere, CollisionNode
+
+class CogdoMazeKey:
+ def __init__(self, id, model, color):
+ self.id = id
+ self.model = model
+ self.model.setPos(0, 0, 0)
+ self.model.reparentTo(render)
+ #self.model.setColor(color)
+
+ self._playerWhoPickedItUp = None
+ self.lock = None
+
+ def destroy(self):
+ self.model.removeNode()
+ del self.model
+
+ def onstage(self):
+ self.model.unstash()
+
+ def offstage(self):
+ self.model.stash()
+
+ def setPosition(self, x, y, z=0):
+ self.model.setPos(x, y, z)
+
+ def heldByPlayer(self, player):
+ self._playerWhoPickedItUp = player
+ self.enable()
+
+ def drop(self):
+ self.model.wrtReparentTo(render)
+ self.model.setZ(0)
+
+ self._playerWhoPickedItUp = None
+
+ def disable(self):
+ self.model.setAlphaScale(0.5)
+
+ def enable(self):
+ self.model.setAlphaScale(1.0)
+
+ def setLock(self, lock):
+ self.lock = lock
+
+
+from direct.fsm.FSM import FSM
+
+class CogdoMazePlayer(FSM):
+ """
+ Controls the animation state of a player toon in the cogdo maze game.
+ """
+ _key = None
+
+ def __init__(self, id, toon):
+ self.id = id
+ self.toon = toon
+
+ def ready(self):
+ self.toon.reparentTo(render)
+ self.toon.setAnimState('Happy', 1.0)
+ self.toon.setSpeed(0, 0)
+ self.toon.startSmooth()
+
+ def holdKey(self, key):
+ if self._key is None:
+ self._key = key
+ self._key.heldByPlayer(self)
+
+ self._key.model.reparentTo(self.toon)
+ self._key.model.setPos(0, 0, self.toon.getHeight() + 2)
+
+ def dropKey(self):
+ key = None
+ if self._key is not None:
+ key = self._key
+
+ self._key.drop()
+ self._key = None
+
+ return key
+
+ def hasKey(self):
+ return self._key is not None
+
+ def getKey(self):
+ return self._key
+
+
+from direct.task.Task import Task
+
+from toontown.minigame.OrthoDrive import OrthoDrive
+from toontown.minigame.OrthoWalk import OrthoWalk
+
+class CogdoMazeLocalPlayer(CogdoMazePlayer):
+ """
+ Controls input, gui, and camera for a local player in the maze game.
+ """
+ def __init__(self, id, toon, game, guiMgr):
+ CogdoMazePlayer.__init__(self, id, toon)
+ self.game = game
+ self.guiMgr = guiMgr
+
+ self.cameraMgr = CogdoMazeCameraManager(self.toon, self.game.maze, camera, render)
+
+ self.enabled = False
+
+ def onstage(self):
+ self.toon.hideName()
+ self.cameraMgr.enable()
+ self.update()
+
+ def offstage(self):
+ self.disable()
+ self.cameraMgr.disable()
+ self.toon.showName()
+
+ def enable(self):
+ if self.enabled: return
+ orthoDrive = OrthoDrive(
+ CogdoMazeGameGlobals.ToonRunSpeed,
+ maxFrameMove=(self.game.maze.cellWidth / 2),
+ customCollisionCallback=self.game.doMazeCollisions
+ )
+
+ self.orthoWalk = OrthoWalk(
+ orthoDrive,
+ broadcast=not self.game.distGame.isSinglePlayer()
+ )
+
+ self.orthoWalk.start()
+
+ self.guiMgr.showTimer(CogdoMazeGameGlobals.GameDuration, self.disable)
+ self.enabled = True
+
+ def disable(self):
+ if not self.enabled: return
+
+ self.guiMgr.hideTimer()
+
+ self.orthoWalk.stop()
+ self.orthoWalk.destroy()
+ del self.orthoWalk
+
+ self.enabled = False
+
+ def update(self):
+ self.cameraMgr.update()
+
+
+import math
+
+from direct.showbase.PythonUtil import bound as clamp
+from pandac.PandaModules import VBase3
+
+class CogdoMazeCameraManager:
+ def __init__(self, toon, maze, cam, root):
+ self.toon = toon
+ self.maze = maze
+ self.camera = cam
+ self.root = root
+
+ self.minPos = self.maze.tile2world(3, 4)
+ self.maxPos = self.maze.tile2world(self.maze.width-3, self.maze.height-2)
+
+ def enable(self):
+ self.parent = render.attachNewNode('GameCamParent')
+ self.parent.reparentTo(self.root)
+ self.parent.setPos(self.toon, 0, 0, 0)
+ self.parent.setHpr(self.root, 0, 0, 0)
+ self.camera.reparentTo(self.parent)
+
+ self.setCameraOffset(
+ CogdoMazeGameGlobals.OverheadCameraAngle,
+ CogdoMazeGameGlobals.OverheadCameraDistance)
+
+ def setCameraOffset(self, radAngle, distance):
+ self.camera.setPos(VBase3(
+ 0,
+ -math.cos(radAngle) * distance,
+ math.sin(radAngle) * distance
+ ))
+ self.camera.lookAt(self.toon)
+
+ def disable(self):
+ self.camera.wrtReparentTo(render)
+
+ self.parent.removeNode()
+ del self.parent
+
+ def update(self):
+ toonPos = self.toon.getPos()
+
+ self.parent.setPos(
+ self.toon.getParent(),
+ clamp(toonPos.getX(), self.minPos[0], self.maxPos[0]),
+ clamp(toonPos.getY(), self.minPos[1], self.maxPos[1]),
+ 0
+ )
+
+from direct.gui.OnscreenText import OnscreenText
+
+from pandac.PandaModules import TextNode
+
+from toontown.toonbase.ToontownTimer import ToontownTimer
+
+class CogdoMazeHud:
+ LETTERS_PER_SECOND = 4.0
+ NEXT_NOTIFICATION_TASK_NAME = "CogdoMazeHud_NextNotification"
+
+ def __init__(self):
+ self._initNotificationText()
+
+ def _initNotificationText(self):
+ self._notificationText = OnscreenText(
+ text="",
+ font=ToontownGlobals.getSignFont(),
+ pos=(0, -0.8),
+ scale=0.11,
+ fg=(1.0, 1.0, 0.0, 1.0),
+ align=TextNode.ACenter,
+ mayChange=True,
+ )
+ self._notificationText.hide()
+
+ def destroy(self):
+ self._stopDelayedNotification()
+
+ self._notificationText.removeNode()
+ del self._notificationText
+
+ def displayNotification(self, messageText, nextMessageText=None):
+ assert messageText is not None
+
+ self._stopDelayedNotification()
+
+ self._notificationText["text"] = messageText
+ self._notificationText.show()
+
+ if nextMessageText is not None:
+ taskMgr.doMethodLater(
+ len(messageText) / CogdoMazeHud.LETTERS_PER_SECOND,
+ self.displayNotification,
+ CogdoMazeHud.NEXT_NOTIFICATION_TASK_NAME,
+ extraArgs=[nextMessageText]
+ )
+
+ def _stopDelayedNotification(self):
+ taskMgr.remove(CogdoMazeHud.NEXT_NOTIFICATION_TASK_NAME)
+
+
+class CogdoMazeGuiManager:
+ def __init__(self, maze):
+ self.maze = maze
+
+ self.mazeMapGui = MazeMapGui(self.maze.collisionTable)
+ self.mazeMapGui.setScale(.25)
+ self.mazeMapGui.setPos(1.07, 0.0, 0.73)
+
+ self.hud = CogdoMazeHud()
+
+ self.timer = None
+
+ def _initTimer(self):
+ self.timer = ToontownTimer()
+ self.timer.hide()
+ self.timer.setPos(1.16, 0, -0.83)
+
+ def destroy(self):
+ self.hud.destroy()
+ self.hud = None
+ self.destroyMazeMap()
+ self.destroyTimer()
+
+ def destroyMazeMap(self):
+ if hasattr(self, "mazeMapGui") and self.mazeMapGui is not None:
+ self.mazeMapGui.destroy()
+ del self.mazeMapGui
+
+ def destroyTimer(self):
+ if self.timer is not None:
+ self.timer.stop()
+ self.timer.destroy()
+ self.timer = None
+
+ def updateMazeMapPlayer(self, player, tileX, tileY):
+ self.mazeMapGui.revealCell(tileX, tileY, player)
+
+ def revealMazeMap(self):
+ self.mazeMapGui.revealAll()
+
+ def addPlayerToMazeMap(self, keyTilePos, doorTilePos, color):
+ self.mazeMapGui.addPlayer(0, 0, color)
+ self.mazeMapGui.addKey(keyTilePos[0], keyTilePos[1], color)
+ self.mazeMapGui.addDoor(doorTilePos[0], doorTilePos[1], color)
+
+ def showTimer(self, duration, timerExpiredCallback=None):
+ if self.timer is None:
+ self._initTimer()
+
+ self.timer.setTime(duration)
+ self.timer.countdown(duration, timerExpiredCallback)
+ self.timer.show()
+
+ def hideTimer(self):
+ assert hasattr(self, "timer") and self.timer is not None
+
+ self.timer.hide()
+ self.timer.stop()
+
+ def displayNotification(self, message, nextMessage=None):
+ self.hud.displayNotification(message, nextMessage)
+ #messenger.send(CogdoMazeAudioManager.PLAY_SFX_EVENT, ["notification"])
+
+class CogdoMazeAudioManager(DirectObject):
+ PLAY_SFX_EVENT = "CogdoMazeAudioManager_PlaySfx"
+
+ def __init__(self):
+ self.currentMusic = None
+
+ self.music = {}
+ for name, file in CogdoMazeGameGlobals.MusicFiles.items():
+ self.music[name] = base.loadMusic(file)
+
+ self.sfx = {}
+ for name, file in CogdoMazeGameGlobals.SfxFiles.items():
+ self.sfx[name] = loader.loadSfx(file)
+
+ self.accept(CogdoMazeAudioManager.PLAY_SFX_EVENT, self.playSfx)
+
+ def destroy(self):
+ self.stopAll()
+ self.ignoreAll()
+
+ def stopMusic(self):
+ if self.currentMusic is not None:
+ self.currentMusic.stop()
+
+ def playMusic(self, name):
+ assert name in self.music.keys()
+
+ if self.currentMusic is not None:
+ self.stopMusic()
+
+ self.currentMusic = self.music[name]
+ self.currentMusic.setTime(0.0)
+ self.currentMusic.setLoop(True)
+ self.currentMusic.play()
+
+ def playSfx(self, name):
+ assert name in self.sfx.keys()
+
+ self.sfx[name].play()
+
+ def stopAll(self):
+ self.stopMusic()
+
+
+
+
diff --git a/toontown/src/cogdominium/DistCogdoMazeGameAI.py b/toontown/src/cogdominium/DistCogdoMazeGameAI.py
new file mode 100644
index 0000000..0fa2113
--- /dev/null
+++ b/toontown/src/cogdominium/DistCogdoMazeGameAI.py
@@ -0,0 +1,195 @@
+"""
+@author: Schell Games
+3-16-2010
+"""
+import random
+
+from direct.task.Task import Task
+
+from toontown.minigame.DistributedMinigameAI import DistributedMinigameAI
+from toontown.minigame.DistributedMinigameAI import EXITED, EXPECTED, JOINED, READY
+
+import CogdoMazeGameGlobals
+from CogdoMazeGameGlobals import CogdoMazeLockInfo
+
+from direct.fsm.FSM import FSM
+class DistCogdoMazeGameAI(DistributedMinigameAI, FSM):
+ """
+ Maze Cogdominium Minigame AI Distributed Object!
+ """
+ notify = directNotify.newCategory("DistCogdoMazeGameAI")
+
+ TIMER_EXPIRED_TASK_NAME = "CogdoMazeGameTimerExpired"
+
+ def __init__(self, air, id):
+ try:
+ self.DistMazeCogdoGameAI_initialized
+ except:
+ self.DistMazeCogdoGameAI_initialized = 1
+ DistributedMinigameAI.__init__(self, air, id)
+
+ self.toonsInDoor = []
+
+ def delete(self):
+ DistributedMinigameAI.delete(self)
+ taskMgr.remove(self.taskName(DistCogdoMazeGameAI.TIMER_EXPIRED_TASK_NAME))
+
+ def _initLocks(self):
+ self.locks = {}
+
+ data = CogdoMazeGameGlobals.TempMazeData
+ width = data["width"]
+ height = data["height"]
+
+ positions = self._getLockPositions(data, width, height)
+
+ for i in range(len(self.avIdList)):
+ self._addLock(self.avIdList[i], positions[i][0], positions[i][1])
+
+ def _getLockPositions(self, data, width, height):
+ """
+ Splits the maze into 4, shuffles the quadrants, and picks random spots
+ to place the locks.
+
+ @return: list of (tileX, tileY) tuples with the positions of the locks
+ """
+ halfWidth = int(width / 2)
+ halfHeight = int(height / 2)
+
+ # 1. Split the maze into 4 quadrants
+ quadrants = [
+ # (x0, y0, x1, y1)
+ (0, 0, halfWidth - 2, halfHeight - 2),
+ (halfWidth + 2, 0, width - 1, halfHeight - 2),
+ (0, halfHeight + 2, halfWidth - 2, height - 1),
+ (halfWidth + 2, halfHeight + 2, width - 1, height - 1)
+ ]
+
+ # 2. Shuffle the quadrant order
+ random.shuffle(quadrants)
+
+ positions = []
+
+ # 3. pick random available spots, populate list
+ for i in range(len(self.avIdList)):
+ quadrant = quadrants[i]
+ tX = -1
+ tY = -1
+
+ while tX < 0 or data["collisionTable"][tY][tX] == 1:
+ tX = random.randint(quadrant[0], quadrant[2])
+ tY = random.randint(quadrant[1], quadrant[3])
+
+ positions.append((tX, tY))
+
+ return positions
+
+ def _addLock(self, toonId, tX, tY):
+ lock = CogdoMazeLockInfo(toonId, tX, tY)
+ self.locks[toonId] = lock
+
+ # getter (required broadcast ram)
+ def getLocks(self):
+ toonIds = []
+ spawnPointsX = []
+ spawnPointsY = []
+ for lock in self.locks.values():
+ toonIds.append(lock.toonId)
+ spawnPointsX.append(lock.tileX)
+ spawnPointsY.append(lock.tileY)
+
+ return toonIds, spawnPointsX, spawnPointsY
+
+ def setExpectedAvatars(self, avIds):
+ DistributedMinigameAI.setExpectedAvatars(self, avIds)
+
+ self._initLocks()
+
+ def areAllPlayersReady(self):
+ return False not in [(state == READY) for state in self.stateDict.values()]
+
+ def setGameStart(self, timestamp):
+ DistributedMinigameAI.setGameStart(self, timestamp)
+
+ self.enterPlay()
+
+ def timerExpiredTask(self, task):
+ self.notify.debugCall()
+
+ self.gameOver()
+ self.d_broadcastDoAction(CogdoMazeGameGlobals.GameActions.GameOver)
+
+ return Task.done
+
+ def gameOver(self):
+ self.exitPlay()
+ DistributedMinigameAI.gameOver(self)
+
+ def isDoorLocked(self):
+ return True in [lock.locked for lock in self.locks.values()]
+
+ def areAllToonsInDoor(self):
+ return len(self.avIdList) == len(self.toonsInDoor)
+
+ def validateSenderId(self, senderId):
+ if senderId in self.avIdList:
+ return True
+
+ # TODO: Report suspicious event
+ return False
+
+#===============================================================================
+# DISTRIBUTED
+#===============================================================================
+
+ # airecv clsend
+ def requestAction(self, action, data):
+ self.notify.debugCall()
+
+ senderId = self.air.getAvatarIdFromSender()
+ if not self.validateSenderId(senderId):
+ return False
+
+ if action == CogdoMazeGameGlobals.GameActions.Unlock:
+ if self.locks[senderId].locked:
+ self.locks[senderId].locked = False
+
+ self.d_broadcastDoAction(action, senderId)
+ else:
+ pass # TODO: Suspicious event...
+
+ elif action == CogdoMazeGameGlobals.GameActions.EnterDoor:
+ if senderId not in self.toonsInDoor:
+ self.toonsInDoor.append(senderId)
+ self.d_broadcastDoAction(action, senderId)
+
+ if self.areAllToonsInDoor():
+ self.gameOver()
+ self.d_broadcastDoAction(CogdoMazeGameGlobals.GameActions.GameOver)
+ else:
+ pass # TODO: Suspicious event...
+
+ elif action == CogdoMazeGameGlobals.GameActions.RevealDoor:
+ self.d_broadcastDoAction(action, senderId)
+
+ elif action == CogdoMazeGameGlobals.GameActions.RevealLock:
+ self.d_broadcastDoAction(action, data)
+
+ else:
+ pass # TODO: Probably report a suspicious event too!
+
+ def d_broadcastDoAction(self, action, data=0):
+ self.sendUpdate("doAction", [action, data])
+
+#===============================================================================
+# FINITE STATE MACHINE
+#===============================================================================
+
+ def enterPlay(self):
+ taskMgr.doMethodLater(CogdoMazeGameGlobals.GameDuration,
+ self.timerExpiredTask,
+ self.taskName(DistCogdoMazeGameAI.TIMER_EXPIRED_TASK_NAME))
+
+ def exitPlay(self):
+ taskMgr.remove(self.taskName(DistCogdoMazeGameAI.TIMER_EXPIRED_TASK_NAME))
+
diff --git a/toontown/src/cogdominium/DistributedCogdoElevatorExt.py b/toontown/src/cogdominium/DistributedCogdoElevatorExt.py
new file mode 100644
index 0000000..bf3424d
--- /dev/null
+++ b/toontown/src/cogdominium/DistributedCogdoElevatorExt.py
@@ -0,0 +1,11 @@
+from toontown.building.DistributedElevatorExt import DistributedElevatorExt
+
+class DistributedCogdoElevatorExt(DistributedElevatorExt):
+ def getElevatorModel(self):
+ return self.bldg.getCogdoElevatorNodePath()
+
+ def getBldgDoorOrigin(self):
+ return self.bldg.getCogdoDoorOrigin()
+
+ def _getDoorsClosedInfo(self):
+ return 'cogdoInterior', 'cogdoInterior'
diff --git a/toontown/src/cogdominium/DistributedCogdoElevatorExtAI.py b/toontown/src/cogdominium/DistributedCogdoElevatorExtAI.py
new file mode 100644
index 0000000..30db8cd
--- /dev/null
+++ b/toontown/src/cogdominium/DistributedCogdoElevatorExtAI.py
@@ -0,0 +1,10 @@
+from direct.directnotify import DirectNotifyGlobal
+from toontown.building.DistributedElevatorExtAI import DistributedElevatorExtAI
+
+class DistributedCogdoElevatorExtAI(DistributedElevatorExtAI):
+
+ notify = DirectNotifyGlobal.directNotify.newCategory("DistributedCogdoElevatorExtAI")
+
+ def _createInterior(self):
+ self.bldg.createCogdoInterior()
+
diff --git a/toontown/src/cogdominium/DistributedCogdoElevatorInt.py b/toontown/src/cogdominium/DistributedCogdoElevatorInt.py
new file mode 100644
index 0000000..de67f94
--- /dev/null
+++ b/toontown/src/cogdominium/DistributedCogdoElevatorInt.py
@@ -0,0 +1,7 @@
+from toontown.building.DistributedElevatorInt import DistributedElevatorInt
+
+class DistributedCogdoElevatorInt(DistributedElevatorInt):
+ def _getDoorsClosedInfo(self):
+ # return loader, where strings
+ return 'cogdoInterior', 'cogdoInterior'
+
diff --git a/toontown/src/cogdominium/DistributedCogdoElevatorIntAI.py b/toontown/src/cogdominium/DistributedCogdoElevatorIntAI.py
new file mode 100644
index 0000000..dd2b0ec
--- /dev/null
+++ b/toontown/src/cogdominium/DistributedCogdoElevatorIntAI.py
@@ -0,0 +1,4 @@
+from toontown.building.DistributedElevatorIntAI import DistributedElevatorIntAI
+
+class DistributedCogdoElevatorIntAI(DistributedElevatorIntAI):
+ pass
diff --git a/toontown/src/cogdominium/DistributedCogdoInterior.py b/toontown/src/cogdominium/DistributedCogdoInterior.py
new file mode 100644
index 0000000..6d8765f
--- /dev/null
+++ b/toontown/src/cogdominium/DistributedCogdoInterior.py
@@ -0,0 +1,669 @@
+""" DistributedCogdoInterior module"""
+
+from direct.interval.IntervalGlobal import *
+from direct.distributed.ClockDelta import *
+from toontown.building.ElevatorConstants import *
+
+from toontown.building import ElevatorUtils
+from toontown.toonbase import ToontownGlobals
+from toontown.toonbase import ToontownBattleGlobals
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObject
+from direct.fsm import State
+from toontown.battle import BattleBase
+from toontown.hood import ZoneUtil
+from toontown.cogdominium.CogdoLayout import CogdoLayout
+
+class DistributedCogdoInterior(DistributedObject.DistributedObject):
+ """
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedCogdoInterior')
+
+ id = 0
+
+ def __init__(self, cr):
+ DistributedObject.DistributedObject.__init__(self, cr)
+
+ self.toons = []
+ self.activeIntervals = {}
+
+ self.openSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_open.mp3")
+ self.closeSfx = base.loadSfx("phase_5/audio/sfx/elevator_door_close.mp3")
+
+ self.suits = []
+ self.reserveSuits = []
+ self.joiningReserves = []
+
+ self.distBldgDoId = None
+
+ # we increment this each time we come out of an elevator:
+ self.currentFloor = -1
+
+ self.elevatorName = self.__uniqueName('elevator')
+ self.floorModel = None
+
+ self.elevatorOutOpen = 0
+
+ # initial cog positions vary based on the cog office model
+ self.BottomFloor_SuitPositions = [
+ Point3(0, 15, 0),
+ Point3(10, 20, 0),
+ Point3(-7, 24, 0),
+ Point3(-10, 0, 0)]
+ self.BottomFloor_SuitHs = [75, 170, -91, -44] # Heading angles
+
+ self.Cubicle_SuitPositions = [
+ Point3(0, 18, 0),
+ Point3(10, 12, 0),
+ Point3(-9, 11, 0),
+ Point3(-3, 13, 0)]
+ self.Cubicle_SuitHs = [170, 56, -52, 10]
+
+ self.BossOffice_SuitPositions = [
+ Point3(0, 15, 0),
+ Point3(10, 20, 0),
+ Point3(-10, 6, 0),
+ Point3(-17, 34, 11),
+ ]
+ self.BossOffice_SuitHs = [170, 120, 12, 38]
+
+ self.waitMusic = base.loadMusic(
+ 'phase_7/audio/bgm/encntr_toon_winning_indoor.mid')
+ self.elevatorMusic = base.loadMusic(
+ 'phase_7/audio/bgm/tt_elevator.mid')
+
+ self.fsm = ClassicFSM.ClassicFSM('DistributedCogdoInterior',
+ [State.State('WaitForAllToonsInside',
+ self.enterWaitForAllToonsInside,
+ self.exitWaitForAllToonsInside,
+ ['Elevator']),
+ State.State('Elevator',
+ self.enterElevator,
+ self.exitElevator,
+ ['Game']),
+ State.State('Game',
+ self.enterGame,
+ self.exitGame,
+ ['Battle']),
+ State.State('Battle',
+ self.enterBattle,
+ self.exitBattle,
+ ['Resting',
+ 'Reward',
+ 'ReservesJoining']),
+ State.State('ReservesJoining',
+ self.enterReservesJoining,
+ self.exitReservesJoining,
+ ['Battle']),
+ State.State('Resting',
+ self.enterResting,
+ self.exitResting,
+ ['Elevator']),
+ State.State('Reward',
+ self.enterReward,
+ self.exitReward,
+ ['Off']),
+ State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['Elevator',
+ 'WaitForAllToonsInside',
+ 'Battle']),
+ ],
+ # Initial State
+ 'Off',
+ # Final State
+ 'Off',
+ )
+
+ # make sure we're in the initial state
+ self.fsm.enterInitialState()
+
+ def __uniqueName(self, name):
+ DistributedCogdoInterior.id += 1
+ return (name + '%d' % DistributedCogdoInterior.id)
+
+ def generate(self):
+ """generate(self)
+ This method is called when the DistributedObject is reintroduced
+ to the world, either for the first time or from the cache.
+ """
+ assert(self.notify.debug("generate()"))
+ DistributedObject.DistributedObject.generate(self)
+
+ # listen for the generate event, which will be thrown after the
+ # required fields are filled in
+ self.announceGenerateName = self.uniqueName('generate')
+ self.accept(self.announceGenerateName, self.handleAnnounceGenerate)
+
+ # Load the elevator model
+ self.elevatorModelIn = loader.loadModel(
+ 'phase_5/models/modules/elevator')
+ self.leftDoorIn = self.elevatorModelIn.find('**/left-door')
+ self.rightDoorIn = self.elevatorModelIn.find('**/right-door')
+
+ self.elevatorModelOut = loader.loadModel(
+ 'phase_5/models/modules/elevator')
+ self.leftDoorOut = self.elevatorModelOut.find('**/left-door')
+ self.rightDoorOut = self.elevatorModelOut.find('**/right-door')
+
+ def setElevatorLights(self, elevatorModel):
+ """
+ Sets up the lights on the interior elevators to represent the
+ number of floors in the building, and to light up the current
+ floor number.
+ """
+ npc=elevatorModel.findAllMatches("**/floor_light_?;+s")
+ for i in range(npc.getNumPaths()):
+ np=npc.getPath(i)
+ # Get the last character, and make it zero based:
+ floor=int(np.getName()[-1:])-1
+
+ if (floor == self.currentFloor):
+ np.setColor(LIGHT_ON_COLOR)
+ elif floor < self.layout.getNumGameFloors():
+ if self.isBossFloor(self.currentFloor):
+ np.setColor(LIGHT_ON_COLOR)
+ else:
+ np.setColor(LIGHT_OFF_COLOR)
+ else:
+ np.hide()
+
+ def handleAnnounceGenerate(self, obj):
+ """
+ handleAnnounceGenerate is called after all of the required fields are
+ filled in
+ 'obj' is another copy of self
+ """
+ self.ignore(self.announceGenerateName)
+
+ assert(self.notify.debug('joining DistributedCogdoInterior'))
+ # Update the minigame AI to join our local toon doId
+ self.sendUpdate('setAvatarJoined', [])
+
+ def disable(self):
+ assert(self.notify.debug('disable()'))
+ self.fsm.requestFinalState()
+ self.__cleanupIntervals()
+ self.ignoreAll()
+ self.__cleanup()
+ DistributedObject.DistributedObject.disable(self)
+
+ def delete(self):
+ assert(self.notify.debug('delete()'))
+ del self.waitMusic
+ del self.elevatorMusic
+ del self.openSfx
+ del self.closeSfx
+ del self.fsm
+ # No more battle multiplier
+ base.localAvatar.inventory.setBattleCreditMultiplier(1)
+ DistributedObject.DistributedObject.delete(self)
+
+ def isBossFloor(self, floorNum):
+ if self.layout.hasBossBattle():
+ if self.layout.getBossBattleFloor() == floorNum:
+ return True
+ return False
+
+ def __cleanup(self):
+ self.toons = []
+ self.suits = []
+ self.reserveSuits = []
+ self.joiningReserves = []
+ # Clean up elevator models
+ if (self.elevatorModelIn != None):
+ self.elevatorModelIn.removeNode()
+ if (self.elevatorModelOut != None):
+ self.elevatorModelOut.removeNode()
+ # Clean up current floor
+ if (self.floorModel != None):
+ self.floorModel.removeNode()
+ self.leftDoorIn = None
+ self.rightDoorIn = None
+ self.leftDoorOut = None
+ self.rightDoorOut = None
+
+ def __addToon(self, toon):
+ assert(self.notify.debug('addToon(%d)' % toon.doId))
+ self.accept(toon.uniqueName('disable'),
+ self.__handleUnexpectedExit, extraArgs=[toon])
+
+ def __handleUnexpectedExit(self, toon):
+ self.notify.warning('handleUnexpectedExit() - toon: %d' % toon.doId)
+ self.__removeToon(toon, unexpected=1)
+
+ def __removeToon(self, toon, unexpected=0):
+ assert(self.notify.debug('removeToon() - toon: %d' % toon.doId))
+ if (self.toons.count(toon) == 1):
+ self.toons.remove(toon)
+ self.ignore(toon.uniqueName('disable'))
+
+ def __finishInterval(self, name):
+ """ Force the specified interval to jump to the end
+ """
+ if (self.activeIntervals.has_key(name)):
+ interval = self.activeIntervals[name]
+ if (interval.isPlaying()):
+ assert(self.notify.debug('finishInterval(): %s' % \
+ interval.getName()))
+ interval.finish()
+
+ def __cleanupIntervals(self):
+ for interval in self.activeIntervals.values():
+ interval.finish()
+ self.activeIntervals = {}
+
+ def __closeInElevator(self):
+ self.leftDoorIn.setPos(3.5, 0, 0)
+ self.rightDoorIn.setPos(-3.5, 0, 0)
+
+ ##### Messages from the server #####
+
+ def getZoneId(self):
+ return self.zoneId
+
+ def setZoneId(self, zoneId):
+ self.zoneId = zoneId
+
+ def getExtZoneId(self):
+ return self.extZoneId
+
+ def setExtZoneId(self, extZoneId):
+ self.extZoneId = extZoneId
+
+ def getDistBldgDoId(self):
+ return self.distBldgDoId
+
+ def setDistBldgDoId(self, distBldgDoId):
+ self.distBldgDoId = distBldgDoId
+
+ def setNumFloors(self, numFloors):
+ self.layout = CogdoLayout(numFloors)
+
+ def getToonIds(self):
+ toonIds = []
+ for toon in self.toons:
+ toonIds.append(toon.doId)
+ return toonIds
+
+ def setToons(self, toonIds, hack):
+ assert(self.notify.debug('setToons(): %s' % toonIds))
+ self.toonIds = toonIds
+ oldtoons = self.toons
+ self.toons = []
+ for toonId in toonIds:
+ if (toonId != 0):
+ if (self.cr.doId2do.has_key(toonId)):
+ toon = self.cr.doId2do[toonId]
+ toon.stopSmooth()
+ self.toons.append(toon)
+ if (oldtoons.count(toon) == 0):
+ assert(self.notify.debug('setToons() - new toon: %d' % \
+ toon.doId))
+ self.__addToon(toon)
+ else:
+ self.notify.warning('setToons() - no toon: %d' % toonId)
+ for toon in oldtoons:
+ if (self.toons.count(toon) == 0):
+ self.__removeToon(toon)
+
+ def setSuits(self, suitIds, reserveIds, values):
+ assert(self.notify.debug('setSuits(): active %s reserve %s values %s' \
+ % (suitIds, reserveIds, values)))
+ oldsuits = self.suits
+ self.suits = []
+ self.joiningReserves = []
+ for suitId in suitIds:
+ if (self.cr.doId2do.has_key(suitId)):
+ suit = self.cr.doId2do[suitId]
+ self.suits.append(suit)
+ # Set this on the client
+ suit.fsm.request('Battle')
+ # This will allow client to respond to setState() from the
+ # server from here on out
+ suit.buildingSuit = 1
+ suit.reparentTo(render)
+ if (oldsuits.count(suit) == 0):
+ assert(self.notify.debug('setSuits() suit: %d joining' % \
+ suit.doId))
+ self.joiningReserves.append(suit)
+ else:
+ self.notify.warning('setSuits() - no suit: %d' % suitId)
+ self.reserveSuits = []
+ assert(len(reserveIds) == len(values))
+ for index in range(len(reserveIds)):
+ suitId = reserveIds[index]
+ if (self.cr.doId2do.has_key(suitId)):
+ suit = self.cr.doId2do[suitId]
+ self.reserveSuits.append((suit, values[index]))
+ else:
+ self.notify.warning('setSuits() - no suit: %d' % suitId)
+
+ if (len(self.joiningReserves) > 0):
+ assert(self.notify.debug('setSuits() reserves joining'))
+ self.fsm.request('ReservesJoining')
+
+ def setState(self, state, timestamp):
+ assert(self.notify.debug("setState(%s, %d)" % \
+ (state, timestamp)))
+ self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+
+ ##### Messages to the server #####
+
+ def d_elevatorDone(self):
+ assert(self.notify.debug('network:elevatorDone(%d)' % base.localAvatar.doId))
+ self.sendUpdate('elevatorDone', [])
+
+ def d_reserveJoinDone(self):
+ assert(self.notify.debug('network:reserveJoinDone(%d)' % base.localAvatar.doId))
+ self.sendUpdate('reserveJoinDone', [])
+
+ # Specific State Functions
+
+ ##### Off state #####
+
+ def enterOff(self, ts=0):
+ assert(self.notify.debug('enterOff()'))
+ return None
+
+ def exitOff(self):
+ return None
+
+ ##### WaitForAllToonsInside state #####
+
+ def enterWaitForAllToonsInside(self, ts=0):
+ assert(self.notify.debug('enterWaitForAllToonsInside()'))
+ return None
+
+ def exitWaitForAllToonsInside(self):
+ return None
+
+ ##### Elevator state #####
+
+ def __playElevator(self, ts, name, callback):
+ # Load the floor model
+
+ SuitHs = [] # Heading angles
+ SuitPositions = []
+
+ if self.floorModel:
+ self.floorModel.removeNode()
+ self.floorModel = None
+
+ if (self.currentFloor == 0):
+ # bottom floor
+ SuitHs = self.BottomFloor_SuitHs
+ SuitPositions = self.BottomFloor_SuitPositions
+ if self.isBossFloor(self.currentFloor):
+ # Top floor
+ self.floorModel = loader.loadModel('phase_7/models/modules/boss_suit_office')
+ SuitHs = self.BossOffice_SuitHs
+ SuitPositions = self.BossOffice_SuitPositions
+ else:
+ # middle floor
+ SuitHs = self.Cubicle_SuitHs
+ SuitPositions = self.Cubicle_SuitPositions
+
+ if self.floorModel:
+ self.floorModel.reparentTo(render)
+
+ # We need to name this something more useful (and we'll need the
+ # location of the opposite elevator as well)
+ elevIn = self.floorModel.find('**/elevator-in')
+ elevOut = self.floorModel.find('**/elevator-out')
+ else:
+ # TODO: TEMP
+ floorModel = loader.loadModel('phase_7/models/modules/boss_suit_office')
+ elevIn = floorModel.find('**/elevator-in').copyTo(render)
+ elevOut = floorModel.find('**/elevator-out').copyTo(render)
+ floorModel.removeNode()
+
+ # store elevOut until it's needed
+ self.elevOut = elevOut
+
+ # Position the suits
+
+ assert(len(self.suits) <= 4)
+ for index in range(len(self.suits)):
+ assert(self.notify.debug('setting suit: %d to pos: %s' % \
+ (self.suits[index].doId, SuitPositions[index])))
+ self.suits[index].setPos(SuitPositions[index])
+ if (len(self.suits) > 2):
+ self.suits[index].setH(SuitHs[index])
+ else:
+ self.suits[index].setH(170) # if there's 2 or 1 suits, make them face fwd since there's no other suits they would be to be talking to
+ self.suits[index].loop('neutral')
+
+ # Position the toons
+ for toon in self.toons:
+ toon.reparentTo(self.elevatorModelIn)
+ assert(self.toonIds.count(toon.doId) == 1)
+ index = self.toonIds.index(toon.doId)
+ assert(index >= 0 and index <= 3)
+ toon.setPos(ElevatorPoints[index][0],
+ ElevatorPoints[index][1],
+ ElevatorPoints[index][2])
+ toon.setHpr(180, 0, 0)
+ toon.loop('neutral')
+
+ # Show the elevator and position it in the correct place for the floor
+ self.elevatorModelIn.reparentTo(elevIn)
+ # Start with the doors in closed position
+ self.leftDoorIn.setPos(3.5, 0, 0)
+ self.rightDoorIn.setPos(-3.5, 0, 0)
+
+ # Position the camera behind the toons
+ camera.reparentTo(self.elevatorModelIn)
+ camera.setH(180)
+ camera.setPos(0, 14, 4)
+
+ # Play elevator music
+ base.playMusic(self.elevatorMusic, looping=1, volume=0.8)
+
+ # Ride the elevator, then open the doors.
+ track = Sequence(
+ ElevatorUtils.getRideElevatorInterval(ELEVATOR_NORMAL),
+ ElevatorUtils.getOpenInterval(self, self.leftDoorIn, self.rightDoorIn,
+ self.openSfx, None, type = ELEVATOR_NORMAL),
+ Func(camera.wrtReparentTo, render),
+ )
+
+ for toon in self.toons:
+ track.append(Func(toon.wrtReparentTo, render))
+ track.append(Func(callback))
+ track.start(ts)
+ self.activeIntervals[name] = track
+
+ def enterElevator(self, ts=0):
+ # Load model for the current floor and the suit models for the floor
+ assert(self.notify.debug('enterElevator()'))
+
+ self.currentFloor += 1
+ self.cr.playGame.getPlace().currentFloor = self.currentFloor
+ self.setElevatorLights(self.elevatorModelIn)
+ self.setElevatorLights(self.elevatorModelOut)
+
+ # hide elevator from previous floor (if any)
+ # unless it's the top floor, in that case leave it where it is
+ if not self.isBossFloor(self.currentFloor):
+ self.elevatorModelOut.detachNode()
+
+ self.__playElevator(ts, self.elevatorName, self.__handleElevatorDone)
+
+ # Get the floor multiplier
+ mult = ToontownBattleGlobals.getCreditMultiplier(self.currentFloor)
+ # Now set the inventory battleCreditMult
+ base.localAvatar.inventory.setBattleCreditMultiplier(mult)
+
+ def __handleElevatorDone(self):
+ assert(self.notify.debug('handleElevatorDone()'))
+ self.d_elevatorDone()
+
+ def exitElevator(self):
+ self.elevatorMusic.stop()
+ self.__finishInterval(self.elevatorName)
+ return None
+
+ def enterGame(self, ts=0):
+ assert(self.notify.debug('enterElevator()'))
+ pass
+
+ def exitGame(self):
+ pass
+
+ ##### Battle state #####
+
+ def __playCloseElevatorOut(self, name):
+ # Close the elevator doors
+ track = Sequence(
+ Wait(SUIT_LEAVE_ELEVATOR_TIME),
+ Parallel(SoundInterval(self.closeSfx),
+ LerpPosInterval(self.leftDoorOut,
+ ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ ElevatorUtils.getLeftClosePoint(ELEVATOR_NORMAL),
+ startPos=Point3(0, 0, 0),
+ blendType='easeOut'),
+ LerpPosInterval(self.rightDoorOut,
+ ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ ElevatorUtils.getRightClosePoint(ELEVATOR_NORMAL),
+ startPos=Point3(0, 0, 0),
+ blendType='easeOut')
+ ),
+ )
+ track.start()
+ self.activeIntervals[name] = track
+
+ def enterBattle(self, ts=0):
+ assert(self.notify.debug('enterBattle()'))
+
+ # now that we're in the barrel room, show the exit elevator
+ # Show the elevator and position it in the correct place for the floor
+ self.elevatorModelOut.reparentTo(self.elevOut)
+ # Start with the doors in closed position
+ self.leftDoorOut.setPos(3.5, 0, 0)
+ self.rightDoorOut.setPos(-3.5, 0, 0)
+
+ if (self.elevatorOutOpen == 1):
+ self.__playCloseElevatorOut(self.uniqueName('close-out-elevator'))
+ # Watch reserve suits as they walk from the elevator
+ camera.setPos(0, -15, 6)
+ camera.headsUp(self.elevatorModelOut)
+ return None
+
+ def exitBattle(self):
+ if (self.elevatorOutOpen == 1):
+ self.__finishInterval(self.uniqueName('close-out-elevator'))
+ self.elevatorOutOpen = 0
+ return None
+
+ ##### ReservesJoining state #####
+
+ def __playReservesJoining(self, ts, name, callback):
+ # Position the joining suits
+ index = 0
+ assert(len(self.joiningReserves) <= 4)
+ for suit in self.joiningReserves:
+ suit.reparentTo(render)
+ suit.setPos(self.elevatorModelOut, Point3(ElevatorPoints[index][0],
+ ElevatorPoints[index][1],
+ ElevatorPoints[index][2]))
+ index += 1
+ suit.setH(180)
+ suit.loop('neutral')
+
+ # Aim the camera at the far elevator
+ track = Sequence(
+ Func(camera.wrtReparentTo, self.elevatorModelOut),
+ Func(camera.setPos, Point3(0, -8, 2)),
+ Func(camera.setHpr, Vec3(0, 10, 0)),
+
+ # Open the elevator doors
+ Parallel(SoundInterval(self.openSfx),
+ LerpPosInterval(self.leftDoorOut,
+ ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ Point3(0, 0, 0),
+ startPos=ElevatorUtils.getLeftClosePoint(ELEVATOR_NORMAL),
+ blendType='easeOut'),
+ LerpPosInterval(self.rightDoorOut,
+ ElevatorData[ELEVATOR_NORMAL]['closeTime'],
+ Point3(0, 0, 0),
+ startPos=ElevatorUtils.getRightClosePoint(ELEVATOR_NORMAL),
+ blendType='easeOut'),
+ ),
+
+ # Hold the camera angle for a couple of beats
+ Wait(SUIT_HOLD_ELEVATOR_TIME),
+
+ # Reparent the camera to render (enterWaitForInput will
+ # position it properly again by the battle)
+ Func(camera.wrtReparentTo, render),
+ Func(callback),
+ )
+ track.start(ts)
+ self.activeIntervals[name] = track
+
+ def enterReservesJoining(self, ts=0):
+ assert(self.notify.debug('enterReservesJoining()'))
+ self.__playReservesJoining(ts, self.uniqueName('reserves-joining'),
+ self.__handleReserveJoinDone)
+ return None
+
+ def __handleReserveJoinDone(self):
+ assert(self.notify.debug('handleReserveJoinDone()'))
+ self.joiningReserves = []
+ self.elevatorOutOpen = 1
+ self.d_reserveJoinDone()
+
+ def exitReservesJoining(self):
+ self.__finishInterval(self.uniqueName('reserves-joining'))
+ return None
+
+ ##### Resting state #####
+
+ def enterResting(self, ts=0):
+ assert(self.notify.debug('enterResting()'))
+ base.playMusic(self.waitMusic, looping=1, volume=0.7)
+ self.__closeInElevator()
+ return
+
+ def exitResting(self):
+ self.waitMusic.stop()
+ return
+
+ ##### Reward state #####
+
+ def enterReward(self, ts=0):
+ assert(self.notify.debug('enterReward()'))
+ base.localAvatar.b_setParent(ToontownGlobals.SPHidden)
+ request = {
+ "loader": ZoneUtil.getBranchLoaderName(self.extZoneId),
+ "where": ZoneUtil.getToonWhereName(self.extZoneId),
+ "how": "elevatorIn",
+ "hoodId": ZoneUtil.getHoodId(self.extZoneId),
+ "zoneId": self.extZoneId,
+ "shardId": None,
+ "avId": -1,
+ "bldgDoId": self.distBldgDoId
+ }
+ # Presumably, suitInterior.py has hung a hook waiting for
+ # this request. I mimicked what DistributedDoor was doing.
+ messenger.send("DSIDoneEvent", [request])
+ return None
+
+ def exitReward(self):
+ return None
+
+ ##### Reset state #####
+
+ #def enterReset(self, ts=0):
+ # assert(self.notify.debug('enterReset()'))
+ # self.__cleanup()
+ # return None
+
+ #def exitReset(self):
+ # return None
diff --git a/toontown/src/cogdominium/DistributedCogdoInteriorAI.py b/toontown/src/cogdominium/DistributedCogdoInteriorAI.py
new file mode 100644
index 0000000..33fdb09
--- /dev/null
+++ b/toontown/src/cogdominium/DistributedCogdoInteriorAI.py
@@ -0,0 +1,829 @@
+from toontown.toonbase.ToontownBattleGlobals import *
+from otp.ai.AIBaseGlobal import *
+from direct.distributed.ClockDelta import *
+from toontown.building.ElevatorConstants import *
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM, State
+from direct.distributed import DistributedObjectAI
+from direct.fsm import State
+from toontown.battle import DistributedBattleBldgAI
+from toontown.battle import BattleBase
+from direct.task import Timer
+from toontown.cogdominium.DistributedCogdoElevatorIntAI import DistributedCogdoElevatorIntAI
+from toontown.cogdominium.CogdoLayout import CogdoLayout
+import copy
+
+from toontown.cogdominium.DistCogdoCraneGameAI import DistCogdoCraneGameAI
+
+class DistributedCogdoInteriorAI(DistributedObjectAI.DistributedObjectAI):
+ """
+ DistributedCogdoInteriorAI class:
+ """
+
+ if __debug__:
+ notify = DirectNotifyGlobal.directNotify.newCategory(
+ 'DistributedCogdoInteriorAI')
+
+ def __init__(self, air, elevator):
+ self.air = air
+ DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+ self.extZoneId, self.zoneId = elevator.bldg.getExteriorAndInteriorZoneId()
+ self._numFloors = elevator.bldg.planner.numFloors
+ self.layout = elevator.bldg._cogdoLayout
+ assert(len(elevator.seats) == 4)
+
+ self.avatarExitEvents = []
+ self.toons = []
+ self.toonSkillPtsGained = {}
+ self.toonExp = {}
+ self.toonOrigQuests = {}
+ self.toonItems = {}
+ self.toonOrigMerits = {}
+ self.toonMerits = {}
+ self.toonParts = {}
+ self.helpfulToons = []
+
+ self.currentFloor = 0
+ self.bldg = elevator.bldg
+ self.elevator = elevator
+
+ self._game = None
+
+ self.suits = []
+ self.activeSuits = []
+ self.reserveSuits = []
+ self.joinedReserves = []
+ self.suitsKilled = []
+ self.suitsKilledPerFloor = []
+ self.battle = None
+
+ self.timer = Timer.Timer()
+
+ self.responses = {}
+ self.ignoreResponses = 0
+ self.ignoreElevatorDone = 0
+ self.ignoreReserveJoinDone = 0
+
+ # Register all the toons
+ self.toonIds = copy.copy(elevator.seats)
+ for toonId in self.toonIds:
+ if (toonId != None):
+ self.__addToon(toonId)
+ assert(len(self.toons) > 0)
+
+ # Build a map of id:(name, style) pairs to have around for the
+ # end in case the toons are successful. These elements are
+ # filled in as each toon registers with the building.
+ self.savedByMap = {}
+
+ self.fsm = ClassicFSM.ClassicFSM(
+ 'DistributedCogdoInteriorAI',
+ [State.State('WaitForAllToonsInside',
+ self.enterWaitForAllToonsInside,
+ self.exitWaitForAllToonsInside,
+ ['Elevator']),
+ State.State('Elevator',
+ self.enterElevator,
+ self.exitElevator,
+ ['Game']),
+ State.State('Game',
+ self.enterGame,
+ self.exitGame,
+ ['Battle']),
+ State.State('Battle',
+ self.enterBattle,
+ self.exitBattle,
+ ['ReservesJoining',
+ 'BattleDone']),
+ State.State('ReservesJoining',
+ self.enterReservesJoining,
+ self.exitReservesJoining,
+ ['Battle']),
+ State.State('BattleDone',
+ self.enterBattleDone,
+ self.exitBattleDone,
+ ['Resting',
+ 'Reward']),
+ State.State('Resting',
+ self.enterResting,
+ self.exitResting,
+ ['Elevator']),
+ State.State('Reward',
+ self.enterReward,
+ self.exitReward,
+ ['Off']),
+ State.State('Off',
+ self.enterOff,
+ self.exitOff,
+ ['WaitForAllToonsInside'])],
+ # Initial state
+ 'Off',
+ # Final state
+ 'Off',
+ onUndefTransition = ClassicFSM.ClassicFSM.ALLOW)
+ self.fsm.enterInitialState()
+
+ def delete(self):
+ assert(self.notify.debug('delete()'))
+ self.ignoreAll()
+ self.toons = []
+ self.toonIds = []
+ self.fsm.requestFinalState()
+ del self.fsm
+ del self.bldg
+ del self.elevator
+ self.timer.stop()
+ del self.timer
+ self._cogdoLayout = None
+ self.__cleanupFloorBattle()
+
+ taskName = self.taskName('deleteInterior')
+ taskMgr.remove(taskName)
+
+ DistributedObjectAI.DistributedObjectAI.delete(self)
+
+ def requestDelete(self):
+ if self._game:
+ self._game.requestDelete()
+ DistributedObjectAI.DistributedObjectAI.requestDelete(self)
+
+ def addGameFSM(self, gameFSM):
+ """ games should call this with their game ClassicFSM """
+ self.fsm.getStateNamed('Game').addChild(gameFSM)
+
+ def removeGameFSM(self, gameFSM):
+ """ games should call this with their game ClassicFSM """
+ self.fsm.getStateNamed('Game').removeChild(gameFSM)
+
+ def __handleUnexpectedExit(self, toonId):
+ self.notify.warning('toon: %d exited unexpectedly' % toonId)
+ if self._game:
+ self._game.handleToonDisconnected(toonId)
+ self.__removeToon(toonId)
+ if (len(self.toons) == 0):
+ assert(self.notify.debug('last toon is gone!'))
+ self.timer.stop()
+ # The last toon exited unexpectedly - if we're in a battle, let
+ # the battle clean up first, if we're in Resting state, let
+ # the interior elevator clean up first, otherwise, just
+ # reset the suit interior.
+ if (self.fsm.getCurrentState().getName() == 'Resting'):
+ pass
+ elif (self.battle == None):
+ self.bldg.deleteCogdoInterior()
+
+ def _handleToonWentSad(self, toonId):
+ # for the game only, the battle handles this differently
+ self.notify.info('toon: %d went sad' % toonId)
+ toon = self.air.getDo(toonId)
+ if toon:
+ self.ignore(toon.getGoneSadMessage())
+ if self._game:
+ self._game.handleToonWentSad(toonId)
+ self.__removeToon(toonId)
+ if (len(self.toons) == 0):
+ assert(self.notify.debug('last toon is gone!'))
+ self.timer.stop()
+ # The last toon exited unexpectedly - if we're in a battle, let
+ # the battle clean up first, if we're in Resting state, let
+ # the interior elevator clean up first, otherwise, just
+ # reset the suit interior.
+ if (self.fsm.getCurrentState().getName() == 'Resting'):
+ pass
+ elif (self.battle == None):
+ # give the last client out time to play out the sad animation sequence
+ self._sadCleanupTask = taskMgr.doMethodLater(
+ 20, self._cleanupAfterLastToonWentSad, self.uniqueName('sadcleanup'))
+
+ def _cleanupAfterLastToonWentSad(self, task):
+ self._sadCleanupTask = None
+ self.bldg.deleteCogdoInterior()
+ return task.done
+
+ def __addToon(self, toonId):
+ assert(self.notify.debug('addToon(%d)' % toonId))
+ if (not self.air.doId2do.has_key(toonId)):
+ self.notify.warning('addToon() - no toon for doId: %d' % toonId)
+ return
+
+ # Handle unexpected exits for the toon
+ event = self.air.getAvatarExitEvent(toonId)
+ self.avatarExitEvents.append(event)
+ self.accept(event, self.__handleUnexpectedExit, extraArgs=[toonId])
+
+ self.toons.append(toonId)
+ assert(not self.responses.has_key(toonId))
+ self.responses[toonId] = 0
+
+ def __removeToon(self, toonId):
+ assert(self.notify.debug('removeToon(%d)' % toonId))
+
+ if self.toons.count(toonId):
+ self.toons.remove(toonId)
+
+ if self.toonIds.count(toonId):
+ self.toonIds[self.toonIds.index(toonId)] = None
+
+ if self.responses.has_key(toonId):
+ del self.responses[toonId]
+
+ # Ignore future exit events for the toon
+ event = self.air.getAvatarExitEvent(toonId)
+ if self.avatarExitEvents.count(event):
+ self.avatarExitEvents.remove(event)
+ self.ignore(event)
+
+ def __resetResponses(self):
+ self.responses = {}
+ for toon in self.toons:
+ self.responses[toon] = 0
+ self.ignoreResponses = 0
+
+ def __allToonsResponded(self):
+ for toon in self.toons:
+ assert(self.responses.has_key(toon))
+ if (self.responses[toon] == 0):
+ return 0
+ self.ignoreResponses = 1
+ return 1
+
+ # Distributed Messages
+
+ # setZoneIdAndBlock
+
+ def getZoneId(self):
+ assert(self.notify.debug('network:getZoneId()'))
+ return self.zoneId
+
+ def getExtZoneId(self):
+ return self.extZoneId
+
+ def getDistBldgDoId(self):
+ return self.bldg.getDoId()
+
+ def getNumFloors(self):
+ return self._numFloors
+
+ # setToons()
+
+ def d_setToons(self):
+ assert(self.notify.debug('network:setToons()'))
+ self.sendUpdate('setToons', self.getToons())
+
+ def getToons(self):
+ sendIds = []
+ for toonId in self.toonIds:
+ if (toonId == None):
+ sendIds.append(0)
+ else:
+ sendIds.append(toonId)
+ assert(self.notify.debug('getToons(): %s' % sendIds))
+ return [sendIds, 0]
+
+ # setSuits()
+
+ def d_setSuits(self):
+ assert(self.notify.debug('network:setSuits()'))
+ self.sendUpdate('setSuits', self.getSuits())
+
+ def getSuits(self):
+ suitIds = []
+ for suit in self.activeSuits:
+ suitIds.append(suit.doId)
+ reserveIds = []
+ values = []
+ for info in self.reserveSuits:
+ reserveIds.append(info[0].doId)
+ values.append(info[1])
+ return [suitIds, reserveIds, values]
+
+ # setState()
+
+ def b_setState(self, state):
+ self.d_setState(state)
+ self.setState(state)
+
+ def d_setState(self, state):
+ assert(self.notify.debug('network:setState(%s)' % state))
+ stime = globalClock.getRealTime() + BattleBase.SERVER_BUFFER_TIME
+ self.sendUpdate('setState', [state, globalClockDelta.localToNetworkTime(stime)])
+
+ def setState(self, state):
+ self.fsm.request(state)
+
+ def getState(self):
+ return [self.fsm.getCurrentState().getName(),
+ globalClockDelta.getRealNetworkTime()]
+
+ ##### Messages from the clients #####
+
+ def setAvatarJoined(self):
+ """setAvatarJoined(self)
+ This message is sent from a client toon to indicate that it
+ has finished loading the interior.
+ """
+ avId = self.air.getAvatarIdFromSender()
+ if (self.toons.count(avId) == 0):
+ self.air.writeServerEvent('suspicious', avId, 'DistributedCogdoInteriorAI.setAvatarJoined from toon not in %s.' % (self.toons))
+ self.notify.warning('setAvatarJoined() - av: %d not in list' % \
+ avId)
+ return
+
+ avatar = self.air.doId2do.get(avId)
+ if avatar != None:
+ self.savedByMap[avId] = (avatar.getName(), avatar.dna.asTuple())
+
+ assert(self.responses.has_key(avId))
+ self.responses[avId] += 1
+ assert(self.notify.debug('toon: %d in suit interior' % avId))
+ if (self.__allToonsResponded()):
+ self.fsm.request('Elevator')
+
+ def elevatorDone(self):
+ """elevatorDone(self)
+ This message is sent from a client toon to indicate that it has
+ finished viewing the elevator movie.
+ """
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ assert(self.notify.debug('elevatorDone() ignoring toon: %d' % \
+ toonId))
+ return
+ elif (self.fsm.getCurrentState().getName() != 'Elevator'):
+ self.notify.warning('elevatorDone() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('elevatorDone() - toon not in toon list: %d' \
+ % toonId)
+ assert(self.notify.debug('toons: %s toonIds: %s' % \
+ (self.toons, self.toonIds)))
+ return
+ assert(self.responses.has_key(toonId))
+ self.responses[toonId] += 1
+ assert(self.notify.debug('toon: %d done with elevator' % toonId))
+ if (self.__allToonsResponded() and
+ self.ignoreElevatorDone == 0):
+ self.b_setState('Game')
+
+ def reserveJoinDone(self):
+ """reserveJoinDone(self)
+ This message is sent from a client toon to indicate that it has
+ finished viewing the ReservesJoining movie.
+ """
+ toonId = self.air.getAvatarIdFromSender()
+ if (self.ignoreResponses == 1):
+ assert(self.notify.debug('reserveJoinDone() ignoring toon: %d' % \
+ toonId))
+ return
+ elif (self.fsm.getCurrentState().getName() != 'ReservesJoining'):
+ self.notify.warning('reserveJoinDone() - in state: %s' % \
+ self.fsm.getCurrentState().getName())
+ return
+ elif (self.toons.count(toonId) == 0):
+ self.notify.warning('reserveJoinDone() - toon not in list: %d' \
+ % toonId)
+ assert(self.notify.debug('toons: %s toonIds: %s' % \
+ (self.toons, self.toonIds)))
+ return
+ assert(self.responses.has_key(toonId))
+ self.responses[toonId] += 1
+ assert(self.notify.debug('toon: %d done with joining reserves' % \
+ toonId))
+ if (self.__allToonsResponded() and
+ self.ignoreReserveJoinDone == 0):
+ self.b_setState('Battle')
+
+ def isBossFloor(self, floorNum):
+ if self.layout.hasBossBattle():
+ if self.layout.getBossBattleFloor() == floorNum:
+ return True
+ return False
+
+ def isTopFloor(self, floorNum):
+ return (self.layout.getNumFloors()-1) == floorNum
+
+ # Specific State Functions
+
+ ##### Off state #####
+
+ def enterOff(self):
+ assert(self.notify.debug('enterOff()'))
+ return None
+
+ def exitOff(self):
+ return None
+
+ ##### WaitForAllToonsInside state #####
+
+ def enterWaitForAllToonsInside(self):
+ assert(self.notify.debug('enterWaitForAllToonsInside()'))
+ self.__resetResponses()
+ return None
+
+ def exitWaitForAllToonsInside(self):
+ self.__resetResponses()
+ return None
+
+ ##### Elevator state #####
+
+ def enterElevator(self):
+ assert(self.notify.debug('enterElevator()'))
+
+ if self.isBossFloor(self.currentFloor):
+ self._populateFloorSuits()
+ else:
+ self.d_setToons()
+
+ self.__resetResponses()
+
+ # create the game here so the players see something when the doors open
+ self._game = self._createGame()
+
+ self.d_setState('Elevator')
+
+ self.timer.startCallback(BattleBase.ELEVATOR_T + \
+ ElevatorData[ELEVATOR_NORMAL]['openTime'] + \
+ BattleBase.SERVER_BUFFER_TIME, self.__serverElevatorDone)
+ return None
+
+ def _createGame(self):
+ game = None
+ if not self.isBossFloor(self.currentFloor):
+ for toonId in self.toonIds:
+ if toonId:
+ toon = self.air.getDo(toonId)
+ if toon:
+ self.accept(toon.getGoneSadMessage(), Functor(self._handleToonWentSad, toonId))
+
+ game = DistCogdoCraneGameAI(self.air, self)
+ game.generateWithRequired(self.zoneId)
+ return game
+
+ def __serverElevatorDone(self):
+ assert(self.notify.debug('serverElevatorDone()'))
+ self.ignoreElevatorDone = 1
+ self.b_setState('Game')
+
+ def exitElevator(self):
+ self.timer.stop()
+ self.__resetResponses()
+ return None
+
+ ##### Game state #####
+
+ def enterGame(self):
+ assert(self.notify.debug('enterGame()'))
+
+ self.d_setState('Game')
+ self.elevator.d_setFloor(self.currentFloor)
+
+ # no game on the top floor, breeze on through
+ if self._game:
+ self._game.start()
+ else:
+ self._gameDone()
+
+ def _populateFloorSuits(self):
+ # Create the suits and place them in their initial positions on
+ # the floor
+ assert(self.currentFloor < self.layout.getNumFloors())
+ suitHandles = self.bldg.planner.genFloorSuits(self.currentFloor)
+ self.suits = suitHandles['activeSuits']
+ assert(len(self.suits) > 0)
+ self.activeSuits = []
+ for suit in self.suits:
+ self.activeSuits.append(suit)
+ self.reserveSuits = suitHandles['reserveSuits']
+
+ self.d_setToons()
+ # do this before setting state to 'battle' otherwise client show will get messed up
+ self.d_setSuits()
+
+ def _gameDone(self):
+ if len(self.toons) == 0:
+ # all toons went sad but game is still running so that the game doesn't disappear
+ # on the player's client as he watches sad animation. ignore this event
+ return
+ if not self.isBossFloor(self.currentFloor):
+ self._populateFloorSuits()
+ self.b_setState('Battle')
+ if self._game:
+ self._game.requestDelete()
+ self._game = None
+
+ for toonId in self.toonIds:
+ if toonId:
+ toon = self.air.getDo(toonId)
+ if toon:
+ self.ignore(toon.getGoneSadMessage())
+
+ def exitGame(self):
+ pass
+
+ ##### Battle state #####
+
+ def __createFloorBattle(self):
+ assert(len(self.toons) > 0)
+ if self.isBossFloor(self.currentFloor):
+ assert(self.notify.debug('createFloorBattle() - boss battle'))
+ bossBattle = 1
+ else:
+ bossBattle = 0
+ # Create the battle for the floor
+ self.battle = DistributedBattleBldgAI.DistributedBattleBldgAI(
+ self.air, self.zoneId,
+ self.__handleRoundDone, self.__handleBattleDone,
+ bossBattle=bossBattle)
+
+ # We store the lists of experience gained and suits killed in
+ # the DistributedCogdoInterior object, and share these pointers
+ # in all battles created for this building. This way, each
+ # battle will actually be modifying the same objects, these,
+ # and will thus accumulate the experience from previous
+ # battles.
+ self.battle.suitsKilled = self.suitsKilled
+ self.battle.suitsKilledPerFloor = self.suitsKilledPerFloor
+ self.battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained
+ self.battle.toonExp = self.toonExp
+ self.battle.toonOrigQuests = self.toonOrigQuests
+ self.battle.toonItems = self.toonItems
+ self.battle.toonOrigMerits = self.toonOrigMerits
+ self.battle.toonMerits = self.toonMerits
+ self.battle.toonParts = self.toonParts
+ self.battle.helpfulToons = self.helpfulToons
+
+ # We must set the members of a building battle before we
+ # generate it.
+ self.battle.setInitialMembers(self.toons, self.suits)
+ self.battle.generateWithRequired(self.zoneId)
+
+ # We get a bonus factor applied toward each attack's experience credit.
+ mult = getCreditMultiplier(self.currentFloor)
+
+ # If there is an invasion, multiply the exp for the duration of this battle
+ # Now, if the invasion ends midway through this battle, the players will
+ # continue getting credit. This is ok I guess.
+ if self.air.suitInvasionManager.getInvading():
+ mult *= getInvasionMultiplier()
+
+ self.battle.battleCalc.setSkillCreditMultiplier(mult)
+
+ def __cleanupFloorBattle(self):
+ for suit in self.suits:
+ self.notify.debug('cleaning up floor suit: %d' % suit.doId)
+ if suit.isDeleted():
+ self.notify.debug('whoops, suit %d is deleted.' % suit.doId)
+ else:
+ suit.requestDelete()
+ self.suits = []
+ self.reserveSuits = []
+ self.activeSuits = []
+ if (self.battle != None):
+ self.battle.requestDelete()
+ self.battle = None
+
+ def __handleRoundDone(self, toonIds, totalHp, deadSuits):
+ # Determine if any reserves need to join
+ assert(self.notify.debug('handleRoundDone() - hp: %d' % totalHp))
+ # Calculate the total max HP for all the suits currently on the floor
+ totalMaxHp = 0
+ for suit in self.suits:
+ totalMaxHp += suit.maxHP
+
+ for suit in deadSuits:
+ self.activeSuits.remove(suit)
+
+ # Determine if any reserve suits need to join
+ if (len(self.reserveSuits) > 0 and len(self.activeSuits) < 4):
+ assert(self.notify.debug('potential reserve suits: %d' % \
+ len(self.reserveSuits)))
+ self.joinedReserves = []
+ assert(totalHp <= totalMaxHp)
+ hpPercent = 100 - (totalHp / totalMaxHp * 100.0)
+ assert(self.notify.debug('totalHp: %d totalMaxHp: %d percent: %f' \
+ % (totalHp, totalMaxHp, hpPercent)))
+ for info in self.reserveSuits:
+ if (info[1] <= hpPercent and
+ len(self.activeSuits) < 4):
+ assert(self.notify.debug('reserve: %d joining percent: %f' \
+ % (info[0].doId, info[1])))
+ self.suits.append(info[0])
+ self.activeSuits.append(info[0])
+ self.joinedReserves.append(info)
+ for info in self.joinedReserves:
+ self.reserveSuits.remove(info)
+ if (len(self.joinedReserves) > 0):
+ # setSuits() triggers the state change on the client
+ self.fsm.request('ReservesJoining')
+ self.d_setSuits()
+ return
+
+ # See if the battle is done
+ if (len(self.activeSuits) == 0):
+ self.fsm.request('BattleDone', [toonIds])
+ else:
+ # No reserve suits to join - tell the battle to continue
+ self.battle.resume()
+
+ def __handleBattleDone(self, zoneId, toonIds):
+ assert(self.notify.debug('%s.handleBattleDone(%s, %s)' % (self.doId, zoneId, toonIds)))
+ #self.fsm.request('BattleDone', [toonIds])
+ if (len(toonIds) == 0):
+ assert(self.notify.debug('handleBattleDone() - last toon gone'))
+
+ # Rather than shutting down immediately, we give the last
+ # toon a few seconds to finish playing his teleport-out
+ # animation before the world goes away.
+
+ taskName = self.taskName('deleteInterior')
+ taskMgr.doMethodLater(10, self.__doDeleteInterior, taskName)
+
+ elif self.isTopFloor(self.currentFloor):
+ # This is not b_setState, because enterReward has to do
+ # some things before the broadcast takes place. enterReward
+ # will call d_setState when it is ready.
+ self.setState('Reward')
+ else:
+ self.b_setState('Resting')
+
+ def __doDeleteInterior(self, task):
+ self.bldg.deleteCogdoInterior()
+
+ def enterBattle(self):
+ assert(self.notify.debug('enterBattle()'))
+
+ if (self.battle == None):
+ self.__createFloorBattle()
+ return None
+
+ def exitBattle(self):
+ return None
+
+ ##### ReservesJoining state #####
+
+ def enterReservesJoining(self):
+ assert(self.notify.debug('enterReservesJoining()'))
+ self.__resetResponses()
+ self.timer.startCallback(
+ ElevatorData[ELEVATOR_NORMAL]['openTime'] + \
+ SUIT_HOLD_ELEVATOR_TIME + \
+ BattleBase.SERVER_BUFFER_TIME,
+ self.__serverReserveJoinDone)
+ return None
+
+ def __serverReserveJoinDone(self):
+ """__serverReserveJoinDone()
+ This callback is made only if some of the toons don't send
+ their reserveJoinDone() message in a reasonable time--rather
+ than waiting for everyone, we simply carry on without them.
+ """
+ assert(self.notify.debug('serverReserveJoinDone()'))
+ self.ignoreReserveJoinDone = 1
+ self.b_setState('Battle')
+
+ def exitReservesJoining(self):
+ self.timer.stop()
+ self.__resetResponses()
+ # Join the suits to the battle and tell it to resume
+ for info in self.joinedReserves:
+ self.battle.suitRequestJoin(info[0])
+ self.battle.resume()
+ self.joinedReserves = []
+ return None
+
+ ##### BattleDone state #####
+
+ def enterBattleDone(self, toonIds):
+ assert(self.notify.debug('enterBattleDone()'))
+ # Find out if any toons are gone
+ if (len(toonIds) != len(self.toons)):
+ deadToons = []
+ for toon in self.toons:
+ if (toonIds.count(toon) == 0):
+ deadToons.append(toon)
+ for toon in deadToons:
+ self.__removeToon(toon)
+ self.d_setToons()
+
+ if (len(self.toons) == 0):
+ self.bldg.deleteCogdoInterior()
+ else:
+ if self.isTopFloor(self.currentFloor):
+ # Toons beat the building
+ self.battle.resume(self.currentFloor, topFloor=1)
+ else:
+ # The building isn't finished yet - gather up experience and
+ # activate the elevator
+ self.battle.resume(self.currentFloor, topFloor=0)
+ return None
+
+ def exitBattleDone(self):
+ self.__cleanupFloorBattle()
+ self.d_setSuits()
+ return None
+
+ ##### Resting state #####
+
+ def __handleEnterElevator(self):
+ self.fsm.request('Elevator')
+
+ def enterResting(self):
+ assert(self.notify.debug('enterResting()'))
+ # Tell the elevator to start accepting entrants
+ self.intElevator = DistributedCogdoElevatorIntAI(
+ self.air, self, self.toons)
+ self.intElevator.generateWithRequired(self.zoneId)
+ return None
+
+ def handleAllAboard(self, seats):
+ if not hasattr(self, "fsm"):
+ # If we've already been cleaned up, never mind.
+ return
+
+ assert(self.fsm.getCurrentState().getName() == "Resting")
+ assert(self.notify.debug('handleAllAboard() - toons: %s' % self.toons))
+
+ # Make sure the number of empty seats is correct. If it is empty,
+ # reset and get us out of here.
+ numOfEmptySeats = seats.count(None)
+ if (numOfEmptySeats == 4):
+ self.bldg.deleteCogdoInterior()
+ return
+ elif (numOfEmptySeats >= 0) and (numOfEmptySeats <=3):
+ pass
+ else:
+ self.error("Bad number of empty seats: %s" % numOfEmptySeats)
+
+ for toon in self.toons:
+ if seats.count(toon) == 0:
+ self.__removeToon(toon)
+ self.toonIds = copy.copy(seats)
+ self.toons = []
+ for toonId in self.toonIds:
+ if (toonId != None):
+ self.toons.append(toonId)
+
+ self.d_setToons()
+
+ # Increment the floor number
+ self.currentFloor += 1
+ self.fsm.request('Elevator')
+ return
+
+ def exitResting(self):
+ self.intElevator.requestDelete()
+ del self.intElevator
+ return None
+
+ ##### Reward state #####
+
+ def enterReward(self):
+ assert(self.notify.debug('enterReward()'))
+ # Tell the building to get ready for the victors to come outside.
+
+ # We pass in a *copy* of the toonIds list, not the list
+ # itself, so that when we pull toons out of our list (for
+ # instance, if they disconnect unexpectedly), it won't affect
+ # the building's victor list.
+ victors = self.toonIds[:]
+
+ # Build a new savedBy list that includes only the Toons that
+ # made it to the end.
+ savedBy = []
+ for v in victors:
+ tuple = self.savedByMap.get(v)
+ if tuple:
+ savedBy.append([v, tuple[0], tuple[1]])
+
+ # Going to waitForVictors deletes the elevator
+ self.bldg.fsm.request("waitForVictorsFromCogdo", [victors, savedBy])
+ # Tell the players to go back outside.
+ self.d_setState('Reward')
+ return None
+
+ def exitReward(self):
+ return None
+
+ ##### Reset state #####
+
+ #def enterReset(self):
+ # assert(self.notify.debug('enterReset()'))
+ # # Unload all floor and suit models, but keep the suit database -
+ # # the contents of the building should remain the same until the
+ # # toons retake the building
+ # self.__cleanupFloorBattle()
+ # self.currentFloor = 0
+ # # -1 means the lobby.
+ # self.elevator.d_setFloor(-1)
+ # # Get rid of the toons
+ # for toon in copy.copy(self.toons):
+ # self.__removeToon(toon)
+ # self.savedByMap = {}
+ # self.b_setState('Off')
+ # self.elevator.open()
+ # self.requestDelete()
+
+ #def exitReset(self):
+ # return None
diff --git a/toontown/src/cogdominium/Sources.pp b/toontown/src/cogdominium/Sources.pp
new file mode 100644
index 0000000..a03ea8c
--- /dev/null
+++ b/toontown/src/cogdominium/Sources.pp
@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+
diff --git a/toontown/src/cogdominium/SuitPlannerCogdoInteriorAI.py b/toontown/src/cogdominium/SuitPlannerCogdoInteriorAI.py
new file mode 100644
index 0000000..46ce21b
--- /dev/null
+++ b/toontown/src/cogdominium/SuitPlannerCogdoInteriorAI.py
@@ -0,0 +1,10 @@
+from toontown.building.SuitPlannerInteriorAI import SuitPlannerInteriorAI
+
+class SuitPlannerCogdoInteriorAI(SuitPlannerInteriorAI):
+ def __init__(self, cogdoLayout, bldgLevel, bldgTrack, zone):
+ self._cogdoLayout = cogdoLayout
+ SuitPlannerInteriorAI.__init__(self, self._cogdoLayout.getNumGameFloors(), bldgLevel, bldgTrack, zone)
+
+ def _genSuitInfos(self, numFloors, bldgLevel, bldgTrack):
+ SuitPlannerInteriorAI._genSuitInfos(self, self._cogdoLayout.getNumFloors(), bldgLevel, bldgTrack)
+
diff --git a/toontown/src/cogdominium/__init__.py b/toontown/src/cogdominium/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/toontown/src/coghq/.cvsignore b/toontown/src/coghq/.cvsignore
new file mode 100644
index 0000000..28f9318
--- /dev/null
+++ b/toontown/src/coghq/.cvsignore
@@ -0,0 +1,5 @@
+.cvsignore
+Makefile
+pp.dep
+*.pyc
+*.autosave
diff --git a/toontown/src/coghq/ActiveCell.py b/toontown/src/coghq/ActiveCell.py
new file mode 100644
index 0000000..79ed2b2
--- /dev/null
+++ b/toontown/src/coghq/ActiveCell.py
@@ -0,0 +1,48 @@
+from pandac.PandaModules import *
+from otp.level import BasicEntities
+from direct.directnotify import DirectNotifyGlobal
+
+class ActiveCell(BasicEntities.DistributedNodePathEntity):
+ notify = DirectNotifyGlobal.directNotify.newCategory("ActiveCell")
+
+ def __init__(self, cr):
+ BasicEntities.DistributedNodePathEntity.__init__(self,cr)
+ self.occupantId = -1
+ self.state = 0
+
+ def announceGenerate(self):
+ BasicEntities.DistributedNodePathEntity.announceGenerate(self)
+ self.loadModel()
+
+ def loadModel(self):
+ # draw something so we know where the activeCell is
+ # do this only in debug mode
+ if 0 and __debug__:
+ grid = self.level.entities.get(self.gridId, None)
+ if grid:
+ pos = grid.getPos() + Vec3(self.col * grid.cellSize,
+ self.row * grid.cellSize,
+ 0)
+ model = loader.loadModel("phase_5/models/modules/suit_walls.bam")
+ model.setScale(grid.cellSize, 1, grid.cellSize)
+ model.setP(-90)
+ model.flattenMedium()
+ model.setZ(.05)
+ model.setColorScale(1,0,0,.5)
+ model.copyTo(self)
+
+ self.setPos(pos)
+ return
+
+ def setState(self, state, objId):
+ assert(self.notify.debug("entId(%d): setState(%d,%d)" % (self.entId, state, objId)))
+ self.state = state
+ self.occupantId = objId
+
+ if __dev__:
+ def attribChanged(self, *args):
+ model = self.find("*")
+ if not model.isEmpty():
+ model.removeNode()
+ self.loadModel()
+
diff --git a/toontown/src/coghq/ActiveCellAI.py b/toontown/src/coghq/ActiveCellAI.py
new file mode 100644
index 0000000..9e4d1a9
--- /dev/null
+++ b/toontown/src/coghq/ActiveCellAI.py
@@ -0,0 +1,62 @@
+from otp.level import DistributedEntityAI
+from direct.directnotify import DirectNotifyGlobal
+
+class ActiveCellAI(DistributedEntityAI.DistributedEntityAI):
+ notify = DirectNotifyGlobal.directNotify.newCategory("ActiveCellAI")
+
+ def __init__(self, level, entId):
+ self.state = 0
+ self.grid = None
+ self.occupantIds = []
+ DistributedEntityAI.DistributedEntityAI.__init__(self, level, entId)
+
+ def setGrid(gridId=self.gridId, self=self):
+ self.grid = self.level.entities.get(gridId, None)
+ if self.grid:
+ # add ourselves to the activeCell list on the grid
+ self.grid.addActiveCell(self)
+ return 1
+ return 0
+
+ if not setGrid():
+ self.accept(self.level.getEntityCreateEvent(self.gridId), setGrid)
+
+ def generate(self):
+ DistributedEntityAI.DistributedEntityAI.generate(self)
+
+ def delete(self):
+ self.notify.debug('delete')
+ self.ignoreAll()
+ DistributedEntityAI.DistributedEntityAI.delete(self)
+
+ def getState(self):
+ return self.state
+
+ def b_setState(self, state, objId = None):
+ self.setState(state, objId)
+ self.d_setState(state, objId)
+
+ def d_setState(self, state, objId = None):
+ # SDN: note, we should send all occupantIds instead
+ # of just the current objId
+ if not objId:
+ objId = 0
+ self.sendUpdate('setState', [state, objId])
+
+ def setState(self, state, objId=None):
+ assert(self.notify.debug("setState(%s,%s)" % (state,objId)))
+ # derived classes should override this method to do something meaningful
+ # when the state of this grid cell changes
+ self.state = state
+
+ # object (i.e. crate, goon) contained in this cell
+ if state:
+ self.occupantIds.append(objId)
+ else:
+ try:
+ self.occupantIds.remove(objId)
+ except:
+ self.notify.warning("couldn't remove %s from active cell" % objId)
+
+ def getRowCol(self):
+ return [self.row,self.col]
diff --git a/toontown/src/coghq/BanquetTableBase.py b/toontown/src/coghq/BanquetTableBase.py
new file mode 100644
index 0000000..426770d
--- /dev/null
+++ b/toontown/src/coghq/BanquetTableBase.py
@@ -0,0 +1,9 @@
+class BanquetTableBase:
+ """Hold methods and constants shared in client and AI versions."""
+ # states of the diners
+ HUNGRY = 1
+ DEAD = 0
+ EATING =2 # distance between each food node
+ ANGRY = 3 # haven't been fed in a while, resets to 0
+ HIDDEN = 4 # we don't show him in the scene
+ INACTIVE = 5 # just sitting in the chair, waiting for battle two to start
diff --git a/toontown/src/coghq/BarrelBase.py b/toontown/src/coghq/BarrelBase.py
new file mode 100644
index 0000000..9fbd13c
--- /dev/null
+++ b/toontown/src/coghq/BarrelBase.py
@@ -0,0 +1,60 @@
+import random
+
+class BarrelBase:
+ # use this random generator whenever you calculate a random value for
+ # the barrel that must be independently the same on the client and AI.
+ # Use for one calculation and then discard.
+ def getRng(self):
+ return random.Random(self.entId * self.level.doId)
+
+ # the member variables that start with _ are the randomly-chosen values
+ def getRewardPerGrab(self):
+ if not hasattr(self, '_reward'):
+ if self.rewardPerGrabMax > self.rewardPerGrab:
+ self._reward = self.getRng().randrange(self.rewardPerGrab,
+ self.rewardPerGrabMax+1)
+ else:
+ self._reward = self.rewardPerGrab
+ return self._reward
+
+ def getGagLevel(self):
+ if not hasattr(self, '_gagLevel'):
+ if self.gagLevelMax > self.gagLevel:
+ self._gagLevel = self.getRng().randrange(self.gagLevel,
+ self.gagLevelMax+1)
+ else:
+ self._gagLevel = self.gagLevel
+ return self._gagLevel
+
+ def getGagTrack(self):
+ if not hasattr(self, '_gagTrack'):
+ if self.gagTrack == 'random':
+ # throw/squirt more likely
+ tracks = (0,1,2,3,4,4,5,5,6,)
+ self._gagTrack = self.getRng().choice(tracks)
+ else:
+ self._gagTrack = self.gagTrack
+ return self._gagTrack
+
+ if __dev__:
+ def setRewardPerGrab(self, rewardPerGrab):
+ if hasattr(self, '_reward'):
+ del self._reward
+ self.rewardPerGrab = rewardPerGrab
+ def setRewardPerGrabMax(self, rewardPerGrabMax):
+ if hasattr(self, '_reward'):
+ del self._reward
+ self.rewardPerGrabMax = rewardPerGrabMax
+
+ def setGagLevel(self, gagLevel):
+ if hasattr(self, '_gagLevel'):
+ del self._gagLevel
+ self.gagLevel = gagLevel
+ def setGagLevelMax(self, gagLevelMax):
+ if hasattr(self, '_gagLevel'):
+ del self._gagLevel
+ self.gagLevelMax = gagLevelMax
+ def setGagTrack(self, gagTrack):
+ if hasattr(self, '_gagTrack'):
+ del self._gagTrack
+ self.gagTrack = gagTrack
diff --git a/toontown/src/coghq/BattleBlocker.py b/toontown/src/coghq/BattleBlocker.py
new file mode 100644
index 0000000..c47b26b
--- /dev/null
+++ b/toontown/src/coghq/BattleBlocker.py
@@ -0,0 +1,130 @@
+"""BattleBlocker module: contains the BattleBlocker class"""
+
+from pandac.PandaModules import *
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from otp.level import BasicEntities
+from toontown.toonbase import ToontownGlobals
+from direct.directnotify import DirectNotifyGlobal
+
+""" BattleBlocker is a collision sphere that is associated with a battle cell. If
+the collision sphere is entered, it sends a collision message to all suits also
+associated with the battle cell. This will trigger a battle. Once all the suits in
+the battle cell have been defeated, the BattleBlockers message will fall on deaf ears,
+and the toon can pass through it without triggering a battle """
+
+# TODO: allow other geometry than a collision sphere
+
+class BattleBlocker(BasicEntities.DistributedNodePathEntity):
+ notify = DirectNotifyGlobal.directNotify.newCategory("BattleBlocker")
+ def __init__(self, cr):
+ BasicEntities.DistributedNodePathEntity.__init__(self, cr)
+ self.suitIds = []
+ self.battleId = None
+
+ def setActive(self, active):
+ self.active = active
+
+ def announceGenerate(self):
+ BasicEntities.DistributedNodePathEntity.announceGenerate(self)
+ self.initCollisionGeom()
+
+ def disable(self):
+ self.ignoreAll()
+ self.unloadCollisionGeom()
+ BasicEntities.DistributedNodePathEntity.disable(self)
+
+ def destroy(self):
+ BasicEntities.DistributedNodePathEntity.destroy(self)
+
+ def setSuits(self, suitIds):
+ self.suitIds = suitIds
+
+ def setBattle(self, battleId):
+ self.battleId = battleId
+
+ def setBattleFinished(self):
+ # this is only called if the Toons won the battle
+ assert(self.notify.debug("setBattleFinished, %s" % self.entId))
+ # A message is already sent on the AI, so we probably
+ # don't need to send one on the client
+ #messenger.send("battleBlocker-"+str(self.entId))
+ # no need to listen for collision events anymore
+ self.ignoreAll()
+
+ def initCollisionGeom(self):
+ self.cSphere = CollisionSphere(0,0,0,self.radius)
+ self.cSphereNode = CollisionNode("battleBlocker-%s-%s" %
+ (self.level.getLevelId(), self.entId))
+ self.cSphereNode.addSolid(self.cSphere)
+ self.cSphereNodePath = self.attachNewNode(self.cSphereNode)
+ self.cSphereNode.setCollideMask(ToontownGlobals.WallBitmask)
+ self.cSphere.setTangible(0)
+
+ self.enterEvent = "enter" + self.cSphereNode.getName()
+ self.accept(self.enterEvent, self.__handleToonEnter)
+
+ """ rather than do this, just use ~cs
+ if __dev__:
+ from otp.otpbase import OTPGlobals
+ self.showCS(OTPGlobals.WallBitmask)
+ """
+
+ def unloadCollisionGeom(self):
+ if hasattr(self, 'cSphereNodePath'):
+ self.ignore(self.enterEvent)
+ del self.cSphere
+ del self.cSphereNode
+ self.cSphereNodePath.removeNode()
+ del self.cSphereNodePath
+
+ def __handleToonEnter(self, collEntry):
+ self.notify.debug ("__handleToonEnter, %s" % self.entId)
+ self.startBattle()
+
+
+ def startBattle(self):
+ if not self.active:
+ return
+
+ # don't listen for any more events from the blocker
+ # this Toon might not win the battle
+ #self.ignoreAll()
+
+ callback = None
+ # first check if there is a valid battle going on
+ if self.battleId != None and self.battleId in base.cr.doId2do:
+ battle = base.cr.doId2do.get(self.battleId)
+ if battle:
+ self.notify.debug("act like we collided with battle %d" % self.battleId)
+ callback = battle.handleBattleBlockerCollision
+ elif len(self.suitIds) > 0:
+ # a battle has not been created yet, pick the first
+ # valid suit to start a battle with
+ for suitId in self.suitIds:
+ suit = base.cr.doId2do.get(suitId)
+ if suit:
+ self.notify.debug("act like we collided with Suit %d ( in state %s )" % (suitId, suit.fsm.getCurrentState().getName()))
+ callback = suit.handleBattleBlockerCollision
+ break
+
+ # the show to explain the local toon getting stopped by this collision sphere
+ self.showReaction(callback)
+
+
+ def showReaction(self, callback=None):
+ if not base.localAvatar.wantBattles:
+ return
+
+ track = Sequence()
+ # add some minimal show
+ #track = Sequence(Func(base.cr.playGame.place.setState,'WaitForBattle'),
+ # ActorInterval(base.localAvatar, animName = 'slip-backward'))
+ if callback:
+ track.append(Func(callback))
+ track.start()
+
+ if __dev__:
+ def attribChanged(self, *args):
+ self.unloadCollisionGeom()
+ self.initCollisionGeom()
diff --git a/toontown/src/coghq/BattleBlockerAI.py b/toontown/src/coghq/BattleBlockerAI.py
new file mode 100644
index 0000000..cc6dca9
--- /dev/null
+++ b/toontown/src/coghq/BattleBlockerAI.py
@@ -0,0 +1,86 @@
+from otp.level import DistributedEntityAI
+from direct.directnotify import DirectNotifyGlobal
+
+class BattleBlockerAI(DistributedEntityAI.DistributedEntityAI):
+ notify = DirectNotifyGlobal.directNotify.newCategory("BattleBlockerAI")
+ def __init__(self, level, entId):
+ DistributedEntityAI.DistributedEntityAI.__init__(self, level, entId)
+ self.suitIds = []
+ self.active = 1
+
+ def destroy(self):
+ self.notify.debug("delete")
+ self.ignoreAll()
+ DistributedEntityAI.DistributedEntityAI.destroy(self)
+
+ def generate(self):
+ DistributedEntityAI.DistributedEntityAI.generate(self)
+ # listen for creation of planner
+ self.accept("plannerCreated-"+str(self.level.doId), self.registerBlocker)
+
+ def registerBlocker(self):
+ # register this blocker with the factory
+ if hasattr(self.level, 'planner'):
+ self.level.planner.battleMgr.addBattleBlocker(self, self.cellId)
+
+ def deactivate(self):
+ if self.isDeleted():
+ return
+ self.active = 0
+ self.sendUpdate('setActive', [self.active])
+
+ def getActive(self):
+ return self.active
+
+ def addSuit(self, suit):
+ self.suitIds.append(suit.doId)
+ self.d_setSuits()
+
+ def removeSuit(self, suit):
+ try:
+ self.suitIds.remove(suit.doId)
+ self.d_setSuits()
+ except:
+ self.notify.debug("didn't have suitId %d" % suit.doId)
+ pass
+
+ def d_setSuits(self):
+ # assume the factory has been generated here
+ self.sendUpdate("setSuits", [self.suitIds])
+
+ def b_setBattle(self, battleId):
+ self.battle = battleId
+ self.d_setBattle(battleId)
+
+ def d_setBattle(self, battleId):
+ self.sendUpdate("setBattle", [battleId])
+
+ def b_setBattleFinished(self):
+ # this is only called if the Toons won the battle
+ self.deactivate()
+ self.setBattleFinished()
+ self.d_setBattleFinished()
+
+ def setBattleFinished(self):
+ self.notify.debug("setBattleFinished: %s" % self.entId)
+ # a door might be listening to this
+ messenger.send("battleBlockerFinished-"+str(self.entId))
+ messenger.send(self.getOutputEventName(),[1])
+
+ def d_setBattleFinished(self):
+ self.sendUpdate("setBattleFinished", [])
+
+ if __dev__:
+ def attribChanged(self, *args):
+ self.suitIds = []
+ suits = self.level.planner.battleCellId2suits.get(self.cellId)
+ if suits:
+ # find suits associated with this cell
+ for suit in suits:
+ self.suitIds.append(suit.doId)
+ else:
+ self.notify.warning(
+ "Couldn't find battle cell id %d in battleCellId2suits" % self.cellId)
+ self.d_setSuits()
+ self.registerBlocker()
+
diff --git a/toontown/src/coghq/BattleExperienceAggregatorAI.py b/toontown/src/coghq/BattleExperienceAggregatorAI.py
new file mode 100644
index 0000000..9f4d223
--- /dev/null
+++ b/toontown/src/coghq/BattleExperienceAggregatorAI.py
@@ -0,0 +1,25 @@
+class BattleExperienceAggregatorAI:
+ """This class holds battle experience data across multiple battles"""
+ def __init__(self):
+ self.suitsKilled = []
+ self.suitsKilledPerFloor = []
+ self.toonSkillPtsGained = {}
+ self.toonExp = {}
+ self.toonOrigQuests = {}
+ self.toonItems = {}
+ self.toonOrigMerits = {}
+ self.toonMerits = {}
+ self.toonParts = {}
+ self.helpfulToons = []
+
+ def attachToBattle(self, battle):
+ battle.suitsKilled = self.suitsKilled
+ battle.suitsKilledPerFloor = self.suitsKilledPerFloor
+ battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained
+ battle.toonExp = self.toonExp
+ battle.toonOrigQuests = self.toonOrigQuests
+ battle.toonItems = self.toonItems
+ battle.toonOrigMerits = self.toonOrigMerits
+ battle.toonMerits = self.toonMerits
+ battle.toonParts = self.toonParts
+ battle.helpfulToons = self.helpfulToons
diff --git a/toontown/src/coghq/BossbotCogHQLoader.py b/toontown/src/coghq/BossbotCogHQLoader.py
new file mode 100644
index 0000000..701b3f0
--- /dev/null
+++ b/toontown/src/coghq/BossbotCogHQLoader.py
@@ -0,0 +1,185 @@
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import StateData
+import CogHQLoader
+from toontown.toonbase import ToontownGlobals
+from direct.gui import DirectGui
+from toontown.toonbase import TTLocalizer
+from toontown.toon import Toon
+from direct.fsm import State
+from toontown.coghq import BossbotHQExterior
+from toontown.coghq import BossbotHQBossBattle
+from toontown.coghq import BossbotOfficeExterior
+from toontown.coghq import CountryClubInterior
+from pandac.PandaModules import DecalEffect, TextEncoder
+import random
+
+# Used to compensate for scaling of Cog tunnel sign's
+# original aspect ratio of 1125x813 to a uniform ratio,
+# scale z by factor of 0.7227
+aspectSF = 0.7227
+
+class BossbotCogHQLoader(CogHQLoader.CogHQLoader):
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("BossbotCogHQLoader")
+ #notify.setDebug(True)
+
+ def __init__(self, hood, parentFSMState, doneEvent):
+ CogHQLoader.CogHQLoader.__init__(self, hood, parentFSMState, doneEvent)
+
+ self.fsm.addState(State.State('countryClubInterior',
+ self.enterCountryClubInterior,
+ self.exitCountryClubInterior,
+ ['quietZone',
+ 'cogHQExterior', # Tunnel
+ ]))
+
+ for stateName in ['start', 'cogHQExterior', 'quietZone']:
+ state = self.fsm.getStateNamed(stateName)
+ state.addTransition('countryClubInterior')
+
+
+ self.musicFile = random.choice(["phase_12/audio/bgm/Bossbot_Entry_v1.mid", "phase_12/audio/bgm/Bossbot_Entry_v2.mid", "phase_12/audio/bgm/Bossbot_Entry_v3.mid"])
+
+ self.cogHQExteriorModelPath = "phase_12/models/bossbotHQ/CogGolfHub"
+ self.factoryExteriorModelPath = "phase_11/models/lawbotHQ/LB_DA_Lobby"
+ self.cogHQLobbyModelPath = "phase_12/models/bossbotHQ/CogGolfCourtyard"
+
+ self.geom = None
+
+ def load(self, zoneId):
+ CogHQLoader.CogHQLoader.load(self, zoneId)
+ # Load anims
+ Toon.loadBossbotHQAnims()
+
+ def unloadPlaceGeom(self):
+ # Get rid of any old geom
+ if self.geom:
+ self.geom.removeNode()
+ self.geom = None
+ CogHQLoader.CogHQLoader.unloadPlaceGeom(self)
+
+ def loadPlaceGeom(self, zoneId):
+ self.notify.info("loadPlaceGeom: %s" % zoneId)
+
+ # We shoud not look at the last 2 digits to match against these constants
+ zoneId = (zoneId - (zoneId %100))
+
+ self.notify.debug("zoneId = %d ToontownGlobals.BossbotHQ=%d" % (zoneId,ToontownGlobals.BossbotHQ))
+
+ if zoneId == ToontownGlobals.BossbotHQ:
+ self.geom = loader.loadModel(self.cogHQExteriorModelPath)
+
+ # Rename the link tunnels so they will hook up properly
+ gzLinkTunnel = self.geom.find("**/LinkTunnel1")
+ gzLinkTunnel.setName("linktunnel_gz_17000_DNARoot")
+
+ # put the signs on the tunnels
+ self.makeSigns()
+
+ # HACK: make tunnel_origin point straight out of tunnel
+ top = self.geom.find("**/TunnelEntrance")
+ origin = top.find("**/tunnel_origin")
+ origin.setH(-33.33)
+
+ elif zoneId == ToontownGlobals.BossbotLobby:
+ self.notify.debug("cogHQLobbyModelPath = %s" % self.cogHQLobbyModelPath)
+ self.geom = loader.loadModel(self.cogHQLobbyModelPath)
+
+ else:
+ # Note: the factory interior has a dynamically allocated zone but
+ # that is ok because we do not need to load any models - they all
+ # get loaded by the distributed object
+ self.notify.warning("loadPlaceGeom: unclassified zone %s" % zoneId)
+
+ CogHQLoader.CogHQLoader.loadPlaceGeom(self, zoneId)
+
+ def makeSigns(self):
+ # helper func
+ def makeSign(topStr, signStr, textId):
+ top = self.geom.find("**/" + topStr)
+ sign = top.find("**/" + signStr)
+ #sign.node().setEffect(DecalEffect.make())
+ locator = top.find("**/sign_origin")
+ signText = DirectGui.OnscreenText(
+ text = TextEncoder.upper(TTLocalizer.GlobalStreetNames[textId][-1]),
+ font = ToontownGlobals.getSuitFont(),
+ scale = TTLocalizer.BCHQLmakeSign,
+ fg = (0, 0, 0, 1),
+ parent = sign)
+ signText.setPosHpr(locator, 0, -0.1, -0.25, 0, 0, 0)
+ signText.setDepthWrite(0)
+
+ makeSign("Gate_2", "Sign_6", 10700)
+ makeSign("TunnelEntrance", "Sign_2", 1000)
+ makeSign("Gate_3", "Sign_3", 10600)
+ makeSign("Gate_4", "Sign_4", 10500)
+ makeSign("GateHouse", "Sign_5", 10200)
+
+ def unload(self):
+ CogHQLoader.CogHQLoader.unload(self)
+ # unload anims
+ Toon.unloadSellbotHQAnims()
+
+# def enterFactoryInterior(self, requestStatus):
+# self.placeClass = FactoryInterior.FactoryInterior
+# self.enterPlace(requestStatus)
+
+ def enterStageInterior(self, requestStatus):
+ self.placeClass = StageInterior.StageInterior
+ self.stageId = requestStatus['stageId']
+ self.enterPlace(requestStatus)
+
+# def exitFactoryInterior(self):
+# self.exitPlace()
+# self.placeClass = None
+
+ def exitStageInterior(self):
+ self.exitPlace()
+ self.placeClass = None
+
+ def getExteriorPlaceClass(self):
+ self.notify.debug("getExteriorPlaceClass")
+ return BossbotHQExterior.BossbotHQExterior
+
+ def getBossPlaceClass(self):
+ self.notify.debug("getBossPlaceClass")
+ return BossbotHQBossBattle.BossbotHQBossBattle
+
+ def enterFactoryExterior(self, requestStatus):
+ self.placeClass = BossbotOfficeExterior.BossbotOfficeExterior
+ self.enterPlace(requestStatus)
+ #self.hood.spawnTitleText(requestStatus['zoneId'])
+
+ def exitFactoryExterior(self):
+ taskMgr.remove("titleText")
+ self.hood.hideTitleText()
+ self.exitPlace()
+ self.placeClass = None
+
+ def enterCogHQBossBattle(self, requestStatus):
+ self.notify.debug("BossbotCogHQLoader.enterCogHQBossBattle")
+ CogHQLoader.CogHQLoader.enterCogHQBossBattle(self, requestStatus)
+ base.cr.forbidCheesyEffects(1)
+
+ def exitCogHQBossBattle(self):
+ self.notify.debug("BossbotCogHQLoader.exitCogHQBossBattle")
+ CogHQLoader.CogHQLoader.exitCogHQBossBattle(self)
+ base.cr.forbidCheesyEffects(0)
+
+
+ def enterCountryClubInterior(self, requestStatus):
+ self.placeClass = CountryClubInterior.CountryClubInterior
+ # MintInterior will grab this off of us
+ self.notify.info('enterCountryClubInterior, requestStatus=%s' % requestStatus)
+ self.countryClubId = requestStatus['countryClubId']
+ self.enterPlace(requestStatus)
+ # spawnTitleText is done by MintInterior once the mint shows up
+
+ def exitCountryClubInterior(self):
+ self.exitPlace()
+ self.placeClass = None
+ del self.countryClubId
+
+
+
diff --git a/toontown/src/coghq/BossbotCountryClubBoilerRoom_Battle00.py b/toontown/src/coghq/BossbotCountryClubBoilerRoom_Battle00.py
new file mode 100644
index 0000000..f441983
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubBoilerRoom_Battle00.py
@@ -0,0 +1,260 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotKartBoardingRm',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 10001: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-1.02925205231,87.0907745361,11.8959827423),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellId': 0,
+ 'radius': 10.0,
+ }, # end entity 10001
+ 10006: {
+ 'type': 'battleBlocker',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-60.9065246582,-3.26905798912,0.117109239101),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellId': 1,
+ 'radius': 15.0,
+ }, # end entity 10006
+ 10047: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10013,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,0.20000000298,1.0),
+ 'cellId': 2,
+ 'radius': 20.0,
+ }, # end entity 10047
+ # GAGBARREL
+ 10041: {
+ 'type': 'gagBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10033,
+ 'pos': Point3(5.40611028671,0.0,0.0),
+ 'hpr': Vec3(199.440032959,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 4,
+ 'rewardPerGrabMax': 6,
+ }, # end entity 10041
+ # HEALBARREL
+ 10034: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10033,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(163.300750732,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 7,
+ 'rewardPerGrabMax': 9,
+ }, # end entity 10034
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,66.1200027466,10.1833248138),
+ 'hpr': Point3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'battle',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10002
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'cogs2',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-53.9246749878,-22.7616195679,0.0),
+ 'hpr': Point3(45.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10003
+ 10005: {
+ 'type': 'nodepath',
+ 'name': 'battle',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10005
+ 10007: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10007
+ 10008: {
+ 'type': 'nodepath',
+ 'name': 'topWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,48.0299987793,10.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10008
+ 10011: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10011
+ 10013: {
+ 'type': 'nodepath',
+ 'name': 'frontCogs',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(25.3957309723,-12.3005743027,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10013
+ 10014: {
+ 'type': 'nodepath',
+ 'name': 'frontPalletWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(45.5494384766,38.2237281799,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10014
+ 10021: {
+ 'type': 'nodepath',
+ 'name': 'middlePalletWallLeft',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(6.0,-37.9928665161,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10021
+ 10023: {
+ 'type': 'nodepath',
+ 'name': 'crateIsland',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(-23.1813278198,7.08758449554,0.00999999977648),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(2.0,2.0,2.0),
+ }, # end entity 10023
+ 10028: {
+ 'type': 'nodepath',
+ 'name': 'rewardCulDeSac',
+ 'comment': '',
+ 'parentEntId': 10045,
+ 'pos': Point3(-8.26172065735,38.377407074,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10028
+ 10033: {
+ 'type': 'nodepath',
+ 'name': 'barrels',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(-4.75077962875,34.1425209045,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10033
+ 10035: {
+ 'type': 'nodepath',
+ 'name': 'backPalletWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(-47.6501731873,40.006893158,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10035
+ 10040: {
+ 'type': 'nodepath',
+ 'name': 'centerCogs',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-23.9375743866,28.353269577,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10040
+ 10045: {
+ 'type': 'nodepath',
+ 'name': 'middlePalletWallRight',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(17.4200000763,-38.2999992371,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10045
+ 10046: {
+ 'type': 'nodepath',
+ 'name': 'middlePalletWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10046
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubBoilerRoom_Battle01.py b/toontown/src/coghq/BossbotCountryClubBoilerRoom_Battle01.py
new file mode 100644
index 0000000..44f346e
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubBoilerRoom_Battle01.py
@@ -0,0 +1,229 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE08a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # GAGBARREL
+ 10006: {
+ 'type': 'gagBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10005,
+ 'pos': Point3(-23.8955783844,-29.8914642334,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 7,
+ }, # end entity 10006
+ # HEALBARREL
+ 10007: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10005,
+ 'pos': Point3(-7.71000003815,6.03817367554,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 8,
+ }, # end entity 10007
+ # LOCATOR
+ 10001: {
+ 'type': 'locator',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'searchPath': '**/EXIT',
+ }, # end entity 10001
+ # MODEL
+ 10002: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/VaultDoorCover.bam',
+ }, # end entity 10002
+ 10011: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(34.3037414551,6.2506942749,0.0),
+ 'hpr': Vec3(306.869903564,0.0,0.0),
+ 'scale': Vec3(1.22879016399,1.22879016399,1.22879016399),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/boiler_A2.bam',
+ }, # end entity 10011
+ 10012: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(-37.5963821411,0.68013381958,0.0),
+ 'hpr': Vec3(45.0,0.0,0.0),
+ 'scale': Vec3(0.761251866817,0.761251866817,0.761251866817),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_D1.bam',
+ }, # end entity 10012
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,68.932258606,9.97146701813),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10003
+ 10004: {
+ 'type': 'nodepath',
+ 'name': 'lower',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10004
+ 10005: {
+ 'type': 'nodepath',
+ 'name': 'barrels',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10005
+ 10008: {
+ 'type': 'nodepath',
+ 'name': 'upperLevel',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,65.4967575073,9.99451065063),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10008
+ 10013: {
+ 'type': 'nodepath',
+ 'name': 'product',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(0.0,17.8199996948,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10013
+ 10023: {
+ 'type': 'nodepath',
+ 'name': 'shelves',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(0.0,1.89410364628,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10023
+ 10027: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,-32.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10027
+ 10032: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,-14.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10032
+ 10039: {
+ 'type': 'nodepath',
+ 'name': 'row3',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,4.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10039
+ 10048: {
+ 'type': 'nodepath',
+ 'name': 'row4',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,22.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10048
+ 10055: {
+ 'type': 'nodepath',
+ 'name': 'row5',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,40.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10055
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubEntrance_Action00.py b/toontown/src/coghq/BossbotCountryClubEntrance_Action00.py
new file mode 100644
index 0000000..400685f
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubEntrance_Action00.py
@@ -0,0 +1,68 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotEntranceRoom',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # ENTRANCEPOINT
+ 10000: {
+ 'type': 'entrancePoint',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 6, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'entranceId': 0,
+ 'radius': 15,
+ 'theta': 20,
+ }, # end entity 10000
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubFairwayRoom_Battle00.py b/toontown/src/coghq/BossbotCountryClubFairwayRoom_Battle00.py
new file mode 100644
index 0000000..3626952
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubFairwayRoom_Battle00.py
@@ -0,0 +1,99 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotFairwayRoom_A',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': {'entType': 'door', 'username': 'rurbino', 'parentEntId': 110001, 'entId': 110002},
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 110200: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(70, 0, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'cellId': 0,
+ 'radius': 10,
+ }, # end entity 110200
+ # DOOR
+ 110202: {
+ 'type': 'door',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 110001,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110200,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110002
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110001: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(101.07, 0, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110001
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubFairwayRoom_Battle00_Cogs.py b/toontown/src/coghq/BossbotCountryClubFairwayRoom_Battle00_Cogs.py
new file mode 100644
index 0000000..7e0b454
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubFairwayRoom_Battle00_Cogs.py
@@ -0,0 +1,62 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 110200
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/BossbotCountryClubGreenRoom_Action00.py b/toontown/src/coghq/BossbotCountryClubGreenRoom_Action00.py
new file mode 100644
index 0000000..3499f3b
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubGreenRoom_Action00.py
@@ -0,0 +1,102 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotGreenRoom_A',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # DOOR
+ 110301: {
+ 'type': 'door',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 110303,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110302,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110301
+ # GOLFGREENGAME
+ 110302: {
+ 'type': 'golfGreenGame',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'cellId': 0,
+ 'puzzleBase': 3,
+ 'puzzlePerPlayer': 1,
+ 'switchId': 0,
+ 'timeToPlay': 120,
+ }, # end entity 110302
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110303: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(40.9635, 2, 0),
+ 'hpr': Vec3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110303
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubGreenRoom_Action01.py b/toontown/src/coghq/BossbotCountryClubGreenRoom_Action01.py
new file mode 100644
index 0000000..1dc750d
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubGreenRoom_Action01.py
@@ -0,0 +1,102 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotGreenRoom_A',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # DOOR
+ 110301: {
+ 'type': 'door',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 110303,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110302,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110301
+ # GOLFGREENGAME
+ 110302: {
+ 'type': 'golfGreenGame',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'puzzleBase': 3,
+ 'puzzlePerPlayer': 2,
+ 'timeToPlay': 140,
+ 'cellId': 0,
+ 'switchId': 0,
+ }, # end entity 100000
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110303: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(40.9635, 2, 0),
+ 'hpr': Vec3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110303
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubGreenRoom_Action02.py b/toontown/src/coghq/BossbotCountryClubGreenRoom_Action02.py
new file mode 100644
index 0000000..1293796
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubGreenRoom_Action02.py
@@ -0,0 +1,102 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotGreenRoom_A',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # DOOR
+ 110301: {
+ 'type': 'door',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 110303,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110302,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110301
+ # GOLFGREENGAME
+ 110302: {
+ 'type': 'golfGreenGame',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'puzzleBase': 3,
+ 'puzzlePerPlayer': 3,
+ 'timeToPlay': 180,
+ 'cellId': 0,
+ 'switchId': 0,
+ }, # end entity 100000
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110303: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(40.9635, 2, 0),
+ 'hpr': Vec3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110303
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubKartRoom_Battle00.py b/toontown/src/coghq/BossbotCountryClubKartRoom_Battle00.py
new file mode 100644
index 0000000..b2584a8
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubKartRoom_Battle00.py
@@ -0,0 +1,87 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotKartBoardingRm',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 110400: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(4, 0, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'cellId': 0,
+ 'radius': 10,
+ }, # end entity 110400
+ # ELEVATORMARKER
+ 110000: {
+ 'type': 'elevatorMarker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(26.854, 0, 0),
+ 'hpr': Vec3(90, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'modelPath': 0,
+ }, # end entity 110000
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110401: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(101.07, 0, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110401
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubKartRoom_Battle00_Cogs.py b/toontown/src/coghq/BossbotCountryClubKartRoom_Battle00_Cogs.py
new file mode 100644
index 0000000..ad6609b
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubKartRoom_Battle00_Cogs.py
@@ -0,0 +1,63 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 110400
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'revives' : 1,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle00.py b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle00.py
new file mode 100644
index 0000000..2d81de7
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle00.py
@@ -0,0 +1,110 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotMazex1_C',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 110000: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-131.21, 84.92, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'cellId': 0,
+ 'radius': 10,
+ }, # end entity 110000
+ # DOOR
+ 110202: {
+ 'type': 'door',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 110001,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110000,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110202
+ # MAZE
+ 110002: {
+ 'type': 'maze',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-141.563, -78.8353, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'numSections': 1,
+ }, # end entity 110002
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110001: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-106.91, 82.6953, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110001
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle00_Cogs.py b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle00_Cogs.py
new file mode 100644
index 0000000..a01fcbb
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle00_Cogs.py
@@ -0,0 +1,62 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 110000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle01.py b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle01.py
new file mode 100644
index 0000000..4ba206b
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle01.py
@@ -0,0 +1,110 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotMazex2_straight_C',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 110000: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(40.29, 84.9249, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'cellId': 0,
+ 'radius': 10,
+ }, # end entity 110000
+ # DOOR
+ 110202: {
+ 'type': 'door',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 110001,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110000,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110202
+ # MAZE
+ 110002: {
+ 'type': 'maze',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-141.563, -78.8353, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'numSections': 2,
+ }, # end entity 110002
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110001: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(61.31, 82.0083, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110001
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle01_Cogs.py b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle01_Cogs.py
new file mode 100644
index 0000000..b192c30
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle01_Cogs.py
@@ -0,0 +1,62 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 110000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle02.py b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle02.py
new file mode 100644
index 0000000..eb62357
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle02.py
@@ -0,0 +1,110 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotMazex4_C',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 110000: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(42.8475, 84.9249, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'cellId': 0,
+ 'radius': 10,
+ }, # end entity 110000
+ # DOOR
+ 110202: {
+ 'type': 'door',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 110001,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110000,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110202
+ # MAZE
+ 110002: {
+ 'type': 'maze',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-141.563, -78.8353, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'numSections': 3,
+ }, # end entity 110002
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110001: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(60.0276, 82.0315, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110001
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle02_Cogs.py b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle02_Cogs.py
new file mode 100644
index 0000000..1f698e6
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle02_Cogs.py
@@ -0,0 +1,61 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 110000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1, },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle03.py b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle03.py
new file mode 100644
index 0000000..c658fc4
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle03.py
@@ -0,0 +1,110 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotMazex4_C',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 110000: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(209.474, 84.9249, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'cellId': 0,
+ 'radius': 10,
+ }, # end entity 110000
+ # DOOR
+ 110202: {
+ 'type': 'door',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 110001,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110000,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110202
+ # MAZE
+ 110002: {
+ 'type': 'maze',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-141.563, -78.8353, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'numSections': 4,
+ }, # end entity 110002
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110001: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(231.424, 83.2459, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110001
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle03_Cogs.py b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle03_Cogs.py
new file mode 100644
index 0000000..d34559c
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubMazeRoom_Battle03_Cogs.py
@@ -0,0 +1,62 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 110000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel-1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/BossbotCountryClubPresidentRoom_Battle00.py b/toontown/src/coghq/BossbotCountryClubPresidentRoom_Battle00.py
new file mode 100644
index 0000000..2e69323
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubPresidentRoom_Battle00.py
@@ -0,0 +1,76 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotPresidentsRm',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 110400: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(4, 0, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'cellId': 0,
+ 'radius': 10,
+ }, # end entity 110400
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110401: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(101.07, 0, 0),
+ 'hpr': Point3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110401
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubPresidentRoom_Battle00_Cogs.py b/toontown/src/coghq/BossbotCountryClubPresidentRoom_Battle00_Cogs.py
new file mode 100644
index 0000000..7be1dc0
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubPresidentRoom_Battle00_Cogs.py
@@ -0,0 +1,64 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 110400
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 1,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.BossbotCountryClubCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ 'revives' : 1,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action00.py b/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action00.py
new file mode 100644
index 0000000..ace8546
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action00.py
@@ -0,0 +1,104 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotTeeOffRoom',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # DOOR
+ 110100: {
+ 'type': 'door',
+ 'name': 'TeeOffExitDoor',
+ 'comment': '',
+ 'parentEntId': 110001,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Point3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110102,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110100
+ # MOLEFIELD
+ 110102: {
+ 'type': 'moleField',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-38.6164, -26.2922, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'numSquaresX': 6,
+ 'numSquaresY': 6,
+ 'spacingX': 10.0,
+ 'spacingY': 10.0,
+ 'timeToPlay' : 60,
+ 'molesBase' : 4,
+ 'molesPerPlayer' : 1,
+ }, # end entity 110102
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110001: {
+ 'type': 'nodepath',
+ 'name': 'doorParent',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(60.2682, 0.55914, 0),
+ 'hpr': Vec3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110001
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action01.py b/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action01.py
new file mode 100644
index 0000000..5692b31
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action01.py
@@ -0,0 +1,104 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotTeeOffRoom',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # DOOR
+ 110100: {
+ 'type': 'door',
+ 'name': 'TeeOffExitDoor',
+ 'comment': '',
+ 'parentEntId': 110001,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Point3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110102,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110100
+ # MOLEFIELD
+ 110102: {
+ 'type': 'moleField',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-38.6164, -26.2922, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'numSquaresX': 6,
+ 'numSquaresY': 6,
+ 'spacingX': 10.0,
+ 'spacingY': 10.0,
+ 'timeToPlay' : 60,
+ 'molesBase' : 4,
+ 'molesPerPlayer' : 2,
+ }, # end entity 110102
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110001: {
+ 'type': 'nodepath',
+ 'name': 'doorParent',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(60.2682, 0.55914, 0),
+ 'hpr': Vec3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110001
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action02.py b/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action02.py
new file mode 100644
index 0000000..aabf3ae
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubTeeOffRoom_Action02.py
@@ -0,0 +1,104 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotTeeOffRoom',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # DOOR
+ 110100: {
+ 'type': 'door',
+ 'name': 'TeeOffExitDoor',
+ 'comment': '',
+ 'parentEntId': 110001,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Point3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'color': Vec4(1, 1, 1, 1),
+ 'isLock0Unlocked': 1,
+ 'isLock1Unlocked': 0,
+ 'isLock2Unlocked': 1,
+ 'isLock3Unlocked': 1,
+ 'isOpen': 0,
+ 'isOpenEvent': 0,
+ 'isVisBlocker': 0,
+ 'secondsOpen': 1,
+ 'unlock0Event': 0,
+ 'unlock1Event': 110102,
+ 'unlock2Event': 0,
+ 'unlock3Event': 0,
+ }, # end entity 110100
+ # MOLEFIELD
+ 110102: {
+ 'type': 'moleField',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-38.6164, -26.2922, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ 'numSquaresX': 6,
+ 'numSquaresY': 6,
+ 'spacingX': 10.0,
+ 'spacingY': 10.0,
+ 'timeToPlay' : 60,
+ 'molesBase' : 4,
+ 'molesPerPlayer' : 3,
+ }, # end entity 110102
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ 110001: {
+ 'type': 'nodepath',
+ 'name': 'doorParent',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(60.2682, 0.55914, 0),
+ 'hpr': Vec3(270, 0, 0),
+ 'scale': Vec3(1, 1, 1),
+ }, # end entity 110001
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotCountryClubTeeOff_Action00.py b/toontown/src/coghq/BossbotCountryClubTeeOff_Action00.py
new file mode 100644
index 0000000..5486e90
--- /dev/null
+++ b/toontown/src/coghq/BossbotCountryClubTeeOff_Action00.py
@@ -0,0 +1,55 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_12/models/bossbotHQ/BossbotTeeOffRoom',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0, 0, 0),
+ 'hpr': Vec3(0, 0, 0),
+ 'scale': 1,
+ }, # end entity 10002
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/BossbotHQBossBattle.py b/toontown/src/coghq/BossbotHQBossBattle.py
new file mode 100644
index 0000000..a6a5e16
--- /dev/null
+++ b/toontown/src/coghq/BossbotHQBossBattle.py
@@ -0,0 +1,48 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from toontown.suit import DistributedBossbotBoss
+from direct.directnotify import DirectNotifyGlobal
+from toontown.coghq import CogHQBossBattle
+
+class BossbotHQBossBattle(CogHQBossBattle.CogHQBossBattle):
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("BossbotHQBossBattle")
+
+ # special methods
+ def __init__(self, loader, parentFSM, doneEvent):
+ CogHQBossBattle.CogHQBossBattle.__init__(self, loader, parentFSM, doneEvent)
+ # This is only used for magic words.
+ self.teleportInPosHpr = (88, -214, 0, 210, 0, 0)
+
+ for stateName in ['movie',]:
+ state = self.fsm.getStateNamed(stateName)
+ state.addTransition('crane')
+
+ # force a finalBattle to finalBattle transition to be valid
+ # makes life easier when a toon is hit while golfing
+ state = self.fsm.getStateNamed('finalBattle')
+ state.addTransition('finalBattle')
+
+
+ def load(self):
+ CogHQBossBattle.CogHQBossBattle.load(self)
+
+ def unload(self):
+ CogHQBossBattle.CogHQBossBattle.unload(self)
+
+ def enter(self, requestStatus):
+ CogHQBossBattle.CogHQBossBattle.enter(self, requestStatus,
+ DistributedBossbotBoss.OneBossCog)
+ # No need for a sky; this scene is entirely interior.
+
+ def exit(self):
+ CogHQBossBattle.CogHQBossBattle.exit(self)
+
+
+
+ def exitCrane(self):
+ CogHQBossBattle.CogHQBossBattle.exitCrane(self)
+
+ # If we leave crane mode for any reason--for instance, we got
+ # zapped--then tell the pitcher or table to relinquish control.
+ messenger.send('exitCrane')
diff --git a/toontown/src/coghq/BossbotHQExterior.py b/toontown/src/coghq/BossbotHQExterior.py
new file mode 100644
index 0000000..f268bf9
--- /dev/null
+++ b/toontown/src/coghq/BossbotHQExterior.py
@@ -0,0 +1,85 @@
+
+from direct.directnotify import DirectNotifyGlobal
+from toontown.battle import BattlePlace
+from direct.fsm import ClassicFSM, State
+from direct.fsm import State
+from toontown.toonbase import ToontownGlobals
+from toontown.building import Elevator
+from pandac.PandaModules import *
+from toontown.coghq import CogHQExterior
+#from toontown.coghq import CogHQLobby
+
+class BossbotHQExterior(CogHQExterior.CogHQExterior):
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("BossbotHQExterior")
+
+ def __init__(self, loader, parentFSM, doneEvent):
+ CogHQExterior.CogHQExterior.__init__(self, loader, parentFSM, doneEvent)
+
+ self.elevatorDoneEvent = "elevatorDone"
+ self.trains = None
+
+ self.fsm.addState( State.State('elevator',
+ self.enterElevator,
+ self.exitElevator,
+ ['walk', 'stopped']))
+ state = self.fsm.getStateNamed('walk')
+ state.addTransition('elevator')
+ # Adding transition from stopped to elevator because this is possible when you are in a boarding group.
+ state = self.fsm.getStateNamed('stopped')
+ state.addTransition('elevator')
+ # Adding transition from stickerBook to elevator because this is possible when you are in a boarding group.
+ state = self.fsm.getStateNamed('stickerBook')
+ state.addTransition('elevator')
+
+ # elevator state
+ # (For boarding a building elevator)
+ def enterElevator(self, distElevator, skipDFABoard = 0):
+ assert(self.notify.debug("enterElevator()"))
+
+ self.accept(self.elevatorDoneEvent, self.handleElevatorDone)
+ self.elevator = Elevator.Elevator(self.fsm.getStateNamed("elevator"),
+ self.elevatorDoneEvent,
+ distElevator)
+ if skipDFABoard:
+ self.elevator.skipDFABoard = 1
+ self.elevator.setReverseBoardingCamera(True)
+ self.elevator.load()
+ self.elevator.enter()
+
+ def exitElevator(self):
+ assert(self.notify.debug("exitElevator()"))
+ self.ignore(self.elevatorDoneEvent)
+ self.elevator.unload()
+ self.elevator.exit()
+ del self.elevator
+
+ def detectedElevatorCollision(self, distElevator):
+ assert(self.notify.debug("detectedElevatorCollision()"))
+ self.fsm.request("elevator", [distElevator])
+
+ def handleElevatorDone(self, doneStatus):
+ assert(self.notify.debug("handleElevatorDone()"))
+ self.notify.debug("handling elevator done event")
+ where = doneStatus['where']
+ if (where == 'reject'):
+ # If there has been a reject the Elevator should show an
+ # elevatorNotifier message and put the toon in the stopped state.
+ # Don't request the walk state here. Let the the toon be stuck in the
+ # stopped state till the player removes that message from his screen.
+ # Removing the message will automatically put him in the walk state there.
+ # Put the player in the walk state only if there is no elevator message.
+ if hasattr(base.localAvatar, "elevatorNotifier") and base.localAvatar.elevatorNotifier.isNotifierOpen():
+ pass
+ else:
+ self.fsm.request("walk")
+
+ elif (where == 'exit'):
+ self.fsm.request("walk")
+ elif (where == 'countryClubInterior'):
+ self.doneStatus = doneStatus
+ messenger.send(self.doneEvent)
+ else:
+ self.notify.error("Unknown mode: " + where +
+ " in handleElevatorDone")
+
\ No newline at end of file
diff --git a/toontown/src/coghq/BossbotOfficeExterior.py b/toontown/src/coghq/BossbotOfficeExterior.py
new file mode 100644
index 0000000..339953b
--- /dev/null
+++ b/toontown/src/coghq/BossbotOfficeExterior.py
@@ -0,0 +1,18 @@
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import ClassicFSM
+from direct.fsm import State
+from toontown.toonbase import ToontownGlobals
+from toontown.building import Elevator
+from pandac.PandaModules import *
+import FactoryExterior
+
+class BossbotOfficeExterior(FactoryExterior.FactoryExterior):
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("LawbotOfficeExterior")
+ def enterWalk(self, teleportIn=0):
+ FactoryExterior.FactoryExterior.enterWalk(self, teleportIn)
+ self.ignore('teleportQuery')
+ base.localAvatar.setTeleportAvailable(0)
+
+
diff --git a/toontown/src/coghq/CashbotCogHQLoader.py b/toontown/src/coghq/CashbotCogHQLoader.py
new file mode 100644
index 0000000..f40756e
--- /dev/null
+++ b/toontown/src/coghq/CashbotCogHQLoader.py
@@ -0,0 +1,114 @@
+
+from direct.directnotify import DirectNotifyGlobal
+from direct.fsm import StateData
+import CogHQLoader, MintInterior
+from toontown.toonbase import ToontownGlobals
+from direct.gui import DirectGui
+from toontown.toonbase import TTLocalizer
+from toontown.toon import Toon
+from direct.fsm import State
+import CashbotHQExterior
+import CashbotHQBossBattle
+from pandac.PandaModules import DecalEffect
+
+class CashbotCogHQLoader(CogHQLoader.CogHQLoader):
+
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("CashbotCogHQLoader")
+ #notify.setDebug(True)
+
+ def __init__(self, hood, parentFSMState, doneEvent):
+ CogHQLoader.CogHQLoader.__init__(self, hood, parentFSMState, doneEvent)
+
+ self.fsm.addState(State.State('mintInterior',
+ self.enterMintInterior,
+ self.exitMintInterior,
+ ['quietZone',
+ 'cogHQExterior', # Tunnel
+ ]))
+ for stateName in ['start', 'cogHQExterior', 'quietZone']:
+ state = self.fsm.getStateNamed(stateName)
+ state.addTransition('mintInterior')
+
+ self.musicFile = "phase_9/audio/bgm/encntr_suit_HQ_nbrhood.mid"
+
+ self.cogHQExteriorModelPath = "phase_10/models/cogHQ/CashBotShippingStation"
+ self.cogHQLobbyModelPath = "phase_10/models/cogHQ/VaultLobby"
+ self.geom = None
+
+ def load(self, zoneId):
+ CogHQLoader.CogHQLoader.load(self, zoneId)
+ # load anims
+ Toon.loadCashbotHQAnims()
+
+ def unloadPlaceGeom(self):
+ # Get rid of any old geom
+ if self.geom:
+ self.geom.removeNode()
+ self.geom = None
+ CogHQLoader.CogHQLoader.unloadPlaceGeom(self)
+
+ def loadPlaceGeom(self, zoneId):
+
+ self.notify.info("loadPlaceGeom: %s" % zoneId)
+
+ # We shoud not look at the last 2 digits to match against these constants
+ zoneId = (zoneId - (zoneId %100))
+
+ if zoneId == ToontownGlobals.CashbotHQ:
+ self.geom = loader.loadModel(self.cogHQExteriorModelPath)
+
+ # Rename the link tunnels so they will hook up properly
+ ddLinkTunnel = self.geom.find("**/LinkTunnel1")
+ ddLinkTunnel.setName("linktunnel_dl_9252_DNARoot")
+
+ # Put a handy sign on the link tunnel
+ locator = self.geom.find('**/sign_origin')
+ backgroundGeom = self.geom.find('**/EntranceFrameFront')
+ backgroundGeom.node().setEffect(DecalEffect.make())
+ signText = DirectGui.OnscreenText(
+ text = TTLocalizer.DonaldsDreamland[-1],
+ font = ToontownGlobals.getSuitFont(),
+ scale = 3,
+ fg = (0.87, 0.87, 0.87, 1),
+ # required for DecalEffect (must be a GeomNode, not a TextNode)
+ mayChange=False,
+ parent = backgroundGeom)
+ signText.setPosHpr(locator, 0, 0, 0, 0, 0, 0)
+ signText.setDepthWrite(0)
+
+ elif zoneId == ToontownGlobals.CashbotLobby:
+ self.geom = loader.loadModel(self.cogHQLobbyModelPath)
+
+ # Note: the factory interior has a dynamically allocated zone but
+ # that is ok because we do not need to load any models - they all
+ # get loaded by the distributed object
+
+ else:
+ self.notify.warning("loadPlaceGeom: unclassified zone %s" % zoneId)
+
+ CogHQLoader.CogHQLoader.loadPlaceGeom(self, zoneId)
+
+
+ def unload(self):
+ CogHQLoader.CogHQLoader.unload(self)
+ # unload anims
+ Toon.unloadCashbotHQAnims()
+
+ def enterMintInterior(self, requestStatus):
+ self.placeClass = MintInterior.MintInterior
+ # MintInterior will grab this off of us
+ self.mintId = requestStatus['mintId']
+ self.enterPlace(requestStatus)
+ # spawnTitleText is done by MintInterior once the mint shows up
+
+ def exitMintInterior(self):
+ self.exitPlace()
+ self.placeClass = None
+ del self.mintId
+
+ def getExteriorPlaceClass(self):
+ return CashbotHQExterior.CashbotHQExterior
+
+ def getBossPlaceClass(self):
+ return CashbotHQBossBattle.CashbotHQBossBattle
diff --git a/toontown/src/coghq/CashbotHQBossBattle.py b/toontown/src/coghq/CashbotHQBossBattle.py
new file mode 100644
index 0000000..fed2154
--- /dev/null
+++ b/toontown/src/coghq/CashbotHQBossBattle.py
@@ -0,0 +1,37 @@
+from pandac.PandaModules import *
+from direct.interval.IntervalGlobal import *
+from toontown.suit import DistributedCashbotBoss
+from direct.directnotify import DirectNotifyGlobal
+from toontown.coghq import CogHQBossBattle
+
+class CashbotHQBossBattle(CogHQBossBattle.CogHQBossBattle):
+ # create a notify category
+ notify = DirectNotifyGlobal.directNotify.newCategory("CashbotHQBossBattle")
+
+ # special methods
+ def __init__(self, loader, parentFSM, doneEvent):
+ CogHQBossBattle.CogHQBossBattle.__init__(self, loader, parentFSM, doneEvent)
+ # This is only used for magic words.
+ self.teleportInPosHpr = (88, -214, 0, 210, 0, 0)
+
+ def load(self):
+ CogHQBossBattle.CogHQBossBattle.load(self)
+
+ def unload(self):
+ CogHQBossBattle.CogHQBossBattle.unload(self)
+
+ def enter(self, requestStatus):
+ CogHQBossBattle.CogHQBossBattle.enter(self, requestStatus,
+ DistributedCashbotBoss.OneBossCog)
+ # No need for a sky; this scene is entirely interior.
+
+ def exit(self):
+ CogHQBossBattle.CogHQBossBattle.exit(self)
+
+
+ def exitCrane(self):
+ CogHQBossBattle.CogHQBossBattle.exitCrane(self)
+
+ # If we leave crane mode for any reason--for instance, we got
+ # zapped--then tell the crane to relinquish control.
+ messenger.send('exitCrane')
diff --git a/toontown/src/coghq/CashbotHQExterior.py b/toontown/src/coghq/CashbotHQExterior.py
new file mode 100644
index 0000000..cb77c16
--- /dev/null
+++ b/toontown/src/coghq/CashbotHQExterior.py
@@ -0,0 +1,116 @@
+from direct.directnotify import DirectNotifyGlobal
+from direct.interval.IntervalGlobal import *
+from direct.fsm import State
+from pandac.PandaModules import *
+from toontown.building import Elevator
+from toontown.coghq import CogHQExterior
+from toontown.safezone import Train
+
+#aka "The Train Station"
+
+class CashbotHQExterior(CogHQExterior.CogHQExterior):
+ notify = DirectNotifyGlobal.directNotify.newCategory("CashbotHQExterior")
+
+ TrackZ = -67
+ TrainTracks = [ { "start":Point3(-1000, -54.45, TrackZ), "end":Point3(2200, -54.45, TrackZ) },
+ { "start":Point3(1800, -133.45, TrackZ), "end":Point3(-1200, -133.45, TrackZ) },
+ { "start":Point3(-1000, -212.45, TrackZ), "end":Point3(2200, -212.45, TrackZ) },
+ { "start":Point3(1800, -291.45, TrackZ), "end":Point3(-1200, -291.45, TrackZ) },
+ ]
+
+ def __init__(self, loader, parentFSM, doneEvent):
+ CogHQExterior.CogHQExterior.__init__(self, loader, parentFSM, doneEvent)
+ self.elevatorDoneEvent = "elevatorDone"
+ self.trains = None
+
+ self.fsm.addState( State.State('elevator',
+ self.enterElevator,
+ self.exitElevator,
+ ['walk', 'stopped']))
+ state = self.fsm.getStateNamed('walk')
+ state.addTransition('elevator')
+ # Adding transition from stopped to elevator because this is possible when you are in a boarding group.
+ state = self.fsm.getStateNamed('stopped')
+ state.addTransition('elevator')
+ # Adding transition from stickerBook to elevator because this is possible when you are in a boarding group.
+ state = self.fsm.getStateNamed('stickerBook')
+ state.addTransition('elevator')
+ # Adding transition from squished to elevator because this is possible when you are in a boarding group
+ # and get squished by a train.
+ state = self.fsm.getStateNamed('squished')
+ state.addTransition('elevator')
+
+ def load(self):
+ CogHQExterior.CogHQExterior.load(self)
+
+ if not self.trains:
+ self.trains = []
+ for track in self.TrainTracks:
+ train = Train.Train(track['start'], track['end'], self.TrainTracks.index(track), len(self.TrainTracks))
+ self.trains.append(train)
+
+ def unload(self):
+ CogHQExterior.CogHQExterior.unload(self)
+
+ # Clear the references to the trains
+ for train in self.trains: train.delete()
+ self.trains = None
+
+ def enter(self, requestStatus):
+ CogHQExterior.CogHQExterior.enter(self, requestStatus)
+
+ for train in self.trains: train.show()
+
+ def exit(self):
+ CogHQExterior.CogHQExterior.exit(self)
+
+ for train in self.trains: train.hide()
+
+ # elevator state
+ # (For boarding a building elevator)
+ def enterElevator(self, distElevator, skipDFABoard = 0):
+ assert(self.notify.debug("enterElevator()"))
+
+ self.accept(self.elevatorDoneEvent, self.handleElevatorDone)
+ self.elevator = Elevator.Elevator(self.fsm.getStateNamed("elevator"),
+ self.elevatorDoneEvent,
+ distElevator)
+ if skipDFABoard:
+ self.elevator.skipDFABoard = 1
+ self.elevator.load()
+ self.elevator.enter()
+
+ def exitElevator(self):
+ assert(self.notify.debug("exitElevator()"))
+ self.ignore(self.elevatorDoneEvent)
+ self.elevator.unload()
+ self.elevator.exit()
+ del self.elevator
+
+ def detectedElevatorCollision(self, distElevator):
+ assert(self.notify.debug("detectedElevatorCollision()"))
+ self.fsm.request("elevator", [distElevator])
+
+ def handleElevatorDone(self, doneStatus):
+ assert(self.notify.debug("handleElevatorDone()"))
+ self.notify.debug("handling elevator done event")
+ where = doneStatus['where']
+ if (where == 'reject'):
+ # If there has been a reject the Elevator should show an
+ # elevatorNotifier message and put the toon in the stopped state.
+ # Don't request the walk state here. Let the the toon be stuck in the
+ # stopped state till the player removes that message from his screen.
+ # Removing the message will automatically put him in the walk state there.
+ # Put the player in the walk state only if there is no elevator message.
+ if hasattr(base.localAvatar, "elevatorNotifier") and base.localAvatar.elevatorNotifier.isNotifierOpen():
+ pass
+ else:
+ self.fsm.request("walk")
+ elif (where == 'exit'):
+ self.fsm.request("walk")
+ elif (where == 'mintInterior'):
+ self.doneStatus = doneStatus
+ messenger.send(self.doneEvent)
+ else:
+ self.notify.error("Unknown mode: " + where +
+ " in handleElevatorDone")
diff --git a/toontown/src/coghq/CashbotMintBoilerRoom_Action00.py b/toontown/src/coghq/CashbotMintBoilerRoom_Action00.py
new file mode 100644
index 0000000..7e641ad
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintBoilerRoom_Action00.py
@@ -0,0 +1,771 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE08a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # ATTRIBMODIFIER
+ 10055: {
+ 'type': 'attribModifier',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'attribName': 'modelPath',
+ 'recursive': 1,
+ 'typeName': 'model',
+ 'value': '',
+ }, # end entity 10055
+ # GAGBARREL
+ 10045: {
+ 'type': 'gagBarrel',
+ 'name': 'gag',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(1.36976861954,0.773027420044,0.0),
+ 'hpr': Vec3(51.1066703796,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10045
+ 10047: {
+ 'type': 'gagBarrel',
+ 'name': 'gag',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(0.137291625142,2.83575630188,0.0),
+ 'hpr': Vec3(-210.47303772,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10047
+ 10054: {
+ 'type': 'gagBarrel',
+ 'name': 'gag',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-2.34864091873,2.16795802116,0.0),
+ 'hpr': Vec3(-141.715744019,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10054
+ # GEAR
+ 10020: {
+ 'type': 'gear',
+ 'name': 'upper',
+ 'comment': '',
+ 'parentEntId': 10044,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.60000002384),
+ 'degreesPerSec': -5.0,
+ 'gearScale': 24.300000000000001,
+ 'modelType': 'mint',
+ 'orientation': 'horizontal',
+ 'phaseShift': 0,
+ }, # end entity 10020
+ # HEALBARREL
+ 10004: {
+ 'type': 'healBarrel',
+ 'name': 'heal',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(0.0,-0.748414576054,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 6,
+ 'rewardPerGrabMax': 8,
+ }, # end entity 10004
+ 10005: {
+ 'type': 'healBarrel',
+ 'name': 'heal',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-2.20195555687,-0.384303599596,0.0),
+ 'hpr': Vec3(-64.4312591553,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 6,
+ 'rewardPerGrabMax': 8,
+ }, # end entity 10005
+ 10037: {
+ 'type': 'healBarrel',
+ 'name': 'atTheEnd',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(64.282081604,42.8509597778,0.0),
+ 'hpr': Vec3(274.906707764,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10037
+ # MINTSHELF
+ 10010: {
+ 'type': 'mintShelf',
+ 'name': 'shelf0',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12700,
+ }, # end entity 10010
+ 10021: {
+ 'type': 'mintShelf',
+ 'name': 'copy of shelf0',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(-13.4654359818,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10021
+ 10022: {
+ 'type': 'mintShelf',
+ 'name': 'copy of shelf0 (2)',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(-26.8826961517,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10022
+ 10025: {
+ 'type': 'mintShelf',
+ 'name': 'shelf0',
+ 'comment': '',
+ 'parentEntId': 10024,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10025
+ 10026: {
+ 'type': 'mintShelf',
+ 'name': 'copy of shelf0',
+ 'comment': '',
+ 'parentEntId': 10024,
+ 'pos': Point3(-13.4654359818,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10026
+ 10027: {
+ 'type': 'mintShelf',
+ 'name': 'copy of shelf0 (2)',
+ 'comment': '',
+ 'parentEntId': 10024,
+ 'pos': Point3(-26.8826961517,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10027
+ # MODEL
+ 10000: {
+ 'type': 'model',
+ 'name': 'crate',
+ 'comment': '',
+ 'parentEntId': 10009,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10000
+ 10007: {
+ 'type': 'model',
+ 'name': 'upper',
+ 'comment': '',
+ 'parentEntId': 10059,
+ 'pos': Point3(0.0,2.0,5.5),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10007
+ 10008: {
+ 'type': 'model',
+ 'name': 'crate',
+ 'comment': '',
+ 'parentEntId': 10009,
+ 'pos': Point3(0.0,-5.79679441452,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10008
+ 10012: {
+ 'type': 'model',
+ 'name': 'copy of crate',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,-5.79679441452,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10012
+ 10013: {
+ 'type': 'model',
+ 'name': 'copy of crate (2)',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10013
+ 10014: {
+ 'type': 'model',
+ 'name': 'copy of crate (2)',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-5.65285158157,-11.6494598389,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10014
+ 10015: {
+ 'type': 'model',
+ 'name': 'copy of crate (2)',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-5.80570077896,-5.79679441452,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10015
+ 10016: {
+ 'type': 'model',
+ 'name': 'copy of crate (3)',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-3.93829965591,-17.6477527618,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10016
+ 10018: {
+ 'type': 'model',
+ 'name': 'copy of upper',
+ 'comment': '',
+ 'parentEntId': 10059,
+ 'pos': Point3(0.0,-3.83362102509,5.5),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10018
+ 10019: {
+ 'type': 'model',
+ 'name': 'copy of upper (2)',
+ 'comment': '',
+ 'parentEntId': 10059,
+ 'pos': Point3(0.0,-9.69304847717,5.5),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10019
+ 10030: {
+ 'type': 'model',
+ 'name': 'lastCrateStack',
+ 'comment': '',
+ 'parentEntId': 10029,
+ 'pos': Point3(47.9848709106,27.71052742,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10030
+ 10031: {
+ 'type': 'model',
+ 'name': 'upper',
+ 'comment': '',
+ 'parentEntId': 10030,
+ 'pos': Point3(0.0,0.0,5.5),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10031
+ 10033: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(-41.8699073792,-36.9582328796,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_D1.bam',
+ }, # end entity 10033
+ 10034: {
+ 'type': 'model',
+ 'name': 'crateStack',
+ 'comment': '',
+ 'parentEntId': 10029,
+ 'pos': Point3(47.9848709106,-3.09666919708,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10034
+ 10035: {
+ 'type': 'model',
+ 'name': 'upper',
+ 'comment': '',
+ 'parentEntId': 10034,
+ 'pos': Point3(0.0,0.0,5.5),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10035
+ 10036: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(0.0,-41.4516029358,30.2685108185),
+ 'hpr': Vec3(180.0,0.0,180.0),
+ 'scale': Vec3(0.850346446037,0.850346446037,0.850346446037),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10036
+ 10041: {
+ 'type': 'model',
+ 'name': 'crateStack',
+ 'comment': '',
+ 'parentEntId': 10040,
+ 'pos': Point3(36.5904769897,-31.6758518219,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10041
+ 10042: {
+ 'type': 'model',
+ 'name': 'upper',
+ 'comment': '',
+ 'parentEntId': 10041,
+ 'pos': Point3(0.0,0.0,5.5),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10042
+ 10043: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(19.5017147064,84.0786056519,10.0058736801),
+ 'hpr': Vec3(171.253845215,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/boiler_B1.bam',
+ }, # end entity 10043
+ 10048: {
+ 'type': 'model',
+ 'name': 'crate',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(0.0,0.0,8.25758934021),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.29999995232,1.29999995232,1.64999997616),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10048
+ 10050: {
+ 'type': 'model',
+ 'name': 'support',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/gears_C2.bam',
+ }, # end entity 10050
+ 10052: {
+ 'type': 'model',
+ 'name': 'crate',
+ 'comment': '',
+ 'parentEntId': 10051,
+ 'pos': Point3(0.0,0.0,8.25758934021),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.29999995232,1.29999995232,1.64999997616),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10052
+ 10053: {
+ 'type': 'model',
+ 'name': 'support',
+ 'comment': '',
+ 'parentEntId': 10051,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/gears_C2.bam',
+ }, # end entity 10053
+ 10056: {
+ 'type': 'model',
+ 'name': 'collision',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-0.62570387125,0.824797034264,0.0),
+ 'hpr': Vec3(318.366455078,0.0,0.0),
+ 'scale': Vec3(0.644617915154,0.639999985695,1.28725671768),
+ 'collisionsOnly': 1,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10056
+ 10057: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10044,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(11.0614013672,11.0614013672,11.0614013672),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/RoundShadow.bam',
+ }, # end entity 10057
+ 10058: {
+ 'type': 'model',
+ 'name': 'shelf',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(62.9968643188,21.712474823,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/shelf_A1.bam',
+ }, # end entity 10058
+ 10062: {
+ 'type': 'model',
+ 'name': 'copy of upper',
+ 'comment': '',
+ 'parentEntId': 10061,
+ 'pos': Point3(0.0,-3.83362102509,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10062
+ 10063: {
+ 'type': 'model',
+ 'name': 'copy of upper (2)',
+ 'comment': '',
+ 'parentEntId': 10061,
+ 'pos': Point3(0.0,-9.69304847717,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10063
+ 10064: {
+ 'type': 'model',
+ 'name': 'upper',
+ 'comment': '',
+ 'parentEntId': 10061,
+ 'pos': Point3(0.0,2.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10064
+ # NODEPATH
+ 10001: {
+ 'type': 'nodepath',
+ 'name': 'crates',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.29999995232,1.29999995232,1.64892423153),
+ }, # end entity 10001
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'rewardBarrels',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-0.719733536243,56.9690589905,10.0021047592),
+ 'hpr': Vec3(61.6992454529,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10002
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'upperWall',
+ 'comment': 'TODO: replace with lines of shelves',
+ 'parentEntId': 0,
+ 'pos': Point3(-20.3202514648,52.6549415588,9.9087305069),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.11429846287,1.11429846287,1.11429846287),
+ }, # end entity 10003
+ 10009: {
+ 'type': 'nodepath',
+ 'name': 'toGear0',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(-26.5593318939,31.8559513092,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10009
+ 10011: {
+ 'type': 'nodepath',
+ 'name': 'toGear1',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(-25.88397789,13.6748971939,0.0),
+ 'hpr': Vec3(41.6335411072,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10011
+ 10023: {
+ 'type': 'nodepath',
+ 'name': 'leftWall',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10023
+ 10024: {
+ 'type': 'nodepath',
+ 'name': 'rightWall',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-26.7111759186,6.85981559753,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10024
+ 10028: {
+ 'type': 'nodepath',
+ 'name': 'lowerPuzzle',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0500000007451),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10028
+ 10029: {
+ 'type': 'nodepath',
+ 'name': 'entranceWall',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10029
+ 10032: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10032
+ 10038: {
+ 'type': 'nodepath',
+ 'name': 'archStompers',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10038
+ 10040: {
+ 'type': 'nodepath',
+ 'name': 'backWall',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10040
+ 10044: {
+ 'type': 'nodepath',
+ 'name': 'gear',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(11.8500003815,-11.3800001144,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10044
+ 10046: {
+ 'type': 'nodepath',
+ 'name': 'supportedCrateBackWall',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(34.904460907,-34.058883667,-1.51686680317),
+ 'hpr': Vec3(63.4349479675,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10046
+ 10051: {
+ 'type': 'nodepath',
+ 'name': 'supportedCrateEntrance',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(48.5076904297,7.75915336609,0.35789707303),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10051
+ 10059: {
+ 'type': 'nodepath',
+ 'name': 'largeStack',
+ 'comment': '',
+ 'parentEntId': 10029,
+ 'pos': Point3(47.9799995422,-16.9799995422,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10059
+ 10061: {
+ 'type': 'nodepath',
+ 'name': 'lower',
+ 'comment': '',
+ 'parentEntId': 10059,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10061
+ # STOMPER
+ 10049: {
+ 'type': 'stomper',
+ 'name': 'second',
+ 'comment': '',
+ 'parentEntId': 10038,
+ 'pos': Point3(62.3684997559,-19.4456634521,18.1217155457),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(3.79999995232,4.30000019073,3.79999995232),
+ 'modelPath': 0,
+ 'motion': 3,
+ 'period': 3.0,
+ 'phaseShift': 0.34000000000000002,
+ 'range': 7.0,
+ 'removeCamBarrierCollisions': 0,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Point3(1.71000003815,2.78999996185,1.71000003815),
+ 'soundLen': 0,
+ 'soundOn': 1,
+ 'soundPath': 1,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 1,
+ 'zOffset': 0,
+ }, # end entity 10049
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintBoilerRoom_Battle00.py b/toontown/src/coghq/CashbotMintBoilerRoom_Battle00.py
new file mode 100644
index 0000000..479e2a8
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintBoilerRoom_Battle00.py
@@ -0,0 +1,571 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE08a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 10001: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-1.02925205231,87.0907745361,11.8959827423),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellId': 0,
+ 'radius': 10.0,
+ }, # end entity 10001
+ 10006: {
+ 'type': 'battleBlocker',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-60.9065246582,-3.26905798912,0.117109239101),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellId': 1,
+ 'radius': 15.0,
+ }, # end entity 10006
+ 10047: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10013,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,0.20000000298,1.0),
+ 'cellId': 2,
+ 'radius': 20.0,
+ }, # end entity 10047
+ # GAGBARREL
+ 10041: {
+ 'type': 'gagBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10033,
+ 'pos': Point3(5.40611028671,0.0,0.0),
+ 'hpr': Vec3(199.440032959,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 4,
+ 'rewardPerGrabMax': 6,
+ }, # end entity 10041
+ # HEALBARREL
+ 10034: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10033,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(163.300750732,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 7,
+ 'rewardPerGrabMax': 9,
+ }, # end entity 10034
+ # MINTPRODUCTPALLET
+ 10015: {
+ 'type': 'mintProductPallet',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10014,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10015
+ 10016: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10014,
+ 'pos': Point3(0.0,13.6865262985,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10016
+ 10017: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10014,
+ 'pos': Point3(0.0,27.3799991608,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10017
+ 10018: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (3)',
+ 'comment': '',
+ 'parentEntId': 10014,
+ 'pos': Point3(0.0,41.0699996948,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10018
+ 10019: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (4)',
+ 'comment': '',
+ 'parentEntId': 10014,
+ 'pos': Point3(0.0,54.7599983215,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10019
+ 10020: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (5)',
+ 'comment': '',
+ 'parentEntId': 10014,
+ 'pos': Point3(0.0,68.4499969482,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10020
+ 10022: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10021,
+ 'pos': Point3(0.0,11.766998291,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10022
+ 10025: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (4)',
+ 'comment': '',
+ 'parentEntId': 10045,
+ 'pos': Point3(0.0,54.7599983215,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10025
+ 10026: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (5)',
+ 'comment': '',
+ 'parentEntId': 10045,
+ 'pos': Point3(0.0,68.4499969482,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10026
+ 10036: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10035,
+ 'pos': Point3(0.0,13.6865262985,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10036
+ 10037: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10035,
+ 'pos': Point3(0.0,27.3799991608,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10037
+ 10038: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (3)',
+ 'comment': '',
+ 'parentEntId': 10035,
+ 'pos': Point3(0.0,41.0699996948,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10038
+ 10043: {
+ 'type': 'mintProductPallet',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(-36.662399292,-39.0314712524,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10043
+ 10044: {
+ 'type': 'mintProductPallet',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10021,
+ 'pos': Point3(0.0,25.4739685059,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10044
+ # MODEL
+ 10004: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10021,
+ 'pos': Point3(0.0,-1.09804749489,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(2.0,2.0,2.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'strong',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10004
+ 10009: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(-3.9962117672,0.695078849792,0.0113303475082),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.20000004768,1.20000004768,1.20000004768),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_E.bam',
+ }, # end entity 10009
+ 10010: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(48.0530014038,-0.531660735607,-0.327078670263),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_C1.bam',
+ }, # end entity 10010
+ 10012: {
+ 'type': 'model',
+ 'name': 'rightCrates',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(36.0373382568,71.3546981812,9.99835586548),
+ 'hpr': Vec3(315.0,0.0,0.0),
+ 'scale': Vec3(1.5,1.5,1.5),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_E.bam',
+ }, # end entity 10012
+ 10024: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(-3.7328555584,27.1218452454,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(2.0,2.0,2.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10024
+ 10027: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(-11.9349050522,38.9528312683,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(2.0,2.0,2.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10027
+ 10029: {
+ 'type': 'model',
+ 'name': 'crate',
+ 'comment': '',
+ 'parentEntId': 10035,
+ 'pos': Point3(0.0,0.863602340221,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(2.0,2.0,2.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10029
+ 10030: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10030
+ 10031: {
+ 'type': 'model',
+ 'name': 'copy of crate',
+ 'comment': '',
+ 'parentEntId': 10029,
+ 'pos': Point3(0.0,0.0,5.46999979019),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10031
+ 10032: {
+ 'type': 'model',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,-5.92218112946,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10032
+ 10039: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(-9.23663234711,0.821143984795,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.5,1.5,1.5),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_F1.bam',
+ }, # end entity 10039
+ 10042: {
+ 'type': 'model',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(3.0,-11.8400001526,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10042
+ 10048: {
+ 'type': 'model',
+ 'name': 'cratesAgainstWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(-37.0983123779,70.2133865356,10.0),
+ 'hpr': Vec3(225.0,0.0,0.0),
+ 'scale': Vec3(1.5,1.5,1.5),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_E.bam',
+ }, # end entity 10048
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,66.1200027466,10.1833248138),
+ 'hpr': Point3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'battle',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10002
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'cogs2',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-53.9246749878,-22.7616195679,0.0),
+ 'hpr': Point3(45.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10003
+ 10005: {
+ 'type': 'nodepath',
+ 'name': 'battle',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10005
+ 10007: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10007
+ 10008: {
+ 'type': 'nodepath',
+ 'name': 'topWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,48.0299987793,10.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10008
+ 10011: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10011
+ 10013: {
+ 'type': 'nodepath',
+ 'name': 'frontCogs',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(25.3957309723,-12.3005743027,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10013
+ 10014: {
+ 'type': 'nodepath',
+ 'name': 'frontPalletWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(45.5494384766,38.2237281799,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10014
+ 10021: {
+ 'type': 'nodepath',
+ 'name': 'middlePalletWallLeft',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(6.0,-37.9928665161,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10021
+ 10023: {
+ 'type': 'nodepath',
+ 'name': 'crateIsland',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(-23.1813278198,7.08758449554,0.00999999977648),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(2.0,2.0,2.0),
+ }, # end entity 10023
+ 10028: {
+ 'type': 'nodepath',
+ 'name': 'rewardCulDeSac',
+ 'comment': '',
+ 'parentEntId': 10045,
+ 'pos': Point3(-8.26172065735,38.377407074,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10028
+ 10033: {
+ 'type': 'nodepath',
+ 'name': 'barrels',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(-4.75077962875,34.1425209045,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10033
+ 10035: {
+ 'type': 'nodepath',
+ 'name': 'backPalletWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(-47.6501731873,40.006893158,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10035
+ 10040: {
+ 'type': 'nodepath',
+ 'name': 'centerCogs',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(-23.9375743866,28.353269577,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10040
+ 10045: {
+ 'type': 'nodepath',
+ 'name': 'middlePalletWallRight',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(17.4200000763,-38.2999992371,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10045
+ 10046: {
+ 'type': 'nodepath',
+ 'name': 'middlePalletWall',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10046
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintBoilerRoom_Battle00_Cogs.py b/toontown/src/coghq/CashbotMintBoilerRoom_Battle00_Cogs.py
new file mode 100644
index 0000000..173e0f8
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintBoilerRoom_Battle00_Cogs.py
@@ -0,0 +1,201 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+LowerCogParent = 10003
+BattleParent = 10002
+LowerBattleParent = 10005
+FrontCogParent = 10013
+CenterCogParent = 10040
+
+# unique IDs for battle cells
+BattleCellId = 0
+LowerBattleCellId = 1
+FrontBattleCellId = 2
+CenterBattleCellId = 3
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : BattleParent,
+ 'pos' : Point3(0,0,0),
+ },
+ LowerBattleCellId : {'parentEntId' : LowerBattleParent,
+ 'pos' : Point3(0,0,0),
+ },
+ FrontBattleCellId : {'parentEntId' : FrontCogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ CenterBattleCellId : {'parentEntId' : CenterCogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+
+ {'parentEntId' : LowerCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : LowerBattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : LowerCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : LowerBattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : LowerCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : LowerBattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : LowerCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : LowerBattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+
+ {'parentEntId' : FrontCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : FrontBattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : FrontCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : FrontBattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : FrontCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : FrontBattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : FrontCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : FrontBattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+
+ {'parentEntId' : CenterCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : CenterBattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CenterCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : CenterBattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CenterCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : CenterBattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CenterCogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : CenterBattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintBoilerRoom_Battle01.py b/toontown/src/coghq/CashbotMintBoilerRoom_Battle01.py
new file mode 100644
index 0000000..4d7041f
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintBoilerRoom_Battle01.py
@@ -0,0 +1,521 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE08a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # GAGBARREL
+ 10006: {
+ 'type': 'gagBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10005,
+ 'pos': Point3(-23.8955783844,-29.8914642334,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 7,
+ }, # end entity 10006
+ # HEALBARREL
+ 10007: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10005,
+ 'pos': Point3(-7.71000003815,6.03817367554,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 8,
+ }, # end entity 10007
+ # LOCATOR
+ 10001: {
+ 'type': 'locator',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'searchPath': '**/EXIT',
+ }, # end entity 10001
+ # MINTPRODUCT
+ 10014: {
+ 'type': 'mintProduct',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10013,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10014
+ 10015: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10013,
+ 'pos': Point3(9.73605537415,0.935430526733,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10015
+ 10016: {
+ 'type': 'mintProduct',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10013,
+ 'pos': Point3(-11.0564117432,0.213024124503,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10016
+ # MINTSHELF
+ 10028: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10027,
+ 'pos': Point3(12.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10028
+ 10029: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10028,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10029
+ 10030: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10027,
+ 'pos': Point3(-12.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10030
+ 10031: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10030,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10031
+ 10033: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10033
+ 10034: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10033,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10034
+ 10035: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(24.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10035
+ 10036: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10035,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10036
+ 10037: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(-24.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10037
+ 10038: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10037,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10038
+ 10040: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10039,
+ 'pos': Point3(12.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10040
+ 10041: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10040,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10041
+ 10044: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10039,
+ 'pos': Point3(-12.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10044
+ 10045: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10044,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10045
+ 10046: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10039,
+ 'pos': Point3(-36.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10046
+ 10047: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10047
+ 10049: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10048,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10049
+ 10050: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10048,
+ 'pos': Point3(24.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10050
+ 10051: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10048,
+ 'pos': Point3(-24.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10051
+ 10052: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10049,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10052
+ 10053: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10050,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10053
+ 10054: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10051,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10054
+ 10056: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10055,
+ 'pos': Point3(12.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10056
+ 10057: {
+ 'type': 'mintShelf',
+ 'name': 'shelfPair',
+ 'comment': '',
+ 'parentEntId': 10055,
+ 'pos': Point3(-12.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10057
+ 10058: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10056,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10058
+ 10059: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10057,
+ 'pos': Point3(0.167918920517,6.80000019073,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10059
+ # MODEL
+ 10002: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/VaultDoorCover.bam',
+ }, # end entity 10002
+ 10011: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(34.3037414551,6.2506942749,0.0),
+ 'hpr': Vec3(306.869903564,0.0,0.0),
+ 'scale': Vec3(1.22879016399,1.22879016399,1.22879016399),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/boiler_A2.bam',
+ }, # end entity 10011
+ 10012: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(-37.5963821411,0.68013381958,0.0),
+ 'hpr': Vec3(45.0,0.0,0.0),
+ 'scale': Vec3(0.761251866817,0.761251866817,0.761251866817),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_D1.bam',
+ }, # end entity 10012
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,68.932258606,9.97146701813),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10003
+ 10004: {
+ 'type': 'nodepath',
+ 'name': 'lower',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10004
+ 10005: {
+ 'type': 'nodepath',
+ 'name': 'barrels',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10005
+ 10008: {
+ 'type': 'nodepath',
+ 'name': 'upperLevel',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,65.4967575073,9.99451065063),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10008
+ 10013: {
+ 'type': 'nodepath',
+ 'name': 'product',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(0.0,17.8199996948,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10013
+ 10023: {
+ 'type': 'nodepath',
+ 'name': 'shelves',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(0.0,1.89410364628,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10023
+ 10027: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,-32.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10027
+ 10032: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,-14.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10032
+ 10039: {
+ 'type': 'nodepath',
+ 'name': 'row3',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,4.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10039
+ 10048: {
+ 'type': 'nodepath',
+ 'name': 'row4',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,22.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10048
+ 10055: {
+ 'type': 'nodepath',
+ 'name': 'row5',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(0.0,40.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10055
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintBoilerRoom_Battle01_Cogs.py b/toontown/src/coghq/CashbotMintBoilerRoom_Battle01_Cogs.py
new file mode 100644
index 0000000..da6f40d
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintBoilerRoom_Battle01_Cogs.py
@@ -0,0 +1,61 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 1,
+ 'level' : ToontownGlobals.CashbotMintBossLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintControlRoom_Battle00.py b/toontown/src/coghq/CashbotMintControlRoom_Battle00.py
new file mode 100644
index 0000000..464babe
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintControlRoom_Battle00.py
@@ -0,0 +1,188 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE31a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # MINTPRODUCT
+ 10013: {
+ 'type': 'mintProduct',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10012,
+ 'pos': Point3(0.0,16.5455417633,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10013
+ 10014: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10012,
+ 'pos': Point3(9.33731842041,15.9028654099,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10014
+ 10015: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10012,
+ 'pos': Point3(-9.30014419556,11.6405067444,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10015
+ # MINTSHELF
+ 10003: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(19.5716362,16.3833560944,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10003
+ 10004: {
+ 'type': 'mintShelf',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(19.5716362,2.93304467201,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10004
+ 10005: {
+ 'type': 'mintShelf',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(19.5716362,-10.4780406952,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10005
+ 10006: {
+ 'type': 'mintShelf',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-19.5699996948,16.3833560944,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10006
+ 10007: {
+ 'type': 'mintShelf',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-19.5699996948,2.93304467201,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10007
+ 10008: {
+ 'type': 'mintShelf',
+ 'name': 'copy of (3)',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-19.5699996948,-10.4780406952,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10008
+ # MODEL
+ 10001: {
+ 'type': 'model',
+ 'name': 'vaultDoor',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,22.9976291656,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/VaultDoorCover.bam',
+ }, # end entity 10001
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10000
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'shelves',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10002
+ 10011: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10011
+ 10012: {
+ 'type': 'nodepath',
+ 'name': 'product',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10012
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintControlRoom_Battle00_Cogs.py b/toontown/src/coghq/CashbotMintControlRoom_Battle00_Cogs.py
new file mode 100644
index 0000000..da6f40d
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintControlRoom_Battle00_Cogs.py
@@ -0,0 +1,61 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 1,
+ 'level' : ToontownGlobals.CashbotMintBossLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintDuctRoom_Action00.py b/toontown/src/coghq/CashbotMintDuctRoom_Action00.py
new file mode 100644
index 0000000..325a63b
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintDuctRoom_Action00.py
@@ -0,0 +1,312 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE15a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # HEALBARREL
+ 10015: {
+ 'type': 'healBarrel',
+ 'name': 'heal',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(16.9929084778,7.15916633606,0.0),
+ 'hpr': Vec3(107.078933716,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 6,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10015
+ # MINTSHELF
+ 10004: {
+ 'type': 'mintShelf',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(41.5774269104,-16.0394973755,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10004
+ 10005: {
+ 'type': 'mintShelf',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(41.5774269104,15.5885248184,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10005
+ # MODEL
+ 10009: {
+ 'type': 'model',
+ 'name': 'crateColl0',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(-21.0479602814,-8.71147918701,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.6654573679,4.67459440231,4.99637460709),
+ 'collisionsOnly': 1,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10009
+ 10013: {
+ 'type': 'model',
+ 'name': 'crate0',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(-21.0,0.735621452332,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.60000002384,1.60000002384,1.60000002384),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10013
+ 10016: {
+ 'type': 'model',
+ 'name': 'copy of crate0',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(-21.0,-8.74976444244,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.60000002384,1.60000002384,1.60000002384),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10016
+ 10017: {
+ 'type': 'model',
+ 'name': 'copy of crate0 (2)',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(-21.0,-18.1307086945,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.60000002384,1.60000002384,1.60000002384),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10017
+ 10019: {
+ 'type': 'model',
+ 'name': 'copy of crate0',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(-21.0,-8.74976444244,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.60000002384,1.60000002384,1.60000002384),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10019
+ 10020: {
+ 'type': 'model',
+ 'name': 'crateColl0',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(-21.0479602814,-8.71147918701,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.69406318665,4.75488471985,5.08219003677),
+ 'collisionsOnly': 1,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10020
+ 10021: {
+ 'type': 'model',
+ 'name': 'crate0',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(-21.0,0.735621452332,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.60000002384,1.60000002384,1.60000002384),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10021
+ 10022: {
+ 'type': 'model',
+ 'name': 'copy of crate0 (2)',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(-21.0,-18.1307086945,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.60000002384,1.60000002384,1.60000002384),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10022
+ 10024: {
+ 'type': 'model',
+ 'name': 'hider',
+ 'comment': '',
+ 'parentEntId': 10023,
+ 'pos': Point3(17.0452461243,-0.882949709892,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.54636645317,1.54636645317,1.54636645317),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10024
+ # NODEPATH
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10003
+ 10010: {
+ 'type': 'nodepath',
+ 'name': 'crates0',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(1.2899544239,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10010
+ 10018: {
+ 'type': 'nodepath',
+ 'name': 'crates1',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-13.2792396545,0.0,0.0),
+ 'hpr': Vec3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10018
+ 10023: {
+ 'type': 'nodepath',
+ 'name': 'heal',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10023
+ # STOMPER
+ 10000: {
+ 'type': 'stomper',
+ 'name': 'stomper0',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-23.0840358734,13.8124275208,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(8.0,8.0,8.0),
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Vec3(1.0,1.0,1.0),
+ 'modelPath': 0,
+ 'motion': 3,
+ 'period': 3.0,
+ 'phaseShift': 0.0,
+ 'range': 1.6000000000000001,
+ 'shaftScale': Point3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 1,
+ 'soundPath': 0,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 1,
+ 'zOffset': 0,
+ }, # end entity 10000
+ 10001: {
+ 'type': 'stomper',
+ 'name': 'stomper1',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-5.92516326904,-0.618411839008,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(8.0,8.0,8.0),
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Vec3(1.0,1.0,1.0),
+ 'modelPath': 0,
+ 'motion': 3,
+ 'period': 3.0,
+ 'phaseShift': 0.33000000000000002,
+ 'range': 1.6000000000000001,
+ 'shaftScale': Point3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 1,
+ 'soundPath': 0,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 1,
+ 'zOffset': 0,
+ }, # end entity 10001
+ 10002: {
+ 'type': 'stomper',
+ 'name': 'stomper2',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(11.6394100189,-14.1471977234,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(8.0,8.0,8.0),
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Vec3(1.0,1.0,1.0),
+ 'modelPath': 0,
+ 'motion': 3,
+ 'period': 3.0,
+ 'phaseShift': 0.66000000000000003,
+ 'range': 1.6000000000000001,
+ 'shaftScale': Point3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 1,
+ 'soundPath': 0,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 1,
+ 'zOffset': 0,
+ }, # end entity 10002
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintDuctRoom_Battle00.py b/toontown/src/coghq/CashbotMintDuctRoom_Battle00.py
new file mode 100644
index 0000000..90a698d
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintDuctRoom_Battle00.py
@@ -0,0 +1,182 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE15a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 10001: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(44.257774353,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellId': 0,
+ 'radius': 10.0,
+ }, # end entity 10001
+ # MODEL
+ 10003: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(39.3964080811,22.2593421936,16.0659122467),
+ 'hpr': Vec3(270.0,0.0,90.0),
+ 'scale': Vec3(0.560864269733,0.560864269733,0.560864269733),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModel',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10003
+ 10005: {
+ 'type': 'model',
+ 'name': 'farLeft',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(41.8226966858,16.5434036255,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(2.28777551651,2.28777551651,2.28777551651),
+ 'collisionsOnly': 1,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10005
+ 10006: {
+ 'type': 'model',
+ 'name': 'farRight',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(41.8226966858,-16.5400009155,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(2.28777551651,2.28777551651,2.28777551651),
+ 'collisionsOnly': 1,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10006
+ 10007: {
+ 'type': 'model',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(39.3192749023,-22.8234348297,13.6092739105),
+ 'hpr': Vec3(270.0,0.0,270.0),
+ 'scale': Point3(0.560000002384,0.560864269733,0.560864269733),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModel',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10007
+ 10008: {
+ 'type': 'model',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-39.3800811768,-22.8855381012,16.0659122467),
+ 'hpr': Point3(90.0,0.0,90.0),
+ 'scale': Vec3(0.560864269733,0.560864269733,0.560864269733),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModel',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10008
+ 10009: {
+ 'type': 'model',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-39.3062858582,22.1807098389,13.6092739105),
+ 'hpr': Point3(90.0,0.0,270.0),
+ 'scale': Point3(0.560000002384,0.560864269733,0.560864269733),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModel',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10009
+ 10010: {
+ 'type': 'model',
+ 'name': 'nearLeft',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(-41.8199996948,16.5434036255,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(2.28777551651,2.28777551651,2.28777551651),
+ 'collisionsOnly': 1,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10010
+ 10011: {
+ 'type': 'model',
+ 'name': 'nearRight',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(-41.8199996948,-16.5400009155,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(2.28777551651,2.28777551651,2.28777551651),
+ 'collisionsOnly': 1,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10011
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10002
+ 10004: {
+ 'type': 'nodepath',
+ 'name': 'collisions',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10004
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintDuctRoom_Battle00_Cogs.py b/toontown/src/coghq/CashbotMintDuctRoom_Battle00_Cogs.py
new file mode 100644
index 0000000..d7a6975
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintDuctRoom_Battle00_Cogs.py
@@ -0,0 +1,61 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintDuctRoom_Battle01.py b/toontown/src/coghq/CashbotMintDuctRoom_Battle01.py
new file mode 100644
index 0000000..8412fc6
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintDuctRoom_Battle01.py
@@ -0,0 +1,188 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE15a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # LOCATOR
+ 10001: {
+ 'type': 'locator',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'searchPath': '**/EXIT',
+ }, # end entity 10001
+ # MINTPRODUCT
+ 10009: {
+ 'type': 'mintProduct',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(37.843006134,8.74360656738,0.0),
+ 'hpr': Point3(-90.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10009
+ 10010: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(36.8768577576,-10.2692861557,0.0),
+ 'hpr': Point3(-90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10010
+ 10011: {
+ 'type': 'mintProduct',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(26.2384986877,-16.2085189819,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10011
+ 10012: {
+ 'type': 'mintProduct',
+ 'name': 'copy of (3)',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(38.4032859802,-3.16089892387,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10012
+ # MODEL
+ 10002: {
+ 'type': 'model',
+ 'name': 'vaultDoor',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/VaultDoorCover.bam',
+ }, # end entity 10002
+ 10004: {
+ 'type': 'model',
+ 'name': 'backRight',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(42.1358146667,-20.2375278473,0.275439172983),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_A.bam',
+ }, # end entity 10004
+ 10005: {
+ 'type': 'model',
+ 'name': 'backLeft',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(38.4724159241,18.6007175446,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.44939172268,1.44939172268,1.44939172268),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_F1.bam',
+ }, # end entity 10005
+ 10006: {
+ 'type': 'model',
+ 'name': 'frontRight',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-42.3298530579,-20.0590000153,0.0695294439793),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.40563333035,1.40563333035,1.40563333035),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_D.bam',
+ }, # end entity 10006
+ 10007: {
+ 'type': 'model',
+ 'name': 'frontLeft',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-42.3731651306,18.05443573,0.0688875243068),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_G1.bam',
+ }, # end entity 10007
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10003
+ 10008: {
+ 'type': 'nodepath',
+ 'name': 'product',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10008
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintDuctRoom_Battle01_Cogs.py b/toontown/src/coghq/CashbotMintDuctRoom_Battle01_Cogs.py
new file mode 100644
index 0000000..da6f40d
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintDuctRoom_Battle01_Cogs.py
@@ -0,0 +1,61 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 1,
+ 'level' : ToontownGlobals.CashbotMintBossLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintEntrance_Action00.py b/toontown/src/coghq/CashbotMintEntrance_Action00.py
new file mode 100644
index 0000000..7996e85
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintEntrance_Action00.py
@@ -0,0 +1,135 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE03a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # ENTRANCEPOINT
+ 10000: {
+ 'type': 'entrancePoint',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'entranceId': 0,
+ 'radius': 15,
+ 'theta': 20,
+ }, # end entity 10000
+ # MINTPRODUCT
+ 10001: {
+ 'type': 'mintProduct',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(-11.4890069962,20.1173057556,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10001
+ 10003: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(-20.4286708832,12.2706327438,0.0),
+ 'hpr': Vec3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10003
+ 10007: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(-19.2144012451,20.1173057556,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12700,
+ }, # end entity 10007
+ # MODEL
+ 10006: {
+ 'type': 'model',
+ 'name': 'crateStack',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(10.5386743546,18.1184597015,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_G1.bam',
+ }, # end entity 10006
+ 10008: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(13.8522205353,-20.3127307892,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_C1.bam',
+ }, # end entity 10008
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10002
+ 10004: {
+ 'type': 'nodepath',
+ 'name': 'product',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10004
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintGearRoom_Action00.py b/toontown/src/coghq/CashbotMintGearRoom_Action00.py
new file mode 100644
index 0000000..9a67687
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintGearRoom_Action00.py
@@ -0,0 +1,300 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE07a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # ATTRIBMODIFIER
+ 10007: {
+ 'type': 'attribModifier',
+ 'name': 'goonStrength',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'attribName': 'strength',
+ 'recursive': 1,
+ 'typeName': 'goon',
+ 'value': '10',
+ }, # end entity 10007
+ # GOON
+ 10002: {
+ 'type': 'goon',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1.5,
+ 'attackRadius': 15,
+ 'crushCellId': None,
+ 'goonType': 'pg',
+ 'gridId': None,
+ 'hFov': 70,
+ 'strength': 10,
+ 'velocity': 4.0,
+ }, # end entity 10002
+ 10004: {
+ 'type': 'goon',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1.5,
+ 'attackRadius': 15,
+ 'crushCellId': None,
+ 'goonType': 'pg',
+ 'gridId': None,
+ 'hFov': 70,
+ 'strength': 10,
+ 'velocity': 4,
+ }, # end entity 10004
+ 10006: {
+ 'type': 'goon',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10005,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1.5,
+ 'attackRadius': 15,
+ 'crushCellId': None,
+ 'goonType': 'pg',
+ 'gridId': None,
+ 'hFov': 70,
+ 'strength': 10,
+ 'velocity': 4,
+ }, # end entity 10006
+ 10009: {
+ 'type': 'goon',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1.5,
+ 'attackRadius': 15,
+ 'crushCellId': None,
+ 'goonType': 'pg',
+ 'gridId': None,
+ 'hFov': 70,
+ 'strength': 10,
+ 'velocity': 4,
+ }, # end entity 10009
+ # HEALBARREL
+ 10011: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10012,
+ 'pos': Point3(2.15899157524,2.29615116119,5.45938539505),
+ 'hpr': Vec3(331.109100342,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 8,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10011
+ # MODEL
+ 10012: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(20.9361133575,13.8672618866,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.920000016689,0.920000016689,0.920000016689),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10012
+ 10013: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(57.0218696594,5.15023899078,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(0.660517215729,0.660517215729,0.660517215729),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10013
+ 10015: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(-25.9598789215,59.4411621094,9.73551368713),
+ 'hpr': Vec3(274.089996338,0.0,0.0),
+ 'scale': Vec3(1.53790044785,1.53790044785,1.53790044785),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_F1.bam',
+ }, # end entity 10015
+ 10016: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(33.3394889832,-18.3643035889,0.0),
+ 'hpr': Vec3(180.0,0.0,0.0),
+ 'scale': Vec3(0.660000026226,0.660000026226,0.660000026226),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_D1.bam',
+ }, # end entity 10016
+ 10017: {
+ 'type': 'model',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(169.699996948,0.0,0.0),
+ 'scale': Vec3(0.902469694614,0.902469694614,0.902469694614),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_D4.bam',
+ }, # end entity 10017
+ 10020: {
+ 'type': 'model',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(-12.071434021,0.0,0.0),
+ 'hpr': Vec3(288.434936523,0.0,0.0),
+ 'scale': Vec3(0.902469694614,0.902469694614,0.902469694614),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_D4.bam',
+ }, # end entity 10020
+ 10022: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10021,
+ 'pos': Point3(-5.97179174423,-60.3133621216,0.0),
+ 'hpr': Vec3(180.0,0.0,0.0),
+ 'scale': Vec3(0.869391143322,0.869391143322,0.869391143322),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10022
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10000
+ 10010: {
+ 'type': 'nodepath',
+ 'name': 'healPuzzle',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(43.1796302795,0.0,0.0),
+ 'hpr': Point3(-90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10010
+ 10018: {
+ 'type': 'nodepath',
+ 'name': 'rightVertPipes',
+ 'comment': '',
+ 'parentEntId': 10021,
+ 'pos': Point3(-16.4536571503,-45.3981781006,-8.39999961853),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(0.649999976158,0.649999976158,1.55999994278),
+ }, # end entity 10018
+ 10021: {
+ 'type': 'nodepath',
+ 'name': 'rightPipes',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10021
+ # PATH
+ 10001: {
+ 'type': 'path',
+ 'name': 'nearPace',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-59.7391967773,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'pathIndex': 3,
+ 'pathScale': 1.0,
+ }, # end entity 10001
+ 10003: {
+ 'type': 'path',
+ 'name': 'bowtie',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-40.0336875916,0.0,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'pathIndex': 2,
+ 'pathScale': 1.0,
+ }, # end entity 10003
+ 10005: {
+ 'type': 'path',
+ 'name': 'bridgePace',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-8.80618190765,-1.5122487545,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'pathIndex': 3,
+ 'pathScale': 1.0,
+ }, # end entity 10005
+ 10008: {
+ 'type': 'path',
+ 'name': 'farPace',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(7.5265827179,7.56240034103,0.0),
+ 'hpr': Vec3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'pathIndex': 3,
+ 'pathScale': 1.0,
+ }, # end entity 10008
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintGearRoom_Battle00.py b/toontown/src/coghq/CashbotMintGearRoom_Battle00.py
new file mode 100644
index 0000000..b7ad1a2
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintGearRoom_Battle00.py
@@ -0,0 +1,113 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE07a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 10001: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-27.3600006104,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellId': 0,
+ 'radius': 10.0,
+ }, # end entity 10001
+ # MODEL
+ 10002: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(57.0218696594,3.79224324226,0.0),
+ 'hpr': Vec3(111.037513733,0.0,0.0),
+ 'scale': Vec3(1.72596073151,1.72596073151,1.72596073151),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/boiler_B1.bam',
+ }, # end entity 10002
+ 10004: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-7.67323350906,-61.4041023254,0.207314386964),
+ 'hpr': Vec3(169.695159912,0.0,0.0),
+ 'scale': Vec3(1.9143627882,1.9143627882,1.9143627882),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/boiler_A2.bam',
+ }, # end entity 10004
+ 10005: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-25.9598789215,44.8260116577,9.73551368713),
+ 'hpr': Vec3(94.0856170654,0.0,0.0),
+ 'scale': Vec3(1.53790044785,1.53790044785,1.53790044785),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_F1.bam',
+ }, # end entity 10005
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-52.7907714844,0.0,0.0),
+ 'hpr': Point3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10003
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintGearRoom_Battle00_Cogs.py b/toontown/src/coghq/CashbotMintGearRoom_Battle00_Cogs.py
new file mode 100644
index 0000000..93ca2f3
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintGearRoom_Battle00_Cogs.py
@@ -0,0 +1,61 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintGearRoom_Battle01.py b/toontown/src/coghq/CashbotMintGearRoom_Battle01.py
new file mode 100644
index 0000000..4fe43ba
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintGearRoom_Battle01.py
@@ -0,0 +1,256 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE07a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # GAGBARREL
+ 10015: {
+ 'type': 'gagBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10009,
+ 'pos': Point3(0.35536468029,1.03268241882,0.0),
+ 'hpr': Point3(99.4599990845,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 10,
+ }, # end entity 10015
+ # GEAR
+ 10007: {
+ 'type': 'gear',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(2.0,-65.0800018311,11.0900001526),
+ 'hpr': Vec3(0.0,90.0,0.0),
+ 'scale': Point3(10.0,10.0,10.0),
+ 'degreesPerSec': 10.0,
+ 'gearScale': 1,
+ 'modelType': 'mint',
+ 'orientation': 'horizontal',
+ 'phaseShift': 0,
+ }, # end entity 10007
+ 10008: {
+ 'type': 'gear',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-15.0,-65.0800018311,15.0),
+ 'hpr': Vec3(0.0,90.0,0.0),
+ 'scale': Point3(10.0,10.0,10.0),
+ 'degreesPerSec': -10.0,
+ 'gearScale': 1,
+ 'modelType': 'mint',
+ 'orientation': 'horizontal',
+ 'phaseShift': 0.26000000000000001,
+ }, # end entity 10008
+ # HEALBARREL
+ 10016: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10009,
+ 'pos': Point3(2.0886592865,-5.19625711441,0.0),
+ 'hpr': Vec3(80.5376815796,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 7,
+ 'rewardPerGrabMax': 10,
+ }, # end entity 10016
+ # LOCATOR
+ 10001: {
+ 'type': 'locator',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'searchPath': '**/EXIT',
+ }, # end entity 10001
+ # MINTPRODUCT
+ 10012: {
+ 'type': 'mintProduct',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10012
+ 10013: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(33.1602783203,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10013
+ 10014: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,-26.4398918152,0.0),
+ 'hpr': Vec3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10014
+ # MODEL
+ 10002: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/VaultDoorCover.bam',
+ }, # end entity 10002
+ 10004: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(28.4207668304,0.886719465256,0.0890568122268),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.42410159111,1.42410159111,1.42410159111),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_D.bam',
+ }, # end entity 10004
+ 10005: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(1.02179563046,-5.71805810928,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(0.912700533867,0.912700533867,0.912700533867),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_G1.bam',
+ }, # end entity 10005
+ 10006: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(28.0896816254,6.65470600128,0.0692733898759),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_E.bam',
+ }, # end entity 10006
+ 10010: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-30.3416347504,-52.2014503479,4.94085407257),
+ 'hpr': Vec3(180.0,270.0,270.0),
+ 'scale': Vec3(0.496759712696,0.496759712696,0.496759712696),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10010
+ 10017: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-47.7702331543,-15.7725839615,0.0),
+ 'hpr': Vec3(180.0,0.0,0.0),
+ 'scale': Vec3(0.75,0.75,0.75),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_C.bam',
+ }, # end entity 10017
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-16.2589473724,49.1446685791,10.2881116867),
+ 'hpr': Vec3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10003
+ 10009: {
+ 'type': 'nodepath',
+ 'name': 'barrels',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(39.311466217,1.05693364143,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10009
+ 10011: {
+ 'type': 'nodepath',
+ 'name': 'product',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-24.7985076904,59.8468132019,10.0710220337),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10011
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintGearRoom_Battle01_Cogs.py b/toontown/src/coghq/CashbotMintGearRoom_Battle01_Cogs.py
new file mode 100644
index 0000000..da6f40d
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintGearRoom_Battle01_Cogs.py
@@ -0,0 +1,61 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : CogParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 1,
+ 'level' : ToontownGlobals.CashbotMintBossLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintLavaRoomFoyer_Action00.py b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Action00.py
new file mode 100644
index 0000000..b45f852
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Action00.py
@@ -0,0 +1,263 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE18a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # ATTRIBMODIFIER
+ 10009: {
+ 'type': 'attribModifier',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'attribName': 'modelPath',
+ 'recursive': 1,
+ 'typeName': 'model',
+ 'value': '',
+ }, # end entity 10009
+ 10017: {
+ 'type': 'attribModifier',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'attribName': 'scale',
+ 'recursive': 1,
+ 'typeName': 'model',
+ 'value': 'Vec3(.955,1,1)',
+ }, # end entity 10017
+ # CRATE
+ 10015: {
+ 'type': 'crate',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10014,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'scale': 0.92000000000000004,
+ 'crushCellId': None,
+ 'gridId': 10014,
+ 'modelType': 1,
+ 'pushable': 1,
+ }, # end entity 10015
+ # GRID
+ 10014: {
+ 'type': 'grid',
+ 'name': 'crateGrid',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-6.73230838776,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellSize': 3.0,
+ 'numCol': 4,
+ 'numRow': 2,
+ }, # end entity 10014
+ # HEALBARREL
+ 10005: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(19.0611743927,-20.78266716,0.0),
+ 'hpr': Vec3(160.016891479,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 8,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10005
+ # MODEL
+ 10001: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(-7.89672088623,21.0129165649,0.0),
+ 'hpr': Vec3(180.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_F1.bam',
+ }, # end entity 10001
+ 10002: {
+ 'type': 'model',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(-17.8739471436,16.2802295685,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_E.bam',
+ }, # end entity 10002
+ 10006: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(20.9172992706,20.2094459534,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10006
+ 10007: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(-18.3651504517,-19.2698841095,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_C1.bam',
+ }, # end entity 10007
+ 10018: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.954999983311,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10018
+ 10019: {
+ 'type': 'model',
+ 'name': 'copy of middle',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(-5.72357320786,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.954999983311,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10019
+ 10020: {
+ 'type': 'model',
+ 'name': 'copy of middle',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(5.71999979019,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.954999983311,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10020
+ 10021: {
+ 'type': 'model',
+ 'name': 'copy of middle',
+ 'comment': '',
+ 'parentEntId': 10008,
+ 'pos': Point3(11.4399995804,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.954999983311,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10021
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10000
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'cratePuzzle',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10003
+ 10008: {
+ 'type': 'nodepath',
+ 'name': 'wall',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(13.4399995804,6.57999992371,0.0),
+ 'hpr': Point3(270.0,0.0,0.0),
+ 'scale': Vec3(1.95812249184,1.5,1.79999995232),
+ }, # end entity 10008
+ # STOMPER
+ 10016: {
+ 'type': 'stomper',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10014,
+ 'pos': Point3(-4.04936361313,3.45528435707,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'crushCellId': None,
+ 'damage': 6,
+ 'headScale': Point3(4.0,3.0,4.0),
+ 'modelPath': 0,
+ 'motion': 3,
+ 'period': 5.0,
+ 'phaseShift': 0.0,
+ 'range': 15.0,
+ 'shaftScale': Point3(0.75,10.0,0.75),
+ 'soundLen': 0,
+ 'soundOn': 1,
+ 'soundPath': 1,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 1,
+ 'zOffset': 0,
+ }, # end entity 10016
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintLavaRoomFoyer_Action01.py b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Action01.py
new file mode 100644
index 0000000..b27f6dd
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Action01.py
@@ -0,0 +1,317 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE18a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # ATTRIBMODIFIER
+ 10000: {
+ 'type': 'attribModifier',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'attribName': 'modelPath',
+ 'recursive': 1,
+ 'typeName': 'model',
+ 'value': '',
+ }, # end entity 10000
+ 10001: {
+ 'type': 'attribModifier',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'attribName': 'scale',
+ 'recursive': 1,
+ 'typeName': 'model',
+ 'value': 'Vec3(.955,1,1)',
+ }, # end entity 10001
+ 10019: {
+ 'type': 'attribModifier',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10015,
+ 'attribName': 'modelPath',
+ 'recursive': 1,
+ 'typeName': 'model',
+ 'value': '',
+ }, # end entity 10019
+ # GEAR
+ 10006: {
+ 'type': 'gear',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'degreesPerSec': -4.0,
+ 'gearScale': 14.193780914463838,
+ 'modelType': 'mint',
+ 'orientation': 'horizontal',
+ 'phaseShift': 0,
+ }, # end entity 10006
+ 10007: {
+ 'type': 'gear',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,4.28999996185),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'degreesPerSec': 4.0,
+ 'gearScale': 14.193780914463838,
+ 'modelType': 'mint',
+ 'orientation': 'horizontal',
+ 'phaseShift': 0,
+ }, # end entity 10007
+ 10009: {
+ 'type': 'gear',
+ 'name': 'copy of (2)',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,8.57999992371),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'degreesPerSec': -4.0,
+ 'gearScale': 14.193780914463838,
+ 'modelType': 'mint',
+ 'orientation': 'horizontal',
+ 'phaseShift': 0.055,
+ }, # end entity 10009
+ 10014: {
+ 'type': 'gear',
+ 'name': 'copy of (3)',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,12.8699998856),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'degreesPerSec': 4.0,
+ 'gearScale': 14.193780914463838,
+ 'modelType': 'mint',
+ 'orientation': 'horizontal',
+ 'phaseShift': 0.059999999999999998,
+ }, # end entity 10014
+ # HEALBARREL
+ 10018: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10017,
+ 'pos': Point3(-2.03643107414,2.34967470169,5.46433734894),
+ 'hpr': Vec3(34.1522636414,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10018
+ # MODEL
+ 10002: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(6.5,6.5,6.5),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/RoundShadow.bam',
+ }, # end entity 10002
+ 10005: {
+ 'type': 'model',
+ 'name': 'doorwayCrate',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(27.0090961456,0.850000023842,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10005
+ 10008: {
+ 'type': 'model',
+ 'name': 'shaft',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(0.0,0.0,7.25891637802),
+ 'hpr': Vec3(0.0,0.0,180.0),
+ 'scale': Vec3(5.35842609406,5.35842609406,5.35842609406),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModel',
+ 'modelPath': 'phase_10/models/cashbotHQ/MintGearPost.bam',
+ }, # end entity 10008
+ 10010: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.954999983311,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10010
+ 10011: {
+ 'type': 'model',
+ 'name': 'copy of middle',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(-5.72357320786,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.954999983311,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10011
+ 10012: {
+ 'type': 'model',
+ 'name': 'copy of middle',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(5.71999979019,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.954999983311,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10012
+ 10013: {
+ 'type': 'model',
+ 'name': 'copy of middle',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(11.4399995804,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.954999983311,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10013
+ 10015: {
+ 'type': 'model',
+ 'name': 'crateStack',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-18.0376968384,20.2023410797,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10015
+ 10016: {
+ 'type': 'model',
+ 'name': 'upper',
+ 'comment': '',
+ 'parentEntId': 10015,
+ 'pos': Point3(0.0,0.0,5.42841148376),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10016
+ 10017: {
+ 'type': 'model',
+ 'name': 'copy of upper',
+ 'comment': '',
+ 'parentEntId': 10016,
+ 'pos': Point3(0.0,0.0,5.43412637711),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10017
+ 10021: {
+ 'type': 'model',
+ 'name': 'crateStack',
+ 'comment': '',
+ 'parentEntId': 10020,
+ 'pos': Point3(21.064825058,20.1899757385,9.87216758728),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_C1.bam',
+ }, # end entity 10021
+ # NODEPATH
+ 10003: {
+ 'type': 'nodepath',
+ 'name': 'gears',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-3.18650078773,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10003
+ 10004: {
+ 'type': 'nodepath',
+ 'name': 'wall',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(19.5468139648,6.37875938416,0.0),
+ 'hpr': Point3(270.0,0.0,0.0),
+ 'scale': Vec3(1.95812249184,1.5,1.79999995232),
+ }, # end entity 10004
+ 10020: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10020
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle00.py b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle00.py
new file mode 100644
index 0000000..873fb6e
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle00.py
@@ -0,0 +1,125 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE18a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 10004: {
+ 'type': 'battleBlocker',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(23.908908844,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellId': 0,
+ 'radius': 10,
+ }, # end entity 10004
+ # MODEL
+ 10002: {
+ 'type': 'model',
+ 'name': 'crates',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(17.3283443451,20.1608715057,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_C1.bam',
+ }, # end entity 10002
+ 10003: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10001,
+ 'pos': Point3(-14.04317379,20.9443073273,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_E.bam',
+ }, # end entity 10003
+ 10006: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10003,
+ 'pos': Point3(-3.16324114799,-0.608929097652,5.57751512527),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_C1.bam',
+ }, # end entity 10006
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10000
+ 10001: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10001
+ 10005: {
+ 'type': 'nodepath',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(-90.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10005
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle00_Cogs.py b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle00_Cogs.py
new file mode 100644
index 0000000..8b16f52
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle00_Cogs.py
@@ -0,0 +1,62 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+BattleParent = 10005
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : BattleParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintSkelecogLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle01.py b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle01.py
new file mode 100644
index 0000000..f98825b
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle01.py
@@ -0,0 +1,209 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE18a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # LOCATOR
+ 10004: {
+ 'type': 'locator',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'searchPath': '**/EXIT1',
+ }, # end entity 10004
+ # MINTPRODUCT
+ 10013: {
+ 'type': 'mintProduct',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10012,
+ 'pos': Point3(-3.94549632072,18.2319583893,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10013
+ 10014: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10012,
+ 'pos': Point3(3.85402703285,18.2319583893,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10014
+ 10015: {
+ 'type': 'mintProduct',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10012,
+ 'pos': Point3(-18.5567684174,14.1500225067,6.5729341507),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'mintId': 12500,
+ }, # end entity 10015
+ # MODEL
+ 10001: {
+ 'type': 'model',
+ 'name': 'vaultDoor',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/VaultDoorCover.bam',
+ }, # end entity 10001
+ 10003: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(13.2311220169,20.3564720154,0.305192321539),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.21849691868,1.21849691868,1.21849691868),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_A.bam',
+ }, # end entity 10003
+ 10007: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-17.5481491089,20.8210849762,0.00756931304932),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.30483365059,1.30483365059,1.30483365059),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_F1.bam',
+ }, # end entity 10007
+ 10008: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(-1.55398654938,-4.84950685501,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.913593888283,0.913593888283,0.913593888283),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10008
+ 10009: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-19.0412902832,-18.4314842224,0.00867449026555),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/crates_G1.bam',
+ }, # end entity 10009
+ 10010: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(18.6662273407,-13.083732605,0.00570194004104),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/CBMetalCrate.bam',
+ }, # end entity 10010
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'cogs',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10000
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'crates',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10002
+ 10005: {
+ 'type': 'nodepath',
+ 'name': 'battle',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(-90.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10005
+ 10011: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Point3(-90.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10011
+ 10012: {
+ 'type': 'nodepath',
+ 'name': 'product',
+ 'comment': '',
+ 'parentEntId': 10011,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10012
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle01_Cogs.py b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle01_Cogs.py
new file mode 100644
index 0000000..acb2a86
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLavaRoomFoyer_Battle01_Cogs.py
@@ -0,0 +1,62 @@
+from SpecImports import *
+from toontown.toonbase import ToontownGlobals
+
+###### TO BE CONVERTED TO ENTITY SYSTEM ######
+# entIds of entities that the cogs are put under
+CogParent = 10000
+BattleParent = 10005
+
+# unique IDs for battle cells
+BattleCellId = 0
+
+BattleCells = {
+ BattleCellId : {'parentEntId' : BattleParent,
+ 'pos' : Point3(0,0,0),
+ },
+ }
+
+CogData = [
+ {'parentEntId' : CogParent,
+ 'boss' : 1,
+ 'level' : ToontownGlobals.CashbotMintBossLevel,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 1,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(-2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(2,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ {'parentEntId' : CogParent,
+ 'boss' : 0,
+ 'level' : ToontownGlobals.CashbotMintCogLevel+1,
+ 'battleCell' : BattleCellId,
+ 'pos' : Point3(6,0,0),
+ 'h' : 180,
+ 'behavior' : 'stand',
+ 'path' : None,
+ 'skeleton' : 0,
+ },
+ ]
+
+ReserveCogData = [
+ ]
diff --git a/toontown/src/coghq/CashbotMintLavaRoom_Action00.py b/toontown/src/coghq/CashbotMintLavaRoom_Action00.py
new file mode 100644
index 0000000..93d8653
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLavaRoom_Action00.py
@@ -0,0 +1,150 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE19a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # ATTRIBMODIFIER
+ 10005: {
+ 'type': 'attribModifier',
+ 'name': 'sinkDuration',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'attribName': 'sinkDuration',
+ 'recursive': 1,
+ 'typeName': 'sinkingPlatform',
+ 'value': '2.0',
+ }, # end entity 10005
+ 10006: {
+ 'type': 'attribModifier',
+ 'name': 'riseDuration',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'attribName': 'riseDuration',
+ 'recursive': 1,
+ 'typeName': 'sinkingPlatform',
+ 'value': '2.0',
+ }, # end entity 10006
+ # HEALBARREL
+ 10004: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-52.5099983215,-3.81641983986,4.99661874771),
+ 'hpr': Vec3(100.165977478,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 8,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10004
+ # MODEL
+ 10008: {
+ 'type': 'model',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(-24.928899765,-4.86700963974,-1.70696532726),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'collisionsOnly': 0,
+ 'loadType': 'loadModel',
+ 'modelPath': 'phase_10/models/cashbotHQ/pipes_A5.bam',
+ }, # end entity 10008
+ # NODEPATH
+ 10002: {
+ 'type': 'nodepath',
+ 'name': 'sinkingPlatforms',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(2.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.864239275455,0.864239275455,0.864239275455),
+ }, # end entity 10002
+ 10007: {
+ 'type': 'nodepath',
+ 'name': 'props',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10007
+ # SINKINGPLATFORM
+ 10000: {
+ 'type': 'sinkingPlatform',
+ 'name': 'plat1',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'pauseBeforeRise': 1,
+ 'riseDuration': 2.0,
+ 'sinkDuration': 2.0,
+ 'verticalRange': 3.0,
+ }, # end entity 10000
+ 10001: {
+ 'type': 'sinkingPlatform',
+ 'name': 'plat0',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(20.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'pauseBeforeRise': 1,
+ 'riseDuration': 2.0,
+ 'sinkDuration': 2.0,
+ 'verticalRange': 3.0,
+ }, # end entity 10001
+ 10003: {
+ 'type': 'sinkingPlatform',
+ 'name': 'plat2',
+ 'comment': '',
+ 'parentEntId': 10002,
+ 'pos': Point3(-20.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'pauseBeforeRise': 1,
+ 'riseDuration': 2.0,
+ 'sinkDuration': 2.0,
+ 'verticalRange': 3.0,
+ }, # end entity 10003
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintLobby_Action00.py b/toontown/src/coghq/CashbotMintLobby_Action00.py
new file mode 100644
index 0000000..b6b134b
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLobby_Action00.py
@@ -0,0 +1,2176 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE04a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # ATTRIBMODIFIER
+ 10005: {
+ 'type': 'attribModifier',
+ 'name': 'stomperPeriod',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'attribName': 'period',
+ 'recursive': 1,
+ 'typeName': 'stomper',
+ 'value': '2.2',
+ }, # end entity 10005
+ 10015: {
+ 'type': 'attribModifier',
+ 'name': 'stomperShaftScale',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'attribName': 'shaftScale',
+ 'recursive': 1,
+ 'typeName': 'stomper',
+ 'value': 'Vec3(1,5,1)',
+ }, # end entity 10015
+ 10067: {
+ 'type': 'attribModifier',
+ 'name': 'stomperDamage',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'attribName': 'damage',
+ 'recursive': 1,
+ 'typeName': 'stomper',
+ 'value': '8',
+ }, # end entity 10067
+ 10130: {
+ 'type': 'attribModifier',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10153,
+ 'attribName': 'modelPath',
+ 'recursive': 1,
+ 'typeName': 'model',
+ 'value': "'phase_10/models/cogHQ/CBMetalCrate2.bam'",
+ }, # end entity 10130
+ 10145: {
+ 'type': 'attribModifier',
+ 'name': 'copy of ',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'attribName': 'modelPath',
+ 'recursive': 1,
+ 'typeName': 'model',
+ 'value': "'phase_10/models/cogHQ/CBMetalCrate2.bam'",
+ }, # end entity 10145
+ 10173: {
+ 'type': 'attribModifier',
+ 'name': 'copy of stomperShaftScale',
+ 'comment': '',
+ 'parentEntId': 10154,
+ 'attribName': 'shaftScale',
+ 'recursive': 1,
+ 'typeName': 'stomper',
+ 'value': 'Vec3(1,5,1)',
+ }, # end entity 10173
+ # GAGBARREL
+ 10169: {
+ 'type': 'gagBarrel',
+ 'name': 'gag',
+ 'comment': '',
+ 'parentEntId': 10170,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(1.0,0.0,0.0),
+ 'scale': 1,
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 6,
+ }, # end entity 10169
+ 10171: {
+ 'type': 'gagBarrel',
+ 'name': 'gagLeft',
+ 'comment': '',
+ 'parentEntId': 10170,
+ 'pos': Point3(-3.0,0.0,0.0),
+ 'hpr': Vec3(-6.0,0.0,0.0),
+ 'scale': 1,
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 6,
+ }, # end entity 10171
+ 10172: {
+ 'type': 'gagBarrel',
+ 'name': 'gagRight',
+ 'comment': '',
+ 'parentEntId': 10170,
+ 'pos': Point3(3.0,0.0,0.0),
+ 'hpr': Vec3(9.0,0.0,0.0),
+ 'scale': 1,
+ 'gagLevel': 5,
+ 'gagLevelMax': 0,
+ 'gagTrack': 'random',
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 6,
+ }, # end entity 10172
+ # HEALBARREL
+ 10177: {
+ 'type': 'healBarrel',
+ 'name': 'healLeft',
+ 'comment': '',
+ 'parentEntId': 10170,
+ 'pos': Point3(-1.05319225788,0.0,4.12134313583),
+ 'hpr': Vec3(20.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 6,
+ }, # end entity 10177
+ 10178: {
+ 'type': 'healBarrel',
+ 'name': 'healRight',
+ 'comment': '',
+ 'parentEntId': 10170,
+ 'pos': Point3(2.16605138779,0.0,4.12134313583),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 5,
+ 'rewardPerGrabMax': 6,
+ }, # end entity 10178
+ 10181: {
+ 'type': 'healBarrel',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-2.92873573303,110.585220337,5.00378036499),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'rewardPerGrab': 10,
+ 'rewardPerGrabMax': 0,
+ }, # end entity 10181
+ # MINTSHELF
+ 10023: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10017,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10023
+ 10026: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10017,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10026
+ 10060: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10059,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10060
+ 10061: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10059,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10061
+ 10064: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10065,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10064
+ 10066: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10065,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10066
+ 10068: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10062,
+ 'pos': Point3(18.0,-1.0,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Point3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10068
+ 10069: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10062,
+ 'pos': Point3(-18.0,-1.0,0.0),
+ 'hpr': Point3(180.0,0.0,0.0),
+ 'scale': Point3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10069
+ 10093: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10092,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10093
+ 10094: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10092,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10094
+ 10096: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10095,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10096
+ 10097: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10095,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10097
+ 10099: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10098,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10099
+ 10100: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10098,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10100
+ 10103: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10101,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10103
+ 10159: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10157,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10159
+ 10161: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10160,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10161
+ 10162: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10160,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10162
+ 10164: {
+ 'type': 'mintShelf',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10163,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10164
+ 10165: {
+ 'type': 'mintShelf',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10163,
+ 'pos': Point3(11.0,0.0,0.0),
+ 'hpr': Vec3(270.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'mintId': 12500,
+ }, # end entity 10165
+ # MODEL
+ 10001: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10001
+ 10002: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10002
+ 10003: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10004,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10003
+ 10008: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10006,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10008
+ 10009: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10006,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10009
+ 10011: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10011
+ 10012: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10012
+ 10013: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10010,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10013
+ 10014: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10022,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10014
+ 10024: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10020,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10024
+ 10025: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10020,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10025
+ 10027: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10021,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10027
+ 10028: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10021,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10028
+ 10029: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10022,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10029
+ 10030: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10022,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10030
+ 10036: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10033,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10036
+ 10037: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10033,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10037
+ 10038: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10033,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10038
+ 10039: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10034,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10039
+ 10040: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10034,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10040
+ 10041: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10034,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10041
+ 10042: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10035,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10042
+ 10043: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10035,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10043
+ 10048: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10044,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10048
+ 10049: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10044,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10049
+ 10050: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10045,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10050
+ 10051: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10045,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10051
+ 10052: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10047,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10052
+ 10053: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10047,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10053
+ 10076: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10075,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10076
+ 10077: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10075,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10077
+ 10078: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10075,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10078
+ 10079: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10047,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10079
+ 10084: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10081,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10084
+ 10085: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10081,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10085
+ 10086: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10081,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10086
+ 10087: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10082,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10087
+ 10088: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10082,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10088
+ 10089: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10082,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10089
+ 10090: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10083,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10090
+ 10091: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10083,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10091
+ 10102: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10072,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10102
+ 10108: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10105,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10108
+ 10109: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10105,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10109
+ 10110: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10072,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10110
+ 10111: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10106,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10111
+ 10112: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10106,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10112
+ 10113: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10072,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10113
+ 10114: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10107,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10114
+ 10115: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10107,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10115
+ 10116: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10107,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10116
+ 10121: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10118,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10121
+ 10122: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10118,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10122
+ 10123: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10118,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10123
+ 10124: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10119,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10124
+ 10125: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10119,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10125
+ 10126: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10119,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10126
+ 10127: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10120,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10127
+ 10128: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10120,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10128
+ 10129: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10073,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10129
+ 10131: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10073,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10131
+ 10132: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10074,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10132
+ 10133: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10074,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10133
+ 10134: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10074,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10134
+ 10139: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10136,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10139
+ 10140: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10136,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10140
+ 10141: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10136,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10141
+ 10143: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10137,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10143
+ 10146: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10137,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10146
+ 10147: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10138,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10147
+ 10148: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10138,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10148
+ 10149: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10144,
+ 'pos': Point3(-6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10149
+ 10150: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10144,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10150
+ 10151: {
+ 'type': 'model',
+ 'name': 'right',
+ 'comment': '',
+ 'parentEntId': 10144,
+ 'pos': Point3(6.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10151
+ 10152: {
+ 'type': 'model',
+ 'name': 'middle',
+ 'comment': '',
+ 'parentEntId': 10138,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cogHQ/CBMetalCrate2.bam',
+ }, # end entity 10152
+ 10158: {
+ 'type': 'model',
+ 'name': 'left',
+ 'comment': '',
+ 'parentEntId': 10157,
+ 'pos': Point3(-11.0,0.0,0.0),
+ 'hpr': Point3(90.0,0.0,0.0),
+ 'scale': Vec3(1.34000003338,1.34000003338,1.34000003338),
+ 'collisionsOnly': 0,
+ 'flattenType': 'light',
+ 'loadType': 'loadModelCopy',
+ 'modelPath': 'phase_10/models/cashbotHQ/shelf_A1.bam',
+ }, # end entity 10158
+ # NODEPATH
+ 10000: {
+ 'type': 'nodepath',
+ 'name': 'crateField',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,-51.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10000
+ 10004: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10004
+ 10006: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10006
+ 10007: {
+ 'type': 'nodepath',
+ 'name': 'crates',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,0.800000011921),
+ }, # end entity 10007
+ 10010: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10018,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10010
+ 10017: {
+ 'type': 'nodepath',
+ 'name': 'wall5',
+ 'comment': '',
+ 'parentEntId': 10063,
+ 'pos': Point3(0.0,90.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10017
+ 10018: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare0',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10018
+ 10019: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare1',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,18.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10019
+ 10020: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10019,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10020
+ 10021: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10019,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10021
+ 10022: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10019,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10022
+ 10031: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare3',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,54.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10031
+ 10032: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare2',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,36.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10032
+ 10033: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10033
+ 10034: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10034
+ 10035: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10032,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10035
+ 10044: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10031,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10044
+ 10045: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10031,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10045
+ 10046: {
+ 'type': 'nodepath',
+ 'name': 'stompers',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(-1.0,0.0,4.40000009537),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10046
+ 10047: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10031,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10047
+ 10059: {
+ 'type': 'nodepath',
+ 'name': 'wall6',
+ 'comment': '',
+ 'parentEntId': 10063,
+ 'pos': Point3(0.0,108.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10059
+ 10062: {
+ 'type': 'nodepath',
+ 'name': 'wall7',
+ 'comment': '',
+ 'parentEntId': 10063,
+ 'pos': Point3(0.0,124.5,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.39999997616,1.0),
+ }, # end entity 10062
+ 10063: {
+ 'type': 'nodepath',
+ 'name': 'walls',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(0.0,-0.019999999553,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10063
+ 10065: {
+ 'type': 'nodepath',
+ 'name': 'wall0',
+ 'comment': '',
+ 'parentEntId': 10063,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10065
+ 10070: {
+ 'type': 'nodepath',
+ 'name': 'leftBranch',
+ 'comment': '',
+ 'parentEntId': 10000,
+ 'pos': Point3(-17.8978881836,72.0,0.0),
+ 'hpr': Vec3(90.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10070
+ 10071: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare0',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10153,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10071
+ 10072: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10071,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10072
+ 10073: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10071,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10073
+ 10074: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10071,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10074
+ 10075: {
+ 'type': 'nodepath',
+ 'name': 'frontCrateRow',
+ 'comment': '',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,-12.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(0.844285488129,1.0,1.0),
+ }, # end entity 10075
+ 10080: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare4',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,72.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10080
+ 10081: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10080,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10081
+ 10082: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10080,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10082
+ 10083: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10080,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10083
+ 10092: {
+ 'type': 'nodepath',
+ 'name': 'wall1',
+ 'comment': '',
+ 'parentEntId': 10063,
+ 'pos': Point3(0.0,18.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10092
+ 10095: {
+ 'type': 'nodepath',
+ 'name': 'wall2',
+ 'comment': '',
+ 'parentEntId': 10063,
+ 'pos': Point3(0.0,36.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10095
+ 10098: {
+ 'type': 'nodepath',
+ 'name': 'wall3',
+ 'comment': '',
+ 'parentEntId': 10063,
+ 'pos': Point3(0.0,54.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10098
+ 10101: {
+ 'type': 'nodepath',
+ 'name': 'wall4',
+ 'comment': '',
+ 'parentEntId': 10063,
+ 'pos': Point3(0.0,72.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10101
+ 10104: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare5',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,90.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10104
+ 10105: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10104,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10105
+ 10106: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10104,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10106
+ 10107: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10104,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10107
+ 10117: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare6',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,108.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10117
+ 10118: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10117,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10118
+ 10119: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10117,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10119
+ 10120: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10117,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10120
+ 10135: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare1',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10153,
+ 'pos': Point3(0.0,18.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10135
+ 10136: {
+ 'type': 'nodepath',
+ 'name': 'row2',
+ 'comment': '',
+ 'parentEntId': 10135,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10136
+ 10137: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10135,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10137
+ 10138: {
+ 'type': 'nodepath',
+ 'name': 'row1',
+ 'comment': '',
+ 'parentEntId': 10135,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10138
+ 10142: {
+ 'type': 'nodepath',
+ 'name': 'crateSquare7',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10007,
+ 'pos': Point3(0.0,126.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10142
+ 10144: {
+ 'type': 'nodepath',
+ 'name': 'row0',
+ 'comment': '',
+ 'parentEntId': 10142,
+ 'pos': Point3(0.0,-6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10144
+ 10153: {
+ 'type': 'nodepath',
+ 'name': 'crates',
+ 'comment': '',
+ 'parentEntId': 10070,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,0.800000011921),
+ }, # end entity 10153
+ 10154: {
+ 'type': 'nodepath',
+ 'name': 'stompers',
+ 'comment': '',
+ 'parentEntId': 10070,
+ 'pos': Point3(-1.0,0.0,4.40000009537),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10154
+ 10156: {
+ 'type': 'nodepath',
+ 'name': 'walls',
+ 'comment': '',
+ 'parentEntId': 10070,
+ 'pos': Point3(0.0,6.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10156
+ 10157: {
+ 'type': 'nodepath',
+ 'name': 'wall0',
+ 'comment': '',
+ 'parentEntId': 10156,
+ 'pos': Point3(0.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10157
+ 10160: {
+ 'type': 'nodepath',
+ 'name': 'wall1',
+ 'comment': '',
+ 'parentEntId': 10156,
+ 'pos': Point3(0.0,18.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10160
+ 10163: {
+ 'type': 'nodepath',
+ 'name': 'wall2',
+ 'comment': '',
+ 'parentEntId': 10156,
+ 'pos': Point3(0.0,36.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Point3(1.0,1.0,1.0),
+ }, # end entity 10163
+ 10168: {
+ 'type': 'nodepath',
+ 'name': 'safeArea',
+ 'comment': '',
+ 'parentEntId': 10070,
+ 'pos': Point3(0.0,40.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ }, # end entity 10168
+ 10170: {
+ 'type': 'nodepath',
+ 'name': 'barrels',
+ 'comment': '',
+ 'parentEntId': 10168,
+ 'pos': Point3(0.0,9.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ }, # end entity 10170
+ # STOMPER
+ 10016: {
+ 'type': 'stomper',
+ 'name': 'stomper6',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10046,
+ 'pos': Point3(1.0,108.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 1,
+ 'soundPath': 2,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10016
+ 10054: {
+ 'type': 'stomper',
+ 'name': 'stomper1',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(1.0,18.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 0,
+ 'soundPath': 2,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10054
+ 10055: {
+ 'type': 'stomper',
+ 'name': 'stomper0',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10046,
+ 'pos': Point3(1.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 1,
+ 'soundPath': 2,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10055
+ 10056: {
+ 'type': 'stomper',
+ 'name': 'stomper2',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(1.0,36.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 0,
+ 'soundPath': 0,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10056
+ 10057: {
+ 'type': 'stomper',
+ 'name': 'stomper3',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(1.0,54.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 0,
+ 'soundPath': 2,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10057
+ 10058: {
+ 'type': 'stomper',
+ 'name': 'stomper4',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(1.0,72.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 0,
+ 'soundPath': 2,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10058
+ 10155: {
+ 'type': 'stomper',
+ 'name': 'stomper5',
+ 'comment': '',
+ 'parentEntId': 10046,
+ 'pos': Point3(1.0,90.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 0,
+ 'soundPath': 0,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10155
+ 10166: {
+ 'type': 'stomper',
+ 'name': 'stomper0',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10154,
+ 'pos': Point3(1.0,0.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 0,
+ 'soundPath': 0,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10166
+ 10167: {
+ 'type': 'stomper',
+ 'name': 'stomper1',
+ 'comment': 'Y=N*18',
+ 'parentEntId': 10154,
+ 'pos': Point3(1.0,18.0,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'animateShadow': 1,
+ 'crushCellId': None,
+ 'damage': 8,
+ 'headScale': Point3(9.0,9.0,9.0),
+ 'modelPath': 0,
+ 'motion': 2,
+ 'period': 2.2000000000000002,
+ 'phaseShift': 0.0,
+ 'range': 13.0,
+ 'removeCamBarrierCollisions': 1,
+ 'removeHeadFloor': 1,
+ 'shaftScale': Vec3(1.0,5.0,1.0),
+ 'soundLen': 0,
+ 'soundOn': 0,
+ 'soundPath': 0,
+ 'style': 'vertical',
+ 'switchId': 0,
+ 'wantShadow': 1,
+ 'wantSmoke': 0,
+ 'zOffset': 0,
+ }, # end entity 10167
+ }
+
+Scenario0 = {
+ }
+
+levelSpec = {
+ 'globalEntities': GlobalEntities,
+ 'scenarios': [
+ Scenario0,
+ ],
+ }
diff --git a/toontown/src/coghq/CashbotMintLobby_Battle00.py b/toontown/src/coghq/CashbotMintLobby_Battle00.py
new file mode 100644
index 0000000..0ddcce7
--- /dev/null
+++ b/toontown/src/coghq/CashbotMintLobby_Battle00.py
@@ -0,0 +1,554 @@
+from toontown.coghq.SpecImports import *
+
+GlobalEntities = {
+ # LEVELMGR
+ 1000: {
+ 'type': 'levelMgr',
+ 'name': 'LevelMgr',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'cogLevel': 0,
+ 'farPlaneDistance': 1500,
+ 'modelFilename': 'phase_10/models/cashbotHQ/ZONE04a',
+ 'wantDoors': 1,
+ }, # end entity 1000
+ # EDITMGR
+ 1001: {
+ 'type': 'editMgr',
+ 'name': 'EditMgr',
+ 'parentEntId': 0,
+ 'insertEntity': None,
+ 'removeEntity': None,
+ 'requestNewEntity': None,
+ 'requestSave': None,
+ }, # end entity 1001
+ # ZONE
+ 0: {
+ 'type': 'zone',
+ 'name': 'UberZone',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'scale': 1,
+ 'description': '',
+ 'visibility': [],
+ }, # end entity 0
+ # BATTLEBLOCKER
+ 10001: {
+ 'type': 'battleBlocker',
+ 'name': 'exitBlocker',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(0.0,76.2264404297,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': Vec3(1.0,1.0,1.0),
+ 'cellId': 0,
+ 'radius': 10.0,
+ }, # end entity 10001
+ 10021: {
+ 'type': 'battleBlocker',
+ 'name': 'middleBlocker',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(9.79564476013,7.17855405807,0.0),
+ 'hpr': Vec3(90.0,0.0,0.0),
+ 'scale': Vec3(1.61347305775,0.225867271423,1.99822974205),
+ 'cellId': 1,
+ 'radius': 10.0,
+ }, # end entity 10021
+ 10061: {
+ 'type': 'battleBlocker',
+ 'name': 'frontBlocker',
+ 'comment': '',
+ 'parentEntId': 0,
+ 'pos': Point3(-45.6075019836,-22.7538051605,0.0),
+ 'hpr': Vec3(45.0,0.0,0.0),
+ 'scale': Vec3(1.61347305775,0.225867271423,1.99822974205),
+ 'cellId': 2,
+ 'radius': 10.0,
+ }, # end entity 10061
+ # MINTPRODUCTPALLET
+ 10025: {
+ 'type': 'mintProductPallet',
+ 'name': '',
+ 'comment': '',
+ 'parentEntId': 10024,
+ 'pos': Point3(0.0,7.96000003815,0.0),
+ 'hpr': Vec3(0.0,0.0,0.0),
+ 'scale': 1,
+ 'mintId': 12500,
+ }, # end entity 10025
+ 10031: {
+ 'type': 'mintProductPallet',
+ 'name': '