Exemplo n.º 1
0
class MonsterBlood(DirectObject):
    def __init__(self):
        self.value = 100
        self.lifeBar = DirectWaitBar(text="Monster",
                                     text_fg=(1, 1, 1, 1),
                                     text_pos=(1.2, -0.18, 0),
                                     text_align=TextNode.ARight,
                                     value=self.value,
                                     barColor=(0, 1, 0.25, 1),
                                     barRelief=DGG.RAISED,
                                     barBorderWidth=(0.03, 0.03),
                                     borderWidth=(0.01, 0.01),
                                     relief=DGG.RIDGE,
                                     frameColor=(0.8, 0.05, 0.10, 1),
                                     frameSize=(0, 1.2, 0, -0.1),
                                     pos=(0.2, 0, base.a2dTop - 0.15))
        self.lifeBar.setTransparency(1)
        self.hide()

    def show(self):
        self.lifeBar["value"] = self.value
        self.lifeBar.show()

    def hide(self):
        self.lifeBar.hide()

    def setLifeBarValue(self, newValue):
        self.lifeBar["value"] = newValue
Exemplo n.º 2
0
class Hud(DirectObject):
    def __init__(self, framework):
        super(Hud, self).__init__()
        self.f = framework

        self.player = DirectWaitBar(
            text="Player",
            text_fg=(1, 1, 1, 1),
            text_pos=(-1.2, -0.15, 0),
            text_align=TextNode.ALeft,
            text_scale=0.075,
            value=100,
            barColor=(0, 1, 0.25, 1),
            barRelief=DGG.FLAT,
            barBorderWidth=(0.03, 0.03),
            borderWidth=(0.01, 0.01),
            relief=DGG.FLAT,
            frameColor=(0.8, 0.05, 0.10, 1),
            frameSize=(-1.2, 0, 0, -0.05),
            pos=(-0.2, 0, self.f.a2dTop - 0.10))
        self.player.set_transparency(1)

        self.bot = DirectWaitBar(
            text="Enemy",
            text_fg=(1, 1, 1, 1),
            text_pos=(1.2, -0.15, 0),
            text_align=TextNode.ARight,
            text_scale=0.075,
            value=100,
            barColor=(0, 1, 0.25, 1),
            barRelief=DGG.FLAT,
            barBorderWidth=(0.03, 0.03),
            borderWidth=(0.01, 0.01),
            relief=DGG.FLAT,
            frameColor=(0.8, 0.05, 0.10, 1),
            frameSize=(0, 1.2, 0, -0.05),
            pos=(0.2, 0, self.f.a2dTop - 0.10))
        self.bot.set_transparency(1)

        self.accept("hud_set_hp", self.set_hp)
        self.hide()

    def show(self):
        self.bot.show()
        self.player.show()

    def hide(self):
        self.bot.hide()
        self.player.hide()

    def set_hp(self, uid, value):
        if uid == "enemy":
            self.bot["value"] = value
        elif uid == "player":
            self.player["value"] = value
Exemplo n.º 3
0
class Hud(DirectObject):
    def __init__(self):
        self.lifeBar1 = DirectWaitBar(
            text = "Player1",
            text_fg = (1,1,1,1),
            text_pos = (-1.2, -0.18, 0),
            text_align = TextNode.ALeft,
            value = 100,
            barColor = (0, 1, 0.25, 1),
            barRelief = DGG.RAISED,
            barBorderWidth = (0.03, 0.03),
            borderWidth = (0.01, 0.01),
            relief = DGG.RIDGE,
            frameColor = (0.8,0.05,0.10,1),
            frameSize = (-1.2, 0, -0.1, 0),
            pos = (-0.2,0,base.a2dTop-0.15))
        self.lifeBar1.setTransparency(1)

        self.lifeBar2 = DirectWaitBar(
            text = "Player2",
            text_fg = (1,1,1,1),
            text_pos = (1.2, -0.18, 0),
            text_align = TextNode.ARight,
            value = 100,
            barColor = (0, 1, 0.25, 1),
            barRelief = DGG.RAISED,
            barBorderWidth = (0.03, 0.03),
            borderWidth = (0.01, 0.01),
            relief = DGG.RIDGE,
            frameColor = (0.8,0.05,0.10,1),
            frameSize = (0, 1.2, -0.1, 0),
            pos = (0.2,0,base.a2dTop-0.15))
        self.lifeBar2.setTransparency(1)

        self.accept("hud_setLifeBarValue", self.setLifeBarValue)
        self.hide()

    def show(self):
        self.lifeBar1["value"] = 100
        self.lifeBar2["value"] = 100
        self.lifeBar1.show()
        self.lifeBar2.show()

    def hide(self):
        self.lifeBar1.hide()
        self.lifeBar2.hide()

    def setLifeBarValue(self, barNr, newValue):
        if barNr == 0:
            self.lifeBar1["value"] = newValue
        elif barNr == 1:
            self.lifeBar2["value"] = newValue
Exemplo n.º 4
0
class ControlPointBar(DirectWaitBar):
    def __init__(self, barColor=(255,0,0,1), pos=(1.0,0,0.9)): 
        self.bar = DirectWaitBar(pos = pos, barColor = barColor, text="",
        value=0, range=30, frameSize=(-0.3,0.3,0,0.03), frameColor=(0,0,255,1))

    def setValue(self, value):
        self.bar['value'] = float(value)

    def hide(self):
        self.bar.hide()
    
    def show(self):
        self.bar.show()
Exemplo n.º 5
0
class ResourceBar(DirectWaitBar):
    def __init__(self, barColor=(200,180,0,1), pos=(0,0,0.9)): 
        self.bar = DirectWaitBar(pos = pos, barColor = barColor, text="",
        value=70, range=100, frameSize=(-0.3,0.3,0,0.03), frameColor=(0,0,0,1))

    def setValue(self, value):
        self.bar['value'] = float(value)

    def hide(self):
        self.bar.hide()
    
    def show(self):
        self.bar.show()
        
Exemplo n.º 6
0
class Bar(DirectObject.DirectObject):
	def __init__( self):
		self.bar = DirectWaitBar(text = "Loading...", value = 0, pos = (0,.4,.4))
		self.bar.hide()

	def incBar(self, arg):
		self.bar['value'] +=	arg
		#text = str(bar['value'])
		#textObject.setText(text)

	def show(self):
		self.bar.show()

	def hide(self):
		self.bar.hide()
Exemplo n.º 7
0
class ControlPointBar(DirectWaitBar):
    def __init__(self, barColor=(255, 0, 0, 1), pos=(1.0, 0, 0.9)):
        self.bar = DirectWaitBar(pos=pos,
                                 barColor=barColor,
                                 text="",
                                 value=0,
                                 range=30,
                                 frameSize=(-0.3, 0.3, 0, 0.03),
                                 frameColor=(0, 0, 255, 1))

    def setValue(self, value):
        self.bar['value'] = float(value)

    def hide(self):
        self.bar.hide()

    def show(self):
        self.bar.show()
Exemplo n.º 8
0
class ResourceBar(DirectWaitBar):
    def __init__(self, barColor=(200, 180, 0, 1), pos=(0, 0, 0.9)):
        self.bar = DirectWaitBar(pos=pos,
                                 barColor=barColor,
                                 text="",
                                 value=70,
                                 range=100,
                                 frameSize=(-0.3, 0.3, 0, 0.03),
                                 frameColor=(0, 0, 0, 1))

    def setValue(self, value):
        self.bar['value'] = float(value)

    def hide(self):
        self.bar.hide()

    def show(self):
        self.bar.show()
Exemplo n.º 9
0
Arquivo: hud.py Projeto: iPazu/Slimy
class Hud(DirectObject):
    def __init__(self):
        self.lifeBar = DirectWaitBar(value=100,
                                     barColor=(0, 1, 0.20, 1),
                                     barRelief=DGG.RAISED,
                                     relief=DGG.RIDGE,
                                     frameColor=(0.8, 0.05, 0.10, 1),
                                     frameSize=(-1.2, 0, 0, -0.1),
                                     pos=(-0.2, 0, base.a2dTop))
        self.lifeBar.setTransparency(1)

        self.font = loader.loadFont(
            str(MAINDIR) + '/assets/fonts/allerdisplay.ttf')

        self.score_text = OnscreenText(text="Score:",
                                       pos=(-1.6, 0.93),
                                       scale=0.05,
                                       fg=(1, 1, 1, 1),
                                       align=TextNode.ACenter,
                                       font=self.font,
                                       mayChange=False)
        self.score = OnscreenText(text="0",
                                  pos=(-1.6, 0.83),
                                  scale=0.07,
                                  fg=(1, 1, 1, 1),
                                  align=TextNode.ACenter,
                                  font=self.font,
                                  mayChange=True)

        self.accept("hud_setLifeBarValue", self.setLifeBarValue)
        self.hide()

    def show(self):
        self.lifeBar["value"] = 100
        self.lifeBar.show()

    def hide(self):
        self.lifeBar.hide()

    def setScore(self, score):
        self.score.setText(str(score))

    def setLifeBarValue(self, newValue):
        self.lifeBar["value"] = newValue
Exemplo n.º 10
0
class BossBlood(DirectObject):
    def __init__(self):
        self.value = 100
        frameTex = loader.loadTexture("assets/gui/blood.jpg")
        barTex = loader.loadTexture("assets/gui/btn_click.png")
        self.lifeBar = DirectWaitBar(
            text="Boss",
            text_fg=(1, 1, 1, 1),
            text_pos=(1.0, 0.07, 0),
            text_align=TextNode.ARight,
            value=self.value,
            #0, 1, 0.25, 1   0.8,0.05,0.10,1
            #barTexture = "assets/gui/btn_click.png",
            barColor=(0.8, 0.05, 0.10, 1),
            frameTexture=frameTex,
            barTexture=barTex,
            barRelief=DGG.RIDGE,
            barBorderWidth=(0.01, 0.01),
            borderWidth=(0.01, 0.01),
            relief=DGG.SUNKEN,
            frameSize=(0, 3.2, 0, 0.05),
            pos=(base.a2dLeft, 0, base.a2dBottom))
        self.lifeBar.setTransparency(1)
        self.hide()

    def show(self):
        self.lifeBar["value"] = self.value
        self.lifeBar.show()

    def hide(self):
        self.lifeBar.hide()

    def setLifeBarValue(self, newValue, name='BOSS'):
        self.value = newValue
        self.lifeBar["value"] = self.value * 100 / self.maxVal
        self.lifeBar["text"] = str(self.value) + "/" + str(
            self.maxVal) + ' BOSS'

    def setLifeBarMaxValue(self, newValue, name='BOSS'):
        self.maxVal = newValue
        self.lifeBar["text"] = str(self.value) + "/" + str(
            self.maxVal) + ' BOSS'
        self.lifeBar["value"] = self.value * 100 / self.maxVal
Exemplo n.º 11
0
class ProgressBar(object):
    '''        
    '''
    def __init__(self):
        '''        
        '''
        self.__wait_bar = DirectWaitBar( text = "Carregando...",
                                         value = 0,
                                         pos = (0, 0, -.95),
                                         text_scale = 0.05,
                                         text_pos = (0, 0.025),
                                         frameSize = (-1.3, 1.3, 0, 0.08) )
        
        self.hide()
        
    def finish(self):
        self.__wait_bar['barColor'] = (0, 1, 0, 1)
        self.__wait_bar.setBarColor()
        self.__wait_bar.finish()
        self.hide()
    
    def hide(self):
        self.__wait_bar.hide()
        
    def show(self):
        self.__wait_bar.show()
        
    def update(self, value):
        
        if value < 25:
            self.__wait_bar['barColor'] = (1, 0, 0, 1)
        elif value > 25 and value < 75:
            self.__wait_bar['barColor'] = (1, 1, 0, 1)
        elif value > 75:
            self.__wait_bar['barColor'] = (0, 1, 0, 1)
            
            
        self.__wait_bar.setBarColor()
        
        self.__wait_bar.update(value)
        
        
Exemplo n.º 12
0
class HeroBlood(DirectObject):
    """docstring for Blood"""
    def __init__(self):
        self.value = 100
        self.maxVal = 100
        #text text_pos value frameSize pos
        frameTex = loader.loadTexture("assets/gui/blood.jpg")
        barTex = loader.loadTexture("assets/gui/btn_click.png")
        self.lifeBar = DirectWaitBar(text=str(self.value),
                                     text_fg=(1, 1, 1, 0.6),
                                     text_pos=(0.8, 1.92, 0),
                                     text_align=TextNode.ALeft,
                                     value=self.value,
                                     barColor=(0, 0.95, 0.25, 1),
                                     frameTexture=frameTex,
                                     barTexture=barTex,
                                     barRelief=DGG.RIDGE,
                                     barBorderWidth=(0.01, 0.01),
                                     borderWidth=(0.01, 0.01),
                                     relief=DGG.SUNKEN,
                                     frameSize=(0.25, 0.8, 1.92, 1.97),
                                     pos=(base.a2dLeft, 0, base.a2dBottom))
        self.lifeBar.setTransparency(1)
        self.hide()

    def show(self):
        self.lifeBar.show()

    def hide(self):
        self.lifeBar.hide()

    def setLifeBarValue(self, newValue, name='Player'):
        self.value = newValue
        self.lifeBar["text"] = str(self.value) + "/" + str(
            self.maxVal) + ' ' + name
        self.lifeBar["value"] = self.value * 100 / self.maxVal

    def setLifeBarMaxValue(self, newValue, name='Player'):
        self.maxVal = newValue
        self.lifeBar["text"] = str(self.value) + "/" + str(
            self.maxVal) + ' ' + name
        self.lifeBar["value"] = self.value * 100 / self.maxVal
Exemplo n.º 13
0
class ControlPointBar(DirectWaitBar):
    def __init__(self, barColor=(255,0,0,1), pos=(1.0,0,0.9)): 
        self.bar = DirectWaitBar(pos = pos, barColor = barColor, text="",
        value=70, range=100, frameSize=(-0.3,0.3,0,0.03), frameColor=(0,0,255,1))
        

    def setValue(self, value):
        self.bar['value'] = float(value)
        self.setPos((0,0,0))

    def hide(self):
        self.bar.hide()
    
    # to make inherited setPos available for this object
    def show(self):
        self.bar.show()
    
    # to make inherited setPos available for this object
    def setPos(self, pos):
        self.bar.setPos(pos)
Exemplo n.º 14
0
class PartyCogActivityGui(DirectObject):
    notify = directNotify.newCategory("PartyCogActivityGui")
    
    def __init__(self):
        DirectObject.__init__(self)
        
        self._piePowerMeter = None
        self._victoryBalanceBar = None
        self._scoreLabel = None
        self._cogTracker = None
        self._piePowerTitle = None
        self._victoryBalanceTitle = None
        self._scoreTitle = None
        self._spamWarning = None
        self._spamWarningIvalName = "PartyCogActivityGui-SpamWarning"       
        
    def load(self):
        self._initPiePowerMeter()
        self._initScore()
        self._initCogTracker()
        self._initSpamWarning()
        self._initControlGui()
        self._initVictoryBalanceBar()
        
    def unload(self):
        if self._cogTracker is not None:
            self._cogTracker.destory()
            self._cogTracker = None
            
        if self._piePowerMeter is not None:
            self._piePowerMeter.destroy()
            self._piePowerMeter = None
            
        if self._piePowerTitle is not None:
            self._piePowerTitle.destroy()
            self._piePowerTitle = None
        
        if self._scoreLabel is not None:
            self._scoreLabel.destroy()
            self._scoreLabel = None
            
        if self._scoreTitle is not None:
            self._scoreTitle.destroy()
            self._scoreTitle = None
            
        taskMgr.remove(self._spamWarningIvalName)
        
        if self._spamWarning:
            self._spamWarning.destroy()
            self._spamWarning = None
            
        if hasattr(self, '_attackKeys'):
            self._attackKeys.detachNode()
            del self._attackKeys
            
        if hasattr(self, '_moveKeys'):
            self._moveKeys.detachNode()
            del self._moveKeys
            
        if self._victoryBalanceBar:
            self._victoryBalanceBar.detachNode()
            self._victoryBalanceBar = None    
            
        if self._victoryBalanceBarOrange:
            self._victoryBalanceBarOrange.detachNode()
            self._victoryBalanceBarOrange = None    
            
        if self._victoryBalanceBarPie:
            self._victoryBalanceBarPie.detachNode()
            self._victoryBalanceBarPie = None
            
        if self._victoryBalanceBarArrow:
            self._victoryBalanceBarArrow.detachNode()
            self._victoryBalanceBarArrow = None
            
    def _initVictoryBalanceBar(self):
        
        h = PartyGlobals.CogActivityPowerMeterHeight / 2.0
        w = PartyGlobals.CogActivityPowerMeterWidth / 2.0
        
        victoryBalanceBar = loader.loadModel("phase_13/models/parties/tt_m_gui_pty_pieToss_balanceBar")
        self._victoryBalanceBar = victoryBalanceBar.find("**/*tt_t_gui_pty_pieToss_balanceBarBG")
        self._victoryBalanceBar.reparentTo(aspect2d)
        self._victoryBalanceBar.setBin("fixed", 0)
        self._victoryBalanceBar.setPos(PartyGlobals.CogActivityVictoryBarPos)
        self._victoryBalanceBar.setScale(1)
        
        self._victoryBalanceBarOrange = victoryBalanceBar.find("**/*tt_t_gui_pty_pieToss_balanceBarOrange")
        self._victoryBalanceBarOrange.reparentTo(self._victoryBalanceBar)
        self._victoryBalanceBarOrange.setBin("fixed", 1)
        self._victoryBalanceBarOrange.setPos(PartyGlobals.CogActivityVictoryBarOrangePos)
        self._victoryBalanceBarOrange.setScale(PartyGlobals.CogActivityBarStartScale, 1.0, 1.0)
        
        self._victoryBalanceBarPie = victoryBalanceBar.find("**/*tt_t_gui_pty_pieToss_balanceBarPie")
        self._victoryBalanceBarPie.reparentTo(self._victoryBalanceBar)
        self._victoryBalanceBarPie.setBin("fixed", 2)
        self._victoryBalanceBarPie.setX(PartyGlobals.CogActivityVictoryBarPiePos[0])         
        self._victoryBalanceBarPie.setY(PartyGlobals.CogActivityVictoryBarPiePos[1])        
        self._victoryBalanceBarPie.setZ(PartyGlobals.CogActivityVictoryBarPiePos[2])        
        self._victoryBalanceBarPie.setScale(PartyGlobals.CogActivityBarPieScale)    
        
        self._victoryBalanceBarArrow = victoryBalanceBar.find("**/*tt_t_gui_pty_pieToss_balanceArrow")
        self._victoryBalanceBarArrow.reparentTo(self._victoryBalanceBarPie)
        self._victoryBalanceBarArrow.setBin("fixed", 2)
        self._victoryBalanceBarArrow.setPos(PartyGlobals.CogActivityVictoryBarArrow)
        self._victoryBalanceBarArrow.setScale(1/PartyGlobals.CogActivityBarPieScale)    
        
        # self._victoryBalanceBar = DirectWaitBar(
            # frameSize = (-h, h, -w, w),
            # relief = DGG.SUNKEN,
            # frameColor = PartyGlobals.CogActivityColors[0],
            # borderWidth = (0.0, 0.0),
            # barColor = PartyGlobals.CogActivityColors[1],
            # pos = PartyGlobals.CogActivityVictoryBarPos,
            # hpr = (0.0, 0.0, -90.0),
            # )
        
        # self._victoryBalanceBar.setBin("fixed", 0)
        # self._victoryBalanceBar["value"] = 50
        
    def _initControlGui(self):
        self._attackIvalName = "PartyCogActivityGui-attackKeys" 
        self._moveIvalName = "PartyCogActivityGui-moveKeys" 
        pieTossControls = loader.loadModel("phase_13/models/parties/tt_m_gui_pty_pieToss_controls")
        self._attackKeys = pieTossControls.find("**/*control*")
        self._moveKeys = pieTossControls.find("**/*arrow*")
        
        self._moveKeys.reparentTo(aspect2d)
        self._moveKeys.setPos(1.0, 0.0, -0.435)
        self._moveKeys.setScale(0.15)
        self._attackKeys.reparentTo(aspect2d)
        self._attackKeys.setPos(0.85, 0.0, -0.45)
        self._attackKeys.setScale(0.15)
        
        self._moveKeys.hide()
        self._attackKeys.hide()
            
    def _initPiePowerMeter(self):
        h = PartyGlobals.CogActivityPowerMeterHeight / 2.0
        w = PartyGlobals.CogActivityPowerMeterWidth / 2.0
    
        self._piePowerMeter = DirectWaitBar(
            frameSize = (-h, h, -w, w),
            relief = DGG.GROOVE,
            frameColor = (0.9, 0.9, 0.9, 1.0),
            borderWidth = (0.01, 0.01),
            barColor = PartyGlobals.CogActivityColors[0],
            pos = PartyGlobals.CogActivityPowerMeterPos,
            hpr = (0.0, 0.0, -90.0),
            )
        
        self._piePowerMeter.setBin("fixed", 0)
        
        self._piePowerTitle = OnscreenText(
            text=TTLocalizer.PartyCogGuiPowerLabel,
            pos=PartyGlobals.CogActivityPowerMeterTextPos,
            scale=0.05,
            fg=(1.0, 1.0, 1.0, 1.0),
            align=TextNode.ACenter,
            )
            
        self._piePowerTitle.setBin("fixed", 0)
        
        self._piePowerMeter.hide()
        self._piePowerTitle.hide()  
    
    def _initScore(self):
        self._scoreLabel = OnscreenText(
            text="0",
            pos=PartyGlobals.CogActivityScorePos,
            scale=PartyGlobals.TugOfWarTextWordScale,
            #fg=base.localAvatar.style.getHeadColor(),
            #fg=PartyGlobals.TeamActivityStatusColor,
            fg=(1.0, 1.0, 0.0, 1.0),
            align=TextNode.ARight,
            font=ToontownGlobals.getSignFont(),
            mayChange=True,
            )
        
        self._scoreTitle = OnscreenText(
            text=TTLocalizer.PartyCogGuiScoreLabel,
            pos=PartyGlobals.CogActivityScoreTitle,
            scale=0.05,
            fg=(1.0, 1.0, 1.0, 1.0),
            #fg=(0.0, 0.0, 0.0, 1.0),
            align=TextNode.ARight,
            )        
        
        self._scoreLabel.hide()
        self._scoreTitle.hide()
    
    def _initCogTracker(self):
        self._cogTracker = PartyCogTrackerGui()
        
    def _initSpamWarning(self):
        self._spamWarning = OnscreenText(
            text = TTLocalizer.PartyCogGuiSpamWarning,
            scale = 0.15,
            fg = (1.0, 1.0, 0, 1.0),
            shadow = (0, 0, 0, 0.62),
            mayChange = False,
            pos = (0, 0.33)
            )
        self._spamWarning.hide()
    
    def showScore(self):
        self._scoreLabel.show()
        self._scoreTitle.show()
        
    def hideScore(self):
        self._scoreLabel.hide()
        self._scoreTitle.hide()
        
    def setScore(self, score=0):
        self._scoreLabel["text"] = str(score)
        
    def resetPiePowerMeter(self):
        self._piePowerMeter["value"] = 0
        
    def showPiePowerMeter(self):
        self._piePowerMeter.show()
        self._piePowerTitle.show()
        
    def hidePiePowerMeter(self):
        self._piePowerMeter.hide()
        self._piePowerTitle.hide()
        
    def updatePiePowerMeter(self, value):
        self._piePowerMeter["value"] = value
        
        # Uncomment this part if you want the bar to by a psychedelic change of colors.
#        self._piePowerMeter["barColor"] = (
#            min(value * 2, 100) / 100.0,
#            min((100 - value) * 2, 100) / 100.0,
#            0.0,
#            1.0
#            )
        
    def getPiePowerMeterValue(self):
        return self._piePowerMeter["value"]

    def hideSpamWarning(self):
        taskMgr.remove(self._spamWarningIvalName)
        
        if self._spamWarning:
            self._spamWarning.hide()
        
    def showSpamWarning(self):
        if self._spamWarning.isHidden():
            self._spamWarning.show()

            taskMgr.remove(self._spamWarningIvalName)
            
            Sequence(
                # Boing it in to grab attention.
                ToontownIntervals.getPulseLargerIval(self._spamWarning, ""),
                
                # Let it sit on-screen for a while.
                Wait(PartyGlobals.CogActivitySpamWarningShowTime),
                
                # Then get rid of it.
                Func(self.hideSpamWarning),
                
                name=self._spamWarningIvalName,
                autoFinish=1
                ).start()
    
    def hide(self):
        self.hidePiePowerMeter()
        self.hideScore()
        self.hideSpamWarning()
        self.hideControls()
        
    def disableToontownHUD(self):
        base.localAvatar.hideName()
        base.localAvatar.laffMeter.hide()
        base.setCellsAvailable(base.bottomCells + [base.rightCells[1]], False)
    
    def enableToontownHUD(self):
        base.localAvatar.showName()
        base.localAvatar.laffMeter.show()
        base.setCellsAvailable(base.bottomCells + [base.rightCells[1]], True)

    def setTeam(self, team):
        self.team = team
        if team == 0:
            self._cogTracker.frame.setR(180)
            
        # The old default power bar color was blue, but that was confusing because there's a blue team.
        # So the idea here is to make the color the same as the player's team.
        self._piePowerMeter["barColor"] = PartyGlobals.CogActivityColors[team]
        
    def startTrackingCogs(self, cogs):
        self.cogs = cogs
        taskMgr.add(self.trackCogs, "trackCogs")
    
    def trackCogs(self, task):
        if self.cogs is None:
            return
            
        self._updateVictoryBar()
        
        for i, cog in enumerate(self.cogs):
            self._cogTracker.updateCog(i,cog,self.team)

        return task.cont
        
    def _updateVictoryBar(self):
        if not ( hasattr(self, "_victoryBalanceBar") and self._victoryBalanceBar):
            return
        netDistance = 0
        for cog in self.cogs:
            netDistance = netDistance + cog.targetDistance
        teamDistance = netDistance/6.0
        self._victoryBalanceBarOrange.setScale(PartyGlobals.CogActivityBarStartScale + \
                                                            (teamDistance * 10 * PartyGlobals.CogActivityBarUnitScale),1.0,1.0)
        #self._victoryBalanceBar["value"] = 50 + (netDistance/6 * 100)
        self._victoryBalanceBarPie.setX(PartyGlobals.CogActivityVictoryBarPiePos[0] + \
                                                      (teamDistance * 10 *PartyGlobals.CogActivityBarPieUnitMove))         
        self._victoryBalanceBarPie.setY(PartyGlobals.CogActivityVictoryBarPiePos[1])        
        self._victoryBalanceBarPie.setZ(PartyGlobals.CogActivityVictoryBarPiePos[2])       
        
        if teamDistance>0.0:
            self._victoryBalanceBarArrow.setColor(PartyGlobals.CogActivityColors[1])
        elif teamDistance<0.0:
            self._victoryBalanceBarArrow.setColor(PartyGlobals.CogActivityColors[0])
        else:
            self._victoryBalanceBarArrow.setColor(VBase4(1.0,1.0,1.0,1.0))
        
    def stopTrackingCogs(self):
        taskMgr.remove("trackCogs")
        
    def showAttackControls(self):
        if self._attackKeys.isHidden():
            self._attackKeys.show()

            taskMgr.remove(self._attackIvalName)
            
            Sequence(
                ToontownIntervals.getPulseLargerIval(self._attackKeys, "", scale = 0.15),
                Wait(PartyGlobals.CogActivityControlsShowTime),
                Func(self.hideAttackControls),
                name=self._attackIvalName,
                autoFinish=1
                ).start()
        
        
    def showMoveControls(self):
        if self._moveKeys.isHidden() and not self._attackKeys.isHidden():
            self._moveKeys.show()

            taskMgr.remove(self._moveIvalName)
            
            Sequence(
                ToontownIntervals.getPulseLargerIval(self._moveKeys, "", scale = 0.15),
                Wait(PartyGlobals.CogActivityControlsShowTime),
                Func(self.hideMoveControls),
                name=self._moveIvalName,
                autoFinish=1
                ).start()
        
    def hideAttackControls(self):
        taskMgr.remove(self._attackIvalName)
        if hasattr(self, "_attackKeys") and self._attackKeys:
            self._attackKeys.hide()
        
    def hideMoveControls(self):
        taskMgr.remove(self._moveIvalName)
        if hasattr(self, "_moveKeys") and self._moveKeys:
            self._moveKeys.hide()
        
    def hideControls(self):
        self.hideMoveControls()
        self.hideAttackControls()
Exemplo n.º 15
0
class ToonFPSGui:

    def __init__(self, base):
        self.base = base
        self.noAmmoLabel = None
        self.ammo_gui = None
        self.hp_meter = None
        self.crosshair = None
        self.stats_container = None
        self.stats_bg = None
        self.stats_lbl = None
        self.kills_lbl = None
        self.deaths_lbl = None
        self.points_lbl = None
        return

    def load(self):
        self.ammo_gui = loader.loadModel('phase_4/models/minigames/gun_ammo_gui.egg')
        self.ammo_gui.setScale(0.15)
        self.ammo_gui.setPos(0.38, 0, 0.1)
        self.hp_meter = DirectWaitBar(text=str(self.base.hp), text_roll=-90, text_scale=0.2, text_pos=(-0.025,
                                                                                                       0), relief=DGG.RAISED, barColor=(1,
                                                                                                                                        0,
                                                                                                                                        0,
                                                                                                                                        1), range=self.base.max_hp, value=self.base.hp, parent=base.a2dBottomRight, scale=0.4, pos=(-0.12,
                                                                                                                                                                                                                                    0,
                                                                                                                                                                                                                                    0.2), frameSize=(-0.4,
                                                                                                                                                                                                                                                     0.4,
                                                                                                                                                                                                                                                     -0.2,
                                                                                                                                                                                                                                                     0.2))
        self.hp_meter.setR(-90)
        self.hp_meter.hide()
        self.crosshair = getCrosshair()
        font = CIGlobals.getToonFont()
        box = DGG.getDefaultDialogGeom()
        if self.base.__class__.__name__ == 'GunGameToonFPS':
            self.stats_container = DirectFrame(parent=base.a2dTopLeft, pos=(0.3, 0.2,
                                                                            -0.185))
            self.stats_bg = OnscreenImage(image=box, color=(1, 1, 0.75, 1), scale=(0.5,
                                                                                   0.3,
                                                                                   0.3), parent=self.stats_container)
            self.stats_lbl = OnscreenText(font=font, text='Stats', pos=(-0.01, 0.08,
                                                                        0), parent=self.stats_container)
            self.kills_lbl = OnscreenText(font=font, text='Kills: ' + str(self.base.kills), pos=(-0.235,
                                                                                                 0.025,
                                                                                                 0), scale=0.055, parent=self.stats_container, align=TextNode.ALeft)
            self.deaths_lbl = OnscreenText(font=font, text='Deaths: ' + str(self.base.deaths), pos=(-0.235,
                                                                                                    -0.04,
                                                                                                    0), scale=0.055, parent=self.stats_container, align=TextNode.ALeft)
            self.points_lbl = OnscreenText(font=font, text='Points: ' + str(self.base.points), pos=(-0.235,
                                                                                                    -0.105,
                                                                                                    0), scale=0.055, parent=self.stats_container, align=TextNode.ALeft)
            self.stats_container.hide()
            del font
            del box

    def start(self):
        self.ammo_gui.reparentTo(base.a2dBottomLeft)
        self.crosshair.show()
        self.hp_meter.show()
        if self.base.__class__.__name__ == 'GunGameToonFPS':
            self.stats_container.show()

    def end(self):
        self.ammo_gui.reparentTo(hidden)
        if self.base.__class__.__name__ == 'GunGameToonFPS':
            self.stats_container.hide()
        self.crosshair.hide()
        self.hp_meter.hide()

    def cleanup(self):
        self.ammo_gui.removeNode()
        self.ammo_gui = None
        self.hp_meter.destroy()
        self.hp_meter = None
        self.crosshair.destroy()
        self.crosshair = None
        self.deleteNoAmmoLabel()
        self.deleteStatsGui()
        return

    def deleteStatsGui(self):
        if self.stats_container:
            self.stats_container.destroy()
            self.stats_container = None
        if self.stats_bg:
            self.stats_bg.destroy()
            self.stats_bg = None
        if self.stats_lbl:
            self.stats_lbl.destroy()
            self.stats_lbl = None
        if self.kills_lbl:
            self.kills_lbl.destroy()
            self.kills_lbl = None
        if self.deaths_lbl:
            self.deaths_lbl.destroy()
            self.deaths_lbl = None
        if self.points_lbl:
            self.points_lbl.destroy()
            self.points_lbl = None
        return

    def updateStats(self):
        self.kills_lbl['text'] = 'Kills: ' + str(self.base.kills)
        self.deaths_lbl['text'] = 'Deaths: ' + str(self.base.deaths)
        self.points_lbl['text'] = 'Points: ' + str(self.base.points)

    def deleteNoAmmoLabel(self):
        if self.noAmmoLabel:
            self.noAmmoLabel.destroy()
            self.noAmmoLabel = None
        return

    def adjustAmmoGui(self):
        self.ammo_gui.find('**/bar_' + str(self.base.ammo + 1)).hide()

    def adjustHpMeter(self):
        self.hp_meter['text'] = str(self.base.hp)
        self.hp_meter['value'] = self.base.hp
        if self.base.hp <= 40:
            self.hp_meter['barColor'] = (1, 0, 0, 1)
        else:
            self.hp_meter['barColor'] = (1, 1, 1, 1)

    def resetAmmo(self):
        for bar in self.ammo_gui.findAllMatches('**/bar_*'):
            bar.show()

    def notifyNoAmmo(self):
        self.deleteNoAmmoLabel()
        self.noAmmoLabel = DirectLabel(text='Press R to reload!', relief=None, text_scale=0.1, text_pos=(0,
                                                                                                         0.5,
                                                                                                         0), text_fg=(1,
                                                                                                                      1,
                                                                                                                      1,
                                                                                                                      1), text_shadow=(0,
                                                                                                                                       0,
                                                                                                                                       0,
                                                                                                                                       1))
        return
class DistributedBanquetTable(DistributedObject.DistributedObject, FSM.FSM, BanquetTableBase.BanquetTableBase):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedBanquetTable')
    rotationsPerSeatIndex = [90,
     90,
     0,
     0,
     -90,
     -90,
     180,
     180]
    pitcherMinH = -360
    pitcherMaxH = 360
    rotateSpeed = 30
    waterPowerSpeed = base.config.GetDouble('water-power-speed', 15)
    waterPowerExponent = base.config.GetDouble('water-power-exponent', 0.75)
    useNewAnimations = True
    TugOfWarControls = False
    OnlyUpArrow = True
    if OnlyUpArrow:
        BASELINE_KEY_RATE = 3
    else:
        BASELINE_KEY_RATE = 6
    UPDATE_KEY_PRESS_RATE_TASK = 'BanquetTableUpdateKeyPressRateTask'
    YELLOW_POWER_THRESHOLD = 0.75
    RED_POWER_THRESHOLD = 0.97

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        FSM.FSM.__init__(self, 'DistributedBanquetTable')
        self.boss = None
        self.index = -1
        self.diners = {}
        self.dinerStatus = {}
        self.serviceLocs = {}
        self.chairLocators = {}
        self.sitLocators = {}
        self.activeIntervals = {}
        self.dinerStatusIndicators = {}
        self.preparedForPhaseFour = False
        self.avId = 0
        self.toon = None
        self.pitcherSmoother = SmoothMover()
        self.pitcherSmoother.setSmoothMode(SmoothMover.SMOn)
        self.smoothStarted = 0
        self.__broadcastPeriod = 0.2
        self.changeSeq = 0
        self.lastChangeSeq = 0
        self.pitcherAdviceLabel = None
        self.fireLength = 250
        self.fireTrack = None
        self.hitObject = None
        self.setupPowerBar()
        self.aimStart = None
        self.toonPitcherPosition = Point3(0, -2, 0)
        self.allowLocalRequestControl = True
        self.fadeTrack = None
        self.grabTrack = None
        self.gotHitByBoss = False
        self.keyTTL = []
        self.keyRate = 0
        self.buttons = [0, 1]
        self.lastPowerFired = 0
        self.moveSound = None
        self.releaseTrack = None
        return

    def disable(self):
        DistributedObject.DistributedObject.disable(self)
        taskMgr.remove(self.triggerName)
        taskMgr.remove(self.smoothName)
        taskMgr.remove(self.watchControlsName)
        taskMgr.remove(self.pitcherAdviceName)
        taskMgr.remove(self.posHprBroadcastName)
        taskMgr.remove(self.waterPowerTaskName)
        if self.releaseTrack:
            self.releaseTrack.finish()
            self.releaseTrack = None
        if self.fireTrack:
            self.fireTrack.finish()
            self.fireTrack = None
        self.cleanupIntervals()
        return

    def delete(self):
        DistributedObject.DistributedObject.delete(self)
        self.boss = None
        self.ignoreAll()
        for indicator in list(self.dinerStatusIndicators.values()):
            indicator.delete()

        self.dinerStatusIndicators = {}
        for diner in list(self.diners.values()):
            diner.delete()

        self.diners = {}
        self.powerBar.destroy()
        self.powerBar = None
        self.pitcherMoveSfx.stop()
        return

    def announceGenerate(self):
        DistributedObject.DistributedObject.announceGenerate(self)
        self.loadAssets()
        self.smoothName = self.uniqueName('pitcherSmooth')
        self.pitcherAdviceName = self.uniqueName('pitcherAdvice')
        self.posHprBroadcastName = self.uniqueName('pitcherBroadcast')
        self.waterPowerTaskName = self.uniqueName('updateWaterPower')
        self.triggerName = self.uniqueName('trigger')
        self.watchControlsName = self.uniqueName('watchControls')

    def setBossCogId(self, bossCogId):
        self.bossCogId = bossCogId
        self.boss = base.cr.doId2do[bossCogId]
        self.boss.setTable(self, self.index)

    def setIndex(self, index):
        self.index = index

    def setState(self, state, avId, extraInfo):
        self.gotHitByBoss = extraInfo
        if state == 'F':
            self.demand('Off')
        elif state == 'N':
            self.demand('On')
        elif state == 'I':
            self.demand('Inactive')
        elif state == 'R':
            self.demand('Free')
        elif state == 'C':
            self.demand('Controlled', avId)
        elif state == 'L':
            self.demand('Flat', avId)
        else:
            self.notify.error('Invalid state from AI: %s' % state)

    def setNumDiners(self, numDiners):
        self.numDiners = numDiners

    def setDinerInfo(self, hungryDurations, eatingDurations, dinerLevels):
        self.dinerInfo = {}
        for i in range(len(hungryDurations)):
            hungryDur = hungryDurations[i]
            eatingDur = eatingDurations[i]
            dinerLevel = dinerLevels[i]
            self.dinerInfo[i] = (hungryDur, eatingDur, dinerLevel)

    def loadAssets(self):
        self.tableGroup = loader.loadModel('phase_12/models/bossbotHQ/BanquetTableChairs')
        tableLocator = self.boss.geom.find('**/TableLocator_%d' % (self.index + 1))
        if tableLocator.isEmpty():
            self.tableGroup.reparentTo(render)
            self.tableGroup.setPos(0, 75, 0)
        else:
            self.tableGroup.reparentTo(tableLocator)
        self.tableGeom = self.tableGroup.find('**/Geometry')
        self.setupDiners()
        self.setupChairCols()
        self.squirtSfx = loader.loadSfx('phase_4/audio/sfx/AA_squirt_seltzer_miss.ogg')
        self.hitBossSfx = loader.loadSfx('phase_5/audio/sfx/SA_watercooler_spray_only.ogg')
        self.hitBossSoundInterval = SoundInterval(self.hitBossSfx, node=self.boss, volume=1.0)
        self.serveFoodSfx = loader.loadSfx('phase_4/audio/sfx/MG_sfx_travel_game_bell_for_trolley.ogg')
        self.pitcherMoveSfx = base.loader.loadSfx('phase_4/audio/sfx/MG_cannon_adjust.ogg')

    def setupDiners(self):
        for i in range(self.numDiners):
            newDiner = self.createDiner(i)
            self.diners[i] = newDiner
            self.dinerStatus[i] = self.HUNGRY

    def createDiner(self, i):
        diner = Suit.Suit()
        diner.dna = SuitDNA.SuitDNA()
        level = self.dinerInfo[i][2]
        level -= 4
        diner.dna.newSuitRandom(level=level, dept='c')
        diner.setDNA(diner.dna)
        diner.nametag.setNametag2d(None)
        diner.nametag.setNametag3d(None)
        if self.useNewAnimations:
            diner.loop('sit', fromFrame=i)
        else:
            diner.pose('landing', 0)
        locator = self.tableGroup.find('**/chair_%d' % (i + 1))
        locatorScale = locator.getNetTransform().getScale()[0]
        correctHeadingNp = locator.attachNewNode('correctHeading')
        self.chairLocators[i] = correctHeadingNp
        heading = self.rotationsPerSeatIndex[i]
        correctHeadingNp.setH(heading)
        sitLocator = correctHeadingNp.attachNewNode('sitLocator')
        base.sitLocator = sitLocator
        pos = correctHeadingNp.getPos(render)
        if SuitDNA.getSuitBodyType(diner.dna.name) == 'c':
            sitLocator.setPos(0.5, 3.65, -3.75)
        else:
            sitLocator.setZ(-2.4)
            sitLocator.setY(2.5)
            sitLocator.setX(0.5)
        self.sitLocators[i] = sitLocator
        diner.setScale(1.0 / locatorScale)
        diner.reparentTo(sitLocator)
        newLoc = NodePath('serviceLoc-%d-%d' % (self.index, i))
        newLoc.reparentTo(correctHeadingNp)
        newLoc.setPos(0, 3.0, 1)
        self.serviceLocs[i] = newLoc
        base.serviceLoc = newLoc
        head = diner.find('**/joint_head')
        newIndicator = DinerStatusIndicator.DinerStatusIndicator(parent=head, pos=Point3(0, 0, 3.5), scale=5.0)
        newIndicator.wrtReparentTo(diner)
        self.dinerStatusIndicators[i] = newIndicator
        return diner

    def setupChairCols(self):
        for i in range(self.numDiners):
            chairCol = self.tableGroup.find('**/collision_chair_%d' % (i + 1))
            colName = 'ChairCol-%d-%d' % (self.index, i)
            chairCol.setTag('chairIndex', str(i))
            chairCol.setName(colName)
            chairCol.setCollideMask(ToontownGlobals.WallBitmask)
            self.accept('enter' + colName, self.touchedChair)

    def touchedChair(self, colEntry):
        chairIndex = int(colEntry.getIntoNodePath().getTag('chairIndex'))
        if chairIndex in self.dinerStatus:
            status = self.dinerStatus[chairIndex]
            if status in (self.HUNGRY, self.ANGRY):
                self.boss.localToonTouchedChair(self.index, chairIndex)

    def serveFood(self, food, chairIndex):
        self.removeFoodModel(chairIndex)
        serviceLoc = self.serviceLocs.get(chairIndex)
        if not food or food.isEmpty():
            foodModel = loader.loadModel('phase_12/models/bossbotHQ/canoffood')
            foodModel.setScale(ToontownGlobals.BossbotFoodModelScale)
            foodModel.reparentTo(serviceLoc)
        else:
            food.wrtReparentTo(serviceLoc)
            tray = food.find('**/tray')
            if not tray.isEmpty():
                tray.hide()
            ivalDuration = 1.5
            foodMoveIval = Parallel(SoundInterval(self.serveFoodSfx, node=food), ProjectileInterval(food, duration=ivalDuration, startPos=food.getPos(serviceLoc), endPos=serviceLoc.getPos(serviceLoc)), LerpHprInterval(food, ivalDuration, Point3(0, -360, 0)))
            intervalName = 'serveFood-%d-%d' % (self.index, chairIndex)
            foodMoveIval.start()
            self.activeIntervals[intervalName] = foodMoveIval

    def setDinerStatus(self, chairIndex, status):
        if chairIndex in self.dinerStatus:
            oldStatus = self.dinerStatus[chairIndex]
            self.dinerStatus[chairIndex] = status
            if oldStatus != status:
                if status == self.EATING:
                    self.changeDinerToEating(chairIndex)
                elif status == self.HUNGRY:
                    self.changeDinerToHungry(chairIndex)
                elif status == self.ANGRY:
                    self.changeDinerToAngry(chairIndex)
                elif status == self.DEAD:
                    self.changeDinerToDead(chairIndex)
                elif status == self.HIDDEN:
                    self.changeDinerToHidden(chairIndex)

    def removeFoodModel(self, chairIndex):
        serviceLoc = self.serviceLocs.get(chairIndex)
        if serviceLoc:
            for i in range(serviceLoc.getNumChildren()):
                serviceLoc.getChild(0).removeNode()

    def changeDinerToEating(self, chairIndex):
        indicator = self.dinerStatusIndicators.get(chairIndex)
        eatingDuration = self.dinerInfo[chairIndex][1]
        if indicator:
            indicator.request('Eating', eatingDuration)
        diner = self.diners[chairIndex]
        intervalName = 'eating-%d-%d' % (self.index, chairIndex)
        eatInTime = 32.0 / 24.0
        eatOutTime = 21.0 / 24.0
        eatLoopTime = 19 / 24.0
        rightHand = diner.getRightHand()
        waitTime = 5
        loopDuration = eatingDuration - eatInTime - eatOutTime - waitTime
        serviceLoc = self.serviceLocs[chairIndex]

        def foodAttach(self = self, diner = diner):
            if self.serviceLocs[chairIndex].getNumChildren() < 1:
                return
            foodModel = self.serviceLocs[chairIndex].getChild(0)
            (foodModel.reparentTo(diner.getRightHand()),)
            (foodModel.setHpr(Point3(0, -94, 0)),)
            (foodModel.setPos(Point3(-0.15, -0.7, -0.4)),)
            scaleAdj = 1
            if SuitDNA.getSuitBodyType(diner.dna.name) == 'c':
                scaleAdj = 0.6
                (foodModel.setPos(Point3(0.1, -0.25, -0.31)),)
            else:
                scaleAdj = 0.8
                (foodModel.setPos(Point3(-0.25, -0.85, -0.34)),)
            oldScale = foodModel.getScale()
            newScale = oldScale * scaleAdj
            foodModel.setScale(newScale)

        def foodDetach(self = self, diner = diner):
            if diner.getRightHand().getNumChildren() < 1:
                return
            foodModel = diner.getRightHand().getChild(0)
            (foodModel.reparentTo(serviceLoc),)
            (foodModel.setPosHpr(0, 0, 0, 0, 0, 0),)
            scaleAdj = 1
            if SuitDNA.getSuitBodyType(diner.dna.name) == 'c':
                scaleAdj = 0.6
            else:
                scakeAdj = 0.8
            oldScale = foodModel.getScale()
            newScale = oldScale / scaleAdj
            foodModel.setScale(newScale)

        eatIval = Sequence(ActorInterval(diner, 'sit', duration=waitTime), ActorInterval(diner, 'sit-eat-in', startFrame=0, endFrame=6), Func(foodAttach), ActorInterval(diner, 'sit-eat-in', startFrame=6, endFrame=32), ActorInterval(diner, 'sit-eat-loop', duration=loopDuration, loop=1), ActorInterval(diner, 'sit-eat-out', startFrame=0, endFrame=12), Func(foodDetach), ActorInterval(diner, 'sit-eat-out', startFrame=12, endFrame=21))
        eatIval.start()
        self.activeIntervals[intervalName] = eatIval

    def changeDinerToHungry(self, chairIndex):
        intervalName = 'eating-%d-%d' % (self.index, chairIndex)
        if intervalName in self.activeIntervals:
            self.activeIntervals[intervalName].finish()
        self.removeFoodModel(chairIndex)
        indicator = self.dinerStatusIndicators.get(chairIndex)
        if indicator:
            indicator.request('Hungry', self.dinerInfo[chairIndex][0])
        diner = self.diners[chairIndex]
        if random.choice([0, 1]):
            diner.loop('sit-hungry-left')
        else:
            diner.loop('sit-hungry-right')

    def changeDinerToAngry(self, chairIndex):
        self.removeFoodModel(chairIndex)
        indicator = self.dinerStatusIndicators.get(chairIndex)
        if indicator:
            indicator.request('Angry')
        diner = self.diners[chairIndex]
        diner.loop('sit-angry')

    def changeDinerToDead(self, chairIndex):

        def removeDeathSuit(suit, deathSuit):
            if not deathSuit.isEmpty():
                deathSuit.detachNode()
                suit.cleanupLoseActor()

        self.removeFoodModel(chairIndex)
        indicator = self.dinerStatusIndicators.get(chairIndex)
        if indicator:
            indicator.request('Dead')
        diner = self.diners[chairIndex]
        deathSuit = diner
        locator = self.tableGroup.find('**/chair_%d' % (chairIndex + 1))
        deathSuit = diner.getLoseActor()
        ival = Sequence(Func(self.notify.debug, 'before actorinterval sit-lose'), ActorInterval(diner, 'sit-lose'), Func(self.notify.debug, 'before deathSuit.setHpr'), Func(deathSuit.setHpr, diner.getHpr()), Func(self.notify.debug, 'before diner.hide'), Func(diner.hide), Func(self.notify.debug, 'before deathSuit.reparentTo'), Func(deathSuit.reparentTo, self.chairLocators[chairIndex]), Func(self.notify.debug, 'befor ActorInterval lose'), ActorInterval(deathSuit, 'lose', duration=MovieUtil.SUIT_LOSE_DURATION), Func(self.notify.debug, 'before remove deathsuit'), Func(removeDeathSuit, diner, deathSuit, name='remove-death-suit-%d-%d' % (chairIndex, self.index)), Func(self.notify.debug, 'diner.stash'), Func(diner.stash))
        spinningSound = base.loader.loadSfx('phase_3.5/audio/sfx/Cog_Death.ogg')
        deathSound = base.loader.loadSfx('phase_3.5/audio/sfx/ENC_cogfall_apart.ogg')
        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))
        intervalName = 'dinerDie-%d-%d' % (self.index, chairIndex)
        deathIval = Parallel(ival, deathSoundTrack)
        deathIval.start()
        self.activeIntervals[intervalName] = deathIval

    def changeDinerToHidden(self, chairIndex):
        self.removeFoodModel(chairIndex)
        indicator = self.dinerStatusIndicators.get(chairIndex)
        if indicator:
            indicator.request('Inactive')
        diner = self.diners[chairIndex]
        diner.hide()

    def setAllDinersToSitNeutral(self):
        startFrame = 0
        for diner in list(self.diners.values()):
            if not diner.isHidden():
                diner.loop('sit', fromFrame=startFrame)
                startFrame += 1

    def cleanupIntervals(self):
        for interval in list(self.activeIntervals.values()):
            interval.finish()

        self.activeIntervals = {}

    def clearInterval(self, name, finish = 1):
        if name in self.activeIntervals:
            ival = self.activeIntervals[name]
            if finish:
                ival.finish()
            else:
                ival.pause()
            if name in self.activeIntervals:
                del self.activeIntervals[name]
        else:
            self.notify.debug('interval: %s already cleared' % name)

    def finishInterval(self, name):
        if name in self.activeIntervals:
            interval = self.activeIntervals[name]
            interval.finish()

    def getNotDeadInfo(self):
        notDeadList = []
        for i in range(self.numDiners):
            if self.dinerStatus[i] != self.DEAD:
                notDeadList.append((self.index, i, 12))

        return notDeadList

    def enterOn(self):
        pass

    def exitOn(self):
        pass

    def enterInactive(self):
        for chairIndex in range(self.numDiners):
            indicator = self.dinerStatusIndicators.get(chairIndex)
            if indicator:
                indicator.request('Inactive')
            self.removeFoodModel(chairIndex)

    def exitInactive(self):
        pass

    def enterFree(self):
        self.resetPowerBar()
        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None
        self.prepareForPhaseFour()
        if self.avId == localAvatar.doId:
            self.tableGroup.setAlphaScale(0.3)
            self.tableGroup.setTransparency(1)
            taskMgr.doMethodLater(5, self.__allowDetect, self.triggerName)
            self.fadeTrack = Sequence(Func(self.tableGroup.setTransparency, 1), self.tableGroup.colorScaleInterval(0.2, VBase4(1, 1, 1, 0.3)))
            self.fadeTrack.start()
            self.allowLocalRequestControl = False
        else:
            self.allowLocalRequestControl = True
        self.avId = 0
        return

    def exitFree(self):
        pass

    def touchedTable(self, colEntry):
        tableIndex = int(colEntry.getIntoNodePath().getTag('tableIndex'))
        if self.state == 'Free' and self.avId == 0 and self.allowLocalRequestControl:
            self.d_requestControl()

    def prepareForPhaseFour(self):
        if not self.preparedForPhaseFour:
            for i in range(8):
                chair = self.tableGroup.find('**/chair_%d' % (i + 1))
                if not chair.isEmpty():
                    chair.hide()
                colChairs = self.tableGroup.findAllMatches('**/ChairCol*')
                for i in range(colChairs.getNumPaths()):
                    col = colChairs.getPath(i)
                    col.stash()

                colChairs = self.tableGroup.findAllMatches('**/collision_chair*')
                for i in range(colChairs.getNumPaths()):
                    col = colChairs.getPath(i)
                    col.stash()

            tableCol = self.tableGroup.find('**/collision_table')
            colName = 'TableCol-%d' % self.index
            tableCol.setTag('tableIndex', str(self.index))
            tableCol.setName(colName)
            tableCol.setCollideMask(ToontownGlobals.WallBitmask | ToontownGlobals.BanquetTableBitmask)
            self.accept('enter' + colName, self.touchedTable)
            self.preparedForPhaseFour = True
            self.waterPitcherModel = loader.loadModel('phase_12/models/bossbotHQ/tt_m_ara_bhq_seltzerBottle')
            lampNode = self.tableGroup.find('**/lamp_med_5')
            pos = lampNode.getPos(self.tableGroup)
            lampNode.hide()
            bottleLocator = self.tableGroup.find('**/bottle_locator')
            pos = bottleLocator.getPos(self.tableGroup)
            self.waterPitcherNode = self.tableGroup.attachNewNode('pitcherNode')
            self.waterPitcherNode.setPos(pos)
            self.waterPitcherModel.reparentTo(self.waterPitcherNode)
            self.nozzle = self.waterPitcherModel.find('**/nozzle_tip')
            self.handLocator = self.waterPitcherModel.find('**/hand_locator')
            self.handPos = self.handLocator.getPos()

    def d_requestControl(self):
        self.sendUpdate('requestControl')

    def d_requestFree(self, gotHitByBoss):
        self.sendUpdate('requestFree', [gotHitByBoss])

    def enterControlled(self, avId):
        self.prepareForPhaseFour()
        self.avId = avId
        toon = base.cr.doId2do.get(avId)
        if not toon:
            return
        self.toon = toon
        self.grabTrack = self.makeToonGrabInterval(toon)
        self.notify.debug('grabTrack=%s' % self.grabTrack)
        self.pitcherCamPos = Point3(0, -50, 40)
        self.pitcherCamHpr = Point3(0, -21, 0)
        if avId == localAvatar.doId:
            self.boss.toMovieMode()
            self.__enableControlInterface()
            self.startPosHprBroadcast()
            self.grabTrack = Sequence(self.grabTrack, Func(camera.wrtReparentTo, localAvatar), LerpPosHprInterval(camera, 1, self.pitcherCamPos, self.pitcherCamHpr), Func(self.boss.toCraneMode))
            if self.TugOfWarControls:
                self.__spawnUpdateKeyPressRateTask()
            self.accept('exitCrane', self.gotBossZapped)
        else:
            self.startSmooth()
            toon.stopSmooth()
        self.grabTrack.start()

    def exitControlled(self):
        self.ignore('exitCrane')
        if self.grabTrack:
            self.grabTrack.finish()
            self.grabTrack = None
        nextState = self.getCurrentOrNextState()
        self.notify.debug('nextState=%s' % nextState)
        if nextState == 'Flat':
            place = base.cr.playGame.getPlace()
            self.notify.debug('%s' % place.fsm)
            if self.avId == localAvatar.doId:
                self.__disableControlInterface()
        else:
            if self.toon and not self.toon.isDisabled():
                self.toon.loop('neutral')
                self.toon.startSmooth()
            self.releaseTrack = self.makeToonReleaseInterval(self.toon)
            self.stopPosHprBroadcast()
            self.stopSmooth()
            if self.avId == localAvatar.doId:
                localAvatar.wrtReparentTo(render)
                self.__disableControlInterface()
                camera.reparentTo(base.localAvatar)
                camera.setPos(base.localAvatar.cameraPositions[0][0])
                camera.setHpr(0, 0, 0)
                self.goToFinalBattle()
                self.safeBossToFinalBattleMode()
            else:
                toon = base.cr.doId2do.get(self.avId)
                if toon:
                    toon.wrtReparentTo(render)
            self.releaseTrack.start()
        return

    def safeBossToFinalBattleMode(self):
        if self.boss:
            self.boss.toFinalBattleMode()

    def goToFinalBattle(self):
        if self.cr:
            place = self.cr.playGame.getPlace()
            if place and hasattr(place, 'fsm'):
                if place.fsm.getCurrentState().getName() == 'crane':
                    place.setState('finalBattle')

    def makeToonGrabInterval(self, toon):
        toon.pose('leverNeutral', 0)
        toon.update()
        rightHandPos = toon.rightHand.getPos(toon)
        self.toonPitcherPosition = Point3(self.handPos[0] - rightHandPos[0], self.handPos[1] - rightHandPos[1], 0)
        destZScale = rightHandPos[2] / self.handPos[2]
        grabIval = Sequence(Func(toon.wrtReparentTo, self.waterPitcherNode), Func(toon.loop, 'neutral'), Parallel(ActorInterval(toon, 'jump'), Sequence(Wait(0.43), Parallel(ProjectileInterval(toon, duration=0.9, startPos=toon.getPos(self.waterPitcherNode), endPos=self.toonPitcherPosition), LerpHprInterval(toon, 0.9, Point3(0, 0, 0)), LerpScaleInterval(self.waterPitcherModel, 0.9, Point3(1, 1, destZScale))))), Func(toon.setPos, self.toonPitcherPosition), Func(toon.loop, 'leverNeutral'))
        return grabIval

    def makeToonReleaseInterval(self, toon):
        temp1 = self.waterPitcherNode.attachNewNode('temp1')
        temp1.setPos(self.toonPitcherPosition)
        temp2 = self.waterPitcherNode.attachNewNode('temp2')
        temp2.setPos(0, -10, -self.waterPitcherNode.getZ())
        startPos = temp1.getPos(render)
        endPos = temp2.getPos(render)
        temp1.removeNode()
        temp2.removeNode()

        def getSlideToPos(toon = toon):
            return render.getRelativePoint(toon, Point3(0, -10, 0))

        if self.gotHitByBoss:
            self.notify.debug('creating zap interval instead')
            grabIval = Sequence(Func(toon.loop, 'neutral'), Func(toon.wrtReparentTo, render), Parallel(ActorInterval(toon, 'slip-backward'), toon.posInterval(0.5, getSlideToPos, fluid=1)))
        else:
            grabIval = Sequence(Func(toon.loop, 'neutral'), Func(toon.wrtReparentTo, render), Parallel(ActorInterval(toon, 'jump'), Sequence(Wait(0.43), ProjectileInterval(toon, duration=0.9, startPos=startPos, endPos=endPos))))
        return grabIval

    def b_clearSmoothing(self):
        self.d_clearSmoothing()
        self.clearSmoothing()

    def d_clearSmoothing(self):
        self.sendUpdate('clearSmoothing', [0])

    def clearSmoothing(self, bogus = None):
        self.pitcherSmoother.clearPositions(1)

    def doSmoothTask(self, task):
        self.pitcherSmoother.computeAndApplySmoothHpr(self.waterPitcherNode)
        return Task.cont

    def startSmooth(self):
        if not self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.reloadPosition()
            taskMgr.add(self.doSmoothTask, taskName)
            self.smoothStarted = 1

    def stopSmooth(self):
        if self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.forceToTruePosition()
            self.smoothStarted = 0

    def __enableControlInterface(self):
        gui = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui')
        self.closeButton = DirectButton(image=(gui.find('**/CloseBtn_UP'),
         gui.find('**/CloseBtn_DN'),
         gui.find('**/CloseBtn_Rllvr'),
         gui.find('**/CloseBtn_UP')), relief=None, scale=2, text=TTLocalizer.BossbotPitcherLeave, text_scale=0.04, text_pos=(0, -0.07), text_fg=VBase4(1, 1, 1, 1), pos=(1.05, 0, -0.82), command=self.__exitPitcher)
        self.accept('escape', self.__exitPitcher)
        self.accept(base.JUMP, 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)
        self.accept(base.MOVE_UP, self.__upArrowKeyPressed)
        self.accept(base.MOVE_DOWN, self.__downArrowKeyPressed)
        taskMgr.add(self.__watchControls, self.watchControlsName)
        taskMgr.doMethodLater(5, self.__displayPitcherAdvice, self.pitcherAdviceName)
        self.arrowVert = 0
        self.arrowHorz = 0
        self.powerBar.show()
        return

    def __disableControlInterface(self):
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = None
        self.__cleanupPitcherAdvice()
        self.ignore('escape')
        self.ignore(base.JUMP)
        self.ignore('control-up')
        self.ignore('InputState-forward')
        self.ignore('InputState-reverse')
        self.ignore('InputState-turnLeft')
        self.ignore('InputState-turnRight')
        self.ignore(base.MOVE_UP)
        self.ignore(base.MOVE_DOWN)
        self.arrowVert = 0
        self.arrowHorz = 0
        taskMgr.remove(self.watchControlsName)
        taskMgr.remove(self.waterPowerTaskName)
        self.resetPowerBar()
        self.aimStart = None
        self.powerBar.hide()
        if self.TugOfWarControls:
            self.__killUpdateKeyPressRateTask()
            self.keyTTL = []
        self.__setMoveSound(None)
        return

    def __displayPitcherAdvice(self, task):
        if self.pitcherAdviceLabel == None:
            self.pitcherAdviceLabel = DirectLabel(text=TTLocalizer.BossbotPitcherAdvice, text_fg=VBase4(1, 1, 1, 1), text_align=TextNode.ACenter, relief=None, pos=(0, 0, 0.69), scale=0.1)
        return

    def __cleanupPitcherAdvice(self):
        if self.pitcherAdviceLabel:
            self.pitcherAdviceLabel.destroy()
            self.pitcherAdviceLabel = None
        taskMgr.remove(self.pitcherAdviceName)
        return

    def showExiting(self):
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = DirectLabel(relief=None, text=TTLocalizer.BossbotPitcherLeaving, pos=(1.05, 0, -0.88), text_pos=(0, 0), text_scale=0.06, text_fg=VBase4(1, 1, 1, 1))
        self.__cleanupPitcherAdvice()
        return

    def __exitPitcher(self):
        self.showExiting()
        self.d_requestFree(False)

    def __controlPressed(self):
        self.__cleanupPitcherAdvice()
        if self.TugOfWarControls:
            if self.power:
                self.aimStart = 1
                self.__endFireWater()
        elif self.state == 'Controlled':
            self.__beginFireWater()

    def __controlReleased(self):
        if self.TugOfWarControls:
            pass
        elif self.state == 'Controlled':
            self.__endFireWater()

    def __upArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupPitcherAdvice()
        if pressed:
            self.arrowVert = 1
        elif self.arrowVert > 0:
            self.arrowVert = 0

    def __downArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupPitcherAdvice()
        if pressed:
            self.arrowVert = -1
        elif self.arrowVert < 0:
            self.arrowVert = 0

    def __rightArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupPitcherAdvice()
        if pressed:
            self.arrowHorz = 1
        elif self.arrowHorz > 0:
            self.arrowHorz = 0

    def __leftArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupPitcherAdvice()
        if pressed:
            self.arrowHorz = -1
        elif self.arrowHorz < 0:
            self.arrowHorz = 0

    def __incrementChangeSeq(self):
        self.changeSeq = self.changeSeq + 1 & 255

    def stopPosHprBroadcast(self):
        taskName = self.posHprBroadcastName
        taskMgr.remove(taskName)

    def startPosHprBroadcast(self):
        taskName = self.posHprBroadcastName
        self.b_clearSmoothing()
        self.d_sendPitcherPos()
        taskMgr.remove(taskName)
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast, taskName)

    def __posHprBroadcast(self, task):
        self.d_sendPitcherPos()
        taskName = self.posHprBroadcastName
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast, taskName)
        return Task.done

    def d_sendPitcherPos(self):
        timestamp = globalClockDelta.getFrameNetworkTime()
        self.sendUpdate('setPitcherPos', [self.changeSeq, self.waterPitcherNode.getH(), timestamp])

    def setPitcherPos(self, changeSeq, h, timestamp):
        self.changeSeq = changeSeq
        if self.smoothStarted:
            now = globalClock.getFrameTime()
            local = globalClockDelta.networkToLocalTime(timestamp, now)
            self.pitcherSmoother.setH(h)
            self.pitcherSmoother.setTimestamp(local)
            self.pitcherSmoother.markPosition()
        else:
            self.waterPitcherNode.setH(h)

    def __watchControls(self, task):
        if self.arrowHorz:
            self.__movePitcher(self.arrowHorz)
        else:
            self.__setMoveSound(None)
        return Task.cont

    def __movePitcher(self, xd):
        dt = globalClock.getDt()
        h = self.waterPitcherNode.getH() - xd * self.rotateSpeed * dt
        h %= 360
        self.notify.debug('rotSpeed=%.2f curH=%.2f  xd =%.2f, dt = %.2f, h=%.2f' % (self.rotateSpeed,
         self.waterPitcherNode.getH(),
         xd,
         dt,
         h))
        limitH = h
        self.waterPitcherNode.setH(limitH)
        if xd:
            self.__setMoveSound(self.pitcherMoveSfx)

    def reloadPosition(self):
        self.pitcherSmoother.clearPositions(0)
        self.pitcherSmoother.setHpr(self.waterPitcherNode.getHpr())
        self.pitcherSmoother.setPhonyTimestamp()

    def forceToTruePosition(self):
        if self.pitcherSmoother.getLatestPosition():
            self.pitcherSmoother.applySmoothHpr(self.waterPitcherNode)
        self.pitcherSmoother.clearPositions(1)

    def getSprayTrack(self, color, origin, target, dScaleUp, dHold, dScaleDown, horizScale = 1.0, vertScale = 1.0, parent = render):
        track = Sequence()
        SPRAY_LEN = 1.5
        sprayProp = MovieUtil.globalPropPool.getProp('spray')
        sprayScale = hidden.attachNewNode('spray-parent')
        sprayRot = hidden.attachNewNode('spray-rotate')
        spray = sprayRot
        spray.setColor(color)
        if color[3] < 1.0:
            spray.setTransparency(1)

        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(showSpray, sprayScale, sprayRot, sprayProp, origin, target, parent))

        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 * horizScale, yScale, yScale * vertScale)
            return targetScale

        track.append(LerpScaleInterval(sprayScale, dScaleUp, calcTargetScale, startScale=Point3(0.01, 0.01, 0.01)))
        track.append(Func(self.checkHitObject))
        track.append(Wait(dHold))

        def prepareToShrinkSpray(spray, sprayProp, origin, target):
            if callable(target):
                target = target()
            if callable(origin):
                origin = origin()
            sprayProp.setPos(Point3(0.0, -SPRAY_LEN, 0.0))
            spray.setPos(target)

        track.append(Func(prepareToShrinkSpray, spray, sprayProp, origin, target))
        track.append(LerpScaleInterval(sprayScale, dScaleDown, Point3(0.01, 0.01, 0.01)))

        def hideSpray(spray, sprayScale, sprayRot, sprayProp, propPool):
            sprayProp.detachNode()
            MovieUtil.removeProp(sprayProp)
            sprayRot.removeNode()
            sprayScale.removeNode()

        track.append(Func(hideSpray, spray, sprayScale, sprayRot, sprayProp, MovieUtil.globalPropPool))
        return track

    def checkHitObject(self):
        if not self.hitObject:
            return
        if self.avId != base.localAvatar.doId:
            return
        tag = self.hitObject.getNetTag('pieCode')
        pieCode = int(tag)
        if pieCode == ToontownGlobals.PieCodeBossCog:
            self.hitBossSoundInterval.start()
            self.sendUpdate('waterHitBoss', [self.index])
            if self.TugOfWarControls:
                damage = 1
                if self.lastPowerFired < self.YELLOW_POWER_THRESHOLD:
                    damage = 1
                elif self.lastPowerFired < self.RED_POWER_THRESHOLD:
                    damage = 2
                else:
                    damage = 3
                self.boss.d_hitBoss(damage)
            else:
                damage = 1
                if self.lastPowerFired < self.YELLOW_POWER_THRESHOLD:
                    damage = 1
                elif self.lastPowerFired < self.RED_POWER_THRESHOLD:
                    damage = 2
                else:
                    damage = 3
                self.boss.d_hitBoss(damage)

    def waterHitBoss(self, tableIndex):
        if self.index == tableIndex:
            self.hitBossSoundInterval.start()

    def setupPowerBar(self):
        self.powerBar = DirectWaitBar(pos=(0.0, 0, -0.94), relief=DGG.SUNKEN, frameSize=(-2.0,
         2.0,
         -0.2,
         0.2), borderWidth=(0.02, 0.02), scale=0.25, range=1, sortOrder=50, frameColor=(0.5, 0.5, 0.5, 0.5), barColor=(0.75, 0.75, 1.0, 0.8), text='', text_scale=0.26, text_fg=(1, 1, 1, 1), text_align=TextNode.ACenter, text_pos=(0, -0.05))
        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar.hide()

    def resetPowerBar(self):
        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar['text'] = ''
        self.keyTTL = []

    def __beginFireWater(self):
        if self.fireTrack and self.fireTrack.isPlaying():
            return
        if self.aimStart != None:
            return
        if not self.state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        time = globalClock.getFrameTime()
        self.aimStart = time
        messenger.send('wakeup')
        taskMgr.add(self.__updateWaterPower, self.waterPowerTaskName)
        return

    def __endFireWater(self):
        if self.aimStart == None:
            return
        if not self.state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        taskMgr.remove(self.waterPowerTaskName)
        messenger.send('wakeup')
        self.aimStart = None
        origin = self.nozzle.getPos(render)
        target = self.boss.getPos(render)
        angle = deg2Rad(self.waterPitcherNode.getH() + 90)
        x = math.cos(angle)
        y = math.sin(angle)
        fireVector = Point3(x, y, 0)
        if self.power < 0.001:
            self.power = 0.001
        self.lastPowerFired = self.power
        fireVector *= self.fireLength * self.power
        target = origin + fireVector
        segment = CollisionSegment(origin[0], origin[1], origin[2], target[0], target[1], target[2])
        fromObject = render.attachNewNode(CollisionNode('pitcherColNode'))
        fromObject.node().addSolid(segment)
        fromObject.node().setFromCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.CameraBitmask | ToontownGlobals.FloorBitmask)
        fromObject.node().setIntoCollideMask(BitMask32.allOff())
        queue = CollisionHandlerQueue()
        base.cTrav.addCollider(fromObject, queue)
        base.cTrav.traverse(render)
        queue.sortEntries()
        self.hitObject = None
        if queue.getNumEntries():
            entry = queue.getEntry(0)
            target = entry.getSurfacePoint(render)
            self.hitObject = entry.getIntoNodePath()
        base.cTrav.removeCollider(fromObject)
        fromObject.removeNode()
        self.d_firingWater(origin, target)
        self.fireWater(origin, target)
        self.resetPowerBar()
        return

    def __updateWaterPower(self, task):
        if not self.powerBar:
            print('### no power bar!!!')
            return task.done
        newPower = self.__getWaterPower(globalClock.getFrameTime())
        self.power = newPower
        self.powerBar['value'] = newPower
        if self.power < self.YELLOW_POWER_THRESHOLD:
            self.powerBar['barColor'] = VBase4(0.75, 0.75, 1.0, 0.8)
        elif self.power < self.RED_POWER_THRESHOLD:
            self.powerBar['barColor'] = VBase4(1.0, 1.0, 0.0, 0.8)
        else:
            self.powerBar['barColor'] = VBase4(1.0, 0.0, 0.0, 0.8)
        return task.cont

    def __getWaterPower(self, time):
        elapsed = max(time - self.aimStart, 0.0)
        t = elapsed / self.waterPowerSpeed
        exponent = self.waterPowerExponent
        if t > 1:
            t = t % 1
        power = 1 - math.pow(1 - t, exponent)
        if power > 1.0:
            power = 1.0
        return power

    def d_firingWater(self, origin, target):
        self.sendUpdate('firingWater', [origin[0],
         origin[1],
         origin[2],
         target[0],
         target[1],
         target[2]])

    def firingWater(self, startX, startY, startZ, endX, endY, endZ):
        origin = Point3(startX, startY, startZ)
        target = Point3(endX, endY, endZ)
        self.fireWater(origin, target)

    def fireWater(self, origin, target):
        color = VBase4(0.75, 0.75, 1, 0.8)
        dScaleUp = 0.1
        dHold = 0.3
        dScaleDown = 0.1
        horizScale = 0.1
        vertScale = 0.1
        sprayTrack = self.getSprayTrack(color, origin, target, dScaleUp, dHold, dScaleDown, horizScale, vertScale)
        duration = self.squirtSfx.length()
        if sprayTrack.getDuration() < duration:
            duration = sprayTrack.getDuration()
        soundTrack = SoundInterval(self.squirtSfx, node=self.waterPitcherModel, duration=duration)
        self.fireTrack = Parallel(sprayTrack, soundTrack)
        self.fireTrack.start()

    def getPos(self, wrt = render):
        return self.tableGroup.getPos(wrt)

    def getLocator(self):
        return self.tableGroup

    def enterFlat(self, avId):
        self.prepareForPhaseFour()
        self.resetPowerBar()
        self.notify.debug('enterFlat %d' % self.index)
        if self.avId:
            toon = base.cr.doId2do.get(self.avId)
            if toon:
                toon.wrtReparentTo(render)
                toon.setZ(0)
        self.tableGroup.setScale(1, 1, 0.01)
        if self.avId and self.avId == localAvatar.doId:
            localAvatar.b_squish(ToontownGlobals.BossCogDamageLevels[ToontownGlobals.BossCogMoveAttack])

    def exitFlat(self):
        self.tableGroup.setScale(1.0)
        if self.avId:
            toon = base.cr.doId2do.get(self.avId)
            if toon:
                if toon == localAvatar:
                    self.boss.toCraneMode()
                    toon.b_setAnimState('neutral')
                toon.setAnimState('neutral')
                toon.loop('leverNeutral')

    def __allowDetect(self, task):
        if self.fadeTrack:
            self.fadeTrack.finish()
        self.fadeTrack = Sequence(self.tableGroup.colorScaleInterval(0.2, VBase4(1, 1, 1, 1)), Func(self.tableGroup.clearColorScale), Func(self.tableGroup.clearTransparency))
        self.fadeTrack.start()
        self.allowLocalRequestControl = True

    def gotBossZapped(self):
        self.showExiting()
        self.d_requestFree(True)

    def __upArrowKeyPressed(self):
        if self.TugOfWarControls:
            self.__pressHandler(0)

    def __downArrowKeyPressed(self):
        if self.TugOfWarControls:
            self.__pressHandler(1)

    def __pressHandler(self, index):
        if index == self.buttons[0]:
            self.keyTTL.insert(0, 1.0)
            if not self.OnlyUpArrow:
                self.buttons.reverse()

    def __spawnUpdateKeyPressRateTask(self):
        taskMgr.remove(self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK))
        taskMgr.doMethodLater(0.1, self.__updateKeyPressRateTask, self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK))

    def __killUpdateKeyPressRateTask(self):
        taskMgr.remove(self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK))

    def __updateKeyPressRateTask(self, task):
        if self.state not in 'Controlled':
            return Task.done
        for i in range(len(self.keyTTL)):
            self.keyTTL[i] -= 0.1

        for i in range(len(self.keyTTL)):
            if self.keyTTL[i] <= 0:
                a = self.keyTTL[0:i]
                del self.keyTTL
                self.keyTTL = a
                break

        self.keyRate = len(self.keyTTL)
        keyRateDiff = self.keyRate - self.BASELINE_KEY_RATE
        diffPower = keyRateDiff / 300.0
        if self.power < 1 and diffPower > 0:
            diffPower = diffPower * math.pow(1 - self.power, 1.25)
        newPower = self.power + diffPower
        if newPower > 1:
            newPower = 1
        elif newPower < 0:
            newPower = 0
        self.notify.debug('diffPower=%.2f keyRate = %d, newPower=%.2f' % (diffPower, self.keyRate, newPower))
        self.power = newPower
        self.powerBar['value'] = newPower
        if self.power < self.YELLOW_POWER_THRESHOLD:
            self.powerBar['barColor'] = VBase4(0.75, 0.75, 1.0, 0.8)
        elif self.power < self.RED_POWER_THRESHOLD:
            self.powerBar['barColor'] = VBase4(1.0, 1.0, 0.0, 0.8)
        else:
            self.powerBar['barColor'] = VBase4(1.0, 0.0, 0.0, 0.8)
        self.__spawnUpdateKeyPressRateTask()
        return Task.done

    def __setMoveSound(self, sfx):
        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)
class DistributedMazeGame(DistributedMinigame):
    notify = directNotify.newCategory('DistributedMazeGame')
    CAMERA_TASK = 'MazeGameCameraTask'
    UPDATE_SUITS_TASK = 'MazeGameUpdateSuitsTask'
    TREASURE_GRAB_EVENT_NAME = 'MazeTreasureGrabbed'

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedMazeGame', [
            State.State('off', self.enterOff, self.exitOff, ['play']),
            State.State('play', self.enterPlay, self.exitPlay,
                        ['cleanup', 'showScores']),
            State.State('showScores', self.enterShowScores,
                        self.exitShowScores, ['cleanup']),
            State.State('cleanup', self.enterCleanup, self.exitCleanup, [])
        ], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.usesLookAround = 1

    def getTitle(self):
        return TTLocalizer.MazeGameTitle

    def getInstructions(self):
        return TTLocalizer.MazeGameInstructions

    def getMaxDuration(self):
        return MazeGameGlobals.GAME_DURATION

    def __defineConstants(self):
        self.TOON_SPEED = 8.0
        self.TOON_Z = 0
        self.MinSuitSpeedRange = [0.8 * self.TOON_SPEED, 0.6 * self.TOON_SPEED]
        self.MaxSuitSpeedRange = [1.1 * self.TOON_SPEED, 2.0 * self.TOON_SPEED]
        self.FASTER_SUIT_CURVE = 1
        self.SLOWER_SUIT_CURVE = self.getDifficulty() < 0.5
        self.slowerSuitPeriods = {
            2000: {
                4: [128, 76],
                8: [128, 99, 81, 68],
                12: [128, 108, 93, 82, 74, 67],
                16: [128, 112, 101, 91, 83, 76, 71, 66]
            },
            1000: {
                4: [110, 69],
                8: [110, 88, 73, 62],
                12: [110, 95, 83, 74, 67, 61],
                16: [110, 98, 89, 81, 75, 69, 64, 60]
            },
            5000: {
                4: [96, 63],
                8: [96, 79, 66, 57],
                12: [96, 84, 75, 67, 61, 56],
                16: [96, 87, 80, 73, 68, 63, 59, 55]
            },
            4000: {
                4: [86, 58],
                8: [86, 71, 61, 53],
                12: [86, 76, 68, 62, 56, 52],
                16: [86, 78, 72, 67, 62, 58, 54, 51]
            },
            3000: {
                4: [78, 54],
                8: [78, 65, 56, 49],
                12: [78, 69, 62, 57, 52, 48],
                16: [78, 71, 66, 61, 57, 54, 51, 48]
            },
            9000: {
                4: [71, 50],
                8: [71, 60, 52, 46],
                12: [71, 64, 58, 53, 49, 45],
                16: [71, 65, 61, 57, 53, 50, 47, 45]
            }
        }
        self.slowerSuitPeriodsCurve = {
            2000: {
                4: [128, 65],
                8: [128, 78, 66, 64],
                12: [128, 88, 73, 67, 64, 64],
                16: [128, 94, 79, 71, 67, 65, 64, 64]
            },
            1000: {
                4: [110, 59],
                8: [110, 70, 60, 58],
                12: [110, 78, 66, 61, 59, 58],
                16: [110, 84, 72, 65, 61, 59, 58, 58]
            },
            5000: {
                4: [96, 55],
                8: [96, 64, 56, 54],
                12: [96, 71, 61, 56, 54, 54],
                16: [96, 76, 65, 59, 56, 55, 54, 54]
            },
            4000: {
                4: [86, 51],
                8: [86, 59, 52, 50],
                12: [86, 65, 56, 52, 50, 50],
                16: [86, 69, 60, 55, 52, 51, 50, 50]
            },
            3000: {
                4: [78, 47],
                8: [78, 55, 48, 47],
                12: [78, 60, 52, 48, 47, 47],
                16: [78, 63, 55, 51, 49, 47, 47, 47]
            },
            9000: {
                4: [71, 44],
                8: [71, 51, 45, 44],
                12: [71, 55, 48, 45, 44, 44],
                16: [71, 58, 51, 48, 45, 44, 44, 44]
            }
        }
        self.fasterSuitPeriods = {
            2000: {
                4: [54, 42],
                8: [59, 52, 47, 42],
                12: [61, 56, 52, 48, 45, 42],
                16: [61, 58, 54, 51, 49, 46, 44, 42]
            },
            1000: {
                4: [50, 40],
                8: [55, 48, 44, 40],
                12: [56, 52, 48, 45, 42, 40],
                16: [56, 53, 50, 48, 45, 43, 41, 40]
            },
            5000: {
                4: [47, 37],
                8: [51, 45, 41, 37],
                12: [52, 48, 45, 42, 39, 37],
                16: [52, 49, 47, 44, 42, 40, 39, 37]
            },
            4000: {
                4: [44, 35],
                8: [47, 42, 38, 35],
                12: [48, 45, 42, 39, 37, 35],
                16: [49, 46, 44, 42, 40, 38, 37, 35]
            },
            3000: {
                4: [41, 33],
                8: [44, 40, 36, 33],
                12: [45, 42, 39, 37, 35, 33],
                16: [45, 43, 41, 39, 38, 36, 35, 33]
            },
            9000: {
                4: [39, 32],
                8: [41, 37, 34, 32],
                12: [42, 40, 37, 35, 33, 32],
                16: [43, 41, 39, 37, 35, 34, 33, 32]
            }
        }
        self.fasterSuitPeriodsCurve = {
            2000: {
                4: [62, 42],
                8: [63, 61, 54, 42],
                12: [63, 63, 61, 56, 50, 42],
                16: [63, 63, 62, 60, 57, 53, 48, 42]
            },
            1000: {
                4: [57, 40],
                8: [58, 56, 50, 40],
                12: [58, 58, 56, 52, 46, 40],
                16: [58, 58, 57, 56, 53, 49, 45, 40]
            },
            5000: {
                4: [53, 37],
                8: [54, 52, 46, 37],
                12: [54, 53, 52, 48, 43, 37],
                16: [54, 54, 53, 51, 49, 46, 42, 37]
            },
            4000: {
                4: [49, 35],
                8: [50, 48, 43, 35],
                12: [50, 49, 48, 45, 41, 35],
                16: [50, 50, 49, 48, 46, 43, 39, 35]
            },
            3000: {
                4: [46, 33],
                8: [47, 45, 41, 33],
                12: [47, 46, 45, 42, 38, 33],
                16: [47, 46, 46, 45, 43, 40, 37, 33]
            },
            9000: {
                4: [43, 32],
                8: [44, 42, 38, 32],
                12: [44, 43, 42, 40, 36, 32],
                16: [44, 44, 43, 42, 40, 38, 35, 32]
            }
        }
        self.CELL_WIDTH = MazeData.CELL_WIDTH
        self.MAX_FRAME_MOVE = self.CELL_WIDTH / 2
        startOffset = 3
        self.startPosHTable = [[Point3(0, startOffset, self.TOON_Z), 0],
                               [Point3(0, -startOffset, self.TOON_Z), 180],
                               [Point3(startOffset, 0, self.TOON_Z), 270],
                               [Point3(-startOffset, 0, self.TOON_Z), 90]]
        self.camOffset = Vec3(0, -19, 45)

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.__defineConstants()
        mazeName = MazeGameGlobals.getMazeName(self.doId, self.numPlayers,
                                               MazeData.mazeNames)
        self.maze = Maze.Maze(mazeName)
        model = loader.loadModel('phase_3.5/models/props/mickeySZ')
        self.treasureModel = model.find('**/mickeySZ')
        model.removeNode()
        self.treasureModel.setScale(1.6)
        self.treasureModel.setP(-90)
        self.music = base.loader.loadMusic('phase_4/audio/bgm/MG_toontag.ogg')
        self.toonHitTracks = {}
        self.scorePanels = []

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        del self.toonHitTracks
        self.maze.destroy()
        del self.maze
        self.treasureModel.removeNode()
        del self.treasureModel
        del self.music
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.maze.onstage()
        self.randomNumGen.shuffle(self.startPosHTable)
        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        self.camParent = render.attachNewNode('mazeGameCamParent')
        self.camParent.reparentTo(base.localAvatar)
        self.camParent.setPos(0, 0, 0)
        self.camParent.setHpr(render, 0, 0, 0)
        camera.reparentTo(self.camParent)
        camera.setPos(self.camOffset)
        self.__spawnCameraTask()
        self.toonRNGs = []
        for i in range(self.numPlayers):
            self.toonRNGs.append(RandomNumGen.RandomNumGen(self.randomNumGen))

        self.treasures = []
        for i in range(self.maze.numTreasures):
            self.treasures.append(
                MazeTreasure.MazeTreasure(self.treasureModel,
                                          self.maze.treasurePosList[i], i,
                                          self.doId))

        self.__loadSuits()
        for suit in self.suits:
            suit.onstage()

        self.sndTable = {
            'hitBySuit': [None] * self.numPlayers,
            'falling': [None] * self.numPlayers
        }
        for i in range(self.numPlayers):
            self.sndTable['hitBySuit'][i] = base.loader.loadSfx(
                'phase_4/audio/sfx/MG_Tag_C.ogg')
            self.sndTable['falling'][i] = base.loader.loadSfx(
                'phase_4/audio/sfx/MG_cannon_whizz.ogg')

        self.grabSounds = []
        for i in range(5):
            self.grabSounds.append(
                base.loader.loadSfx('phase_4/audio/sfx/MG_maze_pickup.ogg'))

        self.grabSoundIndex = 0
        for avId in self.avIdList:
            self.toonHitTracks[avId] = Wait(0.1)

        self.scores = [0] * self.numPlayers
        self.goalBar = DirectWaitBar(parent=render2d,
                                     relief=DGG.SUNKEN,
                                     frameSize=(-0.35, 0.35, -0.15, 0.15),
                                     borderWidth=(0.02, 0.02),
                                     scale=0.42,
                                     pos=(0.84, 0,
                                          0.5 - 0.28 * self.numPlayers + 0.05),
                                     barColor=(0, 0.7, 0, 1))
        self.goalBar.setBin('unsorted', 0)
        self.goalBar.hide()
        self.introTrack = self.getIntroTrack()
        self.introTrack.start()
        return

    def offstage(self):
        self.notify.debug('offstage')
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        del self.introTrack
        for avId in list(self.toonHitTracks.keys()):
            track = self.toonHitTracks[avId]
            if track.isPlaying():
                track.finish()

        self.__killCameraTask()
        camera.wrtReparentTo(render)
        self.camParent.removeNode()
        del self.camParent
        for panel in self.scorePanels:
            panel.cleanup()

        self.scorePanels = []
        self.goalBar.destroy()
        del self.goalBar
        base.setCellsAvailable(base.rightCells, 1)
        for suit in self.suits:
            suit.offstage()

        self.__unloadSuits()
        for treasure in self.treasures:
            treasure.destroy()

        del self.treasures
        del self.sndTable
        del self.grabSounds
        del self.toonRNGs
        self.maze.offstage()
        base.localAvatar.showName()
        DistributedMinigame.offstage(self)

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if self.numPlayers == 1:
            toon.setPos(0, 0, self.TOON_Z)
            toon.setHpr(180, 0, 0)
        else:
            posIndex = self.avIdList.index(avId)
            toon.setPos(self.startPosHTable[posIndex][0])
            toon.setHpr(self.startPosHTable[posIndex][1], 0, 0)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self.__placeToon(avId)
                toon.setAnimState('Happy', 1.0)
                toon.startSmooth()
                toon.startLookAround()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.stopLookAround()

        self.gameFSM.request('play')

    def handleDisabledAvatar(self, avId):
        hitTrack = self.toonHitTracks[avId]
        if hitTrack.isPlaying():
            hitTrack.finish()
        DistributedMinigame.handleDisabledAvatar(self, avId)

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        for i in range(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = MinigameAvatarScorePanel.MinigameAvatarScorePanel(
                avId, avName)
            scorePanel.setPos(1.12, 0.0, 0.5 - 0.28 * i)
            self.scorePanels.append(scorePanel)

        self.goalBar.show()
        self.goalBar['value'] = 0.0
        base.setCellsAvailable(base.rightCells, 0)
        self.__spawnUpdateSuitsTask()
        orthoDrive = OrthoDrive(
            self.TOON_SPEED,
            maxFrameMove=self.MAX_FRAME_MOVE,
            customCollisionCallback=self.__doMazeCollisions,
            priority=1)
        self.orthoWalk = OrthoWalk(orthoDrive,
                                   broadcast=not self.isSinglePlayer())
        self.orthoWalk.start()
        self.accept(MazeSuit.COLLISION_EVENT_NAME, self.__hitBySuit)
        self.accept(self.TREASURE_GRAB_EVENT_NAME, self.__treasureGrabbed)
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(MazeGameGlobals.GAME_DURATION)
        self.timer.countdown(MazeGameGlobals.GAME_DURATION, self.timerExpired)
        self.accept('resetClock', self.__resetClock)
        base.playMusic(self.music, looping=0, volume=0.8)

    def exitPlay(self):
        self.notify.debug('exitPlay')
        self.ignore('resetClock')
        self.ignore(MazeSuit.COLLISION_EVENT_NAME)
        self.ignore(self.TREASURE_GRAB_EVENT_NAME)
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        self.__killUpdateSuitsTask()
        self.timer.stop()
        self.timer.destroy()
        del self.timer
        for avId in self.avIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.loop('neutral')

    def __resetClock(self, tOffset):
        self.notify.debug('resetClock')
        self.gameStartTime += tOffset
        self.timer.countdown(self.timer.currentTime + tOffset,
                             self.timerExpired)

    def __treasureGrabbed(self, treasureNum):
        self.treasures[treasureNum].showGrab()
        self.grabSounds[self.grabSoundIndex].play()
        self.grabSoundIndex = (self.grabSoundIndex + 1) % len(self.grabSounds)
        self.sendUpdate('claimTreasure', [treasureNum])

    def setTreasureGrabbed(self, avId, treasureNum):
        if not self.hasLocalToon:
            return
        if avId != self.localAvId:
            self.treasures[treasureNum].showGrab()
        i = self.avIdList.index(avId)
        self.scores[i] += 1
        self.scorePanels[i].setScore(self.scores[i])
        total = 0
        for score in self.scores:
            total += score

        self.goalBar['value'] = 100.0 * (float(total) /
                                         float(self.maze.numTreasures))

    def __hitBySuit(self, suitNum):
        self.notify.debug('hitBySuit')
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime())
        self.sendUpdate('hitBySuit', [self.localAvId, timestamp])
        self.__showToonHitBySuit(self.localAvId, timestamp)

    def hitBySuit(self, avId, timestamp):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in [
                'play', 'showScores'
        ]:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        self.notify.debug('avatar ' + repr(avId) + ' hit by a suit')
        if avId != self.localAvId:
            self.__showToonHitBySuit(avId, timestamp)

    def __showToonHitBySuit(self, avId, timestamp):
        toon = self.getAvatar(avId)
        if toon == None:
            return
        rng = self.toonRNGs[self.avIdList.index(avId)]
        curPos = toon.getPos(render)
        oldTrack = self.toonHitTracks[avId]
        if oldTrack.isPlaying():
            oldTrack.finish()
        toon.setPos(curPos)
        toon.setZ(self.TOON_Z)
        parentNode = render.attachNewNode('mazeFlyToonParent-' + repr(avId))
        parentNode.setPos(toon.getPos())
        toon.reparentTo(parentNode)
        toon.setPos(0, 0, 0)
        startPos = parentNode.getPos()
        dropShadow = toon.dropShadow.copyTo(parentNode)
        dropShadow.setScale(toon.dropShadow.getScale(render))
        trajectory = Trajectory.Trajectory(0,
                                           Point3(0, 0, 0),
                                           Point3(0, 0, 50),
                                           gravMult=1.0)
        flyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        while 1:
            endTile = [
                rng.randint(2, self.maze.width - 1),
                rng.randint(2, self.maze.height - 1)
            ]
            if self.maze.isWalkable(endTile[0], endTile[1]):
                break
        endWorldCoords = self.maze.tile2world(endTile[0], endTile[1])
        endPos = Point3(endWorldCoords[0], endWorldCoords[1], startPos[2])

        def flyFunc(t,
                    trajectory,
                    startPos=startPos,
                    endPos=endPos,
                    dur=flyDur,
                    moveNode=parentNode,
                    flyNode=toon):
            u = t / dur
            moveNode.setX(startPos[0] + u * (endPos[0] - startPos[0]))
            moveNode.setY(startPos[1] + u * (endPos[1] - startPos[1]))
            flyNode.setPos(trajectory.getPos(t))

        flyTrack = Sequence(LerpFunctionInterval(flyFunc,
                                                 fromData=0.0,
                                                 toData=flyDur,
                                                 duration=flyDur,
                                                 extraArgs=[trajectory]),
                            name=toon.uniqueName('hitBySuit-fly'))
        if avId != self.localAvId:
            cameraTrack = Sequence()
        else:
            self.camParent.reparentTo(parentNode)
            startCamPos = camera.getPos()
            destCamPos = camera.getPos()
            zenith = trajectory.getPos(flyDur / 2.0)[2]
            destCamPos.setZ(zenith * 1.3)
            destCamPos.setY(destCamPos[1] * 0.3)

            def camTask(task,
                        zenith=zenith,
                        flyNode=toon,
                        startCamPos=startCamPos,
                        camOffset=destCamPos - startCamPos):
                u = flyNode.getZ() / zenith
                camera.setPos(startCamPos + camOffset * u)
                camera.lookAt(toon)
                return Task.cont

            camTaskName = 'mazeToonFlyCam-' + repr(avId)
            taskMgr.add(camTask, camTaskName, priority=20)

            def cleanupCamTask(self=self,
                               toon=toon,
                               camTaskName=camTaskName,
                               startCamPos=startCamPos):
                taskMgr.remove(camTaskName)
                self.camParent.reparentTo(toon)
                camera.setPos(startCamPos)
                camera.lookAt(toon)

            cameraTrack = Sequence(Wait(flyDur),
                                   Func(cleanupCamTask),
                                   name='hitBySuit-cameraLerp')

        geomNode = toon.getGeomNode()
        startHpr = geomNode.getHpr()
        destHpr = Point3(startHpr)
        hRot = rng.randrange(1, 8)
        if rng.choice([0, 1]):
            hRot = -hRot
        destHpr.setX(destHpr[0] + hRot * 360)
        spinHTrack = Sequence(LerpHprInterval(geomNode,
                                              flyDur,
                                              destHpr,
                                              startHpr=startHpr),
                              Func(geomNode.setHpr, startHpr),
                              name=toon.uniqueName('hitBySuit-spinH'))
        parent = geomNode.getParent()
        rotNode = parent.attachNewNode('rotNode')
        geomNode.reparentTo(rotNode)
        rotNode.setZ(toon.getHeight() / 2.0)
        oldGeomNodeZ = geomNode.getZ()
        geomNode.setZ(-toon.getHeight() / 2.0)
        startHpr = rotNode.getHpr()
        destHpr = Point3(startHpr)
        pRot = rng.randrange(1, 3)
        if rng.choice([0, 1]):
            pRot = -pRot
        destHpr.setY(destHpr[1] + pRot * 360)
        spinPTrack = Sequence(LerpHprInterval(rotNode,
                                              flyDur,
                                              destHpr,
                                              startHpr=startHpr),
                              Func(rotNode.setHpr, startHpr),
                              name=toon.uniqueName('hitBySuit-spinP'))
        i = self.avIdList.index(avId)
        soundTrack = Sequence(Func(base.playSfx,
                                   self.sndTable['hitBySuit'][i]),
                              Wait(flyDur * (2.0 / 3.0)),
                              SoundInterval(self.sndTable['falling'][i],
                                            duration=flyDur * (1.0 / 3.0)),
                              name=toon.uniqueName('hitBySuit-soundTrack'))

        def preFunc(self=self, avId=avId, toon=toon, dropShadow=dropShadow):
            forwardSpeed = toon.forwardSpeed
            rotateSpeed = toon.rotateSpeed
            if avId == self.localAvId:
                self.orthoWalk.stop()
            else:
                toon.stopSmooth()
            if forwardSpeed or rotateSpeed:
                toon.setSpeed(forwardSpeed, rotateSpeed)
            toon.dropShadow.hide()

        def postFunc(self=self,
                     avId=avId,
                     oldGeomNodeZ=oldGeomNodeZ,
                     dropShadow=dropShadow,
                     parentNode=parentNode):
            if avId == self.localAvId:
                base.localAvatar.setPos(endPos)
                if hasattr(self, 'orthoWalk'):
                    if self.gameFSM.getCurrentState().getName() == 'play':
                        self.orthoWalk.start()
            dropShadow.removeNode()
            del dropShadow
            toon.dropShadow.show()
            geomNode = toon.getGeomNode()
            rotNode = geomNode.getParent()
            baseNode = rotNode.getParent()
            geomNode.reparentTo(baseNode)
            rotNode.removeNode()
            del rotNode
            geomNode.setZ(oldGeomNodeZ)
            toon.reparentTo(render)
            toon.setPos(endPos)
            parentNode.removeNode()
            del parentNode
            if avId != self.localAvId:
                toon.startSmooth()

        preFunc()

        hitTrack = Sequence(Parallel(flyTrack, cameraTrack, spinHTrack,
                                     spinPTrack, soundTrack),
                            Func(postFunc),
                            name=toon.uniqueName('hitBySuit'))

        self.toonHitTracks[avId] = hitTrack

        hitTrack.start(globalClockDelta.localElapsedTime(timestamp))

    def allTreasuresTaken(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('all treasures taken')
        if not MazeGameGlobals.ENDLESS_GAME:
            self.gameFSM.request('showScores')

    def timerExpired(self):
        self.notify.debug('local timer expired')
        if not MazeGameGlobals.ENDLESS_GAME:
            self.gameFSM.request('showScores')

    def __doMazeCollisions(self, oldPos, newPos):
        offset = newPos - oldPos
        WALL_OFFSET = 1.0
        curX = oldPos[0]
        curY = oldPos[1]
        curTX, curTY = self.maze.world2tile(curX, curY)

        def calcFlushCoord(curTile, newTile, centerTile):
            EPSILON = 0.01
            if newTile > curTile:
                return (newTile -
                        centerTile) * self.CELL_WIDTH - EPSILON - WALL_OFFSET
            else:
                return (curTile - centerTile) * self.CELL_WIDTH + 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
        newX = curX + offsetX + WALL_OFFSET_X
        newY = curY
        newTX, newTY = self.maze.world2tile(newX, newY)
        if newTX != curTX:
            if self.maze.collisionTable[newTY][newTX]:
                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:
            if self.maze.collisionTable[newTY][newTX]:
                offset.setY(
                    calcFlushCoord(curTY, newTY, self.maze.originTY) - curY)
        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]:
            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

    def __spawnCameraTask(self):
        self.notify.debug('spawnCameraTask')
        camera.lookAt(base.localAvatar)
        taskMgr.remove(self.CAMERA_TASK)
        taskMgr.add(self.__cameraTask, self.CAMERA_TASK, priority=45)

    def __killCameraTask(self):
        self.notify.debug('killCameraTask')
        taskMgr.remove(self.CAMERA_TASK)

    def __cameraTask(self, task):
        self.camParent.setHpr(render, 0, 0, 0)
        return Task.cont

    def __loadSuits(self):
        self.notify.debug('loadSuits')
        self.suits = []
        self.numSuits = 4 * self.numPlayers
        safeZone = self.getSafezoneId()
        slowerTable = self.slowerSuitPeriods
        if self.SLOWER_SUIT_CURVE:
            slowerTable = self.slowerSuitPeriodsCurve
        slowerPeriods = slowerTable[safeZone][self.numSuits]
        fasterTable = self.fasterSuitPeriods
        if self.FASTER_SUIT_CURVE:
            fasterTable = self.fasterSuitPeriodsCurve
        fasterPeriods = fasterTable[safeZone][self.numSuits]
        suitPeriods = slowerPeriods + fasterPeriods
        self.notify.debug('suit periods: ' + repr(suitPeriods))
        self.randomNumGen.shuffle(suitPeriods)
        for i in range(self.numSuits):
            self.suits.append(
                MazeSuit(i, self.maze, self.randomNumGen, suitPeriods[i],
                         self.getDifficulty()))

    def __unloadSuits(self):
        self.notify.debug('unloadSuits')
        for suit in self.suits:
            suit.destroy()

        del self.suits

    def __spawnUpdateSuitsTask(self):
        self.notify.debug('spawnUpdateSuitsTask')
        for suit in self.suits:
            suit.gameStart(self.gameStartTime)

        taskMgr.remove(self.UPDATE_SUITS_TASK)
        taskMgr.add(self.__updateSuitsTask, self.UPDATE_SUITS_TASK)

    def __killUpdateSuitsTask(self):
        self.notify.debug('killUpdateSuitsTask')
        taskMgr.remove(self.UPDATE_SUITS_TASK)
        for suit in self.suits:
            suit.gameEnd()

    def __updateSuitsTask(self, task):
        curT = globalClock.getFrameTime() - self.gameStartTime
        curTic = int(curT * float(MazeGameGlobals.SUIT_TIC_FREQ))
        suitUpdates = []
        for i in range(len(self.suits)):
            updateTics = self.suits[i].getThinkTimestampTics(curTic)
            suitUpdates.extend(list(zip(updateTics, [i] * len(updateTics))))

        suitUpdates.sort(key=functools.cmp_to_key(lambda a, b: a[0] - b[0]))
        if len(suitUpdates) > 0:
            curTic = 0
            for i in range(len(suitUpdates)):
                update = suitUpdates[i]
                tic = update[0]
                suitIndex = update[1]
                suit = self.suits[suitIndex]
                if tic > curTic:
                    curTic = tic
                    j = i + 1
                    while j < len(suitUpdates):
                        if suitUpdates[j][0] > tic:
                            break
                        self.suits[suitUpdates[j][1]].prepareToThink()
                        j += 1

                unwalkables = []
                for si in range(suitIndex):
                    unwalkables.extend(self.suits[si].occupiedTiles)

                for si in range(suitIndex + 1, len(self.suits)):
                    unwalkables.extend(self.suits[si].occupiedTiles)

                suit.think(curTic, curT, unwalkables)

        return Task.cont

    def enterShowScores(self):
        self.notify.debug('enterShowScores')
        lerpTrack = Parallel()
        lerpDur = 0.5
        lerpTrack.append(
            Parallel(
                LerpPosInterval(self.goalBar,
                                lerpDur,
                                Point3(0, 0, -.6),
                                blendType='easeInOut'),
                LerpScaleInterval(self.goalBar,
                                  lerpDur,
                                  Vec3(self.goalBar.getScale()) * 2.0,
                                  blendType='easeInOut')))
        tY = 0.6
        bY = -.05
        lX = -.5
        cX = 0
        rX = 0.5
        scorePanelLocs = (((cX, bY), ), ((lX, bY), (rX, bY)),
                          ((cX, tY), (lX, bY), (rX, bY)), ((lX, tY), (rX, tY),
                                                           (lX, bY), (rX, bY)))
        scorePanelLocs = scorePanelLocs[self.numPlayers - 1]
        for i in range(self.numPlayers):
            panel = self.scorePanels[i]
            pos = scorePanelLocs[i]
            lerpTrack.append(
                Parallel(
                    LerpPosInterval(panel,
                                    lerpDur,
                                    Point3(pos[0], 0, pos[1]),
                                    blendType='easeInOut'),
                    LerpScaleInterval(panel,
                                      lerpDur,
                                      Vec3(panel.getScale()) * 2.0,
                                      blendType='easeInOut')))

        self.showScoreTrack = Parallel(
            lerpTrack,
            Sequence(Wait(MazeGameGlobals.SHOWSCORES_DURATION),
                     Func(self.gameOver)))
        self.showScoreTrack.start()

    def exitShowScores(self):
        self.showScoreTrack.pause()
        del self.showScoreTrack

    def enterCleanup(self):
        self.notify.debug('enterCleanup')

    def exitCleanup(self):
        pass

    def getIntroTrack(self):
        self.__cameraTask(None)
        origCamParent = camera.getParent()
        origCamPos = camera.getPos()
        origCamHpr = camera.getHpr()
        iCamParent = base.localAvatar.attachNewNode('iCamParent')
        iCamParent.setH(180)
        camera.reparentTo(iCamParent)
        toonHeight = base.localAvatar.getHeight()
        camera.setPos(0, -15, toonHeight * 3)
        camera.lookAt(0, 0, toonHeight / 2.0)
        iCamParent.wrtReparentTo(origCamParent)
        waitDur = 5.0
        lerpDur = 4.5
        lerpTrack = Parallel()
        startHpr = iCamParent.getHpr()
        startHpr.setX(PythonUtil.reduceAngle(startHpr[0]))
        lerpTrack.append(
            LerpPosHprInterval(iCamParent,
                               lerpDur,
                               pos=Point3(0, 0, 0),
                               hpr=Point3(0, 0, 0),
                               startHpr=startHpr,
                               name=self.uniqueName('introLerpParent')))
        lerpTrack.append(
            LerpPosHprInterval(camera,
                               lerpDur,
                               pos=origCamPos,
                               hpr=origCamHpr,
                               blendType='easeInOut',
                               name=self.uniqueName('introLerpCameraPos')))
        base.localAvatar.startLookAround()

        def cleanup(origCamParent=origCamParent,
                    origCamPos=origCamPos,
                    origCamHpr=origCamHpr,
                    iCamParent=iCamParent):
            camera.reparentTo(origCamParent)
            camera.setPos(origCamPos)
            camera.setHpr(origCamHpr)
            iCamParent.removeNode()
            del iCamParent
            base.localAvatar.stopLookAround()

        return Sequence(Wait(waitDur), lerpTrack, Func(cleanup))
Exemplo n.º 18
0
class DistributedGolfSpot(DistributedObject.DistributedObject, FSM.FSM):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGolfSpot')
    positions = ((-45, 100, GolfGlobals.GOLF_BALL_RADIUS),
                 (-15, 100, GolfGlobals.GOLF_BALL_RADIUS),
                 (15, 100, GolfGlobals.GOLF_BALL_RADIUS),
                 (45, 100, GolfGlobals.GOLF_BALL_RADIUS))
    toonGolfOffsetPos = Point3(-2, 0, -GolfGlobals.GOLF_BALL_RADIUS)
    toonGolfOffsetHpr = Point3(-90, 0, 0)
    rotateSpeed = 20
    golfPowerSpeed = base.config.GetDouble('golf-power-speed', 3)
    golfPowerExponent = base.config.GetDouble('golf-power-exponent', 0.75)

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        FSM.FSM.__init__(self, 'DistributedGolfSpot')
        self.boss = None
        self.index = None
        self.avId = 0
        self.toon = None
        self.golfSpotSmoother = SmoothMover()
        self.golfSpotSmoother.setSmoothMode(SmoothMover.SMOn)
        self.smoothStarted = 0
        self.__broadcastPeriod = 0.2
        if self.index > len(self.positions):
            self.notify.error('Invalid index %d' % index)
        self.fadeTrack = None
        self.setupPowerBar()
        self.aimStart = None
        self.golfSpotAdviceLabel = None
        self.changeSeq = 0
        self.lastChangeSeq = 0
        self.controlKeyAllowed = False
        self.flyBallTracks = {}
        self.splatTracks = {}
        self.__flyBallBubble = None
        self.flyBallHandler = None
        self.__flyBallSequenceNum = 0
        self.swingInterval = None
        self.lastHitSequenceNum = -1
        self.goingToReward = False
        self.gotHitByBoss = False
        self.releaseTrack = None
        self.grabTrack = None
        self.restoreScaleTrack = None
        return

    def setBossCogId(self, bossCogId):
        self.bossCogId = bossCogId
        self.boss = base.cr.doId2do[bossCogId]
        self.boss.setGolfSpot(self, self.index)

    def setIndex(self, index):
        self.index = index

    def disable(self):
        DistributedObject.DistributedObject.disable(self)
        self.ignoreAll()

    def delete(self):
        DistributedObject.DistributedObject.delete(self)
        self.ignoreAll()
        self.boss = None
        return

    def announceGenerate(self):
        DistributedObject.DistributedObject.announceGenerate(self)
        self.triggerName = self.uniqueName('trigger')
        self.triggerEvent = 'enter%s' % self.triggerName
        self.smoothName = self.uniqueName('golfSpotSmooth')
        self.golfSpotAdviceName = self.uniqueName('golfSpotAdvice')
        self.posHprBroadcastName = self.uniqueName('golfSpotBroadcast')
        self.ballPowerTaskName = self.uniqueName('updateGolfPower')
        self.adjustClubTaskName = self.uniqueName('adjustClub')
        self.loadAssets()
        self.accept('flyBallHit-%d' % self.index, self.__flyBallHit)

    def loadAssets(self):
        self.root = render.attachNewNode('golfSpot-%d' % self.index)
        self.root.setPos(*self.positions[self.index])
        self.ballModel = loader.loadModel('phase_6/models/golf/golf_ball')
        self.ballColor = VBase4(1, 1, 1, 1)
        if self.index < len(GolfGlobals.PlayerColors):
            self.ballColor = VBase4(*GolfGlobals.PlayerColors[self.index])
            self.ballModel.setColorScale(self.ballColor)
        self.ballModel.reparentTo(self.root)
        self.club = loader.loadModel('phase_6/models/golf/putter')
        self.clubLookatSpot = self.root.attachNewNode('clubLookat')
        self.clubLookatSpot.setY(-(GolfGlobals.GOLF_BALL_RADIUS + 0.1))
        cs = CollisionSphere(0, 0, 0, 1)
        cs.setTangible(0)
        cn = CollisionNode(self.triggerName)
        cn.addSolid(cs)
        cn.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.trigger = self.root.attachNewNode(cn)
        self.trigger.stash()
        self.hitBallSfx = loader.loadSfx('phase_6/audio/sfx/Golf_Hit_Ball.ogg')

    def cleanup(self):
        if self.swingInterval:
            self.swingInterval.finish()
            self.swingInterval = None
        if self.releaseTrack:
            self.releaseTrack.finish()
            self.releaseTrack = None
        flyTracks = self.flyBallTracks.values()
        for track in flyTracks:
            track.finish()

        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None
        if self.restoreScaleTrack:
            self.restoreScaleTrack.finish()
            self.restoreScaleTrack = None
        self.root.removeNode()
        self.ballModel.removeNode()
        self.club.removeNode()
        if self.powerBar:
            self.powerBar.destroy()
            self.powerBar = None
        taskMgr.remove(self.triggerName)
        self.boss = None
        return

    def setState(self, state, avId, extraInfo):
        if not self.isDisabled():
            self.gotHitByBoss = extraInfo
            if state == 'C':
                self.demand('Controlled', avId)
            elif state == 'F':
                self.demand('Free')
            elif state == 'O':
                self.demand('Off')
            else:
                self.notify.error('Invalid state from AI: %s' % state)

    def enterOff(self):
        pass

    def exitOff(self):
        pass

    def enterFree(self):
        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None
        self.restoreScaleTrack = Sequence(Wait(6),
                                          self.getRestoreScaleInterval(),
                                          name='restoreScaleTrack')
        self.restoreScaleTrack.start()
        if self.avId == localAvatar.doId:
            if not self.isDisabled():
                self.ballModel.setAlphaScale(0.3)
                self.ballModel.setTransparency(1)
                taskMgr.doMethodLater(5, self.__allowDetect, self.triggerName)
                self.fadeTrack = Sequence(Func(self.ballModel.setTransparency,
                                               1),
                                          self.ballModel.colorScaleInterval(
                                              0.2, VBase4(1, 1, 1, 0.3)),
                                          name='fadeTrack-enterFree')
                self.fadeTrack.start()
        else:
            self.trigger.unstash()
            self.accept(self.triggerEvent, self.__hitTrigger)
        self.avId = 0
        return

    def exitFree(self):
        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None
        self.restoreScaleTrack.finish()
        self.restoreScaleTrack = None
        taskMgr.remove(self.triggerName)
        self.ballModel.clearTransparency()
        self.trigger.stash()
        self.ignore(self.triggerEvent)
        return

    def enterControlled(self, avId):
        self.avId = avId
        toon = base.cr.doId2do.get(avId)
        if not toon:
            return
        self.enableControlKey()
        self.toon = toon
        self.grabTrack = self.makeToonGrabInterval(toon)
        if avId == localAvatar.doId:
            self.boss.toCraneMode()
            camera.reparentTo(self.root)
            camera.setPosHpr(0, -10, 3, 0, 0, 0)
            localAvatar.setPos(self.root, self.toonGolfOffsetPos)
            localAvatar.setHpr(self.root, self.toonGolfOffsetHpr)
            localAvatar.sendCurrentPosition()
            self.__enableControlInterface()
            self.startPosHprBroadcast()
            self.accept('exitCrane', self.gotBossZapped)
        self.grabTrack.start()

    def exitControlled(self):
        self.grabTrack.finish()
        del self.grabTrack
        if self.swingInterval:
            self.swingInterval.finish()
            self.swingInterval = None
        if not self.ballModel.isEmpty():
            if self.ballModel.isHidden():
                self.notify.debug('ball is hidden scale =%s' %
                                  self.ballModel.getScale())
            else:
                self.notify.debug('ball is showing scale=%s' %
                                  self.ballModel.getScale())
        if self.toon and not self.toon.isDisabled():
            self.toon.startSmooth()
        self.releaseTrack = self.makeToonReleaseInterval(self.toon)
        self.stopPosHprBroadcast()
        self.stopSmooth()
        if self.avId == localAvatar.doId:
            self.__disableControlInterface()
            if not self.goingToReward:
                camera.reparentTo(base.localAvatar)
                camera.setPos(base.localAvatar.cameraPositions[0][0])
                camera.setHpr(0, 0, 0)
        self.stopAdjustClubTask()
        self.releaseTrack.start()
        self.enableControlKey()
        return

    def __allowDetect(self, task):
        if self.fadeTrack:
            self.fadeTrack.finish()
        self.fadeTrack = Sequence(self.ballModel.colorScaleInterval(
            0.2, self.ballColor),
                                  Func(self.ballModel.clearTransparency),
                                  name='fadeTrack-allowDetect')
        self.fadeTrack.start()
        self.trigger.unstash()
        self.accept(self.triggerEvent, self.__hitTrigger)

    def __hitTrigger(self, event):
        self.d_requestControl()

    def getRestoreScaleInterval(self):
        return Sequence()

    def d_requestControl(self):
        self.sendUpdate('requestControl')

    def d_requestFree(self, gotHitByBoss):
        self.sendUpdate('requestFree', [gotHitByBoss])

    def makeToonGrabInterval(self, toon):
        origPos = toon.getPos(self.root)
        origHpr = toon.getHpr(self.root)
        a = self.accomodateToon(toon)
        newPos = toon.getPos()
        newHpr = toon.getHpr()
        origHpr.setX(PythonUtil.fitSrcAngle2Dest(origHpr[0], newHpr[0]))
        self.notify.debug('toon.setPosHpr %s %s' % (origPos, origHpr))
        toon.setPosHpr(origPos, origHpr)
        walkTime = 0.2
        reach = Sequence()
        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(toon.stopLookAround))
        if toon == base.localAvatar:
            i.append(Func(self.switchToAnimState, 'GolfPuttLoop'))
        i.append(Func(self.startAdjustClubTask))
        i = Parallel(i, a)
        return i

    def accomodateToon(self, toon):
        toon.wrtReparentTo(self.root)
        toon.setPos(self.toonGolfOffsetPos)
        toon.setHpr(self.toonGolfOffsetHpr)
        return Sequence()

    def switchToAnimState(self, animStateName, forced=False):
        curAnimState = base.localAvatar.animFSM.getCurrentState()
        curAnimStateName = ''
        if curAnimState:
            curAnimStateName = curAnimState.getName()
        if curAnimStateName != animStateName or forced:
            base.localAvatar.b_setAnimState(animStateName)

    def __enableControlInterface(self):
        gui = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui')
        self.closeButton = DirectButton(image=(gui.find('**/CloseBtn_UP'),
                                               gui.find('**/CloseBtn_DN'),
                                               gui.find('**/CloseBtn_Rllvr'),
                                               gui.find('**/CloseBtn_UP')),
                                        relief=None,
                                        scale=2,
                                        text=TTLocalizer.BossbotGolfSpotLeave,
                                        text_scale=0.04,
                                        text_pos=(0, -0.07),
                                        text_fg=VBase4(1, 1, 1, 1),
                                        pos=(1.05, 0, -0.82),
                                        command=self.__exitGolfSpot)
        self.accept('escape', self.__exitGolfSpot)
        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, 'watchGolfSpotControls')
        taskMgr.doMethodLater(5, self.__displayGolfSpotAdvice,
                              self.golfSpotAdviceName)
        self.arrowVert = 0
        self.arrowHorz = 0
        if self.powerBar:
            self.powerBar.show()
        return

    def __disableControlInterface(self):
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = None
        self.__cleanupGolfSpotAdvice()
        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
        taskMgr.remove('watchGolfSpotControls')
        if self.powerBar:
            self.powerBar.hide()
        else:
            self.notify.debug('self.powerBar is none')
        return

    def setupPowerBar(self):
        self.powerBar = DirectWaitBar(pos=(0.0, 0, -0.94),
                                      relief=DGG.SUNKEN,
                                      frameSize=(-2.0, 2.0, -0.2, 0.2),
                                      borderWidth=(0.02, 0.02),
                                      scale=0.25,
                                      range=100,
                                      sortOrder=50,
                                      frameColor=(0.5, 0.5, 0.5, 0.5),
                                      barColor=(1.0, 0.0, 0.0, 1.0),
                                      text='',
                                      text_scale=0.26,
                                      text_fg=(1, 1, 1, 1),
                                      text_align=TextNode.ACenter,
                                      text_pos=(0, -0.05))
        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar.hide()

    def resetPowerBar(self):
        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar['text'] = ''

    def __displayGolfSpotAdvice(self, task):
        if self.golfSpotAdviceLabel == None:
            self.golfSpotAdviceLabel = DirectLabel(
                text=TTLocalizer.BossbotGolfSpotAdvice,
                text_fg=VBase4(1, 1, 1, 1),
                text_align=TextNode.ACenter,
                relief=None,
                pos=(0, 0, 0.69),
                scale=0.1)
        return

    def __cleanupGolfSpotAdvice(self):
        if self.golfSpotAdviceLabel:
            self.golfSpotAdviceLabel.destroy()
            self.golfSpotAdviceLabel = None
        taskMgr.remove(self.golfSpotAdviceName)
        return

    def showExiting(self):
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = DirectLabel(
                relief=None,
                text=TTLocalizer.BossbotGolfSpotLeaving,
                pos=(1.05, 0, -0.88),
                text_pos=(0, 0),
                text_scale=0.06,
                text_fg=VBase4(1, 1, 1, 1))
        self.__cleanupGolfSpotAdvice()
        return

    def __exitGolfSpot(self):
        self.d_requestFree(False)

    def __controlPressed(self):
        if self.controlKeyAllowed:
            self.__beginFireBall()

    def __controlReleased(self):
        if self.controlKeyAllowed:
            self.__endFireBall()

    def __upArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowVert = 1
        elif self.arrowVert > 0:
            self.arrowVert = 0

    def __downArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowVert = -1
        elif self.arrowVert < 0:
            self.arrowVert = 0

    def __rightArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowHorz = 1
            self.switchToAnimState('GolfRotateLeft')
        elif self.arrowHorz > 0:
            self.arrowHorz = 0
            self.switchToAnimState('GolfPuttLoop')

    def __leftArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowHorz = -1
            self.switchToAnimState('GolfRotateRight')
        elif self.arrowHorz < 0:
            self.arrowHorz = 0
            self.switchToAnimState('GolfPuttLoop')

    def __watchControls(self, task):
        if self.arrowHorz:
            self.__moveGolfSpot(self.arrowHorz)
        return Task.cont

    def __moveGolfSpot(self, xd):
        dt = globalClock.getDt()
        h = self.root.getH() - xd * self.rotateSpeed * dt
        h %= 360
        limitH = h
        self.root.setH(limitH)

    def __incrementChangeSeq(self):
        self.changeSeq = self.changeSeq + 1 & 255

    def __beginFireBall(self):
        if self.aimStart != None:
            return
        if not self.state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        time = globalClock.getFrameTime()
        self.aimStart = time
        messenger.send('wakeup')
        taskMgr.add(self.__updateBallPower, self.ballPowerTaskName)
        return

    def __endFireBall(self):
        if self.aimStart == None:
            return
        if not self.state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        taskMgr.remove(self.ballPowerTaskName)
        self.disableControlKey()
        messenger.send('wakeup')
        self.aimStart = None
        power = self.power
        angle = self.root.getH()
        self.notify.debug('incrementing self.__flyBallSequenceNum')
        self.__flyBallSequenceNum = (self.__flyBallSequenceNum + 1) % 255
        self.sendSwingInfo(power, angle, self.__flyBallSequenceNum)
        self.setSwingInfo(power, angle, self.__flyBallSequenceNum)
        self.resetPowerBar()
        return

    def __updateBallPower(self, task):
        if not self.powerBar:
            print '### no power bar!!!'
            return task.done
        newPower = self.__getBallPower(globalClock.getFrameTime())
        self.power = newPower
        self.powerBar['value'] = newPower
        return task.cont

    def __getBallPower(self, time):
        elapsed = max(time - self.aimStart, 0.0)
        t = elapsed / self.golfPowerSpeed
        t = math.pow(t, self.golfPowerExponent)
        power = int(t * 100) % 200
        if power > 100:
            power = 200 - power
        return power

    def stopPosHprBroadcast(self):
        taskName = self.posHprBroadcastName
        taskMgr.remove(taskName)

    def startPosHprBroadcast(self):
        taskName = self.posHprBroadcastName
        self.b_clearSmoothing()
        self.d_sendGolfSpotPos()
        taskMgr.remove(taskName)
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast,
                              taskName)

    def __posHprBroadcast(self, task):
        self.d_sendGolfSpotPos()
        taskName = self.posHprBroadcastName
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast,
                              taskName)
        return Task.done

    def d_sendGolfSpotPos(self):
        timestamp = globalClockDelta.getFrameNetworkTime()
        self.sendUpdate(
            'setGolfSpotPos',
            [self.changeSeq, self.root.getH(), timestamp])

    def setGolfSpotPos(self, changeSeq, h, timestamp):
        self.changeSeq = changeSeq
        if self.smoothStarted:
            now = globalClock.getFrameTime()
            local = globalClockDelta.networkToLocalTime(timestamp, now)
            self.golfSpotSmoother.setH(h)
            self.golfSpotSmoother.setTimestamp(local)
            self.golfSpotSmoother.markPosition()
        else:
            self.root.setH(h)

    def b_clearSmoothing(self):
        self.d_clearSmoothing()
        self.clearSmoothing()

    def d_clearSmoothing(self):
        self.sendUpdate('clearSmoothing', [0])

    def clearSmoothing(self, bogus=None):
        self.golfSpotSmoother.clearPositions(1)

    def doSmoothTask(self, task):
        self.golfSpotSmoother.computeAndApplySmoothHpr(self.root)
        return Task.cont

    def startSmooth(self):
        if not self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.reloadPosition()
            taskMgr.add(self.doSmoothTask, taskName)
            self.smoothStarted = 1

    def stopSmooth(self):
        if self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.forceToTruePosition()
            self.smoothStarted = 0

    def makeToonReleaseInterval(self, toon):
        def getSlideToPos(toon=toon):
            return render.getRelativePoint(toon, Point3(0, -5, 0))

        if self.gotHitByBoss:
            grabIval = Sequence(Func(self.detachClub),
                                name='makeToonReleaseInterval-gotHitByBoss')
            if not toon.isEmpty():
                toonIval = Sequence(Func(toon.wrtReparentTo, render),
                                    Parallel(
                                        ActorInterval(toon, 'slip-backward'),
                                        toon.posInterval(0.5,
                                                         getSlideToPos,
                                                         fluid=1)),
                                    name='makeToonReleaseInterval-toonIval')
                grabIval.append(toonIval)
        else:
            grabIval = Sequence(Func(self.detachClub))
            if not toon.isEmpty():
                toonIval = Sequence(
                    Parallel(
                        ActorInterval(toon,
                                      'walk',
                                      duration=1.0,
                                      playRate=-1.0),
                        LerpPosInterval(toon,
                                        duration=1.0,
                                        pos=Point3(-10, 0, 0))),
                    Func(toon.wrtReparentTo, render))
                grabIval.append(toonIval)
        if localAvatar.doId == toon.doId:
            if not self.goingToReward and toon.hp > 0:
                grabIval.append(Func(self.goToFinalBattle))
                grabIval.append(
                    Func(self.notify.debug, 'goingToFinalBattlemode'))
                grabIval.append(Func(self.safeBossToFinalBattleMode))
        return grabIval

    def safeBossToFinalBattleMode(self):
        if self.boss:
            self.boss.toFinalBattleMode()

    def goToFinalBattle(self):
        if self.cr:
            place = self.cr.playGame.getPlace()
            if place and hasattr(place, 'fsm'):
                curState = place.fsm.getCurrentState().getName()
                if place.fsm.getCurrentState().getName() == 'crane':
                    place.setState('finalBattle')
                else:
                    self.notify.debug('NOT going to final battle, state=%s' %
                                      curState)

    def attachClub(self, avId, pointToBall=False):
        club = self.club
        if club:
            av = base.cr.doId2do.get(avId)
            if av:
                av.useLOD(1000)
                lHand = av.getLeftHands()[0]
                club.setPos(0, 0, 0)
                club.reparentTo(lHand)
                netScale = club.getNetTransform().getScale()[1]
                counterActToonScale = lHand.find('**/counteractToonScale')
                if counterActToonScale.isEmpty():
                    counterActToonScale = lHand.attachNewNode(
                        'counteractToonScale')
                    counterActToonScale.setScale(1 / netScale)
                    self.notify.debug('creating counterActToonScale for %s' %
                                      av.getName())
                club.reparentTo(counterActToonScale)
                club.setX(-0.25 * netScale)
                if pointToBall:
                    club.lookAt(self.clubLookatSpot)

    def detachClub(self):
        if not self.club.isEmpty():
            self.club.reparentTo(self.root)
            self.club.setZ(-20)
            self.club.setScale(1)

    def adjustClub(self):
        club = self.club
        if club:
            distance = club.getDistance(self.clubLookatSpot)
            scaleFactor = distance / 2.058
            club.setScale(1, scaleFactor, 1)

    def startAdjustClubTask(self):
        taskMgr.add(self.adjustClubTask, self.adjustClubTaskName)

    def stopAdjustClubTask(self):
        taskMgr.remove(self.adjustClubTaskName)

    def adjustClubTask(self, task):
        self.attachClub(self.avId, True)
        self.adjustClub()
        return task.cont

    def enableControlKey(self):
        self.controlKeyAllowed = True

    def disableControlKey(self):
        self.controlKeyAllowed = False

    def sendSwingInfo(self, power, angle, sequenceNum):
        self.sendUpdate('setSwingInfo', [power, angle, sequenceNum])

    def startBallPlayback(self, power, angle, sequenceNum):
        flyBall = self.ballModel.copyTo(NodePath())
        flyBall.setScale(1.0)
        flyBallBubble = self.getFlyBallBubble().instanceTo(NodePath())
        flyBallBubble.reparentTo(flyBall)
        flyBall.setTag('pieSequence', str(sequenceNum))
        flyBall.setTag('throwerId', str(self.avId))
        t = power / 100.0
        t = 1.0 - t
        dist = 300 - 200 * t
        time = 1.5 + 0.5 * t
        proj = ProjectileInterval(None,
                                  startPos=Point3(0, 0, 0),
                                  endPos=Point3(0, dist, 0),
                                  duration=time)
        relVel = proj.startVel

        def getVelocity(root=self.root, relVel=relVel):
            return render.getRelativeVector(root, relVel)

        fly = Sequence(
            Func(flyBall.reparentTo, render),
            Func(flyBall.setPosHpr, self.root, 0, 0, 0, 0, 0, 0),
            Func(base.cTrav.addCollider, flyBallBubble, self.flyBallHandler),
            ProjectileInterval(flyBall, startVel=getVelocity, duration=3),
            Func(flyBall.detachNode),
            Func(base.cTrav.removeCollider, flyBallBubble),
            Func(self.notify.debug, 'removed collider'),
            Func(self.flyBallFinishedFlying, sequenceNum))
        flyWithSound = Parallel(fly,
                                SoundInterval(self.hitBallSfx, node=self.root),
                                name='flyWithSound')
        self.notify.debug('starting flyball track')
        flyWithSound.start()
        self.flyBallTracks[sequenceNum] = flyWithSound
        return

    def setSwingInfo(self, power, angle, sequenceNum):
        av = base.cr.doId2do.get(self.avId)
        self.swingInterval = Sequence()
        if av:
            self.stopAdjustClubTask()
            self.swingInterval = Sequence(
                ActorInterval(av,
                              'swing-putt',
                              startFrame=0,
                              endFrame=GolfGlobals.BALL_CONTACT_FRAME),
                Func(self.startBallPlayback, power, angle, sequenceNum),
                Func(self.ballModel.hide),
                ActorInterval(av,
                              'swing-putt',
                              startFrame=GolfGlobals.BALL_CONTACT_FRAME,
                              endFrame=24), Func(self.ballModel.setScale, 0.1),
                Func(self.ballModel.show),
                LerpScaleInterval(self.ballModel, 1.0, Point3(1, 1, 1)),
                Func(self.enableControlKey))
            if av == localAvatar:
                self.swingInterval.append(
                    Func(self.switchToAnimState, 'GolfPuttLoop', True))
        self.swingInterval.start()

    def getFlyBallBubble(self):
        if self.__flyBallBubble == None:
            bubble = CollisionSphere(0, 0, 0, GolfGlobals.GOLF_BALL_RADIUS)
            node = CollisionNode('flyBallBubble')
            node.addSolid(bubble)
            node.setFromCollideMask(ToontownGlobals.PieBitmask
                                    | ToontownGlobals.CameraBitmask
                                    | ToontownGlobals.FloorBitmask)
            node.setIntoCollideMask(BitMask32.allOff())
            self.__flyBallBubble = NodePath(node)
            self.flyBallHandler = CollisionHandlerEvent()
            self.flyBallHandler.addInPattern('flyBallHit-%d' % self.index)
        return self.__flyBallBubble

    def __flyBallHit(self, entry):
        print entry

    def flyBallFinishedFlying(self, sequence):
        if self.flyBallTracks.has_key(sequence):
            del self.flyBallTracks[sequence]

    def __finishFlyBallTrack(self, sequence):
        if self.flyBallTracks.has_key(sequence):
            flyBallTrack = self.flyBallTracks[sequence]
            del self.flyBallTracks[sequence]
            flyBallTrack.finish()

    def flyBallFinishedSplatting(self, sequence):
        if self.splatTracks.has_key(sequence):
            del self.splatTracks[sequence]

    def __flyBallHit(self, entry):
        if not entry.hasSurfacePoint() or not entry.hasInto():
            return
        if not entry.getInto().isTangible():
            return
        sequence = int(entry.getFromNodePath().getNetTag('pieSequence'))
        self.__finishFlyBallTrack(sequence)
        if self.splatTracks.has_key(sequence):
            splatTrack = self.splatTracks[sequence]
            del self.splatTracks[sequence]
            splatTrack.finish()
        flyBallCode = 0
        flyBallCodeStr = entry.getIntoNodePath().getNetTag('pieCode')
        if flyBallCodeStr:
            flyBallCode = int(flyBallCodeStr)
        pos = entry.getSurfacePoint(render)
        timestamp32 = globalClockDelta.getFrameNetworkTime(bits=32)
        throwerId = int(entry.getFromNodePath().getNetTag('throwerId'))
        splat = self.getFlyBallSplatInterval(pos[0], pos[1], pos[2],
                                             flyBallCode, throwerId)
        splat = Sequence(splat, Func(self.flyBallFinishedSplatting, sequence))
        self.splatTracks[sequence] = splat
        splat.start()
        self.notify.debug(
            'doId=%d into=%s flyBallCode=%d, throwerId=%d' %
            (self.doId, entry.getIntoNodePath(), flyBallCode, throwerId))
        if flyBallCode == ToontownGlobals.PieCodeBossCog and self.avId == localAvatar.doId and self.lastHitSequenceNum != self.__flyBallSequenceNum:
            self.lastHitSequenceNum = self.__flyBallSequenceNum
            self.boss.d_ballHitBoss(10)
        elif flyBallCode == ToontownGlobals.PieCodeToon and self.avId == localAvatar.doId and self.lastHitSequenceNum != self.__flyBallSequenceNum:
            self.lastHitSequenceNum = self.__flyBallSequenceNum
            avatarDoId = entry.getIntoNodePath().getNetTag('avatarDoId')
            if avatarDoId == '':
                self.notify.warning('Toon %s has no avatarDoId tag.' %
                                    repr(entry.getIntoNodePath()))
                return
            doId = int(avatarDoId)
            if doId != localAvatar.doId:
                pass

    def getFlyBallSplatInterval(self, x, y, z, flyBallCode, throwerId):
        from toontown.toonbase import ToontownBattleGlobals
        from toontown.battle import BattleProps
        splatName = 'dust'
        splat = BattleProps.globalPropPool.getProp(splatName)
        splat.setBillboardPointWorld(2)
        color = ToontownGlobals.PieCodeColors.get(flyBallCode)
        if color:
            splat.setColor(*color)
        if flyBallCode == ToontownGlobals.PieCodeBossCog:
            self.notify.debug('changing color to %s' % self.ballColor)
            splat.setColor(self.ballColor)
        sound = loader.loadSfx('phase_11/audio/sfx/LB_evidence_miss.ogg')
        vol = 1.0
        if flyBallCode == ToontownGlobals.PieCodeBossCog:
            sound = loader.loadSfx('phase_4/audio/sfx/Golf_Hit_Barrier_1.ogg')
        soundIval = SoundInterval(sound, node=splat, volume=vol)
        if flyBallCode == ToontownGlobals.PieCodeBossCog and localAvatar.doId == throwerId:
            vol = 1.0
            soundIval = SoundInterval(sound, node=localAvatar, volume=vol)
        ival = Parallel(
            Func(splat.reparentTo, render), Func(splat.setPos, x, y, z),
            soundIval,
            Sequence(ActorInterval(splat, splatName), Func(splat.detachNode)))
        return ival

    def setGoingToReward(self):
        self.goingToReward = True

    def gotBossZapped(self):
        self.showExiting()
        self.d_requestFree(True)
class QuestNote(DirectFrame):
    notify = directNotify.newCategory("QuestNote")

    spots = [(-0.45, 0, 0.3), (0.45, 0, 0.3), (0.45, 0, -0.25),
             (-0.45, 0, -0.25)]
    RewardTextPos = (0, -0.4)
    RewardTextScale = 0.06
    ProgressTextScale = 0.07
    ProgressBarPos = (0, 0, -0.19)
    ProgressTextPos = (0, -0.19)
    TaskInfoTextPos = (0, 0.05)
    NonHeadingTextScale = 0.08
    HeadingTextPos = (0, 0.23)
    HeadingTextScale = 0.1

    def __init__(self, index):
        DirectFrame.__init__(self, scale=0.5)
        stickergui = loader.loadModel(
            'phase_3.5/models/gui/stickerbook_gui.bam')
        self['image'] = stickergui.find('**/paper_note')
        self['image_scale'] = (1.3, 1, 1)
        self.setPos(self.spots[index])
        self.useProgressBar = False
        self.headingText = OnscreenText(parent=self,
                                        text="",
                                        font=CIGlobals.getToonFont(),
                                        pos=self.HeadingTextPos,
                                        scale=self.HeadingTextScale)
        self.taskInfoText = OnscreenText(parent=self,
                                         text="",
                                         font=CIGlobals.getToonFont(),
                                         pos=self.TaskInfoTextPos,
                                         scale=self.NonHeadingTextScale)
        self.progressText = OnscreenText(parent=self,
                                         text="",
                                         font=CIGlobals.getToonFont(),
                                         pos=self.ProgressTextPos,
                                         scale=self.ProgressTextScale)
        self.progressBar = DirectWaitBar(
            parent=self,
            relief=DGG.SUNKEN,
            frameSize=(-0.95, 0.95, -0.1, 0.12),
            borderWidth=(0.025, 0.025),
            scale=0.4,
            frameColor=(251.0 / 255, 252.0 / 255, 176.0 / 255, 1.0),
            barColor=(0.5, 0.7, 0.5, 1),
            text='0/0',
            text_font=CIGlobals.getToonFont(),
            text_scale=0.19,
            text_fg=(0.05, 0.14, 0.4, 1),
            text_align=TextNode.ACenter,
            text_pos=(0, -0.05),  #-0.02
            pos=self.ProgressBarPos)
        self.progressBar.hide()
        self.rewardText = OnscreenText(parent=self,
                                       text="",
                                       font=CIGlobals.getToonFont(),
                                       pos=self.RewardTextPos,
                                       scale=self.RewardTextScale,
                                       fg=(1, 0.1, 0.1, 1.0))
        self.hide()

    def setHeading(self, text):
        self.headingText.setText(text)

    def setTaskInfo(self, text):
        self.taskInfoText.setText(text)

    def setProgress(self, text, range=0, value=0):
        if self.useProgressBar:
            self.progressBar.show()
            self.progressBar['text'] = text
            self.progressBar['range'] = range
            self.progressBar['value'] = value
        else:
            self.progressText['text'] = text

    def setReward(self, text):
        self.rewardText.setText(text)

    def setCompleted(self, value):
        if value:
            self.progressBar.hide()
            self['image_color'] = QuestGlobals.LIGHT_GREEN
class DistributedBanquetTable(DistributedObject.DistributedObject, FSM.FSM, BanquetTableBase.BanquetTableBase):
    notify = DirectNotifyGlobal.directNotify.newCategory("DistributedBanquetTable")
    rotationsPerSeatIndex = [90, 90, 0, 0, -90, -90, 180, 180]
    pitcherMinH = -360
    pitcherMaxH = 360
    rotateSpeed = 30
    waterPowerSpeed = base.config.GetDouble("water-power-speed", 15)
    waterPowerExponent = base.config.GetDouble("water-power-exponent", 0.75)
    useNewAnimations = True
    TugOfWarControls = False
    OnlyUpArrow = True
    if OnlyUpArrow:
        BASELINE_KEY_RATE = 3
    else:
        BASELINE_KEY_RATE = 6
    UPDATE_KEY_PRESS_RATE_TASK = "BanquetTableUpdateKeyPressRateTask"
    YELLOW_POWER_THRESHOLD = 0.75
    RED_POWER_THRESHOLD = 0.96999999999999997

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        FSM.FSM.__init__(self, "DistributedBanquetTable")
        self.boss = None
        self.index = -1
        self.diners = {}
        self.dinerStatus = {}
        self.serviceLocs = {}
        self.chairLocators = {}
        self.sitLocators = {}
        self.activeIntervals = {}
        self.dinerStatusIndicators = {}
        self.preparedForPhaseFour = False
        self.avId = 0
        self.toon = None
        self.pitcherSmoother = SmoothMover()
        self.pitcherSmoother.setSmoothMode(SmoothMover.SMOn)
        self.smoothStarted = 0
        self._DistributedBanquetTable__broadcastPeriod = 0.20000000000000001
        self.changeSeq = 0
        self.lastChangeSeq = 0
        self.pitcherAdviceLabel = None
        self.fireLength = 250
        self.fireTrack = None
        self.hitObject = None
        self.setupPowerBar()
        self.aimStart = None
        self.toonPitcherPosition = Point3(0, -2, 0)
        self.allowLocalRequestControl = True
        self.fadeTrack = None
        self.grabTrack = None
        self.gotHitByBoss = False
        self.keyTTL = []
        self.keyRate = 0
        self.buttons = [0, 1]
        self.lastPowerFired = 0
        self.moveSound = None
        self.releaseTrack = None

    def disable(self):
        DistributedObject.DistributedObject.disable(self)
        taskMgr.remove(self.triggerName)
        taskMgr.remove(self.smoothName)
        taskMgr.remove(self.watchControlsName)
        taskMgr.remove(self.pitcherAdviceName)
        taskMgr.remove(self.posHprBroadcastName)
        taskMgr.remove(self.waterPowerTaskName)
        if self.releaseTrack:
            self.releaseTrack.finish()
            self.releaseTrack = None

        if self.fireTrack:
            self.fireTrack.finish()
            self.fireTrack = None

        self.cleanupIntervals()

    def delete(self):
        DistributedObject.DistributedObject.delete(self)
        self.boss = None
        self.ignoreAll()
        for indicator in self.dinerStatusIndicators.values():
            indicator.delete()

        self.dinerStatusIndicators = {}
        for diner in self.diners.values():
            diner.delete()

        self.diners = {}
        self.powerBar.destroy()
        self.powerBar = None
        self.pitcherMoveSfx.stop()

    def announceGenerate(self):
        DistributedObject.DistributedObject.announceGenerate(self)
        self.loadAssets()
        self.smoothName = self.uniqueName("pitcherSmooth")
        self.pitcherAdviceName = self.uniqueName("pitcherAdvice")
        self.posHprBroadcastName = self.uniqueName("pitcherBroadcast")
        self.waterPowerTaskName = self.uniqueName("updateWaterPower")
        self.triggerName = self.uniqueName("trigger")
        self.watchControlsName = self.uniqueName("watchControls")

    def setBossCogId(self, bossCogId):
        self.bossCogId = bossCogId
        self.boss = base.cr.doId2do[bossCogId]
        self.boss.setTable(self, self.index)

    def setIndex(self, index):
        self.index = index

    def setState(self, state, avId, extraInfo):
        self.gotHitByBoss = extraInfo
        if state == "F":
            self.demand("Off")
        elif state == "N":
            self.demand("On")
        elif state == "I":
            self.demand("Inactive")
        elif state == "R":
            self.demand("Free")
        elif state == "C":
            self.demand("Controlled", avId)
        elif state == "L":
            self.demand("Flat", avId)
        else:
            self.notify.error("Invalid state from AI: %s" % state)

    def setNumDiners(self, numDiners):
        self.numDiners = numDiners

    def setDinerInfo(self, hungryDurations, eatingDurations, dinerLevels):
        self.dinerInfo = {}
        for i in xrange(len(hungryDurations)):
            hungryDur = hungryDurations[i]
            eatingDur = eatingDurations[i]
            dinerLevel = dinerLevels[i]
            self.dinerInfo[i] = (hungryDur, eatingDur, dinerLevel)

    def loadAssets(self):
        self.tableGroup = loader.loadModel("phase_12/models/bossbotHQ/BanquetTableChairs")
        tableLocator = self.boss.geom.find("**/TableLocator_%d" % (self.index + 1))
        if tableLocator.isEmpty():
            self.tableGroup.reparentTo(render)
            self.tableGroup.setPos(0, 75, 0)
        else:
            self.tableGroup.reparentTo(tableLocator)
        self.tableGeom = self.tableGroup.find("**/Geometry")
        self.setupDiners()
        self.setupChairCols()
        self.squirtSfx = loader.loadSfx("phase_4/audio/sfx/AA_squirt_seltzer_miss.mp3")
        self.hitBossSfx = loader.loadSfx("phase_5/audio/sfx/SA_watercooler_spray_only.mp3")
        self.hitBossSoundInterval = SoundInterval(self.hitBossSfx, node=self.boss, volume=1.0)
        self.serveFoodSfx = loader.loadSfx("phase_4/audio/sfx/MG_sfx_travel_game_bell_for_trolley.mp3")
        self.pitcherMoveSfx = base.loadSfx("phase_4/audio/sfx/MG_cannon_adjust.mp3")

    def setupDiners(self):
        for i in xrange(self.numDiners):
            newDiner = self.createDiner(i)
            self.diners[i] = newDiner
            self.dinerStatus[i] = self.HUNGRY

    def createDiner(self, i):
        diner = Suit.Suit()
        diner.dna = SuitDNA.SuitDNA()
        level = self.dinerInfo[i][2]
        level -= 4
        diner.dna.newSuitRandom(level=level, dept="c")
        diner.setDNA(diner.dna)
        if self.useNewAnimations:
            diner.loop("sit", fromFrame=i)
        else:
            diner.pose("landing", 0)
        locator = self.tableGroup.find("**/chair_%d" % (i + 1))
        locatorScale = locator.getNetTransform().getScale()[0]
        correctHeadingNp = locator.attachNewNode("correctHeading")
        self.chairLocators[i] = correctHeadingNp
        heading = self.rotationsPerSeatIndex[i]
        correctHeadingNp.setH(heading)
        sitLocator = correctHeadingNp.attachNewNode("sitLocator")
        base.sitLocator = sitLocator
        pos = correctHeadingNp.getPos(render)
        if SuitDNA.getSuitBodyType(diner.dna.name) == "c":
            sitLocator.setPos(0.5, 3.6499999999999999, -3.75)
        else:
            sitLocator.setZ(-2.3999999999999999)
            sitLocator.setY(2.5)
            sitLocator.setX(0.5)
        self.sitLocators[i] = sitLocator
        diner.setScale(1.0 / locatorScale)
        diner.reparentTo(sitLocator)
        newLoc = NodePath("serviceLoc-%d-%d" % (self.index, i))
        newLoc.reparentTo(correctHeadingNp)
        newLoc.setPos(0, 3.0, 1)
        self.serviceLocs[i] = newLoc
        base.serviceLoc = newLoc
        head = diner.find("**/joint_head")
        newIndicator = DinerStatusIndicator.DinerStatusIndicator(parent=head, pos=Point3(0, 0, 3.5), scale=5.0)
        newIndicator.wrtReparentTo(diner)
        self.dinerStatusIndicators[i] = newIndicator
        return diner

    def setupChairCols(self):
        for i in xrange(self.numDiners):
            chairCol = self.tableGroup.find("**/collision_chair_%d" % (i + 1))
            colName = "ChairCol-%d-%d" % (self.index, i)
            chairCol.setTag("chairIndex", str(i))
            chairCol.setName(colName)
            chairCol.setCollideMask(ToontownGlobals.WallBitmask)
            self.accept("enter" + colName, self.touchedChair)

    def touchedChair(self, colEntry):
        chairIndex = int(colEntry.getIntoNodePath().getTag("chairIndex"))
        if chairIndex in self.dinerStatus:
            status = self.dinerStatus[chairIndex]
            if status in (self.HUNGRY, self.ANGRY):
                self.boss.localToonTouchedChair(self.index, chairIndex)

    def serveFood(self, food, chairIndex):
        self.removeFoodModel(chairIndex)
        serviceLoc = self.serviceLocs.get(chairIndex)
        if not food or food.isEmpty():
            foodModel = loader.loadModel("phase_12/models/bossbotHQ/canoffood")
            foodModel.setScale(ToontownGlobals.BossbotFoodModelScale)
            foodModel.reparentTo(serviceLoc)
        else:
            food.wrtReparentTo(serviceLoc)
            tray = food.find("**/tray")
            if not tray.isEmpty():
                tray.hide()

            ivalDuration = 1.5
            foodMoveIval = Parallel(
                SoundInterval(self.serveFoodSfx, node=food),
                ProjectileInterval(
                    food, duration=ivalDuration, startPos=food.getPos(serviceLoc), endPos=serviceLoc.getPos(serviceLoc)
                ),
                LerpHprInterval(food, ivalDuration, Point3(0, -360, 0)),
            )
            intervalName = "serveFood-%d-%d" % (self.index, chairIndex)
            foodMoveIval.start()
            self.activeIntervals[intervalName] = foodMoveIval

    def setDinerStatus(self, chairIndex, status):
        if chairIndex in self.dinerStatus:
            oldStatus = self.dinerStatus[chairIndex]
            self.dinerStatus[chairIndex] = status
            if oldStatus != status:
                if status == self.EATING:
                    self.changeDinerToEating(chairIndex)
                elif status == self.HUNGRY:
                    self.changeDinerToHungry(chairIndex)
                elif status == self.ANGRY:
                    self.changeDinerToAngry(chairIndex)
                elif status == self.DEAD:
                    self.changeDinerToDead(chairIndex)
                elif status == self.HIDDEN:
                    self.changeDinerToHidden(chairIndex)

    def removeFoodModel(self, chairIndex):
        serviceLoc = self.serviceLocs.get(chairIndex)
        if serviceLoc:
            for i in xrange(serviceLoc.getNumChildren()):
                serviceLoc.getChild(0).removeNode()

    def changeDinerToEating(self, chairIndex):
        indicator = self.dinerStatusIndicators.get(chairIndex)
        eatingDuration = self.dinerInfo[chairIndex][1]
        if indicator:
            indicator.request("Eating", eatingDuration)

        diner = self.diners[chairIndex]
        intervalName = "eating-%d-%d" % (self.index, chairIndex)
        eatInTime = 32.0 / 24.0
        eatOutTime = 21.0 / 24.0
        eatLoopTime = 19 / 24.0
        rightHand = diner.getRightHand()
        waitTime = 5
        loopDuration = eatingDuration - eatInTime - eatOutTime - waitTime
        serviceLoc = self.serviceLocs[chairIndex]

        def foodAttach(self=self, diner=diner):
            foodModel = self.serviceLocs[chairIndex].getChild(0)
            (foodModel.reparentTo(diner.getRightHand()),)
            (foodModel.setHpr(Point3(0, -94, 0)),)
            (foodModel.setPos(Point3(-0.14999999999999999, -0.69999999999999996, -0.40000000000000002)),)
            scaleAdj = 1
            if SuitDNA.getSuitBodyType(diner.dna.name) == "c":
                scaleAdj = 0.59999999999999998
                (foodModel.setPos(Point3(0.10000000000000001, -0.25, -0.31)),)
            else:
                scaleAdj = 0.80000000000000004
                (foodModel.setPos(Point3(-0.25, -0.84999999999999998, -0.34000000000000002)),)
            oldScale = foodModel.getScale()
            newScale = oldScale * scaleAdj
            foodModel.setScale(newScale)

        def foodDetach(self=self, diner=diner):
            foodModel = diner.getRightHand().getChild(0)
            (foodModel.reparentTo(serviceLoc),)
            (foodModel.setPosHpr(0, 0, 0, 0, 0, 0),)
            scaleAdj = 1
            if SuitDNA.getSuitBodyType(diner.dna.name) == "c":
                scaleAdj = 0.59999999999999998
            else:
                scakeAdj = 0.80000000000000004
            oldScale = foodModel.getScale()
            newScale = oldScale / scaleAdj
            foodModel.setScale(newScale)

        eatIval = Sequence(
            ActorInterval(diner, "sit", duration=waitTime),
            ActorInterval(diner, "sit-eat-in", startFrame=0, endFrame=6),
            Func(foodAttach),
            ActorInterval(diner, "sit-eat-in", startFrame=6, endFrame=32),
            ActorInterval(diner, "sit-eat-loop", duration=loopDuration, loop=1),
            ActorInterval(diner, "sit-eat-out", startFrame=0, endFrame=12),
            Func(foodDetach),
            ActorInterval(diner, "sit-eat-out", startFrame=12, endFrame=21),
        )
        eatIval.start()
        self.activeIntervals[intervalName] = eatIval

    def changeDinerToHungry(self, chairIndex):
        intervalName = "eating-%d-%d" % (self.index, chairIndex)
        if intervalName in self.activeIntervals:
            self.activeIntervals[intervalName].finish()

        self.removeFoodModel(chairIndex)
        indicator = self.dinerStatusIndicators.get(chairIndex)
        if indicator:
            indicator.request("Hungry", self.dinerInfo[chairIndex][0])

        diner = self.diners[chairIndex]
        if random.choice([0, 1]):
            diner.loop("sit-hungry-left")
        else:
            diner.loop("sit-hungry-right")

    def changeDinerToAngry(self, chairIndex):
        self.removeFoodModel(chairIndex)
        indicator = self.dinerStatusIndicators.get(chairIndex)
        if indicator:
            indicator.request("Angry")

        diner = self.diners[chairIndex]
        diner.loop("sit-angry")

    def changeDinerToDead(self, chairIndex):
        def removeDeathSuit(suit, deathSuit):
            if not deathSuit.isEmpty():
                deathSuit.detachNode()
                suit.cleanupLoseActor()

        self.removeFoodModel(chairIndex)
        indicator = self.dinerStatusIndicators.get(chairIndex)
        if indicator:
            indicator.request("Dead")

        diner = self.diners[chairIndex]
        deathSuit = diner
        locator = self.tableGroup.find("**/chair_%d" % (chairIndex + 1))
        deathSuit = diner.getLoseActor()
        ival = Sequence(
            Func(self.notify.debug, "before actorinterval sit-lose"),
            ActorInterval(diner, "sit-lose"),
            Func(self.notify.debug, "before deathSuit.setHpr"),
            Func(deathSuit.setHpr, diner.getHpr()),
            Func(self.notify.debug, "before diner.hide"),
            Func(diner.hide),
            Func(self.notify.debug, "before deathSuit.reparentTo"),
            Func(deathSuit.reparentTo, self.chairLocators[chairIndex]),
            Func(self.notify.debug, "befor ActorInterval lose"),
            ActorInterval(deathSuit, "lose", duration=MovieUtil.SUIT_LOSE_DURATION),
            Func(self.notify.debug, "before remove deathsuit"),
            Func(removeDeathSuit, diner, deathSuit, name="remove-death-suit-%d-%d" % (chairIndex, self.index)),
            Func(self.notify.debug, "diner.stash"),
            Func(diner.stash),
        )
        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.80000000000000004),
            SoundInterval(spinningSound, duration=1.2, startTime=1.5, volume=0.20000000000000001, node=deathSuit),
            SoundInterval(
                spinningSound, duration=3.0, startTime=0.59999999999999998, volume=0.80000000000000004, node=deathSuit
            ),
            SoundInterval(deathSound, volume=0.32000000000000001, node=deathSuit),
        )
        intervalName = "dinerDie-%d-%d" % (self.index, chairIndex)
        deathIval = Parallel(ival, deathSoundTrack)
        deathIval.start()
        self.activeIntervals[intervalName] = deathIval

    def changeDinerToHidden(self, chairIndex):
        self.removeFoodModel(chairIndex)
        indicator = self.dinerStatusIndicators.get(chairIndex)
        if indicator:
            indicator.request("Inactive")

        diner = self.diners[chairIndex]
        diner.hide()

    def setAllDinersToSitNeutral(self):
        startFrame = 0
        for diner in self.diners.values():
            if not diner.isHidden():
                diner.loop("sit", fromFrame=startFrame)
                startFrame += 1
                continue

    def cleanupIntervals(self):
        for interval in self.activeIntervals.values():
            interval.finish()

        self.activeIntervals = {}

    def clearInterval(self, name, finish=1):
        if self.activeIntervals.has_key(name):
            ival = self.activeIntervals[name]
            if finish:
                ival.finish()
            else:
                ival.pause()
            if self.activeIntervals.has_key(name):
                del self.activeIntervals[name]

        else:
            self.notify.debug("interval: %s already cleared" % name)

    def finishInterval(self, name):
        if self.activeIntervals.has_key(name):
            interval = self.activeIntervals[name]
            interval.finish()

    def getNotDeadInfo(self):
        notDeadList = []
        for i in xrange(self.numDiners):
            if self.dinerStatus[i] != self.DEAD:
                notDeadList.append((self.index, i, 12))
                continue

        return notDeadList

    def enterOn(self):
        pass

    def exitOn(self):
        pass

    def enterInactive(self):
        for chairIndex in xrange(self.numDiners):
            indicator = self.dinerStatusIndicators.get(chairIndex)
            if indicator:
                indicator.request("Inactive")

            self.removeFoodModel(chairIndex)

    def exitInactive(self):
        pass

    def enterFree(self):
        self.resetPowerBar()
        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None

        self.prepareForPhaseFour()
        if self.avId == localAvatar.doId:
            self.tableGroup.setAlphaScale(0.29999999999999999)
            self.tableGroup.setTransparency(1)
            taskMgr.doMethodLater(5, self._DistributedBanquetTable__allowDetect, self.triggerName)
            self.fadeTrack = Sequence(
                Func(self.tableGroup.setTransparency, 1),
                self.tableGroup.colorScaleInterval(0.20000000000000001, VBase4(1, 1, 1, 0.29999999999999999)),
            )
            self.fadeTrack.start()
            self.allowLocalRequestControl = False
        else:
            self.allowLocalRequestControl = True
        self.avId = 0

    def exitFree(self):
        pass

    def touchedTable(self, colEntry):
        tableIndex = int(colEntry.getIntoNodePath().getTag("tableIndex"))
        if self.state == "Free" and self.avId == 0 and self.allowLocalRequestControl:
            self.d_requestControl()

    def prepareForPhaseFour(self):
        if not self.preparedForPhaseFour:
            for i in xrange(8):
                chair = self.tableGroup.find("**/chair_%d" % (i + 1))
                if not chair.isEmpty():
                    chair.hide()

                colChairs = self.tableGroup.findAllMatches("**/ChairCol*")
                for i in xrange(colChairs.getNumPaths()):
                    col = colChairs.getPath(i)
                    col.stash()

                colChairs = self.tableGroup.findAllMatches("**/collision_chair*")
                for i in xrange(colChairs.getNumPaths()):
                    col = colChairs.getPath(i)
                    col.stash()

            tableCol = self.tableGroup.find("**/collision_table")
            colName = "TableCol-%d" % self.index
            tableCol.setTag("tableIndex", str(self.index))
            tableCol.setName(colName)
            tableCol.setCollideMask(ToontownGlobals.WallBitmask | ToontownGlobals.BanquetTableBitmask)
            self.accept("enter" + colName, self.touchedTable)
            self.preparedForPhaseFour = True
            self.waterPitcherModel = loader.loadModel("phase_12/models/bossbotHQ/tt_m_ara_bhq_seltzerBottle")
            lampNode = self.tableGroup.find("**/lamp_med_5")
            pos = lampNode.getPos(self.tableGroup)
            lampNode.hide()
            bottleLocator = self.tableGroup.find("**/bottle_locator")
            pos = bottleLocator.getPos(self.tableGroup)
            self.waterPitcherNode = self.tableGroup.attachNewNode("pitcherNode")
            self.waterPitcherNode.setPos(pos)
            self.waterPitcherModel.reparentTo(self.waterPitcherNode)
            self.waterPitcherModel.ls()
            self.nozzle = self.waterPitcherModel.find("**/nozzle_tip")
            self.handLocator = self.waterPitcherModel.find("**/hand_locator")
            self.handPos = self.handLocator.getPos()

    def d_requestControl(self):
        self.sendUpdate("requestControl")

    def d_requestFree(self, gotHitByBoss):
        self.sendUpdate("requestFree", [gotHitByBoss])

    def enterControlled(self, avId):
        self.prepareForPhaseFour()
        self.avId = avId
        toon = base.cr.doId2do.get(avId)
        if not toon:
            return None

        self.toon = toon
        self.grabTrack = self.makeToonGrabInterval(toon)
        self.notify.debug("grabTrack=%s" % self.grabTrack)
        self.pitcherCamPos = Point3(0, -50, 40)
        self.pitcherCamHpr = Point3(0, -21, 0)
        if avId == localAvatar.doId:
            self.boss.toMovieMode()
            self._DistributedBanquetTable__enableControlInterface()
            self.startPosHprBroadcast()
            self.grabTrack = Sequence(
                self.grabTrack,
                Func(camera.wrtReparentTo, localAvatar),
                LerpPosHprInterval(camera, 1, self.pitcherCamPos, self.pitcherCamHpr),
                Func(self.boss.toCraneMode),
            )
            if self.TugOfWarControls:
                self._DistributedBanquetTable__spawnUpdateKeyPressRateTask()

            self.accept("exitCrane", self.gotBossZapped)
        else:
            self.startSmooth()
            toon.stopSmooth()
        self.grabTrack.start()

    def exitControlled(self):
        self.ignore("exitCrane")
        if self.grabTrack:
            self.grabTrack.finish()
            self.grabTrack = None

        nextState = self.getCurrentOrNextState()
        self.notify.debug("nextState=%s" % nextState)
        if nextState == "Flat":
            place = base.cr.playGame.getPlace()
            self.notify.debug("%s" % place.fsm)
            if self.avId == localAvatar.doId:
                self._DistributedBanquetTable__disableControlInterface()

        elif self.toon and not self.toon.isDisabled():
            self.toon.loop("neutral")
            self.toon.startSmooth()

        self.releaseTrack = self.makeToonReleaseInterval(self.toon)
        self.stopPosHprBroadcast()
        self.stopSmooth()
        if self.avId == localAvatar.doId:
            localAvatar.wrtReparentTo(render)
            self._DistributedBanquetTable__disableControlInterface()
            camera.reparentTo(base.localAvatar)
            camera.setPos(base.localAvatar.cameraPositions[0][0])
            camera.setHpr(0, 0, 0)
            self.goToFinalBattle()
            self.safeBossToFinalBattleMode()
        else:
            toon = base.cr.doId2do.get(self.avId)
            if toon:
                toon.wrtReparentTo(render)

        self.releaseTrack.start()

    def safeBossToFinalBattleMode(self):
        if self.boss:
            self.boss.toFinalBattleMode()

    def goToFinalBattle(self):
        if self.cr:
            place = self.cr.playGame.getPlace()
            if place and hasattr(place, "fsm"):
                if place.fsm.getCurrentState().getName() == "crane":
                    place.setState("finalBattle")

    def makeToonGrabInterval(self, toon):
        toon.pose("leverNeutral", 0)
        toon.update()
        rightHandPos = toon.rightHand.getPos(toon)
        self.toonPitcherPosition = Point3(self.handPos[0] - rightHandPos[0], self.handPos[1] - rightHandPos[1], 0)
        destZScale = rightHandPos[2] / self.handPos[2]
        grabIval = Sequence(
            Func(toon.wrtReparentTo, self.waterPitcherNode),
            Func(toon.loop, "neutral"),
            Parallel(
                ActorInterval(toon, "jump"),
                Sequence(
                    Wait(0.42999999999999999),
                    Parallel(
                        ProjectileInterval(
                            toon,
                            duration=0.90000000000000002,
                            startPos=toon.getPos(self.waterPitcherNode),
                            endPos=self.toonPitcherPosition,
                        ),
                        LerpHprInterval(toon, 0.90000000000000002, Point3(0, 0, 0)),
                        LerpScaleInterval(self.waterPitcherModel, 0.90000000000000002, Point3(1, 1, destZScale)),
                    ),
                ),
            ),
            Func(toon.setPos, self.toonPitcherPosition),
            Func(toon.loop, "leverNeutral"),
        )
        return grabIval

    def makeToonReleaseInterval(self, toon):
        temp1 = self.waterPitcherNode.attachNewNode("temp1")
        temp1.setPos(self.toonPitcherPosition)
        temp2 = self.waterPitcherNode.attachNewNode("temp2")
        temp2.setPos(0, -10, -self.waterPitcherNode.getZ())
        startPos = temp1.getPos(render)
        endPos = temp2.getPos(render)
        temp1.removeNode()
        temp2.removeNode()

        def getSlideToPos(toon=toon):
            return render.getRelativePoint(toon, Point3(0, -10, 0))

        if self.gotHitByBoss:
            self.notify.debug("creating zap interval instead")
            grabIval = Sequence(
                Func(toon.loop, "neutral"),
                Func(toon.wrtReparentTo, render),
                Parallel(ActorInterval(toon, "slip-backward"), toon.posInterval(0.5, getSlideToPos, fluid=1)),
            )
        else:
            grabIval = Sequence(
                Func(toon.loop, "neutral"),
                Func(toon.wrtReparentTo, render),
                Parallel(
                    ActorInterval(toon, "jump"),
                    Sequence(
                        Wait(0.42999999999999999),
                        ProjectileInterval(toon, duration=0.90000000000000002, startPos=startPos, endPos=endPos),
                    ),
                ),
            )
        return grabIval

    def b_clearSmoothing(self):
        self.d_clearSmoothing()
        self.clearSmoothing()

    def d_clearSmoothing(self):
        self.sendUpdate("clearSmoothing", [0])

    def clearSmoothing(self, bogus=None):
        self.pitcherSmoother.clearPositions(1)

    def doSmoothTask(self, task):
        self.pitcherSmoother.computeAndApplySmoothHpr(self.waterPitcherNode)
        return Task.cont

    def startSmooth(self):
        if not self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.reloadPosition()
            taskMgr.add(self.doSmoothTask, taskName)
            self.smoothStarted = 1

    def stopSmooth(self):
        if self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.forceToTruePosition()
            self.smoothStarted = 0

    def _DistributedBanquetTable__enableControlInterface(self):
        gui = loader.loadModel("phase_3.5/models/gui/avatar_panel_gui")
        self.closeButton = DirectButton(
            image=(
                gui.find("**/CloseBtn_UP"),
                gui.find("**/CloseBtn_DN"),
                gui.find("**/CloseBtn_Rllvr"),
                gui.find("**/CloseBtn_UP"),
            ),
            relief=None,
            scale=2,
            text=TTLocalizer.BossbotPitcherLeave,
            text_scale=0.040000000000000001,
            text_pos=(0, -0.070000000000000007),
            text_fg=VBase4(1, 1, 1, 1),
            pos=(1.05, 0, -0.81999999999999995),
            command=self._DistributedBanquetTable__exitPitcher,
        )
        self.accept("escape", self._DistributedBanquetTable__exitPitcher)
        self.accept("control", self._DistributedBanquetTable__controlPressed)
        self.accept("control-up", self._DistributedBanquetTable__controlReleased)
        self.accept("InputState-forward", self._DistributedBanquetTable__upArrow)
        self.accept("InputState-reverse", self._DistributedBanquetTable__downArrow)
        self.accept("InputState-turnLeft", self._DistributedBanquetTable__leftArrow)
        self.accept("InputState-turnRight", self._DistributedBanquetTable__rightArrow)
        self.accept("arrow_up", self._DistributedBanquetTable__upArrowKeyPressed)
        self.accept("arrow_down", self._DistributedBanquetTable__downArrowKeyPressed)
        taskMgr.add(self._DistributedBanquetTable__watchControls, self.watchControlsName)
        taskMgr.doMethodLater(5, self._DistributedBanquetTable__displayPitcherAdvice, self.pitcherAdviceName)
        self.arrowVert = 0
        self.arrowHorz = 0
        self.powerBar.show()

    def _DistributedBanquetTable__disableControlInterface(self):
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = None

        self._DistributedBanquetTable__cleanupPitcherAdvice()
        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.ignore("arrow_up")
        self.ignore("arrow_down")
        self.arrowVert = 0
        self.arrowHorz = 0
        taskMgr.remove(self.watchControlsName)
        taskMgr.remove(self.waterPowerTaskName)
        self.resetPowerBar()
        self.aimStart = None
        self.powerBar.hide()
        if self.TugOfWarControls:
            self._DistributedBanquetTable__killUpdateKeyPressRateTask()
            self.keyTTL = []

        self._DistributedBanquetTable__setMoveSound(None)

    def _DistributedBanquetTable__displayPitcherAdvice(self, task):
        if self.pitcherAdviceLabel == None:
            self.pitcherAdviceLabel = DirectLabel(
                text=TTLocalizer.BossbotPitcherAdvice,
                text_fg=VBase4(1, 1, 1, 1),
                text_align=TextNode.ACenter,
                relief=None,
                pos=(0, 0, 0.68999999999999995),
                scale=0.10000000000000001,
            )

    def _DistributedBanquetTable__cleanupPitcherAdvice(self):
        if self.pitcherAdviceLabel:
            self.pitcherAdviceLabel.destroy()
            self.pitcherAdviceLabel = None

        taskMgr.remove(self.pitcherAdviceName)

    def showExiting(self):
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = DirectLabel(
                relief=None,
                text=TTLocalizer.BossbotPitcherLeaving,
                pos=(1.05, 0, -0.88),
                text_pos=(0, 0),
                text_scale=0.059999999999999998,
                text_fg=VBase4(1, 1, 1, 1),
            )

        self._DistributedBanquetTable__cleanupPitcherAdvice()

    def _DistributedBanquetTable__exitPitcher(self):
        self.showExiting()
        self.d_requestFree(False)

    def _DistributedBanquetTable__controlPressed(self):
        self._DistributedBanquetTable__cleanupPitcherAdvice()
        if self.TugOfWarControls:
            if self.power:
                self.aimStart = 1
                self._DistributedBanquetTable__endFireWater()

        elif self.state == "Controlled":
            self._DistributedBanquetTable__beginFireWater()

    def _DistributedBanquetTable__controlReleased(self):
        if self.TugOfWarControls:
            pass
        1
        if self.state == "Controlled":
            self._DistributedBanquetTable__endFireWater()

    def _DistributedBanquetTable__upArrow(self, pressed):
        self._DistributedBanquetTable__incrementChangeSeq()
        self._DistributedBanquetTable__cleanupPitcherAdvice()
        if pressed:
            self.arrowVert = 1
        elif self.arrowVert > 0:
            self.arrowVert = 0

    def _DistributedBanquetTable__downArrow(self, pressed):
        self._DistributedBanquetTable__incrementChangeSeq()
        self._DistributedBanquetTable__cleanupPitcherAdvice()
        if pressed:
            self.arrowVert = -1
        elif self.arrowVert < 0:
            self.arrowVert = 0

    def _DistributedBanquetTable__rightArrow(self, pressed):
        self._DistributedBanquetTable__incrementChangeSeq()
        self._DistributedBanquetTable__cleanupPitcherAdvice()
        if pressed:
            self.arrowHorz = 1
        elif self.arrowHorz > 0:
            self.arrowHorz = 0

    def _DistributedBanquetTable__leftArrow(self, pressed):
        self._DistributedBanquetTable__incrementChangeSeq()
        self._DistributedBanquetTable__cleanupPitcherAdvice()
        if pressed:
            self.arrowHorz = -1
        elif self.arrowHorz < 0:
            self.arrowHorz = 0

    def _DistributedBanquetTable__incrementChangeSeq(self):
        self.changeSeq = self.changeSeq + 1 & 255

    def stopPosHprBroadcast(self):
        taskName = self.posHprBroadcastName
        taskMgr.remove(taskName)

    def startPosHprBroadcast(self):
        taskName = self.posHprBroadcastName
        self.b_clearSmoothing()
        self.d_sendPitcherPos()
        taskMgr.remove(taskName)
        taskMgr.doMethodLater(
            self._DistributedBanquetTable__broadcastPeriod, self._DistributedBanquetTable__posHprBroadcast, taskName
        )

    def _DistributedBanquetTable__posHprBroadcast(self, task):
        self.d_sendPitcherPos()
        taskName = self.posHprBroadcastName
        taskMgr.doMethodLater(
            self._DistributedBanquetTable__broadcastPeriod, self._DistributedBanquetTable__posHprBroadcast, taskName
        )
        return Task.done

    def d_sendPitcherPos(self):
        timestamp = globalClockDelta.getFrameNetworkTime()
        self.sendUpdate("setPitcherPos", [self.changeSeq, self.waterPitcherNode.getH(), timestamp])

    def setPitcherPos(self, changeSeq, h, timestamp):
        self.changeSeq = changeSeq
        if self.smoothStarted:
            now = globalClock.getFrameTime()
            local = globalClockDelta.networkToLocalTime(timestamp, now)
            self.pitcherSmoother.setH(h)
            self.pitcherSmoother.setTimestamp(local)
            self.pitcherSmoother.markPosition()
        else:
            self.waterPitcherNode.setH(h)

    def _DistributedBanquetTable__watchControls(self, task):
        if self.arrowHorz:
            self._DistributedBanquetTable__movePitcher(self.arrowHorz)
        else:
            self._DistributedBanquetTable__setMoveSound(None)
        return Task.cont

    def _DistributedBanquetTable__movePitcher(self, xd):
        dt = globalClock.getDt()
        h = self.waterPitcherNode.getH() - xd * self.rotateSpeed * dt
        h %= 360
        self.notify.debug(
            "rotSpeed=%.2f curH=%.2f  xd =%.2f, dt = %.2f, h=%.2f"
            % (self.rotateSpeed, self.waterPitcherNode.getH(), xd, dt, h)
        )
        limitH = h
        self.waterPitcherNode.setH(limitH)
        if xd:
            self._DistributedBanquetTable__setMoveSound(self.pitcherMoveSfx)

    def reloadPosition(self):
        self.pitcherSmoother.clearPositions(0)
        self.pitcherSmoother.setHpr(self.waterPitcherNode.getHpr())
        self.pitcherSmoother.setPhonyTimestamp()

    def forceToTruePosition(self):
        if self.pitcherSmoother.getLatestPosition():
            self.pitcherSmoother.applySmoothHpr(self.waterPitcherNode)

        self.pitcherSmoother.clearPositions(1)

    def getSprayTrack(
        self, color, origin, target, dScaleUp, dHold, dScaleDown, horizScale=1.0, vertScale=1.0, parent=render
    ):
        track = Sequence()
        SPRAY_LEN = 1.5
        sprayProp = MovieUtil.globalPropPool.getProp("spray")
        sprayScale = hidden.attachNewNode("spray-parent")
        sprayRot = hidden.attachNewNode("spray-rotate")
        spray = sprayRot
        spray.setColor(color)
        if color[3] < 1.0:
            spray.setTransparency(1)

        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(showSpray, sprayScale, sprayRot, sprayProp, origin, target, parent))

        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 * horizScale, yScale, yScale * vertScale)
            return targetScale

        track.append(LerpScaleInterval(sprayScale, dScaleUp, calcTargetScale, startScale=Point3(0.01, 0.01, 0.01)))
        track.append(Func(self.checkHitObject))
        track.append(Wait(dHold))

        def prepareToShrinkSpray(spray, sprayProp, origin, target):
            if callable(target):
                target = target()

            if callable(origin):
                origin = origin()

            sprayProp.setPos(Point3(0.0, -SPRAY_LEN, 0.0))
            spray.setPos(target)

        track.append(Func(prepareToShrinkSpray, spray, sprayProp, origin, target))
        track.append(LerpScaleInterval(sprayScale, dScaleDown, Point3(0.01, 0.01, 0.01)))

        def hideSpray(spray, sprayScale, sprayRot, sprayProp, propPool):
            sprayProp.detachNode()
            MovieUtil.removeProp(sprayProp)
            sprayRot.removeNode()
            sprayScale.removeNode()

        track.append(Func(hideSpray, spray, sprayScale, sprayRot, sprayProp, MovieUtil.globalPropPool))
        return track

    def checkHitObject(self):
        if not self.hitObject:
            return None

        if self.avId != base.localAvatar.doId:
            return None

        tag = self.hitObject.getNetTag("pieCode")
        pieCode = int(tag)
        if pieCode == ToontownGlobals.PieCodeBossCog:
            self.hitBossSoundInterval.start()
            self.sendUpdate("waterHitBoss", [self.index])
            if self.TugOfWarControls:
                damage = 1
                if self.lastPowerFired < self.YELLOW_POWER_THRESHOLD:
                    damage = 1
                elif self.lastPowerFired < self.RED_POWER_THRESHOLD:
                    damage = 2
                else:
                    damage = 3
                self.boss.d_hitBoss(damage)
            else:
                damage = 1
                if self.lastPowerFired < self.YELLOW_POWER_THRESHOLD:
                    damage = 1
                elif self.lastPowerFired < self.RED_POWER_THRESHOLD:
                    damage = 2
                else:
                    damage = 3
                self.boss.d_hitBoss(damage)

    def waterHitBoss(self, tableIndex):
        if self.index == tableIndex:
            self.hitBossSoundInterval.start()

    def setupPowerBar(self):
        self.powerBar = DirectWaitBar(
            pos=(0.0, 0, -0.93999999999999995),
            relief=DGG.SUNKEN,
            frameSize=(-2.0, 2.0, -0.20000000000000001, 0.20000000000000001),
            borderWidth=(0.02, 0.02),
            scale=0.25,
            range=1,
            sortOrder=50,
            frameColor=(0.5, 0.5, 0.5, 0.5),
            barColor=(0.75, 0.75, 1.0, 0.80000000000000004),
            text="",
            text_scale=0.26000000000000001,
            text_fg=(1, 1, 1, 1),
            text_align=TextNode.ACenter,
            text_pos=(0, -0.050000000000000003),
        )
        self.power = 0
        self.powerBar["value"] = self.power
        self.powerBar.hide()

    def resetPowerBar(self):
        self.power = 0
        self.powerBar["value"] = self.power
        self.powerBar["text"] = ""
        self.keyTTL = []

    def _DistributedBanquetTable__beginFireWater(self):
        if self.fireTrack and self.fireTrack.isPlaying():
            return None

        if self.aimStart != None:
            return None

        if not self.state == "Controlled":
            return None

        if not self.avId == localAvatar.doId:
            return None

        time = globalClock.getFrameTime()
        self.aimStart = time
        messenger.send("wakeup")
        taskMgr.add(self._DistributedBanquetTable__updateWaterPower, self.waterPowerTaskName)

    def _DistributedBanquetTable__endFireWater(self):
        if self.aimStart == None:
            return None

        if not self.state == "Controlled":
            return None

        if not self.avId == localAvatar.doId:
            return None

        taskMgr.remove(self.waterPowerTaskName)
        messenger.send("wakeup")
        self.aimStart = None
        origin = self.nozzle.getPos(render)
        target = self.boss.getPos(render)
        angle = deg2Rad(self.waterPitcherNode.getH() + 90)
        x = math.cos(angle)
        y = math.sin(angle)
        fireVector = Point3(x, y, 0)
        if self.power < 0.001:
            self.power = 0.001

        self.lastPowerFired = self.power
        fireVector *= self.fireLength * self.power
        target = origin + fireVector
        segment = CollisionSegment(origin[0], origin[1], origin[2], target[0], target[1], target[2])
        fromObject = render.attachNewNode(CollisionNode("pitcherColNode"))
        fromObject.node().addSolid(segment)
        fromObject.node().setFromCollideMask(
            ToontownGlobals.PieBitmask | ToontownGlobals.CameraBitmask | ToontownGlobals.FloorBitmask
        )
        fromObject.node().setIntoCollideMask(BitMask32.allOff())
        queue = CollisionHandlerQueue()
        base.cTrav.addCollider(fromObject, queue)
        base.cTrav.traverse(render)
        queue.sortEntries()
        self.hitObject = None
        if queue.getNumEntries():
            entry = queue.getEntry(0)
            target = entry.getSurfacePoint(render)
            self.hitObject = entry.getIntoNodePath()

        base.cTrav.removeCollider(fromObject)
        fromObject.removeNode()
        self.d_firingWater(origin, target)
        self.fireWater(origin, target)
        self.resetPowerBar()

    def _DistributedBanquetTable__updateWaterPower(self, task):
        if not self.powerBar:
            print "### no power bar!!!"
            return task.done

        newPower = self._DistributedBanquetTable__getWaterPower(globalClock.getFrameTime())
        self.power = newPower
        self.powerBar["value"] = newPower
        if self.power < self.YELLOW_POWER_THRESHOLD:
            self.powerBar["barColor"] = VBase4(0.75, 0.75, 1.0, 0.80000000000000004)
        elif self.power < self.RED_POWER_THRESHOLD:
            self.powerBar["barColor"] = VBase4(1.0, 1.0, 0.0, 0.80000000000000004)
        else:
            self.powerBar["barColor"] = VBase4(1.0, 0.0, 0.0, 0.80000000000000004)
        return task.cont

    def _DistributedBanquetTable__getWaterPower(self, time):
        elapsed = max(time - self.aimStart, 0.0)
        t = elapsed / self.waterPowerSpeed
        exponent = self.waterPowerExponent
        if t > 1:
            t = t % 1

        power = 1 - math.pow(1 - t, exponent)
        if power > 1.0:
            power = 1.0

        return power

    def d_firingWater(self, origin, target):
        self.sendUpdate("firingWater", [origin[0], origin[1], origin[2], target[0], target[1], target[2]])

    def firingWater(self, startX, startY, startZ, endX, endY, endZ):
        origin = Point3(startX, startY, startZ)
        target = Point3(endX, endY, endZ)
        self.fireWater(origin, target)

    def fireWater(self, origin, target):
        color = VBase4(0.75, 0.75, 1, 0.80000000000000004)
        dScaleUp = 0.10000000000000001
        dHold = 0.29999999999999999
        dScaleDown = 0.10000000000000001
        horizScale = 0.10000000000000001
        vertScale = 0.10000000000000001
        sprayTrack = self.getSprayTrack(color, origin, target, dScaleUp, dHold, dScaleDown, horizScale, vertScale)
        duration = self.squirtSfx.length()
        if sprayTrack.getDuration() < duration:
            duration = sprayTrack.getDuration()

        soundTrack = SoundInterval(self.squirtSfx, node=self.waterPitcherModel, duration=duration)
        self.fireTrack = Parallel(sprayTrack, soundTrack)
        self.fireTrack.start()

    def getPos(self, wrt=render):
        return self.tableGroup.getPos(wrt)

    def getLocator(self):
        return self.tableGroup

    def enterFlat(self, avId):
        self.prepareForPhaseFour()
        self.resetPowerBar()
        self.notify.debug("enterFlat %d" % self.index)
        if self.avId:
            toon = base.cr.doId2do.get(self.avId)
            if toon:
                toon.wrtReparentTo(render)
                toon.setZ(0)

        self.tableGroup.setScale(1, 1, 0.01)
        if self.avId and self.avId == localAvatar.doId:
            localAvatar.b_squish(ToontownGlobals.BossCogDamageLevels[ToontownGlobals.BossCogMoveAttack])

    def exitFlat(self):
        self.tableGroup.setScale(1.0)
        if self.avId:
            toon = base.cr.doId2do.get(self.avId)
            if toon:
                if toon == localAvatar:
                    self.boss.toCraneMode()
                    toon.b_setAnimState("neutral")

                toon.setAnimState("neutral")
                toon.loop("leverNeutral")

    def _DistributedBanquetTable__allowDetect(self, task):
        if self.fadeTrack:
            self.fadeTrack.finish()

        self.fadeTrack = Sequence(
            self.tableGroup.colorScaleInterval(0.20000000000000001, VBase4(1, 1, 1, 1)),
            Func(self.tableGroup.clearColorScale),
            Func(self.tableGroup.clearTransparency),
        )
        self.fadeTrack.start()
        self.allowLocalRequestControl = True

    def gotBossZapped(self):
        self.showExiting()
        self.d_requestFree(True)

    def _DistributedBanquetTable__upArrowKeyPressed(self):
        if self.TugOfWarControls:
            self._DistributedBanquetTable__pressHandler(0)

    def _DistributedBanquetTable__downArrowKeyPressed(self):
        if self.TugOfWarControls:
            self._DistributedBanquetTable__pressHandler(1)

    def _DistributedBanquetTable__pressHandler(self, index):
        if index == self.buttons[0]:
            self.keyTTL.insert(0, 1.0)
            if not self.OnlyUpArrow:
                self.buttons.reverse()

    def _DistributedBanquetTable__spawnUpdateKeyPressRateTask(self):
        taskMgr.remove(self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK))
        taskMgr.doMethodLater(
            0.10000000000000001,
            self._DistributedBanquetTable__updateKeyPressRateTask,
            self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK),
        )

    def _DistributedBanquetTable__killUpdateKeyPressRateTask(self):
        taskMgr.remove(self.taskName(self.UPDATE_KEY_PRESS_RATE_TASK))

    def _DistributedBanquetTable__updateKeyPressRateTask(self, task):
        if self.state not in "Controlled":
            return Task.done

        for i in range(len(self.keyTTL)):
            self.keyTTL[i] -= 0.10000000000000001

        for i in range(len(self.keyTTL)):
            if self.keyTTL[i] <= 0:
                a = self.keyTTL[0:i]
                del self.keyTTL
                self.keyTTL = a
                break
                continue

        self.keyRate = len(self.keyTTL)
        keyRateDiff = self.keyRate - self.BASELINE_KEY_RATE
        diffPower = keyRateDiff / 300.0
        if self.power < 1 and diffPower > 0:
            diffPower = diffPower * math.pow(1 - self.power, 1.25)

        newPower = self.power + diffPower
        if newPower > 1:
            newPower = 1
        elif newPower < 0:
            newPower = 0

        self.notify.debug("diffPower=%.2f keyRate = %d, newPower=%.2f" % (diffPower, self.keyRate, newPower))
        self.power = newPower
        self.powerBar["value"] = newPower
        if self.power < self.YELLOW_POWER_THRESHOLD:
            self.powerBar["barColor"] = VBase4(0.75, 0.75, 1.0, 0.80000000000000004)
        elif self.power < self.RED_POWER_THRESHOLD:
            self.powerBar["barColor"] = VBase4(1.0, 1.0, 0.0, 0.80000000000000004)
        else:
            self.powerBar["barColor"] = VBase4(1.0, 0.0, 0.0, 0.80000000000000004)
        self._DistributedBanquetTable__spawnUpdateKeyPressRateTask()
        return Task.done

    def _DistributedBanquetTable__setMoveSound(self, sfx):
        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)
class DistributedGolfSpot(DistributedObject.DistributedObject, FSM.FSM):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGolfSpot')
    positions = ((-45, 100, GolfGlobals.GOLF_BALL_RADIUS),
     (-15, 100, GolfGlobals.GOLF_BALL_RADIUS),
     (15, 100, GolfGlobals.GOLF_BALL_RADIUS),
     (45, 100, GolfGlobals.GOLF_BALL_RADIUS))
    toonGolfOffsetPos = Point3(-2, 0, -GolfGlobals.GOLF_BALL_RADIUS)
    toonGolfOffsetHpr = Point3(-90, 0, 0)
    rotateSpeed = 20
    golfPowerSpeed = base.config.GetDouble('golf-power-speed', 3)
    golfPowerExponent = base.config.GetDouble('golf-power-exponent', 0.75)

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        FSM.FSM.__init__(self, 'DistributedGolfSpot')
        self.boss = None
        self.index = None
        self.avId = 0
        self.toon = None
        self.golfSpotSmoother = SmoothMover()
        self.golfSpotSmoother.setSmoothMode(SmoothMover.SMOn)
        self.smoothStarted = 0
        self.__broadcastPeriod = 0.2
        if self.index > len(self.positions):
            self.notify.error('Invalid index %d' % index)
        self.fadeTrack = None
        self.setupPowerBar()
        self.aimStart = None
        self.golfSpotAdviceLabel = None
        self.changeSeq = 0
        self.lastChangeSeq = 0
        self.controlKeyAllowed = False
        self.flyBallTracks = {}
        self.splatTracks = {}
        self.__flyBallBubble = None
        self.flyBallHandler = None
        self.__flyBallSequenceNum = 0
        self.swingInterval = None
        self.lastHitSequenceNum = -1
        self.goingToReward = False
        self.gotHitByBoss = False
        self.releaseTrack = None
        self.grabTrack = None
        self.restoreScaleTrack = None
        return

    def setBossCogId(self, bossCogId):
        self.bossCogId = bossCogId
        self.boss = base.cr.doId2do[bossCogId]
        self.boss.setGolfSpot(self, self.index)

    def setIndex(self, index):
        self.index = index

    def disable(self):
        DistributedObject.DistributedObject.disable(self)
        self.ignoreAll()

    def delete(self):
        DistributedObject.DistributedObject.delete(self)
        self.ignoreAll()
        self.boss = None
        return

    def announceGenerate(self):
        DistributedObject.DistributedObject.announceGenerate(self)
        self.triggerName = self.uniqueName('trigger')
        self.triggerEvent = 'enter%s' % self.triggerName
        self.smoothName = self.uniqueName('golfSpotSmooth')
        self.golfSpotAdviceName = self.uniqueName('golfSpotAdvice')
        self.posHprBroadcastName = self.uniqueName('golfSpotBroadcast')
        self.ballPowerTaskName = self.uniqueName('updateGolfPower')
        self.adjustClubTaskName = self.uniqueName('adjustClub')
        self.loadAssets()
        self.accept('flyBallHit-%d' % self.index, self.__flyBallHit)

    def loadAssets(self):
        self.root = render.attachNewNode('golfSpot-%d' % self.index)
        self.root.setPos(*self.positions[self.index])
        self.ballModel = loader.loadModel('phase_6/models/golf/golf_ball')
        self.ballColor = VBase4(1, 1, 1, 1)
        if self.index < len(GolfGlobals.PlayerColors):
            self.ballColor = VBase4(*GolfGlobals.PlayerColors[self.index])
            self.ballModel.setColorScale(self.ballColor)
        self.ballModel.reparentTo(self.root)
        self.club = loader.loadModel('phase_6/models/golf/putter')
        self.clubLookatSpot = self.root.attachNewNode('clubLookat')
        self.clubLookatSpot.setY(-(GolfGlobals.GOLF_BALL_RADIUS + 0.1))
        cs = CollisionSphere(0, 0, 0, 1)
        cs.setTangible(0)
        cn = CollisionNode(self.triggerName)
        cn.addSolid(cs)
        cn.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.trigger = self.root.attachNewNode(cn)
        self.trigger.stash()
        self.hitBallSfx = loader.loadSfx('phase_6/audio/sfx/Golf_Hit_Ball.ogg')

    def cleanup(self):
        if self.swingInterval:
            self.swingInterval.finish()
            self.swingInterval = None
        if self.releaseTrack:
            self.releaseTrack.finish()
            self.releaseTrack = None
        flyTracks = self.flyBallTracks.values()
        for track in flyTracks:
            track.finish()

        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None
        if self.restoreScaleTrack:
            self.restoreScaleTrack.finish()
            self.restoreScaleTrack = None
        self.root.removeNode()
        self.ballModel.removeNode()
        self.club.removeNode()
        if self.powerBar:
            self.powerBar.destroy()
            self.powerBar = None
        taskMgr.remove(self.triggerName)
        self.boss = None
        return

    def setState(self, state, avId, extraInfo):
        if not self.isDisabled():
            self.gotHitByBoss = extraInfo
            if state == 'C':
                self.demand('Controlled', avId)
            elif state == 'F':
                self.demand('Free')
            elif state == 'O':
                self.demand('Off')
            else:
                self.notify.error('Invalid state from AI: %s' % state)

    def enterOff(self):
        pass

    def exitOff(self):
        pass

    def enterFree(self):
        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None
        self.restoreScaleTrack = Sequence(Wait(6), self.getRestoreScaleInterval(), name='restoreScaleTrack')
        self.restoreScaleTrack.start()
        if self.avId == localAvatar.doId:
            if not self.isDisabled():
                self.ballModel.setAlphaScale(0.3)
                self.ballModel.setTransparency(1)
                taskMgr.doMethodLater(5, self.__allowDetect, self.triggerName)
                self.fadeTrack = Sequence(Func(self.ballModel.setTransparency, 1), self.ballModel.colorScaleInterval(0.2, VBase4(1, 1, 1, 0.3)), name='fadeTrack-enterFree')
                self.fadeTrack.start()
        else:
            self.trigger.unstash()
            self.accept(self.triggerEvent, self.__hitTrigger)
        self.avId = 0
        return

    def exitFree(self):
        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None
        self.restoreScaleTrack.finish()
        self.restoreScaleTrack = None
        taskMgr.remove(self.triggerName)
        self.ballModel.clearTransparency()
        self.trigger.stash()
        self.ignore(self.triggerEvent)
        return

    def enterControlled(self, avId):
        self.avId = avId
        toon = base.cr.doId2do.get(avId)
        if not toon:
            return
        self.enableControlKey()
        self.toon = toon
        self.grabTrack = self.makeToonGrabInterval(toon)
        if avId == localAvatar.doId:
            self.boss.toCraneMode()
            camera.reparentTo(self.root)
            camera.setPosHpr(0, -10, 3, 0, 0, 0)
            localAvatar.setPos(self.root, self.toonGolfOffsetPos)
            localAvatar.setHpr(self.root, self.toonGolfOffsetHpr)
            localAvatar.sendCurrentPosition()
            self.__enableControlInterface()
            self.startPosHprBroadcast()
            self.accept('exitCrane', self.gotBossZapped)
        self.grabTrack.start()

    def exitControlled(self):
        self.grabTrack.finish()
        del self.grabTrack
        if self.swingInterval:
            self.swingInterval.finish()
            self.swingInterval = None
        if not self.ballModel.isEmpty():
            if self.ballModel.isHidden():
                self.notify.debug('ball is hidden scale =%s' % self.ballModel.getScale())
            else:
                self.notify.debug('ball is showing scale=%s' % self.ballModel.getScale())
        if self.toon and not self.toon.isDisabled():
            self.toon.startSmooth()
        self.releaseTrack = self.makeToonReleaseInterval(self.toon)
        self.stopPosHprBroadcast()
        self.stopSmooth()
        if self.avId == localAvatar.doId:
            self.__disableControlInterface()
            if not self.goingToReward:
                camera.reparentTo(base.localAvatar)
                camera.setPos(base.localAvatar.cameraPositions[0][0])
                camera.setHpr(0, 0, 0)
        self.stopAdjustClubTask()
        self.releaseTrack.start()
        self.enableControlKey()
        return

    def __allowDetect(self, task):
        if self.fadeTrack:
            self.fadeTrack.finish()
        self.fadeTrack = Sequence(self.ballModel.colorScaleInterval(0.2, self.ballColor), Func(self.ballModel.clearTransparency), name='fadeTrack-allowDetect')
        self.fadeTrack.start()
        self.trigger.unstash()
        self.accept(self.triggerEvent, self.__hitTrigger)

    def __hitTrigger(self, event):
        self.d_requestControl()

    def getRestoreScaleInterval(self):
        return Sequence()

    def d_requestControl(self):
        self.sendUpdate('requestControl')

    def d_requestFree(self, gotHitByBoss):
        self.sendUpdate('requestFree', [gotHitByBoss])

    def makeToonGrabInterval(self, toon):
        origPos = toon.getPos(self.root)
        origHpr = toon.getHpr(self.root)
        a = self.accomodateToon(toon)
        newPos = toon.getPos()
        newHpr = toon.getHpr()
        origHpr.setX(PythonUtil.fitSrcAngle2Dest(origHpr[0], newHpr[0]))
        self.notify.debug('toon.setPosHpr %s %s' % (origPos, origHpr))
        toon.setPosHpr(origPos, origHpr)
        walkTime = 0.2
        reach = Sequence()
        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(toon.stopLookAround))
        if toon == base.localAvatar:
            i.append(Func(self.switchToAnimState, 'GolfPuttLoop'))
        i.append(Func(self.startAdjustClubTask))
        i = Parallel(i, a)
        return i

    def accomodateToon(self, toon):
        toon.wrtReparentTo(self.root)
        toon.setPos(self.toonGolfOffsetPos)
        toon.setHpr(self.toonGolfOffsetHpr)
        return Sequence()

    def switchToAnimState(self, animStateName, forced = False):
        curAnimState = base.localAvatar.animFSM.getCurrentState()
        curAnimStateName = ''
        if curAnimState:
            curAnimStateName = curAnimState.getName()
        if curAnimStateName != animStateName or forced:
            base.localAvatar.b_setAnimState(animStateName)

    def __enableControlInterface(self):
        gui = loader.loadModel('phase_3.5/models/gui/avatar_panel_gui')
        self.closeButton = DirectButton(image=(gui.find('**/CloseBtn_UP'),
         gui.find('**/CloseBtn_DN'),
         gui.find('**/CloseBtn_Rllvr'),
         gui.find('**/CloseBtn_UP')), relief=None, scale=2, text=TTLocalizer.BossbotGolfSpotLeave, text_scale=0.04, text_pos=(0, -0.07), text_fg=VBase4(1, 1, 1, 1), pos=(1.05, 0, -0.82), command=self.__exitGolfSpot)
        self.accept('escape', self.__exitGolfSpot)
        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, 'watchGolfSpotControls')
        taskMgr.doMethodLater(5, self.__displayGolfSpotAdvice, self.golfSpotAdviceName)
        self.arrowVert = 0
        self.arrowHorz = 0
        if self.powerBar:
            self.powerBar.show()
        return

    def __disableControlInterface(self):
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = None
        self.__cleanupGolfSpotAdvice()
        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
        taskMgr.remove('watchGolfSpotControls')
        if self.powerBar:
            self.powerBar.hide()
        else:
            self.notify.debug('self.powerBar is none')
        return

    def setupPowerBar(self):
        self.powerBar = DirectWaitBar(pos=(0.0, 0, -0.94), relief=DGG.SUNKEN, frameSize=(-2.0,
         2.0,
         -0.2,
         0.2), borderWidth=(0.02, 0.02), scale=0.25, range=100, sortOrder=50, frameColor=(0.5, 0.5, 0.5, 0.5), barColor=(1.0, 0.0, 0.0, 1.0), text='', text_scale=0.26, text_fg=(1, 1, 1, 1), text_align=TextNode.ACenter, text_pos=(0, -0.05))
        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar.hide()

    def resetPowerBar(self):
        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar['text'] = ''

    def __displayGolfSpotAdvice(self, task):
        if self.golfSpotAdviceLabel == None:
            self.golfSpotAdviceLabel = DirectLabel(text=TTLocalizer.BossbotGolfSpotAdvice, text_fg=VBase4(1, 1, 1, 1), text_align=TextNode.ACenter, relief=None, pos=(0, 0, 0.69), scale=0.1)
        return

    def __cleanupGolfSpotAdvice(self):
        if self.golfSpotAdviceLabel:
            self.golfSpotAdviceLabel.destroy()
            self.golfSpotAdviceLabel = None
        taskMgr.remove(self.golfSpotAdviceName)
        return

    def showExiting(self):
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = DirectLabel(relief=None, text=TTLocalizer.BossbotGolfSpotLeaving, pos=(1.05, 0, -0.88), text_pos=(0, 0), text_scale=0.06, text_fg=VBase4(1, 1, 1, 1))
        self.__cleanupGolfSpotAdvice()
        return

    def __exitGolfSpot(self):
        self.d_requestFree(False)

    def __controlPressed(self):
        if self.controlKeyAllowed:
            self.__beginFireBall()

    def __controlReleased(self):
        if self.controlKeyAllowed:
            self.__endFireBall()

    def __upArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowVert = 1
        elif self.arrowVert > 0:
            self.arrowVert = 0

    def __downArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowVert = -1
        elif self.arrowVert < 0:
            self.arrowVert = 0

    def __rightArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowHorz = 1
            self.switchToAnimState('GolfRotateLeft')
        elif self.arrowHorz > 0:
            self.arrowHorz = 0
            self.switchToAnimState('GolfPuttLoop')

    def __leftArrow(self, pressed):
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowHorz = -1
            self.switchToAnimState('GolfRotateRight')
        elif self.arrowHorz < 0:
            self.arrowHorz = 0
            self.switchToAnimState('GolfPuttLoop')

    def __watchControls(self, task):
        if self.arrowHorz:
            self.__moveGolfSpot(self.arrowHorz)
        return Task.cont

    def __moveGolfSpot(self, xd):
        dt = globalClock.getDt()
        h = self.root.getH() - xd * self.rotateSpeed * dt
        h %= 360
        limitH = h
        self.root.setH(limitH)

    def __incrementChangeSeq(self):
        self.changeSeq = self.changeSeq + 1 & 255

    def __beginFireBall(self):
        if self.aimStart != None:
            return
        if not self.state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        time = globalClock.getFrameTime()
        self.aimStart = time
        messenger.send('wakeup')
        taskMgr.add(self.__updateBallPower, self.ballPowerTaskName)
        return

    def __endFireBall(self):
        if self.aimStart == None:
            return
        if not self.state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        taskMgr.remove(self.ballPowerTaskName)
        self.disableControlKey()
        messenger.send('wakeup')
        self.aimStart = None
        power = self.power
        angle = self.root.getH()
        self.notify.debug('incrementing self.__flyBallSequenceNum')
        self.__flyBallSequenceNum = (self.__flyBallSequenceNum + 1) % 255
        self.sendSwingInfo(power, angle, self.__flyBallSequenceNum)
        self.setSwingInfo(power, angle, self.__flyBallSequenceNum)
        self.resetPowerBar()
        return

    def __updateBallPower(self, task):
        if not self.powerBar:
            print '### no power bar!!!'
            return task.done
        newPower = self.__getBallPower(globalClock.getFrameTime())
        self.power = newPower
        self.powerBar['value'] = newPower
        return task.cont

    def __getBallPower(self, time):
        elapsed = max(time - self.aimStart, 0.0)
        t = elapsed / self.golfPowerSpeed
        t = math.pow(t, self.golfPowerExponent)
        power = int(t * 100) % 200
        if power > 100:
            power = 200 - power
        return power

    def stopPosHprBroadcast(self):
        taskName = self.posHprBroadcastName
        taskMgr.remove(taskName)

    def startPosHprBroadcast(self):
        taskName = self.posHprBroadcastName
        self.b_clearSmoothing()
        self.d_sendGolfSpotPos()
        taskMgr.remove(taskName)
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast, taskName)

    def __posHprBroadcast(self, task):
        self.d_sendGolfSpotPos()
        taskName = self.posHprBroadcastName
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast, taskName)
        return Task.done

    def d_sendGolfSpotPos(self):
        timestamp = globalClockDelta.getFrameNetworkTime()
        self.sendUpdate('setGolfSpotPos', [self.changeSeq, self.root.getH(), timestamp])

    def setGolfSpotPos(self, changeSeq, h, timestamp):
        self.changeSeq = changeSeq
        if self.smoothStarted:
            now = globalClock.getFrameTime()
            local = globalClockDelta.networkToLocalTime(timestamp, now)
            self.golfSpotSmoother.setH(h)
            self.golfSpotSmoother.setTimestamp(local)
            self.golfSpotSmoother.markPosition()
        else:
            self.root.setH(h)

    def b_clearSmoothing(self):
        self.d_clearSmoothing()
        self.clearSmoothing()

    def d_clearSmoothing(self):
        self.sendUpdate('clearSmoothing', [0])

    def clearSmoothing(self, bogus = None):
        self.golfSpotSmoother.clearPositions(1)

    def doSmoothTask(self, task):
        self.golfSpotSmoother.computeAndApplySmoothHpr(self.root)
        return Task.cont

    def startSmooth(self):
        if not self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.reloadPosition()
            taskMgr.add(self.doSmoothTask, taskName)
            self.smoothStarted = 1

    def stopSmooth(self):
        if self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.forceToTruePosition()
            self.smoothStarted = 0

    def makeToonReleaseInterval(self, toon):

        def getSlideToPos(toon = toon):
            return render.getRelativePoint(toon, Point3(0, -5, 0))

        if self.gotHitByBoss:
            grabIval = Sequence(Func(self.detachClub), name='makeToonReleaseInterval-gotHitByBoss')
            if not toon.isEmpty():
                toonIval = Sequence(Func(toon.wrtReparentTo, render), Parallel(ActorInterval(toon, 'slip-backward'), toon.posInterval(0.5, getSlideToPos, fluid=1)), name='makeToonReleaseInterval-toonIval')
                grabIval.append(toonIval)
        else:
            grabIval = Sequence(Func(self.detachClub))
            if not toon.isEmpty():
                toonIval = Sequence(Parallel(ActorInterval(toon, 'walk', duration=1.0, playRate=-1.0), LerpPosInterval(toon, duration=1.0, pos=Point3(-10, 0, 0))), Func(toon.wrtReparentTo, render))
                grabIval.append(toonIval)
        if localAvatar.doId == toon.doId:
            if not self.goingToReward and toon.hp > 0:
                grabIval.append(Func(self.goToFinalBattle))
                grabIval.append(Func(self.notify.debug, 'goingToFinalBattlemode'))
                grabIval.append(Func(self.safeBossToFinalBattleMode))
        return grabIval

    def safeBossToFinalBattleMode(self):
        if self.boss:
            self.boss.toFinalBattleMode()

    def goToFinalBattle(self):
        if self.cr:
            place = self.cr.playGame.getPlace()
            if place and hasattr(place, 'fsm'):
                curState = place.fsm.getCurrentState().getName()
                if place.fsm.getCurrentState().getName() == 'crane':
                    place.setState('finalBattle')
                else:
                    self.notify.debug('NOT going to final battle, state=%s' % curState)

    def attachClub(self, avId, pointToBall = False):
        club = self.club
        if club:
            av = base.cr.doId2do.get(avId)
            if av:
                av.useLOD(1000)
                lHand = av.getLeftHands()[0]
                club.setPos(0, 0, 0)
                club.reparentTo(lHand)
                netScale = club.getNetTransform().getScale()[1]
                counterActToonScale = lHand.find('**/counteractToonScale')
                if counterActToonScale.isEmpty():
                    counterActToonScale = lHand.attachNewNode('counteractToonScale')
                    counterActToonScale.setScale(1 / netScale)
                    self.notify.debug('creating counterActToonScale for %s' % av.getName())
                club.reparentTo(counterActToonScale)
                club.setX(-0.25 * netScale)
                if pointToBall:
                    club.lookAt(self.clubLookatSpot)

    def detachClub(self):
        if not self.club.isEmpty():
            self.club.reparentTo(self.root)
            self.club.setZ(-20)
            self.club.setScale(1)

    def adjustClub(self):
        club = self.club
        if club:
            distance = club.getDistance(self.clubLookatSpot)
            scaleFactor = distance / 2.058
            club.setScale(1, scaleFactor, 1)

    def startAdjustClubTask(self):
        taskMgr.add(self.adjustClubTask, self.adjustClubTaskName)

    def stopAdjustClubTask(self):
        taskMgr.remove(self.adjustClubTaskName)

    def adjustClubTask(self, task):
        self.attachClub(self.avId, True)
        self.adjustClub()
        return task.cont

    def enableControlKey(self):
        self.controlKeyAllowed = True

    def disableControlKey(self):
        self.controlKeyAllowed = False

    def sendSwingInfo(self, power, angle, sequenceNum):
        self.sendUpdate('setSwingInfo', [power, angle, sequenceNum])

    def startBallPlayback(self, power, angle, sequenceNum):
        flyBall = self.ballModel.copyTo(NodePath())
        flyBall.setScale(1.0)
        flyBallBubble = self.getFlyBallBubble().instanceTo(NodePath())
        flyBallBubble.reparentTo(flyBall)
        flyBall.setTag('pieSequence', str(sequenceNum))
        flyBall.setTag('throwerId', str(self.avId))
        t = power / 100.0
        t = 1.0 - t
        dist = 300 - 200 * t
        time = 1.5 + 0.5 * t
        proj = ProjectileInterval(None, startPos=Point3(0, 0, 0), endPos=Point3(0, dist, 0), duration=time)
        relVel = proj.startVel

        def getVelocity(root = self.root, relVel = relVel):
            return render.getRelativeVector(root, relVel)

        fly = Sequence(Func(flyBall.reparentTo, render), Func(flyBall.setPosHpr, self.root, 0, 0, 0, 0, 0, 0), Func(base.cTrav.addCollider, flyBallBubble, self.flyBallHandler), ProjectileInterval(flyBall, startVel=getVelocity, duration=3), Func(flyBall.detachNode), Func(base.cTrav.removeCollider, flyBallBubble), Func(self.notify.debug, 'removed collider'), Func(self.flyBallFinishedFlying, sequenceNum))
        flyWithSound = Parallel(fly, SoundInterval(self.hitBallSfx, node=self.root), name='flyWithSound')
        self.notify.debug('starting flyball track')
        flyWithSound.start()
        self.flyBallTracks[sequenceNum] = flyWithSound
        return

    def setSwingInfo(self, power, angle, sequenceNum):
        av = base.cr.doId2do.get(self.avId)
        self.swingInterval = Sequence()
        if av:
            self.stopAdjustClubTask()
            self.swingInterval = Sequence(ActorInterval(av, 'swing-putt', startFrame=0, endFrame=GolfGlobals.BALL_CONTACT_FRAME), Func(self.startBallPlayback, power, angle, sequenceNum), Func(self.ballModel.hide), ActorInterval(av, 'swing-putt', startFrame=GolfGlobals.BALL_CONTACT_FRAME, endFrame=24), Func(self.ballModel.setScale, 0.1), Func(self.ballModel.show), LerpScaleInterval(self.ballModel, 1.0, Point3(1, 1, 1)), Func(self.enableControlKey))
            if av == localAvatar:
                self.swingInterval.append(Func(self.switchToAnimState, 'GolfPuttLoop', True))
        self.swingInterval.start()

    def getFlyBallBubble(self):
        if self.__flyBallBubble == None:
            bubble = CollisionSphere(0, 0, 0, GolfGlobals.GOLF_BALL_RADIUS)
            node = CollisionNode('flyBallBubble')
            node.addSolid(bubble)
            node.setFromCollideMask(ToontownGlobals.PieBitmask | ToontownGlobals.CameraBitmask | ToontownGlobals.FloorBitmask)
            node.setIntoCollideMask(BitMask32.allOff())
            self.__flyBallBubble = NodePath(node)
            self.flyBallHandler = CollisionHandlerEvent()
            self.flyBallHandler.addInPattern('flyBallHit-%d' % self.index)
        return self.__flyBallBubble

    def __flyBallHit(self, entry):
        print entry

    def flyBallFinishedFlying(self, sequence):
        if self.flyBallTracks.has_key(sequence):
            del self.flyBallTracks[sequence]

    def __finishFlyBallTrack(self, sequence):
        if self.flyBallTracks.has_key(sequence):
            flyBallTrack = self.flyBallTracks[sequence]
            del self.flyBallTracks[sequence]
            flyBallTrack.finish()

    def flyBallFinishedSplatting(self, sequence):
        if self.splatTracks.has_key(sequence):
            del self.splatTracks[sequence]

    def __flyBallHit(self, entry):
        if not entry.hasSurfacePoint() or not entry.hasInto():
            return
        if not entry.getInto().isTangible():
            return
        sequence = int(entry.getFromNodePath().getNetTag('pieSequence'))
        self.__finishFlyBallTrack(sequence)
        if self.splatTracks.has_key(sequence):
            splatTrack = self.splatTracks[sequence]
            del self.splatTracks[sequence]
            splatTrack.finish()
        flyBallCode = 0
        flyBallCodeStr = entry.getIntoNodePath().getNetTag('pieCode')
        if flyBallCodeStr:
            flyBallCode = int(flyBallCodeStr)
        pos = entry.getSurfacePoint(render)
        timestamp32 = globalClockDelta.getFrameNetworkTime(bits=32)
        throwerId = int(entry.getFromNodePath().getNetTag('throwerId'))
        splat = self.getFlyBallSplatInterval(pos[0], pos[1], pos[2], flyBallCode, throwerId)
        splat = Sequence(splat, Func(self.flyBallFinishedSplatting, sequence))
        self.splatTracks[sequence] = splat
        splat.start()
        self.notify.debug('doId=%d into=%s flyBallCode=%d, throwerId=%d' % (self.doId,
         entry.getIntoNodePath(),
         flyBallCode,
         throwerId))
        if flyBallCode == ToontownGlobals.PieCodeBossCog and self.avId == localAvatar.doId and self.lastHitSequenceNum != self.__flyBallSequenceNum:
            self.lastHitSequenceNum = self.__flyBallSequenceNum
            self.boss.d_ballHitBoss(1)
        elif flyBallCode == ToontownGlobals.PieCodeToon and self.avId == localAvatar.doId and self.lastHitSequenceNum != self.__flyBallSequenceNum:
            self.lastHitSequenceNum = self.__flyBallSequenceNum
            avatarDoId = entry.getIntoNodePath().getNetTag('avatarDoId')
            if avatarDoId == '':
                self.notify.warning('Toon %s has no avatarDoId tag.' % repr(entry.getIntoNodePath()))
                return
            doId = int(avatarDoId)
            if doId != localAvatar.doId:
                pass

    def getFlyBallSplatInterval(self, x, y, z, flyBallCode, throwerId):
        from toontown.toonbase import ToontownBattleGlobals
        from toontown.battle import BattleProps
        splatName = 'dust'
        splat = BattleProps.globalPropPool.getProp(splatName)
        splat.setBillboardPointWorld(2)
        color = ToontownGlobals.PieCodeColors.get(flyBallCode)
        if color:
            splat.setColor(*color)
        if flyBallCode == ToontownGlobals.PieCodeBossCog:
            self.notify.debug('changing color to %s' % self.ballColor)
            splat.setColor(self.ballColor)
        sound = loader.loadSfx('phase_11/audio/sfx/LB_evidence_miss.ogg')
        vol = 1.0
        if flyBallCode == ToontownGlobals.PieCodeBossCog:
            sound = loader.loadSfx('phase_4/audio/sfx/Golf_Hit_Barrier_1.ogg')
        soundIval = SoundInterval(sound, node=splat, volume=vol)
        if flyBallCode == ToontownGlobals.PieCodeBossCog and localAvatar.doId == throwerId:
            vol = 1.0
            soundIval = SoundInterval(sound, node=localAvatar, volume=vol)
        ival = Parallel(Func(splat.reparentTo, render), Func(splat.setPos, x, y, z), soundIval, Sequence(ActorInterval(splat, splatName), Func(splat.detachNode)))
        return ival

    def setGoingToReward(self):
        self.goingToReward = True

    def gotBossZapped(self):
        self.showExiting()
        self.d_requestFree(True)
class DistributedCannonDefenseShip(DistributedNPCSimpleShip):
    __module__ = __name__
    specialHitSfx = {}
    coldShotHitSfx = None
    sharkChompSfx = {}

    def __init__(self, cr):
        DistributedNPCSimpleShip.__init__(self, cr)
        self.goldStolenlbl = None
        self.hasGoldlbl = None
        self.hasBNote = None
        self.textureCard = None
        self.goldIcon = None
        self.flameEffects = []
        self.isSinkingWhileOnFire = False
        self.healthModifier = 0
        self.modifierSet = False
        self.shipStatsSet = False
        self.shipStatIndex = None
        self.initHealthBar()
        self.initIndicatorIcons()
        self.sinkTimeScale = CannonDefenseGlobals.SHIP_SINK_DURATION_SCALE
        self.sharkActor = Actor(
            'models/char/pir_r_gam_fsh_lgComTshark.bam',
            {'attack': 'models/char/pir_a_gam_fsh_lgComTshark_attack.bam'})
        self.sharkParallel = None
        self.fader = None
        if not self.coldShotHitSfx:
            DistributedCannonDefenseShip.specialHitSfx = {
                InventoryType.DefenseCannonMineInWater:
                loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_MINE_HIT),
                InventoryType.DefenseCannonBomb:
                loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_BOMB_HIT),
                InventoryType.DefenseCannonHotShot:
                loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_HOTSHOT_HIT),
                InventoryType.DefenseCannonFireStorm:
                loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_FIRESTORM_HIT),
                InventoryType.DefenseCannonChumShot:
                loadSfx(SoundGlobals.SFX_MINIGAME_CANNON_SHARK)
            }
            DistributedCannonDefenseShip.coldShotHitSfx = loadSfx(
                SoundGlobals.SFX_MINIGAME_CANNON_ICE_HIT)
            DistributedCannonDefenseShip.sharkChompSfxs = [
                loadSfx(SoundGlobals.SFX_MONSTER_SMASH_01),
                loadSfx(SoundGlobals.SFX_MONSTER_SMASH_02),
                loadSfx(SoundGlobals.SFX_MONSTER_SMASH_03)
            ]
        return

    def buildShip(self):
        DistributedNPCSimpleShip.buildShip(self)
        self.model.sfxAlternativeStyle = True

    def setupLocalStats(self):
        DistributedNPCSimpleShip.setupLocalStats(self)

    def setShipStatIndex(self, statIndex):
        self.shipStatsSet = True
        self.shipStatIndex = statIndex
        self.maxHp = CannonDefenseGlobals.shipStats[
            self.shipStatIndex]['shipHp']
        self.maxMastHealth = CannonDefenseGlobals.shipStats[
            self.shipStatIndex]['mastHp']
        self.healthBar.setPos(
            0.0, 0.0, CannonDefenseGlobals.shipStats[self.shipStatIndex]
            ['healthBarHeight'])
        if self.modifierSet:
            self.calcModifiedHealth()

    def setHealthModifier(self, modifier):
        self.modifierSet = True
        self.healthModifier = modifier
        if self.shipStatsSet:
            self.calcModifiedHealth()

    def calcModifiedHealth(self):
        if self.healthModifier == 0:
            return
        self.maxHp += self.healthModifier * (
            self.maxHp * CannonDefenseGlobals.ENEMY_DIFFICULTY_INCREASE)
        mastList = CannonDefenseGlobals.shipStats[self.shipStatIndex]['mastHp']
        mastFinalHp = []
        for mastHealth in mastList:
            hp = mastHealth
            hp += self.healthModifier * (
                mastHealth * CannonDefenseGlobals.ENEMY_DIFFICULTY_INCREASE)
            mastFinalHp.append(hp)

        self.maxMastHealth = mastFinalHp

    def setLogo(self, logo):
        self.logo = logo

    def setStyle(self, style):
        self.style = style

    def announceGenerate(self):
        DistributedNPCSimpleShip.announceGenerate(self)
        rad = (self.model.dimensions / 2.0).length()
        cn = NodePath(CollisionNode('c'))
        cs = CollisionSphere(0, 0, 0, rad)
        cn.node().addSolid(cs)
        cn.reparentTo(self.model.modelCollisions)
        cn.setTransform(self.model.center.getTransform(self))
        cn.node().setIntoCollideMask(PiratesGlobals.GenericShipBitmask)
        self.model.defendSphere = cn
        self.model.modelRoot.setScale(CannonDefenseGlobals.SHIP_SCALE)
        self.fadeIn(CannonDefenseGlobals.SHIP_FADEIN)
        self.smoother.setExpectedBroadcastPeriod(0.3)
        self.smoother.setDelay(2)
        self.healthBar.reparentTo(self.model.modelRoot)

    def initHealthBar(self):
        self.healthBar = DirectWaitBar(frameSize=(-0.35, 0.35, -0.04, 0.04),
                                       relief=DGG.FLAT,
                                       frameColor=(0.0, 0.0, 0.0, 0.0),
                                       borderWidth=(0.0, 0.0),
                                       barColor=(0.0, 1.0, 0.0, 1.0),
                                       scale=100)
        self.healthBar.setBillboardPointEye()
        self.healthBar['value'] = 100
        self.healthBar.hide(OTPRender.ReflectionCameraBitmask)
        self.healthBar.setLightOff()

    def initIndicatorIcons(self):
        self.textureCard = loader.loadModel(
            'models/textureCards/pir_m_gam_can_ship_icons')
        if self.textureCard:
            self.goldIcon = self.textureCard.find('**/pir_t_shp_can_gold*')
        self.goldStolenlbl = DirectLabel(
            parent=self.healthBar,
            relief=None,
            pos=(0, 0, 0.2),
            text_align=TextNode.ACenter,
            text_scale=0.5,
            textMayChange=0,
            text='!',
            text_fg=PiratesGuiGlobals.TextFG23,
            text_shadow=PiratesGuiGlobals.TextShadow,
            text_font=PiratesGlobals.getPirateBoldOutlineFont(),
            sortOrder=2)
        self.goldStolenlbl.setTransparency(1)
        self.goldStolenlbl.hide()
        self.hasGoldlbl = DirectLabel(parent=self.healthBar,
                                      relief=None,
                                      pos=(0, 0, 0.35),
                                      image=self.goldIcon,
                                      image_scale=0.5,
                                      image_pos=(0, 0, 0),
                                      sortOrder=2)
        self.hasGoldlbl.setTransparency(1)
        self.hasGoldlbl.hide()
        self.hasBNote = self.healthBar.attachNewNode('noteIndicator')
        self.bnoteBack = loader.loadModel(
            'models/textureCards/skillIcons').find(
                '**/pir_t_gui_can_moneyIcon').copyTo(self.hasBNote)
        self.hasBNote.setZ(0.35)
        self.hasBNote.setScale(0.6)
        self.hasBNote.hide()
        return

    def getHealthBarColor(self, health):
        return CannonDefenseGlobals.SHIP_HEALTH_COLORS[int(
            health / 100.0 *
            (len(CannonDefenseGlobals.SHIP_HEALTH_COLORS) - 1))]

    def setHealthState(self, health):
        DistributedNPCSimpleShip.setHealthState(self, health)
        self.healthBar['value'] = health
        self.healthBar['barColor'] = self.getHealthBarColor(health)

    def setCurrentState(self, state):
        if state == CannonDefenseGlobals.SHIP_STATE_STEALING:
            self.goldStolenlbl.show()
        else:
            if state == CannonDefenseGlobals.SHIP_STATE_HASTREASURE:
                self.hasGoldlbl.show()
                self.goldStolenlbl.hide()
            else:
                if state == CannonDefenseGlobals.SHIP_STATE_HASBNOTES:
                    self.hasBNote.show()
                    self.goldStolenlbl.hide()
                else:
                    self.hasGoldlbl.hide()
                    self.hasBNote.hide()
                    self.goldStolenlbl.hide()
        rad = (self.model.dimensions / 2.0).length()
        cn = NodePath(CollisionNode('c'))
        cs = CollisionSphere(0, 0, 0, rad)
        cn.node().addSolid(cs)
        cn.reparentTo(self.model.modelCollisions)
        cn.setTransform(self.model.center.getTransform(self))
        cn.node().setIntoCollideMask(PiratesGlobals.GenericShipBitmask)
        self.model.defendSphere = cn
        self.model.modelRoot.setScale(CannonDefenseGlobals.SHIP_SCALE)
        self.fadeIn(CannonDefenseGlobals.SHIP_FADEIN)
        self.smoother.setExpectedBroadcastPeriod(0.3)
        self.smoother.setDelay(2)
        self.healthBar.reparentTo(self.model.modelRoot)

    def initHealthBar(self):
        self.healthBar = DirectWaitBar(frameSize=(-0.35, 0.35, -0.04, 0.04),
                                       relief=DGG.FLAT,
                                       frameColor=(0.0, 0.0, 0.0, 0.0),
                                       borderWidth=(0.0, 0.0),
                                       barColor=(0.0, 1.0, 0.0, 1.0),
                                       scale=100)
        self.healthBar.setBillboardPointEye()
        self.healthBar['value'] = 100
        self.healthBar.hide(OTPRender.ReflectionCameraBitmask)
        self.healthBar.setLightOff()

    def initIndicatorIcons(self):
        self.textureCard = loader.loadModel(
            'models/textureCards/pir_m_gam_can_ship_icons')
        if self.textureCard:
            self.goldIcon = self.textureCard.find('**/pir_t_shp_can_gold*')
        self.goldStolenlbl = DirectLabel(
            parent=self.healthBar,
            relief=None,
            pos=(0, 0, 0.2),
            text_align=TextNode.ACenter,
            text_scale=0.5,
            textMayChange=0,
            text='!',
            text_fg=PiratesGuiGlobals.TextFG23,
            text_shadow=PiratesGuiGlobals.TextShadow,
            text_font=PiratesGlobals.getPirateBoldOutlineFont(),
            sortOrder=2)
        self.goldStolenlbl.setTransparency(1)
        self.goldStolenlbl.hide()
        self.hasGoldlbl = DirectLabel(parent=self.healthBar,
                                      relief=None,
                                      pos=(0, 0, 0.35),
                                      image=self.goldIcon,
                                      image_scale=0.5,
                                      image_pos=(0, 0, 0),
                                      sortOrder=2)
        self.hasGoldlbl.setTransparency(1)
        self.hasGoldlbl.hide()
        self.hasBNote = self.healthBar.attachNewNode('noteIndicator')
        self.bnoteBack = loader.loadModel(
            'models/textureCards/skillIcons').find(
                '**/pir_t_gui_can_moneyIcon').copyTo(self.hasBNote)
        self.hasBNote.setZ(0.35)
        self.hasBNote.setScale(0.6)
        self.hasBNote.hide()
        return

    def getHealthBarColor(self, health):
        return CannonDefenseGlobals.SHIP_HEALTH_COLORS[int(
            health / 100.0 *
            (len(CannonDefenseGlobals.SHIP_HEALTH_COLORS) - 1))]

    def setHealthState(self, health):
        DistributedNPCSimpleShip.setHealthState(self, health)
        self.healthBar['value'] = health
        self.healthBar['barColor'] = self.getHealthBarColor(health)

    def setCurrentState(self, state):
        if state == CannonDefenseGlobals.SHIP_STATE_STEALING:
            self.goldStolenlbl.show()
        else:
            if state == CannonDefenseGlobals.SHIP_STATE_HASTREASURE:
                self.hasGoldlbl.show()
                self.goldStolenlbl.hide()
            else:
                if state == CannonDefenseGlobals.SHIP_STATE_HASBNOTES:
                    self.hasBNote.show()
                    self.goldStolenlbl.hide()
                else:
                    self.hasGoldlbl.hide()
                    self.hasBNote.hide()
                    self.goldStolenlbl.hide()

    def calculateLook(self):
        pass

    def playProjectileHitSfx(self, ammoSkillId, hitSail):
        if ammoSkillId in [
                InventoryType.DefenseCannonColdShotInWater,
                InventoryType.DefenseCannonSmokePowder
        ]:
            return
        if ammoSkillId in self.specialHitSfx:
            sfx = self.specialHitSfx[ammoSkillId]
            base.playSfx(sfx, node=self, cutoff=2000)
            return
        DistributedNPCSimpleShip.playProjectileHitSfx(self, ammoSkillId,
                                                      hitSail)

    def projectileWeaponHit(self,
                            skillId,
                            ammoSkillId,
                            skillResult,
                            targetEffects,
                            pos,
                            normal,
                            codes,
                            attacker,
                            itemEffects=[]):
        DistributedNPCSimpleShip.projectileWeaponHit(self, skillId,
                                                     ammoSkillId, skillResult,
                                                     targetEffects, pos,
                                                     normal, codes, attacker,
                                                     itemEffects)

    def sinkingBegin(self):
        self.healthBar.reparentTo(hidden)
        if len(self.flameEffects) > 0:
            self.isSinkingWhileOnFire = True
        DistributedNPCSimpleShip.sinkingBegin(self)

    def sinkingEnd(self):
        while len(self.flameEffects) > 0:
            effect = self.flameEffects.pop()
            effect.stopLoop()

        DistributedNPCSimpleShip.sinkingEnd(self)

    def addStatusEffect(self,
                        effectId,
                        attackerId,
                        duration=0,
                        timeLeft=0,
                        timestamp=0,
                        buffData=[0]):
        if effectId == WeaponGlobals.C_CANNON_DEFENSE_FIRE:
            self.addFireEffect(self.getModelRoot().getPos())
        if effectId == WeaponGlobals.C_CANNON_DEFENSE_ICE:
            base.playSfx(self.coldShotHitSfx, node=self, cutoff=2000)
        DistributedNPCSimpleShip.addStatusEffect(self, effectId, attackerId,
                                                 duration, timeLeft, timestamp,
                                                 buffData)

    def removeStatusEffect(self, effectId, attackerId):
        DistributedNPCSimpleShip.removeStatusEffect(self, effectId, attackerId)
        if effectId == WeaponGlobals.C_CANNON_DEFENSE_FIRE:
            if not self.isSinkingWhileOnFire:
                while len(self.flameEffects) > 0:
                    effect = self.flameEffects.pop()
                    effect.stopLoop()

    def addFireEffect(self, pos):
        fireEffect = ShipFire.getEffect()
        if fireEffect:
            fireEffect.reparentTo(self.getModelRoot())
            fireEffect.setPos(pos)
            fireEffect.setHpr(90, -15, 0)
            fireEffect.startLoop()
            fireEffect.setEffectScale(20.0)
            self.flameEffects.append(fireEffect)

    def playSharkAttack(self, pos):
        if self.sharkActor.getCurrentAnim() == None:
            self.sharkActor.wrtReparentTo(self.getModelRoot())
            self.sharkActor.setPos(self, pos)
            self.sharkActor.setScale(20)
            self.sharkActor.play('attack')
            sharkAttackEffect = None
            if base.options.getSpecialEffectsSetting(
            ) >= base.options.SpecialEffectsLow:
                effect = CannonSplash.getEffect()
                if effect:
                    effect.reparentTo(base.effectsRoot)
                    effect.setPos(self, pos)
                    effect.setZ(1)
                    effect.play()
                sharkAttackEffect = Func(self.playAttackEffect, pos)
            hprIvalShip = LerpHprInterval(self.getModelRoot(),
                                          duration=3,
                                          hpr=(0, 0, -180))
            s1 = Sequence(Wait(0.75), hprIvalShip,
                          Func(self.getModelRoot().stash))
            s2 = Sequence(Wait(0.75), sharkAttackEffect, Wait(0.5),
                          sharkAttackEffect)
            self.sharkParallel = Parallel(s1, s2)
            self.sharkParallel.start()
            taskMgr.doMethodLater(self.sharkActor.getDuration('attack'),
                                  self.detachShark,
                                  self.uniqueName('playSharkAttack'),
                                  extraArgs=[])
        return

    def playAttackEffect(self, pos):
        effect = BulletEffect.getEffect()
        if effect:
            effect.reparentTo(base.effectsRoot)
            effect.setPos(self, pos)
            effect.loadObjects(6)
            effect.play()
        sfx = random.choice(self.sharkChompSfxs)
        base.playSfx(sfx, node=self.getModelRoot(), cutoff=2000)

    def detachShark(self):
        self.sharkActor.detachNode()

    def delete(self):
        if self.fader:
            self.fader.pause()
            self.fader = None
        DistributedNPCSimpleShip.delete(self)
        return

    def destroy(self):
        if self.goldStolenlbl:
            self.goldStolenlbl.destroy()
            self.goldStolenlbl = None
        if self.hasGoldlbl:
            self.hasGoldlbl.destroy()
            self.hasGoldlbl = None
        if self.textureCard:
            self.textureCard.removeNode()
            self.textureCard = None
            self.goldIcon = None
        if self.healthBar:
            self.healthBar.destroy()
        if self.sharkActor:
            self.sharkActor.cleanUp()
            self.sharkActor.removeNode()
        if self.sharkParallel:
            self.sharkParallel.pause()
            self.sharkParallel = None
        DistributedNPCSimpleShip.destroy(self)
        return

    def fadeIn(self, length):
        if self.fader:
            self.fader.finish()
            self.fader = None
        self.setTransparency(1)
        self.fader = Sequence(
            self.colorScaleInterval(length, Vec4(1, 1, 1, 1), Vec4(1, 1, 1,
                                                                   0)),
            Func(self.clearTransparency))
        self.fader.start()
        return

    def fadeOut(self, length):
        if self.fader:
            self.fader.finish()
            self.fader = None
        self.setTransparency(1)
        self.fader = Sequence(
            self.colorScaleInterval(length, Vec4(1, 1, 1, 0), Vec4(1, 1, 1,
                                                                   1)),
            Func(self.hide), Func(self.clearTransparency))
        self.fader.start()
        return
class PartyCogActivityGui(DirectObject):
    notify = directNotify.newCategory('PartyCogActivityGui')

    def __init__(self):
        DirectObject.__init__(self)
        self._piePowerMeter = None
        self._victoryBalanceBar = None
        self._scoreLabel = None
        self._cogTracker = None
        self._piePowerTitle = None
        self._victoryBalanceTitle = None
        self._scoreTitle = None
        self._spamWarning = None
        self._spamWarningIvalName = 'PartyCogActivityGui-SpamWarning'

    def load(self):
        self._initPiePowerMeter()
        self._initScore()
        self._initCogTracker()
        self._initSpamWarning()
        self._initControlGui()
        self._initVictoryBalanceBar()

    def unload(self):
        if self._cogTracker is not None:
            self._cogTracker.destory()
            self._cogTracker = None
        if self._piePowerMeter is not None:
            self._piePowerMeter.destroy()
            self._piePowerMeter = None
        if self._piePowerTitle is not None:
            self._piePowerTitle.destroy()
            self._piePowerTitle = None
        if self._scoreLabel is not None:
            self._scoreLabel.destroy()
            self._scoreLabel = None
        if self._scoreTitle is not None:
            self._scoreTitle.destroy()
            self._scoreTitle = None
        taskMgr.remove(self._spamWarningIvalName)
        if self._spamWarning:
            self._spamWarning.destroy()
            self._spamWarning = None
        if hasattr(self, '_attackKeys'):
            self._attackKeys.detachNode()
            del self._attackKeys
        if hasattr(self, '_moveKeys'):
            self._moveKeys.detachNode()
            del self._moveKeys
        if self._victoryBalanceBar:
            self._victoryBalanceBar.detachNode()
            self._victoryBalanceBar = None
        if self._victoryBalanceBarOrange:
            self._victoryBalanceBarOrange.detachNode()
            self._victoryBalanceBarOrange = None
        if self._victoryBalanceBarPie:
            self._victoryBalanceBarPie.detachNode()
            self._victoryBalanceBarPie = None
        if self._victoryBalanceBarArrow:
            self._victoryBalanceBarArrow.detachNode()
            self._victoryBalanceBarArrow = None

    def _initVictoryBalanceBar(self):
        h = PartyGlobals.CogActivityPowerMeterHeight / 2.0
        w = PartyGlobals.CogActivityPowerMeterWidth / 2.0
        victoryBalanceBar = loader.loadModel('phase_13/models/parties/tt_m_gui_pty_pieToss_balanceBar')
        self._victoryBalanceBar = victoryBalanceBar.find('**/*tt_t_gui_pty_pieToss_balanceBarBG')
        self._victoryBalanceBar.reparentTo(aspect2d)
        self._victoryBalanceBar.setBin('fixed', 0)
        self._victoryBalanceBar.setPos(PartyGlobals.CogActivityVictoryBarPos)
        self._victoryBalanceBar.setScale(1)
        self._victoryBalanceBarOrange = victoryBalanceBar.find('**/*tt_t_gui_pty_pieToss_balanceBarOrange')
        self._victoryBalanceBarOrange.reparentTo(self._victoryBalanceBar)
        self._victoryBalanceBarOrange.setBin('fixed', 1)
        self._victoryBalanceBarOrange.setPos(PartyGlobals.CogActivityVictoryBarOrangePos)
        self._victoryBalanceBarOrange.setScale(PartyGlobals.CogActivityBarStartScale, 1.0, 1.0)
        self._victoryBalanceBarPie = victoryBalanceBar.find('**/*tt_t_gui_pty_pieToss_balanceBarPie')
        self._victoryBalanceBarPie.reparentTo(self._victoryBalanceBar)
        self._victoryBalanceBarPie.setBin('fixed', 2)
        self._victoryBalanceBarPie.setX(PartyGlobals.CogActivityVictoryBarPiePos[0])
        self._victoryBalanceBarPie.setY(PartyGlobals.CogActivityVictoryBarPiePos[1])
        self._victoryBalanceBarPie.setZ(PartyGlobals.CogActivityVictoryBarPiePos[2])
        self._victoryBalanceBarPie.setScale(PartyGlobals.CogActivityBarPieScale)
        self._victoryBalanceBarArrow = victoryBalanceBar.find('**/*tt_t_gui_pty_pieToss_balanceArrow')
        self._victoryBalanceBarArrow.reparentTo(self._victoryBalanceBarPie)
        self._victoryBalanceBarArrow.setBin('fixed', 2)
        self._victoryBalanceBarArrow.setPos(PartyGlobals.CogActivityVictoryBarArrow)
        self._victoryBalanceBarArrow.setScale(1 / PartyGlobals.CogActivityBarPieScale)

    def _initControlGui(self):
        self._attackIvalName = 'PartyCogActivityGui-attackKeys'
        self._moveIvalName = 'PartyCogActivityGui-moveKeys'
        pieTossControls = loader.loadModel('phase_13/models/parties/tt_m_gui_pty_pieToss_controls')
        self._attackKeys = pieTossControls.find('**/*control*')
        self._moveKeys = pieTossControls.find('**/*arrow*')
        self._moveKeys.reparentTo(aspect2d)
        self._moveKeys.setPos(1.0, 0.0, -0.435)
        self._moveKeys.setScale(0.15)
        self._attackKeys.reparentTo(aspect2d)
        self._attackKeys.setPos(0.85, 0.0, -0.45)
        self._attackKeys.setScale(0.15)
        self._moveKeys.hide()
        self._attackKeys.hide()

    def _initPiePowerMeter(self):
        h = PartyGlobals.CogActivityPowerMeterHeight / 2.0
        w = PartyGlobals.CogActivityPowerMeterWidth / 2.0
        self._piePowerMeter = DirectWaitBar(frameSize=(-h,
         h,
         -w,
         w), relief=DGG.GROOVE, frameColor=(0.9, 0.9, 0.9, 1.0), borderWidth=(0.01, 0.01), barColor=PartyGlobals.CogActivityColors[0], pos=PartyGlobals.CogActivityPowerMeterPos, hpr=(0.0, 0.0, -90.0))
        self._piePowerMeter.setBin('fixed', 0)
        self._piePowerTitle = OnscreenText(text=TTLocalizer.PartyCogGuiPowerLabel, pos=PartyGlobals.CogActivityPowerMeterTextPos, scale=0.05, fg=(1.0, 1.0, 1.0, 1.0), align=TextNode.ACenter)
        self._piePowerTitle.setBin('fixed', 0)
        self._piePowerMeter.hide()
        self._piePowerTitle.hide()

    def _initScore(self):
        self._scoreLabel = OnscreenText(text='0', pos=PartyGlobals.CogActivityScorePos, scale=PartyGlobals.TugOfWarTextWordScale, fg=(1.0, 1.0, 0.0, 1.0), align=TextNode.ARight, font=ToontownGlobals.getSignFont(), mayChange=True)
        self._scoreTitle = OnscreenText(text=TTLocalizer.PartyCogGuiScoreLabel, pos=PartyGlobals.CogActivityScoreTitle, scale=0.05, fg=(1.0, 1.0, 1.0, 1.0), align=TextNode.ARight)
        self._scoreLabel.hide()
        self._scoreTitle.hide()

    def _initCogTracker(self):
        self._cogTracker = PartyCogTrackerGui()

    def _initSpamWarning(self):
        self._spamWarning = OnscreenText(text=TTLocalizer.PartyCogGuiSpamWarning, scale=0.15, fg=(1.0, 1.0, 0, 1.0), shadow=(0, 0, 0, 0.62), mayChange=False, pos=(0, 0.33))
        self._spamWarning.hide()

    def showScore(self):
        self._scoreLabel.show()
        self._scoreTitle.show()

    def hideScore(self):
        self._scoreLabel.hide()
        self._scoreTitle.hide()

    def setScore(self, score = 0):
        self._scoreLabel['text'] = str(score)

    def resetPiePowerMeter(self):
        self._piePowerMeter['value'] = 0

    def showPiePowerMeter(self):
        self._piePowerMeter.show()
        self._piePowerTitle.show()

    def hidePiePowerMeter(self):
        self._piePowerMeter.hide()
        self._piePowerTitle.hide()

    def updatePiePowerMeter(self, value):
        self._piePowerMeter['value'] = value

    def getPiePowerMeterValue(self):
        return self._piePowerMeter['value']

    def hideSpamWarning(self):
        taskMgr.remove(self._spamWarningIvalName)
        if self._spamWarning:
            self._spamWarning.hide()

    def showSpamWarning(self):
        if self._spamWarning.isHidden():
            self._spamWarning.show()
            taskMgr.remove(self._spamWarningIvalName)
            Sequence(ToontownIntervals.getPulseLargerIval(self._spamWarning, ''), Wait(PartyGlobals.CogActivitySpamWarningShowTime), Func(self.hideSpamWarning), name=self._spamWarningIvalName, autoFinish=1).start()

    def hide(self):
        self.hidePiePowerMeter()
        self.hideScore()
        self.hideSpamWarning()
        self.hideControls()

    def disableToontownHUD(self):
        base.localAvatar.hideName()
        base.localAvatar.laffMeter.hide()
        base.setCellsAvailable(base.bottomCells + [base.rightCells[1]], False)

    def enableToontownHUD(self):
        base.localAvatar.showName()
        base.localAvatar.laffMeter.show()
        base.setCellsAvailable(base.bottomCells + [base.rightCells[1]], True)

    def setTeam(self, team):
        self.team = team
        if team == 0:
            self._cogTracker.frame.setR(180)
        self._piePowerMeter['barColor'] = PartyGlobals.CogActivityColors[team]

    def startTrackingCogs(self, cogs):
        self.cogs = cogs
        taskMgr.add(self.trackCogs, 'trackCogs')

    def trackCogs(self, task):
        if self.cogs is None:
            return
        self._updateVictoryBar()
        for i, cog in enumerate(self.cogs):
            self._cogTracker.updateCog(i, cog, self.team)

        return task.cont

    def _updateVictoryBar(self):
        if not (hasattr(self, '_victoryBalanceBar') and self._victoryBalanceBar):
            return
        netDistance = 0
        for cog in self.cogs:
            netDistance = netDistance + cog.targetDistance

        teamDistance = netDistance / 6.0
        self._victoryBalanceBarOrange.setScale(PartyGlobals.CogActivityBarStartScale + teamDistance * 10 * PartyGlobals.CogActivityBarUnitScale, 1.0, 1.0)
        self._victoryBalanceBarPie.setX(PartyGlobals.CogActivityVictoryBarPiePos[0] + teamDistance * 10 * PartyGlobals.CogActivityBarPieUnitMove)
        self._victoryBalanceBarPie.setY(PartyGlobals.CogActivityVictoryBarPiePos[1])
        self._victoryBalanceBarPie.setZ(PartyGlobals.CogActivityVictoryBarPiePos[2])
        if teamDistance > 0.0:
            self._victoryBalanceBarArrow.setColor(PartyGlobals.CogActivityColors[1])
        elif teamDistance < 0.0:
            self._victoryBalanceBarArrow.setColor(PartyGlobals.CogActivityColors[0])
        else:
            self._victoryBalanceBarArrow.setColor(VBase4(1.0, 1.0, 1.0, 1.0))

    def stopTrackingCogs(self):
        taskMgr.remove('trackCogs')

    def showAttackControls(self):
        if self._attackKeys.isHidden():
            self._attackKeys.show()
            taskMgr.remove(self._attackIvalName)
            Sequence(ToontownIntervals.getPulseLargerIval(self._attackKeys, '', scale=0.15), Wait(PartyGlobals.CogActivityControlsShowTime), Func(self.hideAttackControls), name=self._attackIvalName, autoFinish=1).start()

    def showMoveControls(self):
        if self._moveKeys.isHidden() and not self._attackKeys.isHidden():
            self._moveKeys.show()
            taskMgr.remove(self._moveIvalName)
            Sequence(ToontownIntervals.getPulseLargerIval(self._moveKeys, '', scale=0.15), Wait(PartyGlobals.CogActivityControlsShowTime), Func(self.hideMoveControls), name=self._moveIvalName, autoFinish=1).start()

    def hideAttackControls(self):
        taskMgr.remove(self._attackIvalName)
        if hasattr(self, '_attackKeys') and self._attackKeys:
            self._attackKeys.hide()

    def hideMoveControls(self):
        taskMgr.remove(self._moveIvalName)
        if hasattr(self, '_moveKeys') and self._moveKeys:
            self._moveKeys.hide()

    def hideControls(self):
        self.hideMoveControls()
        self.hideAttackControls()
class QuestPoster(DirectFrame):
    notify = directNotify.newCategory('QuestPoster')

    def __init__(self, quest, parent=aspect2d, **kw):
        DirectFrame.__init__(self, relief=None)
        self.quest = quest

        # Let's begin building the quest poster.
        bookModel = loader.loadModel(
            'phase_3.5/models/gui/stickerbook_gui.bam')
        questCard = bookModel.find('**/questCard')
        optiondefs = (('relief', None, None), ('image', questCard, None),
                      ('image_scale', (0.8, 1.0, 0.58),
                       None), ('state', DGG.NORMAL, None))
        self.defineoptions(kw, optiondefs)
        self._deleteCallback = None
        self.questFrame = DirectFrame(parent=self, relief=None)
        self.headline = DirectLabel(parent=self.questFrame,
                                    relief=None,
                                    text='',
                                    text_font=CIGlobals.getMinnieFont(),
                                    text_fg=self.normalTextColor,
                                    text_scale=0.05,
                                    text_align=TextNode.ACenter,
                                    text_wordwrap=12.0,
                                    textMayChange=1,
                                    pos=(0, 0, 0.23))
        self.questInfo = DirectLabel(parent=self.questFrame,
                                     relief=None,
                                     text='',
                                     text_fg=self.normalTextColor,
                                     text_scale=TEXT_SCALE,
                                     text_align=TextNode.ACenter,
                                     text_wordwrap=TEXT_WORDWRAP,
                                     textMayChange=1,
                                     pos=(0, 0, -0.0625))
        self.rewardText = DirectLabel(parent=self.questFrame,
                                      relief=None,
                                      text='',
                                      text_fg=self.colors['rewardRed'],
                                      text_scale=0.0425,
                                      text_align=TextNode.ALeft,
                                      text_wordwrap=17.0,
                                      textMayChange=1,
                                      pos=(-0.36, 0, -0.26))
        self.rewardText.hide()
        self.lPictureFrame = DirectFrame(
            parent=self.questFrame,
            relief=None,
            image=bookModel.find('**/questPictureFrame'),
            image_scale=IMAGE_SCALE_SMALL,
            text='',
            text_pos=(0, -0.11),
            text_fg=self.normalTextColor,
            text_scale=TEXT_SCALE,
            text_align=TextNode.ACenter,
            text_wordwrap=11.0,
            textMayChange=1)
        self.lPictureFrame.hide()
        self.rPictureFrame = DirectFrame(
            parent=self.questFrame,
            relief=None,
            image=bookModel.find('**/questPictureFrame'),
            image_scale=IMAGE_SCALE_SMALL,
            text='',
            text_pos=(0, -0.11),
            text_fg=self.normalTextColor,
            text_scale=TEXT_SCALE,
            text_align=TextNode.ACenter,
            text_wordwrap=11.0,
            textMayChange=1,
            pos=(0.18, 0, 0.13))
        self.rPictureFrame.hide()
        self.lQuestIcon = DirectFrame(parent=self.lPictureFrame,
                                      relief=None,
                                      text=' ',
                                      text_font=CIGlobals.getSuitFont(),
                                      text_pos=(0, -0.03),
                                      text_fg=self.normalTextColor,
                                      text_scale=0.13,
                                      text_align=TextNode.ACenter,
                                      text_wordwrap=13.0,
                                      textMayChange=1)
        self.lQuestIcon.setColorOff(-1)
        self.rQuestIcon = DirectFrame(parent=self.rPictureFrame,
                                      relief=None,
                                      text=' ',
                                      text_font=CIGlobals.getSuitFont(),
                                      text_pos=(0, -0.03),
                                      text_fg=self.normalTextColor,
                                      text_scale=0.13,
                                      text_align=TextNode.ACenter,
                                      text_wordwrap=13.0,
                                      textMayChange=1)
        self.rQuestIcon.setColorOff(-1)
        self.auxText = DirectLabel(parent=self.questFrame,
                                   relief=None,
                                   text='',
                                   text_scale=QuestGlobals.QPauxText,
                                   text_fg=self.normalTextColor,
                                   text_align=TextNode.ACenter,
                                   textMayChange=1)
        self.auxText.hide()
        self.questProgress = DirectWaitBar(parent=self.questFrame,
                                           relief=DGG.SUNKEN,
                                           frameSize=(-0.95, 0.95, -0.1, 0.12),
                                           borderWidth=(0.025, 0.025),
                                           scale=0.2,
                                           frameColor=(0.945, 0.875, 0.706,
                                                       1.0),
                                           barColor=(0.5, 0.7, 0.5, 1),
                                           text='0/0',
                                           text_scale=0.19,
                                           text_fg=(0.05, 0.14, 0.4, 1),
                                           text_align=TextNode.ACenter,
                                           text_pos=(0, -0.04),
                                           pos=(0, 0, -0.195))
        self.questProgress.hide()

        # This is the rotated text on the side.
        self.sideInfo = DirectLabel(parent=self.questFrame,
                                    relief=None,
                                    text=QuestGlobals.JUST_FOR_FUN,
                                    text_fg=(0.0, 0.439, 1.0, 1.0),
                                    text_shadow=(0, 0, 0, 1),
                                    pos=(-0.2825, 0, 0.2),
                                    scale=0.03)
        self.sideInfo.setR(-30)
        self.sideInfo.hide()

        bookModel.removeNode()
        self.laffMeter = None
        return

    def destroy(self):
        self._deleteGeoms()
        DirectFrame.destroy(self)

    def _deleteGeoms(self):
        for icon in (self.lQuestIcon, self.rQuestIcon):
            geom = icon['geom']
            if geom:
                if hasattr(geom, 'delete'):
                    geom.delete()
class DistributedMazeGame(DistributedMinigame):
    notify = directNotify.newCategory('DistributedMazeGame')
    CAMERA_TASK = 'MazeGameCameraTask'
    UPDATE_SUITS_TASK = 'MazeGameUpdateSuitsTask'
    TREASURE_GRAB_EVENT_NAME = 'MazeTreasureGrabbed'

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedMazeGame', [State.State('off', self.enterOff, self.exitOff, ['play']),
         State.State('play', self.enterPlay, self.exitPlay, ['cleanup', 'showScores']),
         State.State('showScores', self.enterShowScores, self.exitShowScores, ['cleanup']),
         State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.usesLookAround = 1

    def getTitle(self):
        return TTLocalizer.MazeGameTitle

    def getInstructions(self):
        return TTLocalizer.MazeGameInstructions

    def getMaxDuration(self):
        return MazeGameGlobals.GAME_DURATION

    def __defineConstants(self):
        self.TOON_SPEED = 8.0
        self.TOON_Z = 0
        self.MinSuitSpeedRange = [0.8 * self.TOON_SPEED, 0.6 * self.TOON_SPEED]
        self.MaxSuitSpeedRange = [1.1 * self.TOON_SPEED, 2.0 * self.TOON_SPEED]
        self.FASTER_SUIT_CURVE = 1
        self.SLOWER_SUIT_CURVE = self.getDifficulty() < 0.5
        self.slowerSuitPeriods = {2000: {4: [128, 76],
                8: [128,
                    99,
                    81,
                    68],
                12: [128,
                     108,
                     93,
                     82,
                     74,
                     67],
                16: [128,
                     112,
                     101,
                     91,
                     83,
                     76,
                     71,
                     66]},
         1000: {4: [110, 69],
                8: [110,
                    88,
                    73,
                    62],
                12: [110,
                     95,
                     83,
                     74,
                     67,
                     61],
                16: [110,
                     98,
                     89,
                     81,
                     75,
                     69,
                     64,
                     60]},
         5000: {4: [96, 63],
                8: [96,
                    79,
                    66,
                    57],
                12: [96,
                     84,
                     75,
                     67,
                     61,
                     56],
                16: [96,
                     87,
                     80,
                     73,
                     68,
                     63,
                     59,
                     55]},
         4000: {4: [86, 58],
                8: [86,
                    71,
                    61,
                    53],
                12: [86,
                     76,
                     68,
                     62,
                     56,
                     52],
                16: [86,
                     78,
                     72,
                     67,
                     62,
                     58,
                     54,
                     51]},
         3000: {4: [78, 54],
                8: [78,
                    65,
                    56,
                    49],
                12: [78,
                     69,
                     62,
                     57,
                     52,
                     48],
                16: [78,
                     71,
                     66,
                     61,
                     57,
                     54,
                     51,
                     48]},
         9000: {4: [71, 50],
                8: [71,
                    60,
                    52,
                    46],
                12: [71,
                     64,
                     58,
                     53,
                     49,
                     45],
                16: [71,
                     65,
                     61,
                     57,
                     53,
                     50,
                     47,
                     45]}}
        self.slowerSuitPeriodsCurve = {2000: {4: [128, 65],
                8: [128,
                    78,
                    66,
                    64],
                12: [128,
                     88,
                     73,
                     67,
                     64,
                     64],
                16: [128,
                     94,
                     79,
                     71,
                     67,
                     65,
                     64,
                     64]},
         1000: {4: [110, 59],
                8: [110,
                    70,
                    60,
                    58],
                12: [110,
                     78,
                     66,
                     61,
                     59,
                     58],
                16: [110,
                     84,
                     72,
                     65,
                     61,
                     59,
                     58,
                     58]},
         5000: {4: [96, 55],
                8: [96,
                    64,
                    56,
                    54],
                12: [96,
                     71,
                     61,
                     56,
                     54,
                     54],
                16: [96,
                     76,
                     65,
                     59,
                     56,
                     55,
                     54,
                     54]},
         4000: {4: [86, 51],
                8: [86,
                    59,
                    52,
                    50],
                12: [86,
                     65,
                     56,
                     52,
                     50,
                     50],
                16: [86,
                     69,
                     60,
                     55,
                     52,
                     51,
                     50,
                     50]},
         3000: {4: [78, 47],
                8: [78,
                    55,
                    48,
                    47],
                12: [78,
                     60,
                     52,
                     48,
                     47,
                     47],
                16: [78,
                     63,
                     55,
                     51,
                     49,
                     47,
                     47,
                     47]},
         9000: {4: [71, 44],
                8: [71,
                    51,
                    45,
                    44],
                12: [71,
                     55,
                     48,
                     45,
                     44,
                     44],
                16: [71,
                     58,
                     51,
                     48,
                     45,
                     44,
                     44,
                     44]}}
        self.fasterSuitPeriods = {2000: {4: [54, 42],
                8: [59,
                    52,
                    47,
                    42],
                12: [61,
                     56,
                     52,
                     48,
                     45,
                     42],
                16: [61,
                     58,
                     54,
                     51,
                     49,
                     46,
                     44,
                     42]},
         1000: {4: [50, 40],
                8: [55,
                    48,
                    44,
                    40],
                12: [56,
                     52,
                     48,
                     45,
                     42,
                     40],
                16: [56,
                     53,
                     50,
                     48,
                     45,
                     43,
                     41,
                     40]},
         5000: {4: [47, 37],
                8: [51,
                    45,
                    41,
                    37],
                12: [52,
                     48,
                     45,
                     42,
                     39,
                     37],
                16: [52,
                     49,
                     47,
                     44,
                     42,
                     40,
                     39,
                     37]},
         4000: {4: [44, 35],
                8: [47,
                    42,
                    38,
                    35],
                12: [48,
                     45,
                     42,
                     39,
                     37,
                     35],
                16: [49,
                     46,
                     44,
                     42,
                     40,
                     38,
                     37,
                     35]},
         3000: {4: [41, 33],
                8: [44,
                    40,
                    36,
                    33],
                12: [45,
                     42,
                     39,
                     37,
                     35,
                     33],
                16: [45,
                     43,
                     41,
                     39,
                     38,
                     36,
                     35,
                     33]},
         9000: {4: [39, 32],
                8: [41,
                    37,
                    34,
                    32],
                12: [42,
                     40,
                     37,
                     35,
                     33,
                     32],
                16: [43,
                     41,
                     39,
                     37,
                     35,
                     34,
                     33,
                     32]}}
        self.fasterSuitPeriodsCurve = {2000: {4: [62, 42],
                8: [63,
                    61,
                    54,
                    42],
                12: [63,
                     63,
                     61,
                     56,
                     50,
                     42],
                16: [63,
                     63,
                     62,
                     60,
                     57,
                     53,
                     48,
                     42]},
         1000: {4: [57, 40],
                8: [58,
                    56,
                    50,
                    40],
                12: [58,
                     58,
                     56,
                     52,
                     46,
                     40],
                16: [58,
                     58,
                     57,
                     56,
                     53,
                     49,
                     45,
                     40]},
         5000: {4: [53, 37],
                8: [54,
                    52,
                    46,
                    37],
                12: [54,
                     53,
                     52,
                     48,
                     43,
                     37],
                16: [54,
                     54,
                     53,
                     51,
                     49,
                     46,
                     42,
                     37]},
         4000: {4: [49, 35],
                8: [50,
                    48,
                    43,
                    35],
                12: [50,
                     49,
                     48,
                     45,
                     41,
                     35],
                16: [50,
                     50,
                     49,
                     48,
                     46,
                     43,
                     39,
                     35]},
         3000: {4: [46, 33],
                8: [47,
                    45,
                    41,
                    33],
                12: [47,
                     46,
                     45,
                     42,
                     38,
                     33],
                16: [47,
                     46,
                     46,
                     45,
                     43,
                     40,
                     37,
                     33]},
         9000: {4: [43, 32],
                8: [44,
                    42,
                    38,
                    32],
                12: [44,
                     43,
                     42,
                     40,
                     36,
                     32],
                16: [44,
                     44,
                     43,
                     42,
                     40,
                     38,
                     35,
                     32]}}
        self.CELL_WIDTH = MazeData.CELL_WIDTH
        self.MAX_FRAME_MOVE = self.CELL_WIDTH / 2
        startOffset = 3
        self.startPosHTable = [[Point3(0, startOffset, self.TOON_Z), 0],
         [Point3(0, -startOffset, self.TOON_Z), 180],
         [Point3(startOffset, 0, self.TOON_Z), 270],
         [Point3(-startOffset, 0, self.TOON_Z), 90]]
        self.camOffset = Vec3(0, -19, 45)

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.__defineConstants()
        mazeName = MazeGameGlobals.getMazeName(self.doId, self.numPlayers, MazeData.mazeNames)
        self.maze = Maze.Maze(mazeName)
        model = loader.loadModel('phase_3.5/models/props/mickeySZ')
        self.treasureModel = model.find('**/mickeySZ')
        model.removeNode()
        self.treasureModel.setScale(1.6)
        self.treasureModel.setP(-90)
        self.music = base.loadMusic('phase_4/audio/bgm/MG_toontag.ogg')
        self.toonHitTracks = {}
        self.scorePanels = []

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        del self.toonHitTracks
        self.maze.destroy()
        del self.maze
        self.treasureModel.removeNode()
        del self.treasureModel
        del self.music
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.maze.onstage()
        self.randomNumGen.shuffle(self.startPosHTable)
        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        self.camParent = render.attachNewNode('mazeGameCamParent')
        self.camParent.reparentTo(base.localAvatar)
        self.camParent.setPos(0, 0, 0)
        self.camParent.setHpr(render, 0, 0, 0)
        camera.reparentTo(self.camParent)
        camera.setPos(self.camOffset)
        self.__spawnCameraTask()
        self.toonRNGs = []
        for i in xrange(self.numPlayers):
            self.toonRNGs.append(RandomNumGen.RandomNumGen(self.randomNumGen))

        self.treasures = []
        for i in xrange(self.maze.numTreasures):
            self.treasures.append(MazeTreasure.MazeTreasure(self.treasureModel, self.maze.treasurePosList[i], i, self.doId))

        self.__loadSuits()
        for suit in self.suits:
            suit.onstage()

        self.sndTable = {'hitBySuit': [None] * self.numPlayers,
         'falling': [None] * self.numPlayers}
        for i in xrange(self.numPlayers):
            self.sndTable['hitBySuit'][i] = base.loadSfx('phase_4/audio/sfx/MG_Tag_C.ogg')
            self.sndTable['falling'][i] = base.loadSfx('phase_4/audio/sfx/MG_cannon_whizz.ogg')

        self.grabSounds = []
        for i in xrange(5):
            self.grabSounds.append(base.loadSfx('phase_4/audio/sfx/MG_maze_pickup.ogg'))

        self.grabSoundIndex = 0
        for avId in self.avIdList:
            self.toonHitTracks[avId] = Wait(0.1)

        self.scores = [0] * self.numPlayers
        self.goalBar = DirectWaitBar(parent=render2d, relief=DGG.SUNKEN, frameSize=(-0.35,
         0.35,
         -0.15,
         0.15), borderWidth=(0.02, 0.02), scale=0.42, pos=(0.84, 0, 0.5 - 0.28 * self.numPlayers + 0.05), barColor=(0, 0.7, 0, 1))
        self.goalBar.setBin('unsorted', 0)
        self.goalBar.hide()
        self.introTrack = self.getIntroTrack()
        self.introTrack.start()
        return

    def offstage(self):
        self.notify.debug('offstage')
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        del self.introTrack
        for avId in self.toonHitTracks.keys():
            track = self.toonHitTracks[avId]
            if track.isPlaying():
                track.finish()

        self.__killCameraTask()
        camera.wrtReparentTo(render)
        self.camParent.removeNode()
        del self.camParent
        for panel in self.scorePanels:
            panel.cleanup()

        self.scorePanels = []
        self.goalBar.destroy()
        del self.goalBar
        base.setCellsAvailable(base.rightCells, 1)
        for suit in self.suits:
            suit.offstage()

        self.__unloadSuits()
        for treasure in self.treasures:
            treasure.destroy()

        del self.treasures
        del self.sndTable
        del self.grabSounds
        del self.toonRNGs
        self.maze.offstage()
        base.localAvatar.showName()
        DistributedMinigame.offstage(self)

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if self.numPlayers == 1:
            toon.setPos(0, 0, self.TOON_Z)
            toon.setHpr(180, 0, 0)
        else:
            posIndex = self.avIdList.index(avId)
            toon.setPos(self.startPosHTable[posIndex][0])
            toon.setHpr(self.startPosHTable[posIndex][1], 0, 0)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self.__placeToon(avId)
                toon.setAnimState('Happy', 1.0)
                toon.startSmooth()
                toon.startLookAround()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        if self.introTrack.isPlaying():
            self.introTrack.finish()
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.stopLookAround()

        self.gameFSM.request('play')

    def handleDisabledAvatar(self, avId):
        hitTrack = self.toonHitTracks[avId]
        if hitTrack.isPlaying():
            hitTrack.finish()
        DistributedMinigame.handleDisabledAvatar(self, avId)

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        for i in xrange(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = MinigameAvatarScorePanel.MinigameAvatarScorePanel(avId, avName)
            scorePanel.reparentTo(base.a2dTopRight)
            scorePanel.setPos(-0.213, 0.0, -0.5 - 0.28 * i)
            self.scorePanels.append(scorePanel)

        self.goalBar.show()
        self.goalBar['value'] = 0.0
        base.setCellsAvailable(base.rightCells, 0)
        self.__spawnUpdateSuitsTask()
        orthoDrive = OrthoDrive(self.TOON_SPEED, maxFrameMove=self.MAX_FRAME_MOVE, customCollisionCallback=self.__doMazeCollisions, priority=1)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=not self.isSinglePlayer())
        self.orthoWalk.start()
        self.accept(MazeSuit.COLLISION_EVENT_NAME, self.__hitBySuit)
        self.accept(self.TREASURE_GRAB_EVENT_NAME, self.__treasureGrabbed)
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(MazeGameGlobals.GAME_DURATION)
        self.timer.countdown(MazeGameGlobals.GAME_DURATION, self.timerExpired)
        self.accept('resetClock', self.__resetClock)
        base.playMusic(self.music, looping=0, volume=0.8)

    def exitPlay(self):
        self.notify.debug('exitPlay')
        self.ignore('resetClock')
        self.ignore(MazeSuit.COLLISION_EVENT_NAME)
        self.ignore(self.TREASURE_GRAB_EVENT_NAME)
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        self.__killUpdateSuitsTask()
        self.timer.stop()
        self.timer.destroy()
        del self.timer
        for avId in self.avIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.loop('neutral')

    def __resetClock(self, tOffset):
        self.notify.debug('resetClock')
        self.gameStartTime += tOffset
        self.timer.countdown(self.timer.currentTime + tOffset, self.timerExpired)

    def __treasureGrabbed(self, treasureNum):
        self.treasures[treasureNum].showGrab()
        self.grabSounds[self.grabSoundIndex].play()
        self.grabSoundIndex = (self.grabSoundIndex + 1) % len(self.grabSounds)
        self.sendUpdate('claimTreasure', [treasureNum])

    def setTreasureGrabbed(self, avId, treasureNum):
        if not self.hasLocalToon:
            return
        if avId != self.localAvId:
            self.treasures[treasureNum].showGrab()
        i = self.avIdList.index(avId)
        self.scores[i] += 1
        self.scorePanels[i].setScore(self.scores[i])
        total = 0
        for score in self.scores:
            total += score

        self.goalBar['value'] = 100.0 * (float(total) / float(self.maze.numTreasures))

    def __hitBySuit(self, suitNum):
        self.notify.debug('hitBySuit')
        timestamp = globalClockDelta.localToNetworkTime(globalClock.getFrameTime())
        self.sendUpdate('hitBySuit', [self.localAvId, timestamp])
        self.__showToonHitBySuit(self.localAvId, timestamp)

    def hitBySuit(self, avId, timestamp):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ['play', 'showScores']:
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        self.notify.debug('avatar ' + `avId` + ' hit by a suit')
        if avId != self.localAvId:
            self.__showToonHitBySuit(avId, timestamp)

    def __showToonHitBySuit(self, avId, timestamp):
        toon = self.getAvatar(avId)
        if toon == None:
            return
        rng = self.toonRNGs[self.avIdList.index(avId)]
        curPos = toon.getPos(render)
        oldTrack = self.toonHitTracks[avId]
        if oldTrack.isPlaying():
            oldTrack.finish()
        toon.setPos(curPos)
        toon.setZ(self.TOON_Z)
        parentNode = render.attachNewNode('mazeFlyToonParent-' + `avId`)
        parentNode.setPos(toon.getPos())
        toon.reparentTo(parentNode)
        toon.setPos(0,0,0)
        startPos = parentNode.getPos()
        dropShadow = toon.dropShadow.copyTo(parentNode)
        dropShadow.setScale(toon.dropShadow.getScale(render))
        trajectory = Trajectory.Trajectory(
            0,
            Point3(0,0,0),
            Point3(0,0,50),
            gravMult=1.0)
        flyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        while 1:
            endTile = [rng.randint(2, self.maze.width-1), rng.randint(2, self.maze.height-1)]
            if self.maze.isWalkable(endTile[0], endTile[1]):
                break
        endWorldCoords = self.maze.tile2world(endTile[0], endTile[1])
        endPos = Point3(endWorldCoords[0], endWorldCoords[1], startPos[2])
        def flyFunc(t, trajectory, startPos = startPos, endPos = endPos, dur = flyDur, moveNode = parentNode, flyNode = toon):
            u = t/dur
            moveNode.setX(startPos[0] + u * (endPos[0]-startPos[0]))
            moveNode.setY(startPos[1] + u * (endPos[1]-startPos[1]))
            flyNode.setPos(trajectory.getPos(t))
        flyTrack = Sequence(
            LerpFunctionInterval(flyFunc, fromData=0.0, toData=flyDur, duration=flyDur, extraArgs=[trajectory]),
            name=toon.uniqueName('hitBySuit-fly'))
        if avId != self.localAvId:
            cameraTrack = Sequence()
        else:
            self.camParent.reparentTo(parentNode)
            startCamPos = camera.getPos()
            destCamPos = camera.getPos()
            zenith = trajectory.getPos(flyDur/2.0)[2]
            destCamPos.setZ(zenith*1.3)
            destCamPos.setY(destCamPos[1]*0.3)
            def camTask(task, zenith = zenith, flyNode = toon, startCamPos = startCamPos, camOffset = destCamPos - startCamPos):
                u = flyNode.getZ()/zenith
                camera.setPos(startCamPos + camOffset*u)
                camera.lookAt(toon)
                return Task.cont
            camTaskName = 'mazeToonFlyCam-' + `avId`
            taskMgr.add(camTask, camTaskName, priority=20)
            def cleanupCamTask(self = self, toon = toon, camTaskName = camTaskName, startCamPos = startCamPos):
                taskMgr.remove(camTaskName)
                self.camParent.reparentTo(toon)
                camera.setPos(startCamPos)
                camera.lookAt(toon)

            cameraTrack = Sequence(
                Wait(flyDur),
                Func(cleanupCamTask),
                name='hitBySuit-cameraLerp')

        geomNode = toon.getGeomNode()
        startHpr = geomNode.getHpr()
        destHpr = Point3(startHpr)
        hRot = rng.randrange(1, 8)
        if rng.choice([0, 1]):
            hRot = -hRot
        destHpr.setX(destHpr[0] + hRot*360)
        spinHTrack = Sequence(
            LerpHprInterval(geomNode, flyDur, destHpr, startHpr=startHpr),
            Func(geomNode.setHpr, startHpr),
            name=toon.uniqueName('hitBySuit-spinH'))
        parent = geomNode.getParent()
        rotNode = parent.attachNewNode('rotNode')
        geomNode.reparentTo(rotNode)
        rotNode.setZ(toon.getHeight()/2.0)
        oldGeomNodeZ = geomNode.getZ()
        geomNode.setZ(-toon.getHeight()/2.0)
        startHpr = rotNode.getHpr()
        destHpr = Point3(startHpr)
        pRot = rng.randrange(1,3)
        if rng.choice([0, 1]):
            pRot = -pRot
        destHpr.setY(destHpr[1] + pRot*360)
        spinPTrack = Sequence(
            LerpHprInterval(rotNode, flyDur, destHpr, startHpr=startHpr),
            Func(rotNode.setHpr, startHpr),
            name=toon.uniqueName('hitBySuit-spinP'))
        i = self.avIdList.index(avId)
        soundTrack = Sequence(
            Func(base.playSfx, self.sndTable['hitBySuit'][i]),
            Wait(flyDur * (2.0/3.0)),
            SoundInterval(self.sndTable['falling'][i],
                          duration=flyDur * (1.0/3.0)),
            name=toon.uniqueName('hitBySuit-soundTrack'))

        def preFunc(self = self, avId = avId, toon = toon, dropShadow = dropShadow):
            forwardSpeed = toon.forwardSpeed
            rotateSpeed = toon.rotateSpeed
            if avId == self.localAvId:
                self.orthoWalk.stop()
            else:
                toon.stopSmooth()
            if forwardSpeed or rotateSpeed:
                toon.setSpeed(forwardSpeed, rotateSpeed)
            toon.dropShadow.hide()

        def postFunc(self = self, avId = avId, oldGeomNodeZ = oldGeomNodeZ, dropShadow = dropShadow, parentNode = parentNode):
            if avId == self.localAvId:
                base.localAvatar.setPos(endPos)
                if hasattr(self, 'orthoWalk'):
                    if self.gameFSM.getCurrentState().getName() == 'play':
                        self.orthoWalk.start()
            dropShadow.removeNode()
            del dropShadow
            toon.dropShadow.show()
            geomNode = toon.getGeomNode()
            rotNode = geomNode.getParent()
            baseNode = rotNode.getParent()
            geomNode.reparentTo(baseNode)
            rotNode.removeNode()
            del rotNode
            geomNode.setZ(oldGeomNodeZ)
            toon.reparentTo(render)
            toon.setPos(endPos)
            parentNode.removeNode()
            del parentNode
            if avId != self.localAvId:
                toon.startSmooth()

        preFunc()

        hitTrack = Sequence(Parallel(flyTrack, cameraTrack,
                                     spinHTrack, spinPTrack, soundTrack),
                            Func(postFunc),
                            name=toon.uniqueName('hitBySuit'))

        self.toonHitTracks[avId] = hitTrack

        hitTrack.start(globalClockDelta.localElapsedTime(timestamp))

    def allTreasuresTaken(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('all treasures taken')
        if not MazeGameGlobals.ENDLESS_GAME:
            self.gameFSM.request('showScores')

    def timerExpired(self):
        self.notify.debug('local timer expired')
        if not MazeGameGlobals.ENDLESS_GAME:
            self.gameFSM.request('showScores')

    def __doMazeCollisions(self, oldPos, newPos):
        offset = newPos - oldPos
        WALL_OFFSET = 1.0
        curX = oldPos[0]
        curY = oldPos[1]
        curTX, curTY = self.maze.world2tile(curX, curY)

        def calcFlushCoord(curTile, newTile, centerTile):
            EPSILON = 0.01
            if newTile > curTile:
                return (newTile - centerTile) * self.CELL_WIDTH - EPSILON - WALL_OFFSET
            else:
                return (curTile - centerTile) * self.CELL_WIDTH + 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
        newX = curX + offsetX + WALL_OFFSET_X
        newY = curY
        newTX, newTY = self.maze.world2tile(newX, newY)
        if newTX != curTX:
            if self.maze.collisionTable[newTY][newTX]:
                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:
            if self.maze.collisionTable[newTY][newTX]:
                offset.setY(calcFlushCoord(curTY, newTY, self.maze.originTY) - curY)
        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]:
            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

    def __spawnCameraTask(self):
        self.notify.debug('spawnCameraTask')
        camera.lookAt(base.localAvatar)
        taskMgr.remove(self.CAMERA_TASK)
        taskMgr.add(self.__cameraTask, self.CAMERA_TASK, priority=45)

    def __killCameraTask(self):
        self.notify.debug('killCameraTask')
        taskMgr.remove(self.CAMERA_TASK)

    def __cameraTask(self, task):
        self.camParent.setHpr(render, 0, 0, 0)
        return Task.cont

    def __loadSuits(self):
        self.notify.debug('loadSuits')
        self.suits = []
        self.numSuits = 4 * self.numPlayers
        safeZone = self.getSafezoneId()
        slowerTable = self.slowerSuitPeriods
        if self.SLOWER_SUIT_CURVE:
            slowerTable = self.slowerSuitPeriodsCurve
        slowerPeriods = slowerTable[safeZone][self.numSuits]
        fasterTable = self.fasterSuitPeriods
        if self.FASTER_SUIT_CURVE:
            fasterTable = self.fasterSuitPeriodsCurve
        fasterPeriods = fasterTable[safeZone][self.numSuits]
        suitPeriods = slowerPeriods + fasterPeriods
        self.notify.debug('suit periods: ' + `suitPeriods`)
        self.randomNumGen.shuffle(suitPeriods)
        for i in xrange(self.numSuits):
            self.suits.append(MazeSuit(i, self.maze, self.randomNumGen, suitPeriods[i], self.getDifficulty()))

    def __unloadSuits(self):
        self.notify.debug('unloadSuits')
        for suit in self.suits:
            suit.destroy()

        self.suits = []

    def __spawnUpdateSuitsTask(self):
        self.notify.debug('spawnUpdateSuitsTask')
        for suit in self.suits:
            suit.gameStart(self.gameStartTime)

        taskMgr.remove(self.UPDATE_SUITS_TASK)
        taskMgr.add(self.__updateSuitsTask, self.UPDATE_SUITS_TASK)

    def __killUpdateSuitsTask(self):
        self.notify.debug('killUpdateSuitsTask')
        taskMgr.remove(self.UPDATE_SUITS_TASK)
        for suit in self.suits:
            suit.gameEnd()

    def __updateSuitsTask(self, task):
        curT = globalClock.getFrameTime() - self.gameStartTime
        curTic = int(curT * float(MazeGameGlobals.SUIT_TIC_FREQ))
        suitUpdates = []
        for i in xrange(len(self.suits)):
            updateTics = self.suits[i].getThinkTimestampTics(curTic)
            suitUpdates.extend(zip(updateTics, [i] * len(updateTics)))

        suitUpdates.sort(lambda a, b: a[0] - b[0])
        if len(suitUpdates) > 0:
            curTic = 0
            for i in xrange(len(suitUpdates)):
                update = suitUpdates[i]
                tic = update[0]
                suitIndex = update[1]
                suit = self.suits[suitIndex]
                if tic > curTic:
                    curTic = tic
                    j = i + 1
                    while j < len(suitUpdates):
                        if suitUpdates[j][0] > tic:
                            break
                        self.suits[suitUpdates[j][1]].prepareToThink()
                        j += 1

                unwalkables = []
                for si in xrange(suitIndex):
                    unwalkables.extend(self.suits[si].occupiedTiles)

                for si in xrange(suitIndex + 1, len(self.suits)):
                    unwalkables.extend(self.suits[si].occupiedTiles)

                suit.think(curTic, curT, unwalkables)

        return Task.cont

    def enterShowScores(self):
        self.notify.debug('enterShowScores')
        lerpTrack = Parallel()
        lerpDur = 0.5
        lerpTrack.append(Parallel(LerpPosInterval(self.goalBar, lerpDur, Point3(0, 0, -.6), blendType='easeInOut'), LerpScaleInterval(self.goalBar, lerpDur, Vec3(self.goalBar.getScale()) * 2.0, blendType='easeInOut')))
        tY = 0.6
        bY = -.05
        lX = -.5
        cX = 0
        rX = 0.5
        scorePanelLocs = (((cX, bY),),
         ((lX, bY), (rX, bY)),
         ((cX, tY), (lX, bY), (rX, bY)),
         ((lX, tY),
          (rX, tY),
          (lX, bY),
          (rX, bY)))
        scorePanelLocs = scorePanelLocs[self.numPlayers - 1]
        for i in xrange(self.numPlayers):
            panel = self.scorePanels[i]
            pos = scorePanelLocs[i]
            panel.wrtReparentTo(aspect2d)
            lerpTrack.append(Parallel(LerpPosInterval(panel, lerpDur, Point3(pos[0], 0, pos[1]), blendType='easeInOut'), LerpScaleInterval(panel, lerpDur, Vec3(panel.getScale()) * 2.0, blendType='easeInOut')))

        self.showScoreTrack = Parallel(lerpTrack, Sequence(Wait(MazeGameGlobals.SHOWSCORES_DURATION), Func(self.gameOver)))
        self.showScoreTrack.start()

        #For the Alpha Blueprint ARG
        if config.GetBool('want-blueprint4-ARG', False):
            MinigameGlobals.generateDebugARGPhrase()

    def exitShowScores(self):
        self.showScoreTrack.pause()
        del self.showScoreTrack

    def enterCleanup(self):
        self.notify.debug('enterCleanup')

    def exitCleanup(self):
        pass

    def getIntroTrack(self):
        self.__cameraTask(None)
        origCamParent = camera.getParent()
        origCamPos = camera.getPos()
        origCamHpr = camera.getHpr()
        iCamParent = base.localAvatar.attachNewNode('iCamParent')
        iCamParent.setH(180)
        camera.reparentTo(iCamParent)
        toonHeight = base.localAvatar.getHeight()
        camera.setPos(0, -15, toonHeight * 3)
        camera.lookAt(0, 0, toonHeight / 2.0)
        iCamParent.wrtReparentTo(origCamParent)
        waitDur = 5.0
        lerpDur = 4.5
        lerpTrack = Parallel()
        startHpr = iCamParent.getHpr()
        startHpr.setX(PythonUtil.reduceAngle(startHpr[0]))
        lerpTrack.append(LerpPosHprInterval(iCamParent, lerpDur, pos=Point3(0, 0, 0), hpr=Point3(0, 0, 0), startHpr=startHpr, name=self.uniqueName('introLerpParent')))
        lerpTrack.append(LerpPosHprInterval(camera, lerpDur, pos=origCamPos, hpr=origCamHpr, blendType='easeInOut', name=self.uniqueName('introLerpCameraPos')))
        base.localAvatar.startLookAround()

        def cleanup(origCamParent = origCamParent, origCamPos = origCamPos, origCamHpr = origCamHpr, iCamParent = iCamParent):
            camera.reparentTo(origCamParent)
            camera.setPos(origCamPos)
            camera.setHpr(origCamHpr)
            iCamParent.removeNode()
            del iCamParent
            base.localAvatar.stopLookAround()

        return Sequence(Wait(waitDur),
                        lerpTrack,
                        Func(cleanup))
Exemplo n.º 26
0
class PartyCogActivityGui(DirectObject):
    notify = directNotify.newCategory('PartyCogActivityGui')

    def __init__(self):
        DirectObject.__init__(self)
        self._piePowerMeter = None
        self._victoryBalanceBar = None
        self._scoreLabel = None
        self._cogTracker = None
        self._piePowerTitle = None
        self._victoryBalanceTitle = None
        self._scoreTitle = None
        self._spamWarning = None
        self._spamWarningIvalName = 'PartyCogActivityGui-SpamWarning'
        return

    def load(self):
        self._initPiePowerMeter()
        self._initScore()
        self._initCogTracker()
        self._initSpamWarning()
        self._initControlGui()
        self._initVictoryBalanceBar()

    def unload(self):
        if self._cogTracker is not None:
            self._cogTracker.destory()
            self._cogTracker = None
        if self._piePowerMeter is not None:
            self._piePowerMeter.destroy()
            self._piePowerMeter = None
        if self._piePowerTitle is not None:
            self._piePowerTitle.destroy()
            self._piePowerTitle = None
        if self._scoreLabel is not None:
            self._scoreLabel.destroy()
            self._scoreLabel = None
        if self._scoreTitle is not None:
            self._scoreTitle.destroy()
            self._scoreTitle = None
        taskMgr.remove(self._spamWarningIvalName)
        if self._spamWarning:
            self._spamWarning.destroy()
            self._spamWarning = None
        if hasattr(self, '_attackKeys'):
            self._attackKeys.detachNode()
            del self._attackKeys
        if hasattr(self, '_moveKeys'):
            self._moveKeys.detachNode()
            del self._moveKeys
        if self._victoryBalanceBar:
            self._victoryBalanceBar.detachNode()
            self._victoryBalanceBar = None
        if self._victoryBalanceBarOrange:
            self._victoryBalanceBarOrange.detachNode()
            self._victoryBalanceBarOrange = None
        if self._victoryBalanceBarPie:
            self._victoryBalanceBarPie.detachNode()
            self._victoryBalanceBarPie = None
        if self._victoryBalanceBarArrow:
            self._victoryBalanceBarArrow.detachNode()
            self._victoryBalanceBarArrow = None
        return

    def _initVictoryBalanceBar(self):
        h = PartyGlobals.CogActivityPowerMeterHeight / 2.0
        w = PartyGlobals.CogActivityPowerMeterWidth / 2.0
        victoryBalanceBar = loader.loadModel(
            'phase_13/models/parties/tt_m_gui_pty_pieToss_balanceBar')
        self._victoryBalanceBar = victoryBalanceBar.find(
            '**/*tt_t_gui_pty_pieToss_balanceBarBG')
        self._victoryBalanceBar.reparentTo(aspect2d)
        self._victoryBalanceBar.setBin('fixed', 0)
        self._victoryBalanceBar.setPos(PartyGlobals.CogActivityVictoryBarPos)
        self._victoryBalanceBar.setScale(1)
        self._victoryBalanceBarOrange = victoryBalanceBar.find(
            '**/*tt_t_gui_pty_pieToss_balanceBarOrange')
        self._victoryBalanceBarOrange.reparentTo(self._victoryBalanceBar)
        self._victoryBalanceBarOrange.setBin('fixed', 1)
        self._victoryBalanceBarOrange.setPos(
            PartyGlobals.CogActivityVictoryBarOrangePos)
        self._victoryBalanceBarOrange.setScale(
            PartyGlobals.CogActivityBarStartScale, 1.0, 1.0)
        self._victoryBalanceBarPie = victoryBalanceBar.find(
            '**/*tt_t_gui_pty_pieToss_balanceBarPie')
        self._victoryBalanceBarPie.reparentTo(self._victoryBalanceBar)
        self._victoryBalanceBarPie.setBin('fixed', 2)
        self._victoryBalanceBarPie.setX(
            PartyGlobals.CogActivityVictoryBarPiePos[0])
        self._victoryBalanceBarPie.setY(
            PartyGlobals.CogActivityVictoryBarPiePos[1])
        self._victoryBalanceBarPie.setZ(
            PartyGlobals.CogActivityVictoryBarPiePos[2])
        self._victoryBalanceBarPie.setScale(
            PartyGlobals.CogActivityBarPieScale)
        self._victoryBalanceBarArrow = victoryBalanceBar.find(
            '**/*tt_t_gui_pty_pieToss_balanceArrow')
        self._victoryBalanceBarArrow.reparentTo(self._victoryBalanceBarPie)
        self._victoryBalanceBarArrow.setBin('fixed', 2)
        self._victoryBalanceBarArrow.setPos(
            PartyGlobals.CogActivityVictoryBarArrow)
        self._victoryBalanceBarArrow.setScale(
            1 / PartyGlobals.CogActivityBarPieScale)

    def _initControlGui(self):
        self._attackIvalName = 'PartyCogActivityGui-attackKeys'
        self._moveIvalName = 'PartyCogActivityGui-moveKeys'
        pieTossControls = loader.loadModel(
            'phase_13/models/parties/tt_m_gui_pty_pieToss_controls')
        self._attackKeys = pieTossControls.find('**/*control*')
        self._moveKeys = pieTossControls.find('**/*arrow*')
        self._moveKeys.reparentTo(aspect2d)
        self._moveKeys.setPos(1.0, 0.0, -0.435)
        self._moveKeys.setScale(0.15)
        self._attackKeys.reparentTo(aspect2d)
        self._attackKeys.setPos(0.85, 0.0, -0.45)
        self._attackKeys.setScale(0.15)
        self._moveKeys.hide()
        self._attackKeys.hide()

    def _initPiePowerMeter(self):
        h = PartyGlobals.CogActivityPowerMeterHeight / 2.0
        w = PartyGlobals.CogActivityPowerMeterWidth / 2.0
        self._piePowerMeter = DirectWaitBar(
            frameSize=(-h, h, -w, w),
            relief=DGG.GROOVE,
            frameColor=(0.9, 0.9, 0.9, 1.0),
            borderWidth=(0.01, 0.01),
            barColor=PartyGlobals.CogActivityColors[0],
            pos=PartyGlobals.CogActivityPowerMeterPos,
            hpr=(0.0, 0.0, -90.0))
        self._piePowerMeter.setBin('fixed', 0)
        self._piePowerTitle = OnscreenText(
            text=TTLocalizer.PartyCogGuiPowerLabel,
            pos=PartyGlobals.CogActivityPowerMeterTextPos,
            scale=0.05,
            fg=(1.0, 1.0, 1.0, 1.0),
            align=TextNode.ACenter)
        self._piePowerTitle.setBin('fixed', 0)
        self._piePowerMeter.hide()
        self._piePowerTitle.hide()

    def _initScore(self):
        self._scoreLabel = OnscreenText(
            text='0',
            pos=PartyGlobals.CogActivityScorePos,
            scale=PartyGlobals.TugOfWarTextWordScale,
            fg=(1.0, 1.0, 0.0, 1.0),
            align=TextNode.ARight,
            font=ToontownGlobals.getSignFont(),
            mayChange=True)
        self._scoreTitle = OnscreenText(text=TTLocalizer.PartyCogGuiScoreLabel,
                                        pos=PartyGlobals.CogActivityScoreTitle,
                                        scale=0.05,
                                        fg=(1.0, 1.0, 1.0, 1.0),
                                        align=TextNode.ARight)
        self._scoreLabel.hide()
        self._scoreTitle.hide()

    def _initCogTracker(self):
        self._cogTracker = PartyCogTrackerGui()

    def _initSpamWarning(self):
        self._spamWarning = OnscreenText(
            text=TTLocalizer.PartyCogGuiSpamWarning,
            scale=0.15,
            fg=(1.0, 1.0, 0, 1.0),
            shadow=(0, 0, 0, 0.62),
            mayChange=False,
            pos=(0, 0.33))
        self._spamWarning.hide()

    def showScore(self):
        self._scoreLabel.show()
        self._scoreTitle.show()

    def hideScore(self):
        self._scoreLabel.hide()
        self._scoreTitle.hide()

    def setScore(self, score=0):
        self._scoreLabel['text'] = str(score)

    def resetPiePowerMeter(self):
        self._piePowerMeter['value'] = 0

    def showPiePowerMeter(self):
        self._piePowerMeter.show()
        self._piePowerTitle.show()

    def hidePiePowerMeter(self):
        self._piePowerMeter.hide()
        self._piePowerTitle.hide()

    def updatePiePowerMeter(self, value):
        self._piePowerMeter['value'] = value

    def getPiePowerMeterValue(self):
        return self._piePowerMeter['value']

    def hideSpamWarning(self):
        taskMgr.remove(self._spamWarningIvalName)
        if self._spamWarning:
            self._spamWarning.hide()

    def showSpamWarning(self):
        if self._spamWarning.isHidden():
            self._spamWarning.show()
            taskMgr.remove(self._spamWarningIvalName)
            Sequence(ToontownIntervals.getPulseLargerIval(
                self._spamWarning, ''),
                     Wait(PartyGlobals.CogActivitySpamWarningShowTime),
                     Func(self.hideSpamWarning),
                     name=self._spamWarningIvalName,
                     autoFinish=1).start()

    def hide(self):
        self.hidePiePowerMeter()
        self.hideScore()
        self.hideSpamWarning()
        self.hideControls()

    def disableToontownHUD(self):
        base.localAvatar.hideName()
        base.localAvatar.laffMeter.hide()
        base.setCellsActive(base.bottomCells + [base.rightCells[1]], False)

    def enableToontownHUD(self):
        base.localAvatar.showName()
        base.localAvatar.laffMeter.show()
        base.setCellsActive(base.bottomCells + [base.rightCells[1]], True)

    def setTeam(self, team):
        self.team = team
        if team == 0:
            self._cogTracker.frame.setR(180)
        self._piePowerMeter['barColor'] = PartyGlobals.CogActivityColors[team]

    def startTrackingCogs(self, cogs):
        self.cogs = cogs
        taskMgr.add(self.trackCogs, 'trackCogs')

    def trackCogs(self, task):
        if self.cogs is None:
            return
        self._updateVictoryBar()
        for i, cog in enumerate(self.cogs):
            self._cogTracker.updateCog(i, cog, self.team)

        return task.cont

    def _updateVictoryBar(self):
        if not (hasattr(self, '_victoryBalanceBar')
                and self._victoryBalanceBar):
            return
        netDistance = 0
        for cog in self.cogs:
            netDistance = netDistance + cog.targetDistance

        teamDistance = netDistance / 6.0
        self._victoryBalanceBarOrange.setScale(
            PartyGlobals.CogActivityBarStartScale +
            teamDistance * 10 * PartyGlobals.CogActivityBarUnitScale, 1.0, 1.0)
        self._victoryBalanceBarPie.setX(
            PartyGlobals.CogActivityVictoryBarPiePos[0] +
            teamDistance * 10 * PartyGlobals.CogActivityBarPieUnitMove)
        self._victoryBalanceBarPie.setY(
            PartyGlobals.CogActivityVictoryBarPiePos[1])
        self._victoryBalanceBarPie.setZ(
            PartyGlobals.CogActivityVictoryBarPiePos[2])
        if teamDistance > 0.0:
            self._victoryBalanceBarArrow.setColor(
                PartyGlobals.CogActivityColors[1])
        elif teamDistance < 0.0:
            self._victoryBalanceBarArrow.setColor(
                PartyGlobals.CogActivityColors[0])
        else:
            self._victoryBalanceBarArrow.setColor(VBase4(1.0, 1.0, 1.0, 1.0))

    def stopTrackingCogs(self):
        taskMgr.remove('trackCogs')

    def showAttackControls(self):
        if self._attackKeys.isHidden():
            self._attackKeys.show()
            taskMgr.remove(self._attackIvalName)
            Sequence(ToontownIntervals.getPulseLargerIval(self._attackKeys,
                                                          '',
                                                          scale=0.15),
                     Wait(PartyGlobals.CogActivityControlsShowTime),
                     Func(self.hideAttackControls),
                     name=self._attackIvalName,
                     autoFinish=1).start()

    def showMoveControls(self):
        if self._moveKeys.isHidden() and not self._attackKeys.isHidden():
            self._moveKeys.show()
            taskMgr.remove(self._moveIvalName)
            Sequence(ToontownIntervals.getPulseLargerIval(self._moveKeys,
                                                          '',
                                                          scale=0.15),
                     Wait(PartyGlobals.CogActivityControlsShowTime),
                     Func(self.hideMoveControls),
                     name=self._moveIvalName,
                     autoFinish=1).start()

    def hideAttackControls(self):
        taskMgr.remove(self._attackIvalName)
        if hasattr(self, '_attackKeys') and self._attackKeys:
            self._attackKeys.hide()

    def hideMoveControls(self):
        taskMgr.remove(self._moveIvalName)
        if hasattr(self, '_moveKeys') and self._moveKeys:
            self._moveKeys.hide()

    def hideControls(self):
        self.hideMoveControls()
        self.hideAttackControls()
class QuestPoster(DirectFrame):
    notify = directNotify.newCategory('QuestPoster')

    def __init__(self, quest, parent = aspect2d, **kw):
        self.quest = quest

        # Let's begin building the quest poster.
        bookModel = loader.loadModel('phase_3.5/models/gui/stickerbook_gui.bam')
        questCard = bookModel.find('**/questCard')
        optiondefs = (('relief', None, None),
         ('image', questCard, None),
         ('image_scale', (0.8, 1.0, 0.58), None),
         ('state', DGG.NORMAL, None))
        self.defineoptions(kw, optiondefs)
        DirectFrame.__init__(self, relief = None)
        self.initialiseoptions(QuestPoster)

        self.questFrame = DirectFrame(parent = self, relief = None)

        # Quest title text
        self.headline = DirectLabel(parent = self.questFrame, relief = None,
            text = self.quest.getName(),
            text_font = CIGlobals.getMinnieFont(),
            text_fg = QuestGlobals.TEXT_COLOR,
            text_scale = 0.05,
            text_align = TextNode.ACenter,
            text_wordwrap = 25.0, textMayChange = 1,
        pos = (0, 0, 0.23))

        # Quest information
        self.questInfo = DirectLabel(parent = self.questFrame, relief = None,
            text = '',
            text_font = CIGlobals.getToonFont(),
            text_fg = QuestGlobals.TEXT_COLOR,
            text_scale = 0.04,
            text_align = TextNode.ACenter,
            text_wordwrap = TEXT_WORDWRAP,
            textMayChange = 1,
        pos = (QuestGlobals.DEFAULT_INFO_POS))
        self.questInfo.hide()

        self.questInfo02 = DirectLabel(parent = self.questFrame, relief = None,
            text = '',
            text_font = CIGlobals.getToonFont(),
            text_fg = QuestGlobals.TEXT_COLOR,
            text_scale = 0.04,
            text_align = TextNode.ACenter,
            text_wordwrap = TEXT_WORDWRAP,
            textMayChange = 1,
        pos = (QuestGlobals.DEFAULT_INFO2_POS))
        self.questInfo02.hide()

        self.locationInfo = DirectLabel(parent = self.questFrame, relief = None,
            text = 'N/A',
            text_font = CIGlobals.getToonFont(),
            text_fg = QuestGlobals.TEXT_COLOR,
            text_scale = TEXT_SCALE,
            text_align = TextNode.ACenter,
            text_wordwrap = TEXT_WORDWRAP,
            textMayChange = 1,
        pos = (0, 0, -0.115))
        self.locationInfo.hide()

        # C'mon Brian this one is obvious
        self.rewardText = DirectLabel(parent = self.questFrame, relief = None,
            text = '',
            text_fg = QuestGlobals.REWARD_RED,
            text_scale = 0.0425,
            text_align = TextNode.ALeft,
            text_wordwrap = 17.0,
            textMayChange = 1,
        pos = (-0.36, 0, -0.26))
        self.rewardText.hide()

        self.lPictureFrame = DirectFrame(parent = self.questFrame, relief = None,
            image = bookModel.find('**/questPictureFrame'),
            image_scale = QuestGlobals.IMAGE_SCALE_SMALL,
            text = '',
            text_pos = (0, -0.11),
            text_fg = QuestGlobals.TEXT_COLOR,
            text_scale = TEXT_SCALE,
            text_align = TextNode.ACenter,
            text_wordwrap = 11.0,
            pos = (QuestGlobals.DEFAULT_LEFT_PICTURE_POS),
        textMayChange = 1)
        self.lPictureFrame.hide()

        self.rPictureFrame = DirectFrame(parent = self.questFrame, relief = None,
            image = bookModel.find('**/questPictureFrame'),
            image_scale = QuestGlobals.IMAGE_SCALE_SMALL,
            text = '', text_pos = (0, -0.11),
            text_fg = QuestGlobals.TEXT_COLOR,
            text_scale = TEXT_SCALE,
            text_align = TextNode.ACenter,
            text_wordwrap = 11.0,
            textMayChange = 1,
        pos = (QuestGlobals.DEFAULT_RIGHT_PICTURE_POS))
        self.rPictureFrame['image_color'] = Vec4(*QuestGlobals.GREEN)
        self.rPictureFrame.hide()

        self.lQuestIcon = DirectFrame(parent = self.lPictureFrame, relief = None,
            text = ' ', text_font = CIGlobals.getSuitFont(),
            text_pos = (0, -0.03),
            text_fg = QuestGlobals.TEXT_COLOR,
            text_scale = 0.13,
            text_align = TextNode.ACenter,
            text_wordwrap = 13.0,
        textMayChange = 1)
        self.lQuestIcon.setColorOff(-1)

        self.rQuestIcon = DirectFrame(parent = self.rPictureFrame, relief = None,
            text = ' ',
            text_font = CIGlobals.getSuitFont(),
            text_pos = (0, -0.03),
            text_fg = QuestGlobals.TEXT_COLOR,
            text_scale = 0.13,
            text_align = TextNode.ACenter,
            text_wordwrap = 13.0,
        textMayChange = 1)
        self.rQuestIcon.setColorOff(-1)

        head = SuitBank.PennyPincher.getHead().generate()
        head.setDepthTest(True)
        head.setDepthWrite(True)
        head.setScale(0.25)
        for part in head.getChildren():
            part.setDepthTest(True)
            part.setDepthWrite(True)
        self.fitGeometry(head, fFlip = 1)
        self.rQuestIcon['geom'] = head
        self.rQuestIcon['geom_scale'] = QuestGlobals.IMAGE_SCALE_SMALL
        self.rQuestIcon['geom_pos'] = Point3(0, 10, -0.05)
        self.rQuestIcon['geom_hpr'] = Point3(180, 0, 0)
        self.rQuestIcon.initialiseoptions(DirectFrame)

        self.auxText = DirectLabel(parent = self.questFrame, relief = None,
            text = 'Recover',
            text_font = CIGlobals.getToonFont(),
            text_scale = QuestGlobals.QPauxText,
            text_fg = QuestGlobals.TEXT_COLOR,
            text_align = TextNode.ACenter,
            pos = (QuestGlobals.DEFAULT_AUX_POS),
        textMayChange=1)
        self.auxText.hide()

        self.middleText = DirectLabel(parent = self.questFrame, relief = None,
            text = 'from:',
            text_font = CIGlobals.getToonFont(),
            text_scale = QuestGlobals.QPauxText,
            text_fg = QuestGlobals.TEXT_COLOR,
            text_align = TextNode.ACenter,
            pos = (QuestGlobals.DEFAULT_MIDDLE_POS),
        textMayChange=1)
        self.middleText.hide()

        self.questProgress = DirectWaitBar(parent = self.questFrame, relief = DGG.SUNKEN,
            frameSize=(-0.95, 0.95, -0.1, 0.12),
            borderWidth = (0.025, 0.025),
            scale = 0.2,
            frameColor = (0.945, 0.875, 0.706, 1.0),
            barColor=(0.5, 0.7, 0.5, 1),
            text='0/0',
            text_font = CIGlobals.getToonFont(),
            text_scale = 0.19,
            text_fg = (0.05, 0.14, 0.4, 1),
            text_align = TextNode.ACenter,
            text_pos = (0, -0.05), #-0.02
        pos = (0, 0, -0.2425))
        self.questProgress.hide()

        rewardFrameGeom = loader.loadModel('phase_4/models/gui/gag_shop_purchase_gui.bam')
        self.rewardFrame = DirectFrame(parent = self.questFrame, relief = None,
            geom = rewardFrameGeom.find('**/Goofys_Sign'),
            geom_scale = (0.615, 0, 0.4),
            pos = (-0.01, 0, -0.25)
        )

        jellybeanJar = QuestGlobals.getFilmIcon()
        self.lRewardFrame = DirectFrame(parent = self.rewardFrame, relief = None,
            geom = jellybeanJar,
            geom_scale = QuestGlobals.TP_ACCESS_SCALE,
            sortOrder = 1,
        pos = (QuestGlobals.LEFT_TP_ACCESS_POS))
        self.lRewardFrame.setBin('gui-popup', 30)

        self.lRewardAmt = DirectFrame(parent = self.questFrame, relief = None,
            geom = rewardFrameGeom.find('**/Char_Pnl'),
            geom_scale = (0.15, 0, 0.1275),
            text = '#1',
            text_font = CIGlobals.getToonFont(),
            text_scale = 0.04,
            text_fg = (0, 0, 0, 1),
            text_align = TextNode.ACenter,
            text_pos = (0, -0.01),
            sortOrder = 2,
        pos = (-0.285, 0, -0.255))
        self.lRewardAmt.setBin('gui-popup', 40)

        self.rRewardFrame = DirectFrame(parent = self.rewardFrame, relief = None,
            geom = QuestGlobals.getJBIcon(),
            geom_scale = QuestGlobals.JB_JAR_SCALE,
        pos = QuestGlobals.RIGHT_JB_JAR_POS)

        self.rRewardAmt = DirectFrame(parent = self.questFrame, relief = None,
            geom = rewardFrameGeom.find('**/Char_Pnl'),
            geom_scale = (0.15, 0, 0.1275),
            text = '25',
            text_font = CIGlobals.getToonFont(),
            text_scale = 0.04,
            text_fg = (0, 0, 0, 1),
            text_align = TextNode.ACenter,
            text_pos = (0, -0.01),
        pos = (0.2725, 0, -0.255))
        self.rRewardAmt.setBin('gui-popup', 40)

        rewardFrameGeom.removeNode()

        # This is the rotated text on the side.
        self.sideInfo = DirectLabel(parent = self.questFrame, relief = None,
            text = QuestGlobals.JUST_FOR_FUN,
            text_fg = (0.0, 0.439, 1.0, 1.0),
            text_shadow = (0, 0, 0, 1),
            pos = (-0.2825, 0, 0.2),
        scale = 0.03)
        self.sideInfo.setR(-30)
        self.sideInfo.hide()

        bookModel.removeNode()
        self.laffMeter = None
        
        self.hide()
        return
    
    def handleIcon(self, objective, geom, scale, icon):
        isHead = True if type(geom) == ToonHead else geom.getName() == ('%sHead' % CIGlobals.Suit)

        if isHead:
            geom.setDepthWrite(1)
            geom.setDepthTest(1)
            self.fitGeometry(geom, fFlip = 1)
            
            if isinstance(objective, VisitNPCObjective) and icon == self.rQuestIcon:
                icon.setPos(icon.getX(), icon.getY(), icon.getZ() + 0.05)
                icon.setH(180)
            elif isinstance(objective, CogObjective):
                icon.setScale(QuestGlobals.IMAGE_SCALE_SMALL)
                icon.setPos(icon.getX(), icon.getY(), icon.getZ() - 0.04)
                if icon == self.lQuestIcon:
                    icon.setH(180)
            
            icon['geom'] = geom
            icon['geom_scale'] = QuestGlobals.IMAGE_SCALE_SMALL
        else:
            icon['geom'] = geom
            icon['geom_scale'] = scale

    def update(self):
        objective = self.quest.getCurrentObjective()
        objective.updateInfo()

        # Let's setup the quest info.
        self.questInfo.setPos(self.quest.getInfoPos())
        self.questInfo['text'] = self.quest.getInfoText()
        self.questInfo02.setPos(self.quest.getInfo02Pos())
        self.questInfo02['text'] = self.quest.getInfo02Text()
        
        # Let's move the picture frames to the positions we want them in.
        self.lPictureFrame.setPos(self.quest.getLeftPicturePos())
        self.rPictureFrame.setPos(self.quest.getRightPicturePos())
        
        editLeftAtr = isinstance(objective, VisitNPCObjective) or isinstance(objective, CogObjective)
        if editLeftAtr and objective.getDidEditLeft() or not editLeftAtr:
            geom = self.quest.getLeftIconGeom()
            scale = self.quest.getLeftIconScale()
            icon = self.lQuestIcon
            self.handleIcon(objective, geom, scale, icon)
            self.handleIcon(objective, self.quest.getRightIconGeom(), self.quest.getRightIconScale(), self.rQuestIcon)
        else:
            geom = self.quest.getRightIconGeom()
            scale = self.quest.getRightIconScale()
            icon = self.rQuestIcon
            self.handleIcon(objective, geom, scale, icon)
            self.handleIcon(objective, self.quest.getLeftIconGeom(), self.quest.getLeftIconScale(), self.lQuestIcon)

        if self.questInfo02['text'] == '':
            self.rPictureFrame.hide()
            self.questInfo02.hide()
        else:
            self.rPictureFrame.show()
            self.questInfo02.show()

        self.middleText['text'] = self.quest.getMiddleText()
        if not self.middleText['text'] == '':
            self.middleText.show()

        self.questInfo.show()
        self.lPictureFrame.show()

        # Let's set the location text.
        self.locationInfo['text'] = self.quest.getLocationText()
        self.locationInfo['text_pos'] = (0, self.quest.getLocationY())
        self.locationInfo.show()

        # Let's set the progress bar up.
        self.questProgress['text'] = self.quest.getProgressText()
        if len(self.questProgress['text']) > 0 and not objective.finished():
            self.questProgress.show()
            self.questProgress['range'] = objective.getNeededAmount()
            self.questProgress['value'] = objective.getProgress() & pow(2, 16) - 1
        else:
            self.questProgress.hide()

        # Let's setup the aux text.
        self.auxText.setPos(self.quest.getAuxPos())
        self.auxText['text'] = self.quest.getAuxText()
        self.auxText.show()

        maxHP = base.localAvatar.getMaxHealth()
        
        # Let's setup the rewards.
        for i in xrange(0, len(self.quest.getRewards())):
            reward = self.quest.getRewards()[i]
            frame = self.lRewardFrame if (i == 0) else self.rRewardFrame
            info = self.lRewardAmt if (i == 0) else self.rRewardAmt
            rType = reward.getType()
            if(rType == RewardType.JELLYBEANS):
                frame['pos'] = QuestGlobals.LEFT_JB_JAR_POS if (i == 0) else QuestGlobals.RIGHT_JB_JAR_POS
                frame['geom'] = QuestGlobals.getJBIcon()
                frame['geom_scale'] = QuestGlobals.JB_JAR_SCALE
                info['text'] = str(reward.getModifier())
            elif(rType == RewardType.TELEPORT_ACCESS or rType == RewardType.GAG_FRAME):
                frame['pos'] = QuestGlobals.LEFT_TP_ACCESS_POS if(i == 0) else QuestGlobals.RIGHT_TP_ACCESS_POS
                frame['geom'] = QuestGlobals.getTPAccessIcon() if(rType == RewardType.TELEPORT_ACCESS) else QuestGlobals.getFilmIcon()
                frame['geom_scale'] = QuestGlobals.TP_ACCESS_SCALE
                info['text'] = 'N/A' if(rType == RewardType.TELEPORT_ACCESS) else '#%s' % (str(reward.getModifier()))
            elif(rType == RewardType.LAFF_POINTS):
                frame.initialiseoptions(DirectFrame)
                r, g, b, _ = base.localAvatar.getHeadColor()
                pos = QuestGlobals.LEFT_LAFF_METER_POS if(i == 0) else QuestGlobals.RIGHT_LAFF_METER_POS

                # Create the laff meter with the new health.
                hp = maxHP + reward.getModifier()
                laffMeter = LaffOMeter()
                laffMeter.generate(r, g, b, base.localAvatar.getAnimal(), maxHP = hp, initialHP = hp)

                # Let's position the laff meter.
                frame['geom'] = laffMeter
                frame['geom_scale'] = QuestGlobals.LAFF_METER_SCALE
                frame.setPos(pos)
                info['text'] = '+%s' % (str(reward.getModifier()))
                laffMeter.destroy()
                laffMeter = None

        # Hide or show the other reward depending on if there's 2 rewards.
        if(len(self.quest.getRewards()) == 1):
            self.rRewardFrame.hide()
            self.rRewardAmt.hide()
        else:
            self.rRewardFrame.show()
            self.rRewardAmt.show()

        if objective.finished():
            self.setColor(Vec4(*QuestGlobals.LIGHT_GREEN))
            self.sideInfo['text'] = 'Completed!'
            self.sideInfo.show()
            
        self.questInfo.initialiseoptions(DirectLabel)
        self.questInfo02.initialiseoptions(DirectLabel)
        self.locationInfo.initialiseoptions(DirectLabel)
        self.lPictureFrame.initialiseoptions(DirectFrame)
        self.rPictureFrame.initialiseoptions(DirectFrame)
        self.lQuestIcon.initialiseoptions(DirectFrame)
        self.rQuestIcon.initialiseoptions(DirectFrame)
        self.auxText.initialiseoptions(DirectLabel)
        self.middleText.initialiseoptions(DirectLabel)
        self.sideInfo.initialiseoptions(DirectLabel)
        self.lPictureFrame['image_color'] = self.quest.getPictureFrameColor()
        self.rPictureFrame['image_color'] = self.quest.getPictureFrameColor()

    def fitGeometry(self, geom, fFlip = 0, dimension = 0.8):
        p1 = Point3()
        p2 = Point3()
        geom.calcTightBounds(p1, p2)
        if fFlip:
            t = p1[0]
            p1.setX(-p2[0])
            p2.setX(-t)
        d = p2 - p1
        biggest = max(d[0], d[2])
        s = dimension / biggest
        mid = (p1 + d / 2.0) * s
        geomXform = hidden.attachNewNode('geomXform')
        for child in geom.getChildren():
            child.reparentTo(geomXform)

        geomXform.setPosHprScale(-mid[0], -mid[1] + 1, -mid[2], 180, 0, 0, s, s, s)
        geomXform.reparentTo(geom)

    def destroy(self):
        self._deleteGeoms()
        DirectFrame.destroy(self)

    def _deleteGeoms(self):
        for icon in (self.lQuestIcon, self.rQuestIcon):
            geom = icon['geom']
            if geom and hasattr(geom, 'delete'):
                geom.delete()
                
    def getQuest(self):
        return self.quest
Exemplo n.º 28
0
class DistributedGolfSpot(DistributedObject.DistributedObject, FSM.FSM):
    """ This is one of four golf spots to appear in the corner of the CEO banquet
    room.  """

    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedGolfSpot')
    positions = ((-45, 100, GolfGlobals.GOLF_BALL_RADIUS),
                 (-15, 100, GolfGlobals.GOLF_BALL_RADIUS),
                 (15, 100, GolfGlobals.GOLF_BALL_RADIUS),
                 (45, 100, GolfGlobals.GOLF_BALL_RADIUS))
    toonGolfOffsetPos = Point3(-2, 0, -GolfGlobals.GOLF_BALL_RADIUS)
    toonGolfOffsetHpr = Point3(-90, 0, 0)
    rotateSpeed = 20  # degrees per second

    # The number of seconds it takes to move the power meter to
    # full the first time.
    golfPowerSpeed = base.config.GetDouble('golf-power-speed', 3)

    # The exponent that controls the factor at which the power
    # meter slows down over time.  Values closer to 1.0 slow down less
    # quickly.
    golfPowerExponent = base.config.GetDouble('golf-power-exponent', 0.75)

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        FSM.FSM.__init__(self, 'DistributedGolfSpot')
        self.boss = None
        self.index = None
        self.avId = 0
        self.toon = None  # the actual toon which should match up with self.avId
        self.golfSpotSmoother = SmoothMover()
        self.golfSpotSmoother.setSmoothMode(SmoothMover.SMOn)
        self.smoothStarted = 0
        self.__broadcastPeriod = 0.2
        if (self.index is not None) and (self.index > len(self.positions)):
            self.notify.error("Invalid index %d" % self.index)
        self.fadeTrack = None

        # stuff related to power bar
        self.setupPowerBar()
        self.aimStart = None

        self.golfSpotAdviceLabel = None

        # 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

        self.controlKeyAllowed = False
        self.flyBallTracks = {}
        self.splatTracks = {}
        self.__flyBallBubble = None
        self.flyBallHandler = None
        self.__flyBallSequenceNum = 0
        self.swingInterval = None
        self.lastHitSequenceNum = -1
        self.goingToReward = False
        self.gotHitByBoss = False

        self.releaseTrack = None
        self.grabTrack = None
        self.restoreScaleTrack = None

    def setBossCogId(self, bossCogId):
        """Handle receiving the CEO doId from the server."""
        self.bossCogId = 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.
        self.boss = base.cr.doId2do[bossCogId]
        self.boss.setGolfSpot(self, self.index)

    def setIndex(self, index):
        """Handle receiving the index which identifies our side the server."""
        self.index = index
        # WARNING debug only, remove this
        #if (index == 0):
        #    base.gs = self

    def disable(self):
        """Disable ourself."""
        DistributedObject.DistributedObject.disable(self)
        self.ignoreAll()

    def delete(self):
        """Delete ourself."""

        DistributedObject.DistributedObject.delete(self)
        self.ignoreAll()

        self.boss = None

    def announceGenerate(self):
        """Do more setup once all required fields are in."""
        DistributedObject.DistributedObject.announceGenerate(self)
        self.triggerName = self.uniqueName('trigger')
        self.triggerEvent = 'enter%s' % (self.triggerName)
        self.smoothName = self.uniqueName('golfSpotSmooth')
        self.golfSpotAdviceName = self.uniqueName('golfSpotAdvice')
        self.posHprBroadcastName = self.uniqueName('golfSpotBroadcast')
        self.ballPowerTaskName = self.uniqueName('updateGolfPower')
        self.adjustClubTaskName = self.uniqueName('adjustClub')
        self.loadAssets()
        self.accept('flyBallHit-%d' % self.index, self.__flyBallHit)

    def loadAssets(self):
        """Load our assets."""
        self.root = render.attachNewNode('golfSpot-%d' % self.index)
        self.root.setPos(*self.positions[self.index])
        self.ballModel = loader.loadModel('phase_6/models/golf/golf_ball')
        self.ballColor = VBase4(1, 1, 1, 1)
        if self.index < len(GolfGlobals.PlayerColors):
            self.ballColor = VBase4(*GolfGlobals.PlayerColors[self.index])
            self.ballModel.setColorScale(self.ballColor)
        self.ballModel.reparentTo(self.root)
        self.club = loader.loadModel('phase_6/models/golf/putter')
        self.clubLookatSpot = self.root.attachNewNode('clubLookat')
        self.clubLookatSpot.setY(-(GolfGlobals.GOLF_BALL_RADIUS + 0.1))

        # create a collision sphere to trigger when we touch the ball

        # 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, 0, 0, 1)
        cs.setTangible(0)
        cn = CollisionNode(self.triggerName)
        cn.addSolid(cs)
        cn.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.trigger = self.root.attachNewNode(cn)
        self.trigger.stash()

        self.hitBallSfx = loader.loadSfx('phase_6/audio/sfx/Golf_Hit_Ball.mp3')

    def cleanup(self):
        if self.swingInterval:
            self.swingInterval.finish()
            self.swingInterval = None
        if self.releaseTrack:
            self.releaseTrack.finish()
            self.releaseTrack = None
        flyTracks = list(self.flyBallTracks.values())
        for track in flyTracks:
            track.finish()

        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None

        if self.restoreScaleTrack:
            self.restoreScaleTrack.finish()
            self.restoreScaleTrack = None

        self.root.removeNode()
        self.ballModel.removeNode()
        self.club.removeNode()
        if self.powerBar:
            self.powerBar.destroy()
            self.powerBar = None

        taskMgr.remove(self.triggerName)

        assert self.notify.debugStateCall(self)

        self.boss = None

    def setState(self, state, avId, extraInfo):
        """Handle the AI telling us the current state, and who controls us."""
        if not self.isDisabled():
            self.gotHitByBoss = extraInfo
            if state == 'C':
                self.demand('Controlled', avId)
            elif state == 'F':
                self.demand('Free')
            elif state == 'O':
                self.demand('Off')
            else:
                self.notify.error("Invalid state from AI: %s" % (state))

    ### FSM States ###

    def enterOff(self):
        """Handle entering the off state."""
        assert self.notify.debugStateCall(self)
        pass

    def exitOff(self):
        """Handle exiting the off state."""
        assert self.notify.debugStateCall(self)
        pass

    def enterFree(self):
        """Handle entering the free state."""
        assert self.notify.debugStateCall(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(),
                                          name="restoreScaleTrack")
        self.restoreScaleTrack.start()

        if self.avId == localAvatar.doId:
            # Five second timeout on grabbing the same crane again.  Go
            # get a different crane!
            if not self.isDisabled():
                self.ballModel.setAlphaScale(0.3)
                self.ballModel.setTransparency(1)
                taskMgr.doMethodLater(5, self.__allowDetect, self.triggerName)

                self.fadeTrack = Sequence(Func(self.ballModel.setTransparency,
                                               1),
                                          self.ballModel.colorScaleInterval(
                                              0.2, VBase4(1, 1, 1, 0.3)),
                                          name='fadeTrack-enterFree')
                self.fadeTrack.start()

        else:
            # Other players can grab this crane immediately.
            self.trigger.unstash()
            self.accept(self.triggerEvent, self.__hitTrigger)

        self.avId = 0

    def exitFree(self):
        """Handle exiting the free state."""
        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None

        self.restoreScaleTrack.finish()  # make sure we don't see a small ball
        self.restoreScaleTrack = None

        taskMgr.remove(self.triggerName)
        #self.ballModel.clearColorScale()
        self.ballModel.clearTransparency()

        self.trigger.stash()
        self.ignore(self.triggerEvent)
        pass

    def enterControlled(self, avId):
        """Handle entering the controlled state."""
        assert self.notify.debugStateCall(self)
        self.avId = avId
        toon = base.cr.doId2do.get(avId)
        if not toon:
            return
        self.enableControlKey()
        self.toon = toon
        self.grabTrack = self.makeToonGrabInterval(toon)

        if avId == localAvatar.doId:
            # The local toon is beginning to control the crane.

            self.boss.toCraneMode()

            camera.reparentTo(self.root)
            camera.setPosHpr(0, -10, 3, 0, 0, 0)
            #self.tube.stash()

            #localAvatar.setPosHpr(self.controls, 0, 0, 0, 0, 0, 0)
            localAvatar.setPos(self.root, self.toonGolfOffsetPos)
            localAvatar.setHpr(self.root, self.toonGolfOffsetHpr)

            localAvatar.sendCurrentPosition()

            #self.__activatePhysics()
            self.__enableControlInterface()
            self.startPosHprBroadcast()
            #self.startShadow()

            # If we get a message from the Place that we exited Crane
            # mode--for instance, because we got hit by flying
            # gears--then ask the AI to yield us up.
            self.accept('exitCrane', self.gotBossZapped)

        else:
            pass
            #self.startSmooth()
            #toon.stopSmooth()
            #self.grabTrack = Sequence(self.grabTrack,
            #                          Func(toon.startSmooth))

        self.grabTrack.start()

        pass

    def exitControlled(self):
        """Handle exiting the controlled state."""
        assert self.notify.debugStateCall(self)
        self.grabTrack.finish()
        del self.grabTrack

        if self.swingInterval:
            self.swingInterval.finish()
            self.swingInterval = None

        if not self.ballModel.isEmpty():
            if self.ballModel.isHidden():
                self.notify.debug('ball is hidden scale =%s' %
                                  self.ballModel.getScale())
            else:
                self.notify.debug('ball is showing scale=%s' %
                                  self.ballModel.getScale())

        if self.toon and not self.toon.isDisabled():
            #self.toon.loop('neutral')
            #self.notify.debug('looping neutral')
            self.toon.startSmooth()

        self.releaseTrack = self.makeToonReleaseInterval(self.toon)
        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()
            if not self.goingToReward:
                camera.reparentTo(base.localAvatar)
                camera.setPos(base.localAvatar.cameraPositions[0][0])
                camera.setHpr(0, 0, 0)

        #self.__straightenCable()
        #self.avId = 0
        #self.toon = None
        self.stopAdjustClubTask()
        self.releaseTrack.start()
        self.enableControlKey()

    def __allowDetect(self, task):
        if self.fadeTrack:
            self.fadeTrack.finish()
        self.fadeTrack = Sequence(
            self.ballModel.colorScaleInterval(0.2, self.ballColor),
            #Func(self.ballModel.clearColorScale),
            Func(self.ballModel.clearTransparency),
            name='fadeTrack-allowDetect')

        self.fadeTrack.start()

        self.trigger.unstash()
        self.accept(self.triggerEvent, self.__hitTrigger)

    def __hitTrigger(self, event):
        self.d_requestControl()

    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.
        return Sequence()
        #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 d_requestControl(self):
        self.sendUpdate('requestControl')

    def d_requestFree(self, gotHitByBoss):
        self.sendUpdate('requestFree', [gotHitByBoss])

    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(self.root)
        origHpr = toon.getHpr(self.root)
        a = self.accomodateToon(toon)
        newPos = toon.getPos()
        newHpr = toon.getHpr()
        origHpr.setX(PythonUtil.fitSrcAngle2Dest(origHpr[0], newHpr[0]))
        self.notify.debug('toon.setPosHpr %s %s' % (origPos, origHpr))
        toon.setPosHpr(origPos, origHpr)

        walkTime = 0.2
        reach = Sequence()  #ActorInterval(toon, 'GolfPuttLoop')
        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)
            Func(toon.stopLookAround),
        )
        if toon == base.localAvatar:
            i.append(Func(self.switchToAnimState, 'GolfPuttLoop'))
        i.append(Func(self.startAdjustClubTask))
        i = Parallel(i, a)

        return i

    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.
        toon.wrtReparentTo(self.root)
        toon.setPos(self.toonGolfOffsetPos)
        toon.setHpr(self.toonGolfOffsetHpr)
        return Sequence()

    def switchToAnimState(self, animStateName, forced=False):
        """Switch the toon to another anim state if not in it already."""
        # temp since we only have anims for male medium torso medium legs
        #dna = base.localAvatar.getStyle()
        #if not( dna.gender == 'm' and (dna.torso =='ms' or dna.torso =='ls')\
        #        and (dna.legs=='m' or dna.legs=='l')):
        #    return
        curAnimState = base.localAvatar.animFSM.getCurrentState()
        #self.notify.debug('curAnimState=%s' % curAnimState)
        curAnimStateName = ''
        if curAnimState:
            curAnimStateName = curAnimState.getName()
        if curAnimStateName != animStateName or forced:
            base.localAvatar.b_setAnimState(animStateName)

    def __enableControlInterface(self):
        """Enable the control interface."""
        gui = loader.loadModel("phase_3.5/models/gui/avatar_panel_gui")

        self.closeButton = DirectButton(
            image=(
                gui.find("**/CloseBtn_UP"),
                gui.find("**/CloseBtn_DN"),
                gui.find("**/CloseBtn_Rllvr"),
                gui.find("**/CloseBtn_UP"),
            ),
            relief=None,
            scale=2,
            text=TTLocalizer.BossbotGolfSpotLeave,
            text_scale=0.04,
            text_pos=(0, -0.07),
            text_fg=VBase4(1, 1, 1, 1),
            pos=(1.05, 0, -0.82),
            command=self.__exitGolfSpot,
        )

        self.accept('escape', self.__exitGolfSpot)

        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, 'watchGolfSpotControls')

        # In case they don't figure it out, hit them over the head
        # with it after a few seconds.
        taskMgr.doMethodLater(5, self.__displayGolfSpotAdvice,
                              self.golfSpotAdviceName)
        #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
        if self.powerBar:
            self.powerBar.show()

    def __disableControlInterface(self):
        """Disable the control interface."""
        #self.__turnOffMagnet()

        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = None

        self.__cleanupGolfSpotAdvice()
        #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('watchGolfSpotControls')
        if self.powerBar:
            self.powerBar.hide()
        else:
            self.notify.debug('self.powerBar is none')
        #self.__setMoveSound(None)

    def setupPowerBar(self):
        """Create the power bar for the water golfSpot."""
        self.powerBar = DirectWaitBar(
            pos=(0.0, 0, -0.94),
            relief=DGG.SUNKEN,
            frameSize=(-2.0, 2.0, -0.2, 0.2),
            borderWidth=(0.02, 0.02),
            scale=0.25,
            range=100,
            sortOrder=50,
            frameColor=(0.5, 0.5, 0.5, 0.5),
            barColor=(1.0, 0.0, 0.0, 1.0),
            text="",
            text_scale=0.26,
            text_fg=(1, 1, 1, 1),
            text_align=TextNode.ACenter,
            text_pos=(0, -0.05),
        )

        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar.hide()

    def resetPowerBar(self):
        """Bring the power and power bar to zero."""
        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar['text'] = ''

    def __displayGolfSpotAdvice(self, task):
        """Display golfSpot advice on the screen."""
        if self.golfSpotAdviceLabel == None:
            self.golfSpotAdviceLabel = DirectLabel(
                text=TTLocalizer.BossbotGolfSpotAdvice,
                text_fg=VBase4(1, 1, 1, 1),
                text_align=TextNode.ACenter,
                relief=None,
                pos=(0, 0, 0.69),
                scale=0.1)

    def __cleanupGolfSpotAdvice(self):
        """Remove golfSpot advice from the screen."""
        if self.golfSpotAdviceLabel:
            self.golfSpotAdviceLabel.destroy()
            self.golfSpotAdviceLabel = None
        taskMgr.remove(self.golfSpotAdviceName)

    def showExiting(self):
        """Indicate that we've sent an exiting message to AI."""
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = DirectLabel(
                relief=None,
                text=TTLocalizer.BossbotGolfSpotLeaving,
                pos=(1.05, 0, -0.88),
                text_pos=(0, 0),
                text_scale=0.06,
                text_fg=VBase4(1, 1, 1, 1),
            )

        self.__cleanupGolfSpotAdvice()

    def __exitGolfSpot(self):
        """Handle the toon clicking on exit button."""

        self.d_requestFree(False)

    def __controlPressed(self):
        """Handle control key being pressed."""
        if self.controlKeyAllowed:
            self.__beginFireBall()
        pass
        #self.__cleanupMagnetAdvice()
        #self.__turnOnMagnet()

    def __controlReleased(self):
        """Handle control key being released."""
        if self.controlKeyAllowed:
            self.__endFireBall()

    def __upArrow(self, pressed):
        """Handle up arrow key being pressed."""
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowVert = 1
        elif self.arrowVert > 0:
            self.arrowVert = 0

    def __downArrow(self, pressed):
        """Handle down arrow key being pressed."""
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowVert = -1
        elif self.arrowVert < 0:
            self.arrowVert = 0

    def __rightArrow(self, pressed):
        """Handle right arrow key being pressed."""
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowHorz = 1
            self.switchToAnimState('GolfRotateLeft')
        elif self.arrowHorz > 0:
            self.arrowHorz = 0
            self.switchToAnimState('GolfPuttLoop')

    def __leftArrow(self, pressed):
        """Handle left arrow key being pressed."""
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowHorz = -1
            self.switchToAnimState('GolfRotateRight')
        elif self.arrowHorz < 0:
            self.arrowHorz = 0
            self.switchToAnimState('GolfPuttLoop')

    def __watchControls(self, task):
        """Check the arrow key press and call move golfSpot if needed."""
        if self.arrowHorz:
            self.__moveGolfSpot(self.arrowHorz)
        else:
            pass
            #self.__setMoveSound(None)
        return Task.cont

    def __moveGolfSpot(self, xd):
        """Rotate the golfSpot by the given xdelta."""
        dt = globalClock.getDt()

        h = self.root.getH() - xd * self.rotateSpeed * dt
        h %= 360
        limitH = h
        self.root.setH(limitH)
        #self.__setMoveSound(self.craneMoveSfx)

    def __incrementChangeSeq(self):
        """Increment our change counter."""
        self.changeSeq = (self.changeSeq + 1) & 0xff

    def __beginFireBall(self):
        """Handle player pressing control and starting the power meter."""
        # The control key was pressed.
        if self.aimStart != None:
            # This is probably just key-repeat.
            return
        if not self._state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        time = globalClock.getFrameTime()
        self.aimStart = time
        messenger.send('wakeup')
        taskMgr.add(self.__updateBallPower, self.ballPowerTaskName)

    def __endFireBall(self):
        """Handle player releasing control and shooting the ball."""
        # The control key was released.  Fire the ball.
        if self.aimStart == None:
            return
        if not self._state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        #if not self.power:
        #    return
        taskMgr.remove(self.ballPowerTaskName)
        self.disableControlKey()
        messenger.send('wakeup')
        self.aimStart = None
        power = self.power
        angle = self.root.getH()
        self.notify.debug('incrementing self.__flyBallSequenceNum')
        self.__flyBallSequenceNum = (self.__flyBallSequenceNum + 1) % 0xff

        self.sendSwingInfo(power, angle, self.__flyBallSequenceNum)
        self.setSwingInfo(power, angle, self.__flyBallSequenceNum)

        self.resetPowerBar()
        pass
        #self.__turnOffMagnet()

    def __updateBallPower(self, task):
        """Change the value of the power meter."""
        if not self.powerBar:
            print("### no power bar!!!")
            return task.done

        newPower = self.__getBallPower(globalClock.getFrameTime())
        self.power = newPower
        self.powerBar['value'] = newPower
        return task.cont

    def __getBallPower(self, time):
        """Return a value between 0 and 100 to indicate golf power."""
        elapsed = max(time - self.aimStart, 0.0)
        t = elapsed / self.golfPowerSpeed
        t = math.pow(t, self.golfPowerExponent)
        power = int(t * 100) % 200
        if power > 100:
            power = 200 - power
        return power

    def stopPosHprBroadcast(self):
        """Stop the pitcher rotation broadcast task."""
        taskName = self.posHprBroadcastName
        taskMgr.remove(taskName)

    def startPosHprBroadcast(self):
        """Start the golfSpot rotation broadcast task."""
        taskName = self.posHprBroadcastName

        # Broadcast our initial position
        self.b_clearSmoothing()
        self.d_sendGolfSpotPos()

        # remove any old tasks
        taskMgr.remove(taskName)
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast,
                              taskName)

    def __posHprBroadcast(self, task):
        """Periodically broadcast the golfSpot rotation."""
        self.d_sendGolfSpotPos()
        taskName = self.posHprBroadcastName
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast,
                              taskName)
        return Task.done

    def d_sendGolfSpotPos(self):
        """Send the golfSpot rotation to the other clients."""
        timestamp = globalClockDelta.getFrameNetworkTime()

        self.sendUpdate(
            'setGolfSpotPos',
            [self.changeSeq, self.root.getH(), timestamp])

    def setGolfSpotPos(self, changeSeq, h, timestamp):
        """Handle another client sending an update on the golfSpot rotation."""
        #assert self.notify.debugStateCall(self)
        self.changeSeq = changeSeq
        if self.smoothStarted:
            now = globalClock.getFrameTime()
            local = globalClockDelta.networkToLocalTime(timestamp, now)

            #self.golfSpotSmoother.setY(y)
            self.golfSpotSmoother.setH(h)
            self.golfSpotSmoother.setTimestamp(local)
            self.golfSpotSmoother.markPosition()
        else:
            #self.crane.setY(y)
            self.root.setH(h)

    ### Handle smoothing of distributed updates.  This is similar to
    ### code in DistributedSmoothNode, but streamlined for our
    ### purposes.

    def b_clearSmoothing(self):
        """Tell us and other clients to clear smoothing."""
        self.d_clearSmoothing()
        self.clearSmoothing()

    def d_clearSmoothing(self):
        """Tell other clients to clear smoothing."""
        self.sendUpdate("clearSmoothing", [0])

    def clearSmoothing(self, bogus=None):
        """Invalidate old position reports."""
        # Call this to invalidate all the old position reports
        # (e.g. just before popping to a new position).
        self.golfSpotSmoother.clearPositions(1)

    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.golfSpotSmoother.computeAndApplySmoothHpr(self.root)

        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 makeToonReleaseInterval(self, toon):
        """Return an interval of the toon jumping to pitcher position."""
        def getSlideToPos(toon=toon):
            return render.getRelativePoint(toon, Point3(0, -5, 0))

        if self.gotHitByBoss:
            grabIval = Sequence(Func(self.detachClub),
                                name='makeToonReleaseInterval-gotHitByBoss')
            if not toon.isEmpty():
                toonIval = Sequence(Func(toon.wrtReparentTo, render),
                                    Parallel(
                                        ActorInterval(toon, 'slip-backward'),
                                        toon.posInterval(0.5,
                                                         getSlideToPos,
                                                         fluid=1)),
                                    name='makeToonReleaseInterval-toonIval')
                #Func(toon.loop, 'neutral'),
                grabIval.append(toonIval)

        else:
            grabIval = Sequence(Func(self.detachClub), )
            if not toon.isEmpty():
                toonIval = Sequence(
                    Parallel(
                        ActorInterval(toon,
                                      'walk',
                                      duration=1.0,
                                      playRate=-1.0),
                        LerpPosInterval(
                            toon,
                            duration=1.0,
                            pos=Point3(-10, 0, 0),
                        )),
                    Func(toon.wrtReparentTo, render),
                    #Func(toon.loop, 'neutral'),
                )
                grabIval.append(toonIval)

        if localAvatar.doId == toon.doId:
            if not self.goingToReward and toon.hp > 0:
                grabIval.append(Func(self.goToFinalBattle))
                grabIval.append(
                    Func(self.notify.debug, 'goingToFinalBattlemode'))
                grabIval.append(Func(self.safeBossToFinalBattleMode))

        return grabIval

    def safeBossToFinalBattleMode(self):
        """Call boss.toFinalBattleMode if self.boss is valid."""
        if self.boss:
            self.boss.toFinalBattleMode()

    def goToFinalBattle(self):
        """Go to final battle if we're in crane mode."""
        # This is a bit hacky.  Go back to finalBattle mode, but
        # only if we're still in crane mode.  (We might have been
        # zapped to 'ouch' mode by a hit.)
        if self.cr:
            place = self.cr.playGame.getPlace()
            if place and hasattr(place, 'fsm'):
                curState = place.fsm.getCurrentState().getName()
                if place.fsm.getCurrentState().getName() == 'crane':
                    place.setState('finalBattle')
                else:
                    self.notify.debug('NOT going to final battle, state=%s' %
                                      curState)

    def attachClub(self, avId, pointToBall=False):
        """Attach the club to the right hand."""
        club = self.club
        if club:
            av = base.cr.doId2do.get(avId)
            if av:
                av.useLOD(1000)
                lHand = av.getLeftHands()[0]
                club.setPos(0, 0, 0)
                club.reparentTo(lHand)
                # we have to account for small toons like the mouse
                netScale = club.getNetTransform().getScale()[1]
                counterActToonScale = lHand.find('**/counteractToonScale')
                if counterActToonScale.isEmpty():
                    counterActToonScale = lHand.attachNewNode(
                        'counteractToonScale')
                    counterActToonScale.setScale(1 / netScale)
                    self.notify.debug('creating counterActToonScale for %s' %
                                      av.getName())
                club.reparentTo(counterActToonScale)
                club.setX(-0.25 * netScale)
                if pointToBall:
                    club.lookAt(self.clubLookatSpot)
                # self.notify.debug('after lookat, hpr = %s' % club.getHpr())

    def detachClub(self):
        """Detach the club and store it someplace safe."""
        if not self.club.isEmpty():
            self.club.reparentTo(self.root)
            self.club.setZ(-20)
            self.club.setScale(1)

    def adjustClub(self):
        """Change the club so that the head is more or less behind the ball."""
        club = self.club
        if club:
            distance = club.getDistance(self.clubLookatSpot)
            # from maya the club has a length of 2.058,
            scaleFactor = distance / 2.058
            # self.notify.debug('scaleFactor  = %s' % scaleFactor)
            club.setScale(1, scaleFactor, 1)

    def startAdjustClubTask(self):
        """Start the task to automatically adjust the club so it looks right."""
        taskMgr.add(self.adjustClubTask, self.adjustClubTaskName)

    def stopAdjustClubTask(self):
        """Stop the task to automatically adjust the club so it looks right."""
        taskMgr.remove(self.adjustClubTaskName)

    def adjustClubTask(self, task):
        """Continuously adjust the club so the head is behind the ball."""
        self.attachClub(self.avId, True)
        self.adjustClub()
        return task.cont

    def enableControlKey(self):
        """Allow control key events to come in."""
        self.controlKeyAllowed = True

    def disableControlKey(self):
        """Disable control key events from coming in."""
        self.controlKeyAllowed = False

    def sendSwingInfo(self, power, angle, sequenceNum):
        """Tell the other clients we're firing."""
        self.sendUpdate('setSwingInfo', [power, angle, sequenceNum])

    def startBallPlayback(self, power, angle, sequenceNum):
        """Start the ball flying in the air."""
        assert self.notify.debugStateCall(self)
        # duplicate our current ball model
        flyBall = self.ballModel.copyTo(NodePath())
        flyBall.setScale(1.0)

        flyBallBubble = self.getFlyBallBubble().instanceTo(NodePath())
        flyBallBubble.reparentTo(flyBall)

        flyBall.setTag('pieSequence', str(sequenceNum))
        flyBall.setTag('throwerId', str(self.avId))

        # First, create a ProjectileInterval to compute the relative
        # velocity.

        t = power / 100.0

        # make the ball travel farther, the longer you press the bar
        t = 1.0 - t

        # Distance ranges from 300 - 100 ft, time ranges from 1.5 - 2 s.
        dist = 300 - 200 * t
        time = 1.5 + 0.5 * t
        proj = ProjectileInterval(
            None,
            startPos=Point3(0, 0, 0),
            endPos=Point3(0, dist, 0),
            duration=time,
        )
        relVel = proj.startVel

        def getVelocity(root=self.root, relVel=relVel):
            return render.getRelativeVector(root, relVel)

        fly = Sequence(
            #Func(self.ballModel.hide),
            Func(flyBall.reparentTo, render),
            Func(flyBall.setPosHpr, self.root, 0, 0, 0, 0, 0, 0),
            Func(base.cTrav.addCollider, flyBallBubble, self.flyBallHandler),
            ProjectileInterval(flyBall, startVel=getVelocity, duration=3),
            Func(flyBall.detachNode),
            Func(base.cTrav.removeCollider, flyBallBubble),
            Func(self.notify.debug, "removed collider"),
            Func(self.flyBallFinishedFlying, sequenceNum))
        flyWithSound = Parallel(fly,
                                SoundInterval(self.hitBallSfx, node=self.root),
                                name='flyWithSound')
        self.notify.debug('starting flyball track')
        flyWithSound.start()
        self.flyBallTracks[sequenceNum] = flyWithSound

        pass

    def setSwingInfo(self, power, angle, sequenceNum):
        """Handle a toon swinging at the golf ball."""
        assert self.notify.debugStateCall(self)
        av = base.cr.doId2do.get(self.avId)
        self.swingInterval = Sequence()
        if av:
            self.stopAdjustClubTask()
            self.swingInterval = Sequence(
                ActorInterval(av,
                              'swing-putt',
                              startFrame=0,
                              endFrame=GolfGlobals.BALL_CONTACT_FRAME),
                Func(self.startBallPlayback, power, angle, sequenceNum),
                Func(self.ballModel.hide),
                ActorInterval(av,
                              'swing-putt',
                              startFrame=GolfGlobals.BALL_CONTACT_FRAME,
                              endFrame=24),
                Func(self.ballModel.setScale, 0.1),
                Func(self.ballModel.show),
                LerpScaleInterval(self.ballModel, 1.0, Point3(1, 1, 1)),
                Func(self.enableControlKey),
            )
            if av == localAvatar:
                self.swingInterval.append(
                    Func(self.switchToAnimState, 'GolfPuttLoop', True))

        self.swingInterval.start()

    def getFlyBallBubble(self):
        if self.__flyBallBubble == None:
            bubble = CollisionSphere(0, 0, 0, GolfGlobals.GOLF_BALL_RADIUS)
            node = CollisionNode('flyBallBubble')
            node.addSolid(bubble)
            node.setFromCollideMask(ToontownGlobals.PieBitmask
                                    | ToontownGlobals.CameraBitmask
                                    | ToontownGlobals.FloorBitmask)
            node.setIntoCollideMask(BitMask32.allOff())
            self.__flyBallBubble = NodePath(node)
            self.flyBallHandler = CollisionHandlerEvent()
            self.flyBallHandler.addInPattern('flyBallHit-%d' % self.index)
            #self.flyBallHandler.addInPattern('flyBallHit-%d--%in')
        return self.__flyBallBubble

    def __flyBallHit(self, entry):
        """Handle the flying golf ball hitting something in the world."""
        #import pdb; pdb.set_trace()
        print(entry)
        pass

    def flyBallFinishedFlying(self, sequence):
        """Handle the flyball sequence finishing."""
        if sequence in self.flyBallTracks:
            del self.flyBallTracks[sequence]

    def __finishFlyBallTrack(self, sequence):
        """Force the flyball sequence to finish prematurely."""
        if sequence in self.flyBallTracks:
            flyBallTrack = self.flyBallTracks[sequence]
            del self.flyBallTracks[sequence]
            flyBallTrack.finish()

    def flyBallFinishedSplatting(self, sequence):
        """Handle the flyball splatt finishing."""
        if sequence in self.splatTracks:
            del self.splatTracks[sequence]

    def __flyBallHit(self, entry):
        """Handle the flyball colliding against something in the world."""
        if not entry.hasSurfacePoint() or not entry.hasInto():
            # Not a collision solid we understand.  Weird.
            return
        if not entry.getInto().isTangible():
            # Just a trigger polygon.  Ignore it.
            return

        sequence = int(entry.getFromNodePath().getNetTag('pieSequence'))
        self.__finishFlyBallTrack(sequence)

        if sequence in self.splatTracks:
            splatTrack = self.splatTracks[sequence]
            del self.splatTracks[sequence]
            splatTrack.finish()

        # The pie hit something solid.  Generate a distributed splat.

        # Check the thing we hit for a pieCode.  If it has one, it
        # gets passed along with the message.  This may mean something
        # different according to context (it may indicate, for
        # instance, the kind of target we hit).
        flyBallCode = 0
        flyBallCodeStr = entry.getIntoNodePath().getNetTag('pieCode')
        if flyBallCodeStr:
            flyBallCode = int(flyBallCodeStr)

        pos = entry.getSurfacePoint(render)
        timestamp32 = globalClockDelta.getFrameNetworkTime(bits=32)
        throwerId = int(entry.getFromNodePath().getNetTag('throwerId'))
        splat = self.getFlyBallSplatInterval(pos[0], pos[1], pos[2],
                                             flyBallCode, throwerId)

        splat = Sequence(splat, Func(self.flyBallFinishedSplatting, sequence))
        assert sequence not in self.splatTracks
        self.splatTracks[sequence] = splat
        splat.start()

        self.notify.debug(
            'doId=%d into=%s flyBallCode=%d, throwerId=%d' %
            (self.doId, entry.getIntoNodePath(), flyBallCode, throwerId))

        if flyBallCode ==  ToontownGlobals.PieCodeBossCog and \
           self.avId == localAvatar.doId and \
           self.lastHitSequenceNum != self.__flyBallSequenceNum:
            self.lastHitSequenceNum = self.__flyBallSequenceNum
            self.boss.d_ballHitBoss(1)
        elif flyBallCode == ToontownGlobals.PieCodeToon and \
             self.avId == localAvatar.doId and \
             self.lastHitSequenceNum != self.__flyBallSequenceNum:
            self.lastHitSequenceNum = self.__flyBallSequenceNum
            avatarDoId = entry.getIntoNodePath().getNetTag('avatarDoId')
            if avatarDoId == '':
                self.notify.warning("Toon %s has no avatarDoId tag." %
                                    (repr(entry.getIntoNodePath())))
                return
            doId = int(avatarDoId)
            if doId != localAvatar.doId:
                # turn this off since food comes out of the belt
                # self.boss.d_hitToon(doId)
                pass

    def getFlyBallSplatInterval(self, x, y, z, flyBallCode, throwerId):
        """Return an interval showing a flyBall hitting a target in the world."""
        from toontown.toonbase import ToontownBattleGlobals
        from toontown.battle import BattleProps

        splatName = 'dust'
        splat = BattleProps.globalPropPool.getProp(splatName)
        splat.setBillboardPointWorld(2)

        color = ToontownGlobals.PieCodeColors.get(flyBallCode)
        if color:
            splat.setColor(*color)
        if flyBallCode == ToontownGlobals.PieCodeBossCog:
            self.notify.debug('changing color to %s' % self.ballColor)
            splat.setColor(self.ballColor)

        sound = loader.loadSfx('phase_11/audio/sfx/LB_evidence_miss.mp3')
        vol = 1.0
        if flyBallCode == ToontownGlobals.PieCodeBossCog:
            sound = loader.loadSfx('phase_4/audio/sfx/Golf_Hit_Barrier_1.mp3')
        soundIval = SoundInterval(sound, node=splat, volume=vol)

        if flyBallCode == ToontownGlobals.PieCodeBossCog and \
           localAvatar.doId == throwerId:
            vol = 1.0
            soundIval = SoundInterval(sound, node=localAvatar, volume=vol)

        ival = Parallel(
            Func(splat.reparentTo, render),
            Func(splat.setPos, x, y, z),
            soundIval,
            Sequence(ActorInterval(splat, splatName), Func(splat.detachNode)),
        )
        return ival

    def setGoingToReward(self):
        """Flag that the boss battle is going to the reward state."""
        assert self.notify.debugStateCall(self)
        self.goingToReward = True

    def gotBossZapped(self):
        """Handle the local toon getting hit by a ranged attack."""
        self.showExiting()
        self.d_requestFree(True)
Exemplo n.º 29
0
class ThrowGag(Gag):
    def __init__(self,
                 name,
                 model,
                 damage,
                 hitSfx,
                 splatColor,
                 anim=None,
                 scale=1):
        Gag.__init__(self,
                     name,
                     model,
                     damage,
                     GagType.THROW,
                     hitSfx,
                     anim=anim,
                     scale=scale)
        self.splatScale = GagGlobals.splatSizes[self.name]
        self.splatColor = splatColor
        self.entities = []
        self.timeout = 1.0
        self.power = 50
        self.powerBar = None
        self.tossPieStart = 0
        self.pieSpeed = 0.2
        self.pieExponent = 0.75

    def setAvatar(self, avatar):
        Gag.setAvatar(self, avatar)
        if self.isLocal():
            self.powerBar = DirectWaitBar(range=150,
                                          frameColor=(1, 1, 1, 1),
                                          barColor=(0.286, 0.901, 1, 1),
                                          relief=DGG.RAISED,
                                          borderWidth=(0.04, 0.04),
                                          pos=(0, 0, 0.85),
                                          scale=0.2,
                                          hpr=(0, 0, 0),
                                          parent=aspect2d,
                                          frameSize=(-0.85, 0.85, -0.12, 0.12))
            self.powerBar.hide()

    def __getPiePower(self, time):
        elapsed = max(time - self.tossPieStart, 0.0)
        t = elapsed / self.pieSpeed
        t = math.pow(t, self.pieExponent)
        power = int(t * 150) % 300
        if power > 150:
            power = 300 - power
        return power

    def build(self):
        if not self.gag:
            Gag.build(self)
            self.equip()
            if self.anim and self.gag:
                self.gag.loop('chan')
        return self.gag

    def start(self):
        super(ThrowGag, self).start()
        self.build()
        self.avatar.setPlayRate(self.playRate, 'pie')
        self.avatar.play('pie', fromFrame=0, toFrame=45)
        if self.isLocal():
            taskMgr.remove("hidePowerBarTask" + str(hash(self)))
            self.powerBar.show()
            self.startPowerBar()

    def startPowerBar(self):
        self.tossPieStart = globalClock.getFrameTime()
        taskMgr.add(self.__powerBarUpdate, "powerBarUpdate" + str(hash(self)))

    def __powerBarUpdate(self, task):
        if self.powerBar is None:
            return task.done
        self.powerBar['value'] = self.__getPiePower(globalClock.getFrameTime())
        return task.cont

    def stopPowerBar(self):
        taskMgr.remove("powerBarUpdate" + str(hash(self)))
        self.power = self.powerBar['value']

    def __hidePowerBarTask(self, task):
        self.powerBar.hide()

    def throw(self):
        if self.isLocal():
            self.stopPowerBar()
            self.power += 50
            self.power = 250 - self.power
            print self.power
            # Make other toons set the throw power on my gag.
            base.localAvatar.sendUpdate('setThrowPower', [self.id, self.power])
            self.startTimeout()
            taskMgr.doMethodLater(1.5, self.__hidePowerBarTask,
                                  "hidePowerBarTask" + str(hash(self)))
        self.avatar.play('pie', fromFrame=45, toFrame=90)
        if not self.gag:
            self.build()

    def setPower(self, power):
        self.power = power

    def release(self):
        Gag.release(self)
        base.audio3d.attachSoundToObject(self.woosh, self.gag)
        base.playSfx(self.woosh, node=self.gag)

        throwPath = NodePath('ThrowPath')
        throwPath.reparentTo(self.avatar)
        throwPath.setScale(render, 1)
        throwPath.setPos(0, self.power, -90)
        throwPath.setHpr(90, -90, 90)

        entity = self.gag

        if not entity:
            entity = self.build()

        entity.wrtReparentTo(render)
        entity.setHpr(throwPath.getHpr(render))
        self.gag = None

        if not self.handJoint:
            self.handJoint = self.avatar.find('**/def_joint_right_hold')

        track = ProjectileInterval(entity,
                                   startPos=self.handJoint.getPos(render),
                                   endPos=throwPath.getPos(render),
                                   gravityMult=0.9,
                                   duration=3)
        event = self.avatar.uniqueName('throwIvalDone') + '-' + str(
            hash(entity))
        track.setDoneEvent(event)
        base.acceptOnce(event, self.__handlePieIvalDone, [entity])
        track.start()
        self.entities.append([entity, track])
        if self.isLocal():
            self.buildCollisions(entity)
            base.localAvatar.sendUpdate('usedGag', [self.id])
        self.reset()

    def __handlePieIvalDone(self, pie):
        if not pie.isEmpty():
            pie.removeNode()

    def handleSplat(self):
        base.audio3d.detachSound(self.woosh)
        if self.woosh: self.woosh.stop()
        self.buildSplat(self.splatScale, self.splatColor)
        base.audio3d.attachSoundToObject(self.hitSfx, self.splat)
        self.splat.reparentTo(render)
        self.splat.setPos(self.splatPos)
        base.playSfx(self.hitSfx, node=self.splat)
        self.cleanupEntity(self.splatPos)
        self.splatPos = None
        taskMgr.doMethodLater(0.5, self.delSplat, 'Delete Splat')
        return

    def cleanupEntity(self, pos):
        closestPie = None
        trackOfClosestPie = None
        pieHash2range = {}
        for entity, track in self.entities:
            if not entity.isEmpty():
                pieHash2range[hash(entity)] = (entity.getPos(render) -
                                               pos).length()
        ranges = []
        for distance in pieHash2range.values():
            ranges.append(distance)
        ranges.sort()
        for pieHash in pieHash2range.keys():
            distance = pieHash2range[pieHash]
            if not distance is None and distance == ranges[0]:
                for entity, track in self.entities:
                    if hash(entity) == pieHash:
                        closestPie = entity
                        trackOfClosestPie = track
                        break
            break
        if closestPie != None and trackOfClosestPie != None:
            if [closestPie, trackOfClosestPie] in self.entities:
                self.entities.remove([closestPie, trackOfClosestPie])
            if not closestPie.isEmpty():
                if isinstance(closestPie, Actor):
                    closestPie.cleanup()
                closestPie.removeNode()

    def onCollision(self, entry):
        intoNP = entry.getIntoNodePath()
        avNP = intoNP.getParent()
        fromNP = entry.getFromNodePath().getParent()

        if fromNP.isEmpty():
            return

        for key in base.cr.doId2do.keys():
            obj = base.cr.doId2do[key]
            if obj.__class__.__name__ in CIGlobals.SuitClasses:
                if obj.getKey() == avNP.getKey():
                    obj.sendUpdate('hitByGag', [self.getID()])
            elif obj.__class__.__name__ == "DistributedToon":
                if obj.getKey() == avNP.getKey():
                    if obj.getHealth() < obj.getMaxHealth():
                        if obj != self.avatar:
                            self.avatar.sendUpdate(
                                'toonHitByPie',
                                [obj.doId, self.getID()])
                        else:
                            self.avatar.acceptOnce('gagSensor-into',
                                                   self.onCollision)
                            return
            elif obj.__class__.__name__ == "DistributedPieTurret":
                if obj.getKey() == avNP.getKey():
                    if obj.getHealth() < obj.getMaxHealth():
                        self.avatar.sendUpdate(
                            'toonHitByPie', [obj.doId, self.getID()])

        self.splatPos = fromNP.getPos()
        self.avatar.sendUpdate('setSplatPos', [
            self.getID(),
            self.splatPos.getX(),
            self.splatPos.getY(),
            self.splatPos.getZ()
        ])
        self.handleSplat()

    def buildCollisions(self, entity):
        pieSphere = CollisionSphere(0, 0, 0, 1)
        pieSensor = CollisionNode('gagSensor')
        pieSensor.addSolid(pieSphere)
        pieNP = entity.attachNewNode(pieSensor)
        pieNP.setCollideMask(BitMask32(0))
        pieNP.node().setFromCollideMask(CIGlobals.WallBitmask
                                        | CIGlobals.FloorBitmask)

        event = CollisionHandlerEvent()
        event.set_in_pattern("%fn-into")
        event.set_out_pattern("%fn-out")
        base.cTrav.add_collider(pieNP, event)
        self.avatar.acceptOnce('gagSensor-into', self.onCollision)

    def unEquip(self):
        taskMgr.remove("hidePowerBarTask" + str(hash(self)))
        if self.powerBar:
            self.powerBar.hide()
        Gag.unEquip(self)

    def delete(self):
        taskMgr.remove("powerBarUpdate" + str(hash(self)))
        taskMgr.remove("hidePowerBarTask" + str(hash(self)))
        if self.powerBar:
            self.powerBar.destroy()
            self.powerBar = None
        Gag.delete(self)