Beispiel #1
0
 def doSpray(self, scaleUp, scaleDown, hold):
     base.audio3d.attachSoundToObject(self.spraySfx, self.gag)
     self.spraySfx.play()
     spraySequence = Sequence(Func(self.getSprayTrack(self.origin, self.sprayRange, scaleUp, hold, scaleDown).start))
     sprayParallel = Parallel()
     sprayParallel.append(Func(spraySequence.start))
     sprayParallel.start()
Beispiel #2
0
  def movementTask( self, task ):
    # show and hide depending on distance to camera
    if (base.camera.getPos( render ) - self.getPos()).length() > 100.0:
      self.pandaActor.hide()
    else:
      self.pandaActor.show()
    
    try:
      # calculate time passed / we cant use task.time in a doMethodLater
      deltaT = time.time() - self.oldTaskTime
      # save current time
      self.oldTaskTime = time.time()
    except:
      # save current time
      self.oldTaskTime = time.time()
      deltaT = 0.0
    
    # get future rotation and position
    rot = self.getRotation( UPDATETIME, random.randint( -10, 10) )
    pos = self.getForwardPos( UPDATETIME * (random.random()/2.0+1.0) )
    posZ = self.getHeight( (pos.getX(), pos.getY()) )
    pos.setZ( posZ )
    
    # create movement and rotation intervals
    myInterval1=self.posInterval(UPDATETIME*1.25,pos)
    myInterval2=self.hprInterval(UPDATETIME*1.25,rot)
#    myInterval1=self.globalPandaNode.posInterval(UPDATETIME*1.25,pos)
#    myInterval2=self.globalPandaNode.hprInterval(UPDATETIME*1.25,rot)
    myParallel=Parallel(myInterval1,myInterval2) 
    myParallel.start()
    
    return Task.again
Beispiel #3
0
class JugglingBalls(ToonUpGag):

    def __init__(self):
        ToonUpGag.__init__(self, CIGlobals.JugglingBalls, 'phase_5/models/props/cubes-mod.bam', 90, 120, 100, GagGlobals.JUGGLE_SFX, 1, anim='phase_5/models/props/cubes-chan.bam')
        self.setImage('phase_3.5/maps/juggling-cubes.png')
        self.track = None
        self.soundInterval = None
        return

    def start(self):
        super(JugglingBalls, self).start()
        self.setupHips()
        self.build()
        self.placeProp(self.hips, self.gag)
        self.soundInterval = self.getSoundTrack(0.7, self.gag, 7.7)
        propInterval = Sequence()
        propInterval.append(ActorInterval(self.gag, 'chan'))
        if self.avatar == base.localAvatar:
            propInterval.append(Func(self.setHealAmount))
            propInterval.append(Func(self.healNearbyAvatars, 25))
        propInterval.append(Func(self.cleanupGag))
        propInterval.append(Func(self.reset))
        self.track = Parallel(ActorInterval(self.avatar, 'juggle'), propInterval, Func(self.soundInterval.start))
        self.track.start()

    def equip(self):
        super(JugglingBalls, self).equip()

    def unEquip(self):
        if self.track:
            self.soundInterval.finish()
            self.track.finish()
            self.track = None
        self.reset()
        return
Beispiel #4
0
    def startEntity(self, cog):
        if not cog:
            self.completeSquirt()
            return
        scaleUpPoint = Point3(1.5, 1.5, 1.5)
        rainDelay = 1
        effectDelay = 0.3
        cloudHold = 4.7
        tContact = 2.4
        if cog.isDead():
            cloudHold = 1.7
        cloud01, trickleFx, rainEffects, entity = self.buildEntity()
        cloud01.setZ(cog.suitPlan.getNametagZ() + 2)
        cloud01.reparentTo(cog)
        cloud02 = Actor(self.model, {'chan': GagGlobals.getProp(4, 'stormcloud-chan')})
        cloud02.reparentTo(cloud01)

        def damageCog():
            if self.isLocal():
                self.avatar.sendUpdate('suitHitByPie', [cog.doId, self.getID()])

        def __getCloudTrack(cloud, useEffect = 1):
            track = Sequence(Func(cloud.pose, 'chan', 0), LerpScaleInterval(cloud, 1.5, scaleUpPoint, startScale=GagGlobals.PNT3NEAR0), Wait(rainDelay))
            if useEffect == 1:
                pTrack = Parallel()
                delay = trickleDuration = cloudHold * 0.25
                trickleTrack = ParticleInterval(trickleFx, cloud, worldRelative=0, duration=trickleDuration, cleanup=True)
                track.append(trickleTrack)
                for i in range(0, 3):
                    dur = cloudHold - 2 * trickleDuration
                    pTrack.append(Sequence(Wait(delay), ParticleInterval(rainEffects[i], cloud, worldRelative=0, duration=dur, cleanup=True)))
                    delay += effectDelay

                pTrack.append(Sequence(Wait(3 * effectDelay), ActorInterval(cloud, 'chan', startTime=1, duration=cloudHold)))
                damageTrack = Sequence()
                if cog.getHealth() - self.getDamage() <= 0:
                    damageTrack.append(Func(cog.d_disableMovement, wantRay=True))
                    damageTrack.append(Func(damageCog))
                else:
                    damageTrack.append(Wait(tContact))
                    damageTrack.append(Func(damageCog))
                pTrack.append(damageTrack)
                track.append(pTrack)
            else:
                track.append(ActorInterval(cloud, 'chan', startTime=1, duration=cloudHold))
            track.append(LerpScaleInterval(cloud, 0.5, GagGlobals.PNT3NEAR0))
            track.append(Func(GagUtils.destroyProp, cloud))
            return track

        tracks = Parallel()
        soundTrack01 = self.getSoundTrack(1.3, self.avatar)
        soundTrack02 = self.getSoundTrack(3.4, self.avatar)
        tracks.append(soundTrack01)
        tracks.append(soundTrack02)
        cloud01Track = __getCloudTrack(cloud01)
        cloud02Track = __getCloudTrack(cloud02, useEffect=0)
        tracks.append(cloud01Track)
        tracks.append(cloud02Track)
        tracks.append(Func(self.destroyEntity, entity))
        tracks.start()
Beispiel #5
0
    def start(self):
        SoundGag.start(self)
        tracks = Parallel()
        delayTime = 2.45
        delayUntilAppearSound = 1.0
        INSTRUMENT_SCALE_MODIFIER = 0.5
        instrMin = Vec3(0.001, 0.001, 0.001)
        instrMax1 = Vec3(0.1, 0.1, 0.1)
        instrMax1 *= INSTRUMENT_SCALE_MODIFIER
        instrMax2 = Vec3(0.3, 0.3, 0.3)
        instrMax2 *= INSTRUMENT_SCALE_MODIFIER
        instrStretch = Vec3(0.4, 0.4, 0.4)
        instrStretch *= INSTRUMENT_SCALE_MODIFIER

        def setInstrumentStats():
            self.gag.setPos(-0.8, -0.9, 0.2)
            self.gag.setHpr(145, 0, 0)
            self.gag.setScale(instrMin)

        megaphoneSh = Sequence(Func(self.placeProp, self.handJoint, self.megaphone), Func(self.placeProp, self.handJoint, self.gag), Func(setInstrumentStats))
        grow = self.getScaleIntervals(self.gag, duration=1, startScale=instrMin, endScale=instrMax1)
        instrumentAppear = Parallel(grow)
        stretchInstr = self.getScaleBlendIntervals(self.gag, duration=0.3, startScale=instrMax2, endScale=instrStretch, blendType='easeOut')
        backInstr = self.getScaleBlendIntervals(self.gag, duration=1.0, startScale=instrStretch, endScale=instrMin, blendType='easeIn')
        spinInstr = LerpHprInterval(self.gag, duration=1.5, startHpr=Vec3(145, 0, 0), hpr=Vec3(145, 0, 90), blendType='easeInOut')
        attackTrack = Parallel(Sequence(Wait(0.2), spinInstr), Sequence(stretchInstr, Wait(0.5), backInstr))
        megaphoneTrack = Sequence(megaphoneSh, Wait(delayUntilAppearSound), SoundInterval(self.appearSfx, node=self.avatar), Wait(delayTime + 1.0), instrumentAppear)
        tracks.append(megaphoneTrack)
        tracks.append(ActorInterval(self.avatar, 'sound'))
        soundTrack = Sequence(Wait(delayTime), Parallel(attackTrack, SoundInterval(self.soundSfx, node=self.avatar), Func(self.damageCogsNearby), Wait(0.4), Func(self.finish)))
        tracks.append(soundTrack)
        tracks.start()
Beispiel #6
0
 def append(self,text,width,height,spacing = 0, fadein = 0, fadeinType = 0):
     '''Add a NodePath that contains a generated text geom
     This method is called by SogalText
     '''
     self.__lock.acquire()
     
     self.items.append(text)
     text.reparentTo(self)
     textPos = self.currentPtr
     text.setPos(textPos)
     if fadein:
         interval = Parallel()
         if fadeinType == 0 or fadeinType == 'normal':
             interval.append(LerpFunc(_modifyAlphaScale,fadein,0,1,blendType = 'easeOut',extraArgs = [text]))
         if fadeinType == 1 or fadeinType == 'flyin':
             interval.append(LerpFunc(_modifyAlphaScale,fadein,0,1,blendType = 'easeOut',extraArgs = [text]))
             interval.append(LerpPosInterval(text,fadein,
                                             self.currentPtr,
                                             (self.currentPtr[0],self.currentPtr[1],self.currentPtr[2] -0.3),
                                             blendType = 'easeOut'))            
         interval.start()
         self.__lerpIntervals.append(interval)
         
     self.lineWidth = self.currentPtr[0] + width
     self.lineHeight = max(height, self.lineHeight)
     self.currentPtr = (self.lineWidth + spacing, 0, 0)
     
     self.__lock.release()
Beispiel #7
0
    def start(self):
        SoundGag.start(self)
        tracks = Parallel()
        delay = 2.45
        INSTRUMENT_SCALE_MODIFIER = 0.5
        instrMin = Vec3(0.001, 0.001, 0.001)
        instrMax1 = Vec3(1.7, 1.7, 1.7)
        instrMax1 *= INSTRUMENT_SCALE_MODIFIER
        instrMax2 = Vec3(2.2, 2.2, 2.2)
        instrMax2 *= INSTRUMENT_SCALE_MODIFIER
        instrStretch = Vec3(0.4, 0.4, 0.4)
        instrStretch *= INSTRUMENT_SCALE_MODIFIER
        head = self.gag.find('**/opera_singer')
        head.setPos(0, 0, 0)

        def setInstrumentStats():
            newPos = Vec3(-0.8, -0.9, 0.2)
            newPos *= 1.3
            self.gag.setPos(newPos[0], newPos[1], newPos[2])
            self.gag.setHpr(145, 0, 90)
            self.gag.setScale(instrMin)

        megaphoneShow = Sequence(Func(self.placeProp, self.handJoint, self.megaphone), Func(self.placeProp, self.handJoint, self.gag), Func(setInstrumentStats))
        grow1 = self.getScaleBlendIntervals(self.gag, duration=1, startScale=instrMin, endScale=instrMax1, blendType='easeOut')
        grow2 = self.getScaleBlendIntervals(self.gag, duration=1.1, startScale=instrMax1, endScale=instrMax2, blendType='easeIn')
        shrink2 = self.getScaleIntervals(self.gag, duration=0.1, startScale=instrMax2, endScale=instrMin)
        instrumentAppear = Parallel(Sequence(grow1, grow2, Wait(6.0), shrink2, Wait(0.4), Func(self.finish)), SoundInterval(self.appearSfx, node=self.avatar))
        delayTime = delay
        megaphoneTrack = Sequence(megaphoneShow, Wait(delayTime + 1.0), instrumentAppear)
        tracks.append(megaphoneTrack)
        toonTrack = self.__createToonInterval(0)
        tracks.append(toonTrack)
        soundTrack = Sequence(Wait(delayTime), Parallel(SoundInterval(self.soundSfx, node=self.avatar), Func(self.damageCogsNearby)))
        tracks.append(soundTrack)
        tracks.start()
Beispiel #8
0
    def start(self):
        SoundGag.start(self)
        INSTRUMENT_SCALE_MODIFIER = 0.5
        delayTime = 2.45
        delayUntilAppearSound = 1.0
        tracks = Parallel()
        instrMin = Vec3(0.001, 0.001, 0.001)
        instrMax = Vec3(0.2, 0.2, 0.2)
        instrMax *= INSTRUMENT_SCALE_MODIFIER
        instrStretch = Vec3(0.25, 0.25, 0.25)
        instrStretch *= INSTRUMENT_SCALE_MODIFIER

        def setInstrumentStats():
            self.gag.setPos(-1.2, -1.3, 0.1)
            self.gag.setHpr(145, 0, 85)
            self.gag.setScale(instrMin)

        megaphoneShow = Sequence(Func(self.placeProp, self.handJoint, self.megaphone), Func(self.placeProp, self.handJoint, self.gag), Func(setInstrumentStats))
        grow = self.getScaleIntervals(self.gag, duration=0.2, startScale=instrMin, endScale=instrMax)
        instrumentAppear = grow
        stretchInstr = self.getScaleBlendIntervals(self.gag, duration=0.2, startScale=instrMax, endScale=instrStretch, blendType='easeOut')
        backInstr = self.getScaleBlendIntervals(self.gag, duration=0.2, startScale=instrStretch, endScale=instrMax, blendType='easeIn')
        attackTrack = Sequence(stretchInstr, backInstr)
        megaphoneTrack = Sequence(megaphoneShow, Wait(delayUntilAppearSound), SoundInterval(self.appearSfx, node=self.avatar), instrumentAppear)
        tracks.append(megaphoneTrack)
        tracks.append(ActorInterval(self.avatar, 'sound'))
        instrumentshrink = self.getScaleIntervals(self.gag, duration=0.1, startScale=instrMax, endScale=instrMin)
        soundTrack = Sequence(Wait(delayTime), Parallel(attackTrack, SoundInterval(self.soundSfx, node=self.avatar), Wait(0.2), instrumentshrink, Func(self.damageCogsNearby), Wait(0.4), Func(self.finish)))
        tracks.append(soundTrack)
        tracks.start()
 def handleMatch(self, cardA, cardB, withBonus):
     self.notify.debug('we got a match %d %d' % (cardA, cardB))
     self.matches += 1
     if cardA in self.faceUpList:
         self.faceUpList.remove(cardA)
     
     if cardB in self.faceUpList:
         self.faceUpList.remove(cardB)
     
     self.inactiveList.append(cardA)
     self.inactiveList.append(cardB)
     matchIval = Parallel()
     for card in [
         cardA,
         cardB]:
         self.cards[card].setTransparency(1)
         cardSeq = Sequence(LerpColorScaleInterval(self.cards[card], duration = 1, colorScale = Vec4(1.0, 1.0, 1.0, 0.0)), Func(self.cards[card].hide))
         matchIval.append(cardSeq)
     
     if withBonus:
         matchIval.append(SoundInterval(self.matchWithBonusSfx, node = self.cards[card], listenerNode = base.localAvatar, cutOff = 240))
     else:
         matchIval.append(SoundInterval(self.matchSfx, node = self.cards[card], listenerNode = base.localAvatar, cutOff = 240))
     matchIval.start()
     if len(self.inactiveList) == len(self.cards):
         self.sendUpdate('reportDone')
Beispiel #10
0
 def AnimateIn(self, blade):
     x, y, z = blade.getPos()
     ending_position = (self.originals[blade])
     position1 = blade.posInterval(1, ending_position, (x, y, z))
     rotate1 = blade.hprInterval(1, (0, 0, 0), blade.getHpr())
     scale1 = blade.scaleInterval(1, (1, 1, 1), blade.getScale())
     move = Parallel(position1, rotate1, scale1, name="move")
     move.start()
     blade.setTag('state', 'in')
Beispiel #11
0
 def AnimateOut(self, blade):
     x, y, z = blade.getPos()
     self.originals[blade] = (x, y, z)
     ending_position = (40 + random.uniform(0,120), y-(random.uniform(20,100)), z + random.uniform(-10,50))
     position1 = blade.posInterval(1, ending_position, (x, y, z))
     rotate1 = blade.hprInterval(1, (90, 0, 0), blade.getHpr())
     scale1 = blade.scaleInterval(1, (2, 2, 2), blade.getScale())
     move = Parallel(position1, rotate1, scale1, name="move")
     move.start()
     blade.setTag('state', 'out')
Beispiel #12
0
class Megaphone(ToonUpGag):

    def __init__(self):
        ToonUpGag.__init__(self, CIGlobals.Megaphone, 'phase_5/models/props/megaphone.bam', 10, 20, 100, GagGlobals.TELLJOKE_SFX, 1)
        self.setImage('phase_3.5/maps/megaphone.png')
        self.track = None
        self.soundInterval = None
        return

    def start(self):
        super(Megaphone, self).start()
        if not self.gag:
            self.build()
        if self.avatar == base.localAvatar:
            question, answer = random.choice(CIGlobals.ToonHealJokes)
        self.setupHandJoints()
        self.placeProp(self.handJoint, self.gag)
        self.soundInterval = self.getSoundTrack(self.avatar.getDuration('shout', fromFrame=0, toFrame=18), self.gag, 5.5)
        propInterval = Sequence()
        propInterval.append(Wait(self.avatar.getDuration('shout', fromFrame=0, toFrame=25)))
        if self.avatar == base.localAvatar:
            propInterval.append(Func(base.localAvatar.b_setChat, question))
        propInterval.append(Wait(self.avatar.getDuration('shout', fromFrame=26, toFrame=75)))
        if self.avatar == base.localAvatar:
            propInterval.append(Func(base.localAvatar.b_setChat, answer))
        propInterval.append(Wait(self.avatar.getDuration('shout', fromFrame=76, toFrame=118)))
        if self.avatar == base.localAvatar:
            propInterval.append(Func(self.setHealAmount))
            propInterval.append(Func(self.healNearbyAvatars, 20))
        propInterval.append(Wait(self.avatar.getDuration('shout', fromFrame=118)))
        propInterval.append(Func(self.cleanupGag))
        propInterval.append(Func(self.reset))
        self.track = Parallel(propInterval, ActorInterval(self.avatar, 'shout'), self.soundInterval)
        self.track.start()

    def equip(self):
        super(Megaphone, self).equip()
        self.build()

    def unEquip(self):
        if self.track:
            self.track.finish()
            self.track = None
            self.soundInterval = None
        self.cleanupGag()
        self.reset()
        return

    def cleanupGag(self):
        if self.gag:
            self.gag.removeNode()
            self.gag = None
        return
Beispiel #13
0
class PickPocketAttack(Attack):
    notify = directNotify.newCategory('PickPocketAttack')
    attack = 'pickpocket'

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.dollar = None
        self.pickSfx = None
        return

    def doAttack(self, ts = 0):
        self.dollar = loader.loadModel('phase_5/models/props/1dollar-bill-mod.bam')
        self.dollar.setY(0.22)
        self.dollar.setHpr(289.18, 252.75, 0.0)
        if hasattr(self.suit, 'uniqueName'):
            name = self.suit.uniqueName('doPickPocketAttack')
        else:
            name = 'doPickPocketAttack'
        self.suitTrack = Parallel(ActorInterval(self.suit, 'pickpocket'), Sequence(Wait(0.4), Func(self.attemptDamage)), name=name)
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        self.suitTrack.delayDelete = DelayDelete.DelayDelete(self.suit, name)
        self.suitTrack.start(ts)

    def attemptDamage(self):
        shouldDamage = False
        suitH = self.suit.getH(render) % 360
        myH = base.localAvatar.getH(render) % 360
        if not -90.0 <= suitH - myH <= 90.0:
            if base.localAvatar.getDistance(self.suit) <= 15.0:
                shouldDamage = True
        if shouldDamage:
            self.playWeaponSound()
            self.dollar.reparentTo(self.suit.find('**/joint_Rhold'))
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)

    def playWeaponSound(self):
        self.pickSfx = base.audio3d.loadSfx('phase_5/audio/sfx/SA_pick_pocket.mp3')
        base.audio3d.attachSoundToObject(self.pickSfx, self.suit)
        self.pickSfx.play()

    def cleanup(self):
        Attack.cleanup(self)
        if self.pickSfx:
            self.pickSfx.stop()
            self.pickSfx = None
        if self.dollar:
            self.dollar.removeNode()
            self.dollar = None
        return
class CogdoMazeSplattable:
    def __init__(self, object, name, collisionRadius):
        self.object = object
        self.splat = CogdoUtil.loadMazeModel('splash')
        self.splat.setBillboardPointEye()
        self.splat.setBin('fixed', 40)
        self.splat.setDepthTest(False)
        self.splat.setDepthWrite(False)
        self.splatTrack = None
        self._splatSfxIval = base.cogdoGameAudioMgr.createSfxIval('splat')
        self.initGagCollision(name, collisionRadius)
        return

    def destroy(self):
        self.disableGagCollision()
        if self._splatSfxIval.isPlaying():
            self._splatSfxIval.finish()
        del self._splatSfxIval

    def initGagCollision(self, name, radius):
        self.gagCollisionName = name
        collision = CollisionTube(0, 0, 0, 0, 0, 4, radius)
        collision.setTangible(1)
        self.gagCollNode = CollisionNode(self.gagCollisionName)
        self.gagCollNode.setIntoCollideMask(ToontownGlobals.PieBitmask)
        self.gagCollNode.addSolid(collision)
        self.gagCollNodePath = self.object.attachNewNode(self.gagCollNode)

    def disableGagCollision(self):
        self.gagCollNodePath.removeNode()

    def doSplat(self):
        if self.splatTrack and self.splatTrack.isPlaying():
            self.splatTrack.finish()
        self.splat.reparentTo(render)
        self.splat.setPos(self.object, 0, 0, 3.0)
        self.splat.setY(self.splat.getY() - 1.0)
        self._splatSfxIval.node = self.splat
        self.splatTrack = Parallel(
            self._splatSfxIval,
            Sequence(
                Func(self.splat.showThrough),
                LerpScaleInterval(
                    self.splat,
                    duration=0.5,
                    scale=6,
                    startScale=1,
                    blendType='easeOut'), Func(self.splat.hide)))
        self.splatTrack.start()
    def showToonThrowingGag(self, heading, pos):
        gag = self.equippedGag
        if gag is None:
            return
        self.removeGag()
        tossTrack, flyTrack, object = self.getThrowInterval(gag, pos[0], pos[1], pos[2], heading, 0, 0)

        def matchRunningAnim(toon = self.toon):
            toon.playingAnim = None
            toon.setSpeed(toon.forwardSpeed, toon.rotateSpeed)

        newTossTrack = Sequence(tossTrack, Func(matchRunningAnim))
        throwTrack = Parallel(newTossTrack, flyTrack)
        throwTrack.start(0)
        return object
Beispiel #16
0
	def showVisibleSquares(self, dt="default"):
		visibles = defaultdict(lambda: False)
		for p in [(x,y) for (x,y) in self.pieces if 0 <= x < 8 and 0 <= y < 8]:
			if self.pieces[p]:
				if self.pieces[p].color == self.player:
					for s in self.pieces[p].visibleSquares(self.pieces):
						visibles[s] = True

		par = Parallel()
		for s in self.squares:
			if visibles[s]:
				par.append(self.showSquare(s, dt))
			else:
				par.append(self.hideSquare(s, dt))
		par.start()
		return par
 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 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 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
    def showToonThrowingPie(self, avId, timestamp, heading, pos):
        toon = self.getAvatar(avId)
        if toon:
            (tossTrack, pieTrack, flyPie) = self.getTossPieInterval(toon, pos[0], pos[1], pos[2], heading, 0, 0, 0)
            
            def removePieFromTraverser(flyPie = flyPie):
                if base.cTrav:
                    if flyPie:
                        base.cTrav.removeCollider(flyPie)
                    
                

            if avId == self.localAvId:
                flyPie.setTag('throwerId', str(avId))
                collSphere = CollisionSphere(0, 0, 0, 0.5)
                collSphere.setTangible(0)
                name = 'PieSphere-%d' % avId
                collSphereName = self.uniqueName(name)
                collNode = CollisionNode(collSphereName)
                collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
                collNode.addSolid(collSphere)
                colNp = flyPie.attachNewNode(collNode)
                colNp.show()
                base.cTrav.addCollider(colNp, self.pieHandler)
                self.accept('pieHit-' + collSphereName, self.handlePieHitting)
            
            
            def matchRunningAnim(toon = toon):
                toon.playingAnim = None
                toon.setSpeed(toon.forwardSpeed, toon.rotateSpeed)

            newTossTrack = Sequence(tossTrack, Func(matchRunningAnim))
            pieTrack = Parallel(newTossTrack, pieTrack)
            elapsedTime = globalClockDelta.localElapsedTime(timestamp)
            if elapsedTime < 16.0 / 24.0:
                elapsedTime = 16.0 / 24.0
            
            pieTrack.start(elapsedTime)
            self.toonPieTracks[avId] = pieTrack
Beispiel #21
0
    def doLipstick(self, target):
        dScale = 0.5
        tLips = 2.5
        animDuration = base.localAvatar.getDuration(self.avAnim)
        tThrow = 115.0 / base.localAvatar.getFrameRate(self.avAnim)

        def doHeal():
            if self.isLocal():
                self.setHealAmount()
                self.healAvatar(target, 'conked')
                base.localAvatar.sendUpdate('gagRelease', [self.getID()])
            else:
                ActorInterval(target, 'conked').start()

        self.placeProp(self.handJoint, self.gag, Point3(-0.27, -0.24, -0.95), Point3(-118, -10.6, -25.9))
        stickScaleUp = self.getScaleTrack([self.gag], dScale, GagGlobals.PNT3NEAR0, GagGlobals.PNT3NORMAL)
        stickScaleDn = self.getScaleTrack([self.gag], dScale, GagGlobals.PNT3NORMAL, GagGlobals.PNT3NEAR0)
        stickTrack = Sequence(stickScaleUp, Wait(animDuration - 2.0 * dScale), stickScaleDn)
        lipsTrack = Sequence(Wait(tLips), Func(self.avatar.pose, self.avAnim, 57), Func(self.avatar.update, 0), Func(self.placeProp, render, self.lips, self.gag.getPos(render)), Func(self.lips.setBillboardPointWorld), LerpScaleInterval(self.lips, dScale, Point3(3, 3, 3), startScale=GagGlobals.PNT3NEAR0), Wait(tThrow - tLips - dScale), LerpPosInterval(self.lips, dScale, Point3(target.getPos()), startPos=self.gag.getPos(render)), Sequence(Func(doHeal)), Func(self.cleanupLips))
        delay = tThrow + dScale
        mainTrack = Parallel(stickTrack, lipsTrack, self.getSoundTrack(delay, self.avatar, 2), Sequence(ActorInterval(self.avatar, self.avAnim)))
        mainTrack.start()
    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.ogg')
        deathSound = base.loadSfx('phase_3.5/audio/sfx/ENC_cogfall_apart_%s.ogg' % random.randint(1, 6))
        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 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 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 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
Beispiel #26
0
    def start(self):
        SoundGag.start(self)
        tracks = Parallel()
        INSTRUMENT_SCALE_MODIFIER = 0.5
        instrMin = Vec3(0.001, 0.001, 0.001)
        instrMax1 = Vec3(0.3, 0.4, 0.2)
        instrMax1 *= INSTRUMENT_SCALE_MODIFIER
        instrMax2 = Vec3(0.3, 0.3, 0.3)
        instrMax2 *= INSTRUMENT_SCALE_MODIFIER
        instrStretch1 = Vec3(0.3, 0.5, 0.25)
        instrStretch1 *= INSTRUMENT_SCALE_MODIFIER
        instrStretch2 = Vec3(0.3, 0.7, 0.3)
        instrStretch2 *= INSTRUMENT_SCALE_MODIFIER

        def setInstrumentStats():
            self.gag.setPos(-0.6, -0.9, 0.15)
            self.gag.setHpr(145, 0, 85)
            self.gag.setScale(instrMin)

        megaphoneShow = Sequence(Func(self.placeProp, self.handJoint, self.megaphone), Func(self.placeProp, self.handJoint, self.gag), Func(setInstrumentStats))
        grow1 = self.getScaleIntervals(self.gag, duration=0.3, startScale=instrMin, endScale=instrMax1)
        grow2 = self.getScaleIntervals(self.gag, duration=0.3, startScale=instrMax1, endScale=instrMax2)
        instrumentAppear = Parallel(Sequence(grow1, grow2))
        stretchInstr1 = self.getScaleBlendIntervals(self.gag, duration=0.1, startScale=instrMax2, endScale=instrStretch1, blendType='easeOut')
        stretchInstr2 = self.getScaleBlendIntervals(self.gag, duration=0.1, startScale=instrStretch1, endScale=instrStretch2, blendType='easeOut')
        stretchInstr = Sequence(stretchInstr1, stretchInstr2)
        backInstr = self.getScaleBlendIntervals(self.gag, duration=0.1, startScale=instrStretch2, endScale=instrMax2, blendType='easeOut')
        attackTrack = Sequence(stretchInstr, Wait(1), backInstr)
        delayTime = 2.45
        delayUntilAppearSound = 1.0
        megaphoneTrack = Sequence(megaphoneShow, Wait(delayUntilAppearSound), SoundInterval(self.appearSfx, node=self.avatar), Wait(delayTime + 1.0), instrumentAppear)
        tracks.append(megaphoneTrack)
        tracks.append(ActorInterval(self.avatar, 'sound'))
        instrumentshrink = self.getScaleIntervals(self.gag, duration=0.1, startScale=instrMax2, endScale=instrMin)
        soundTrack = Sequence(Wait(delayTime), Parallel(attackTrack, SoundInterval(self.soundSfx, node=self.avatar), Sequence(Wait(1.5), instrumentshrink), Func(self.damageCogsNearby), Wait(0.4), Func(self.finish)))
        tracks.append(soundTrack)
        tracks.start()
Beispiel #27
0
    def start(self):
        SoundGag.start(self)
        INSTRUMENT_SCALE_MODIFIER = 0.5
        tracks = Parallel()
        instrMin = Vec3(0.001, 0.001, 0.001)
        instrMax = Vec3(0.4, 0.4, 0.4)
        instrMax *= INSTRUMENT_SCALE_MODIFIER
        instrStretch = Vec3(0.5, 0.5, 0.5)
        instrStretch *= INSTRUMENT_SCALE_MODIFIER

        def setInstrumentStats():
            self.gag.setPos(-1.3, -1.4, 0.1)
            self.gag.setHpr(145, 0, 85)
            self.gag.setScale(instrMin)

        def longshake(models, num):
            inShake = self.getScaleBlendIntervals(models, duration=0.2, startScale=instrMax, endScale=instrStretch, blendType='easeInOut')
            outShake = self.getScaleBlendIntervals(models, duration=0.2, startScale=instrStretch, endScale=instrMax, blendType='easeInOut')
            i = 1
            seq = Sequence()
            while i < num:
                if i % 2 == 0:
                    seq.append(inShake)
                else:
                    seq.append(outShake)
                i += 1

            seq.start()

        megaphoneShow = Sequence(Func(self.placeProp, self.handJoint, self.megaphone), Func(self.placeProp, self.handJoint, self.gag), Func(setInstrumentStats))
        grow = self.getScaleBlendIntervals(self.gag, duration=1, startScale=instrMin, endScale=instrMax, blendType='easeInOut')
        instrumentshrink = self.getScaleIntervals(self.gag, duration=0.1, startScale=instrMax, endScale=instrMin)
        instrumentAppear = Sequence(grow, Wait(0), Func(longshake, self.gag, 5))
        megaphoneTrack = Parallel(Sequence(Wait(1.7), SoundInterval(self.soundSfx, node=self.avatar)), Sequence(megaphoneShow, Wait(1.7), instrumentAppear, Wait(1), Func(self.damageCogsNearby), instrumentshrink, Wait(0.4), Func(self.finish)))
        tracks.append(megaphoneTrack)
        tracks.append(ActorInterval(self.avatar, 'sound'))
        tracks.start()
Beispiel #28
0
    def showToonThrowingPie(self, avId, timestamp, heading, pos):
        toon = self.getAvatar(avId)
        if toon:
            tossTrack, pieTrack, flyPie = self.getTossPieInterval(
                toon, pos[0], pos[1], pos[2], heading, 0, 0, 0)

            def removePieFromTraverser(flyPie=flyPie):
                if base.cTrav:
                    if flyPie:
                        base.cTrav.removeCollider(flyPie)

            if avId == self.localAvId:
                flyPie.setTag('throwerId', str(avId))
                collSphere = CollisionSphere(0, 0, 0, 0.5)
                collSphere.setTangible(0)
                name = 'PieSphere-%d' % avId
                collSphereName = self.uniqueName(name)
                collNode = CollisionNode(collSphereName)
                collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
                collNode.addSolid(collSphere)
                colNp = flyPie.attachNewNode(collNode)
                colNp.show()
                base.cTrav.addCollider(colNp, self.pieHandler)
                self.accept('pieHit-' + collSphereName, self.handlePieHitting)

            def matchRunningAnim(toon=toon):
                toon.playingAnim = None
                toon.setSpeed(toon.forwardSpeed, toon.rotateSpeed)
                return

            newTossTrack = Sequence(tossTrack, Func(matchRunningAnim))
            pieTrack = Parallel(newTossTrack, pieTrack)
            elapsedTime = globalClockDelta.localElapsedTime(timestamp)
            if elapsedTime < 16.0 / 24.0:
                elapsedTime = 16.0 / 24.0
            pieTrack.start(elapsedTime)
            self.toonPieTracks[avId] = pieTrack
    def start(self):
        SoundGag.start(self)
        tracks = Parallel()
        delay = 2.45
        INSTRUMENT_SCALE_MODIFIER = 0.5
        instrMin = Vec3(0.001, 0.001, 0.001)
        instrMax1 = Vec3(1.7, 1.7, 1.7)
        instrMax1 *= INSTRUMENT_SCALE_MODIFIER
        instrMax2 = Vec3(2.2, 2.2, 2.2)
        instrMax2 *= INSTRUMENT_SCALE_MODIFIER
        instrStretch = Vec3(0.4, 0.4, 0.4)
        instrStretch *= INSTRUMENT_SCALE_MODIFIER
        head = self.gag.find('**/opera_singer')
        head.setPos(0, 0, 0)

        def setInstrumentStats():
            newPos = Vec3(-0.8, -0.9, 0.2)
            newPos *= 1.3
            self.gag.setPos(newPos[0], newPos[1], newPos[2])
            self.gag.setHpr(145, 0, 90)
            self.gag.setScale(instrMin)

        megaphoneShow = Sequence(Func(self.placeProp, self.handJoint, self.megaphone), Func(self.placeProp, self.handJoint, self.gag), Func(setInstrumentStats))
        grow1 = self.getScaleBlendIntervals(self.gag, duration=1, startScale=instrMin, endScale=instrMax1, blendType='easeOut')
        grow2 = self.getScaleBlendIntervals(self.gag, duration=1.1, startScale=instrMax1, endScale=instrMax2, blendType='easeIn')
        shrink2 = self.getScaleIntervals(self.gag, duration=0.1, startScale=instrMax2, endScale=instrMin)
        instrumentAppear = Parallel(Sequence(grow1, grow2, Wait(6.0), shrink2, Wait(0.4), Func(self.finish)), SoundInterval(self.appearSfx, node=self.avatar))
        delayTime = delay
        megaphoneTrack = Sequence(megaphoneShow, Wait(delayTime + 1.0), instrumentAppear)
        tracks.append(megaphoneTrack)
        toonTrack = self.__createToonInterval(0)
        tracks.append(toonTrack)
        soundTrack = Sequence(Wait(delayTime), Parallel(SoundInterval(self.soundSfx, node=self.avatar), Func(self.damageCogsNearby)))
        tracks.append(soundTrack)
        tracks.start()
        self.tracks = tracks
Beispiel #30
0
def makeDeltaTextEffect(delta, parent, pos):
    from direct.gui.DirectGui import OnscreenText
    text = OnscreenText(text = "", font = getMickeyFont())
    if delta > 0:
        text['fg'] = (0, 1, 0, 1)
        textstr = "+"
    else:
        text['fg'] = (1, 0, 0, 1)
        textstr = ""
    textstr += str(delta)
    text['text'] = textstr
    text.reparentTo(parent)
    text.setPos(pos[0], pos[2])
    text.setTransparency(True, 1)

    from panda3d.core import Point3
    from direct.interval.IntervalGlobal import Sequence, LerpPosInterval, LerpColorScaleInterval, Wait, Parallel
    seq = Parallel()
    seq.append(LerpPosInterval(text, 1.0, Point3(pos) + (0, 0, 0.1), pos))
    seq.append(Sequence(LerpColorScaleInterval(text, 0.25, (1, 1, 1, 1), (1, 1, 1, 0)),
                        Wait(0.5),
                        LerpColorScaleInterval(text, 0.25, (1, 1, 1, 0), (1, 1, 1, 1)),
                        Func(text.destroy)))
    seq.start()
Beispiel #31
0
class DistributedBoat(DistributedObject):
    notify = directNotify.newCategory("DistributedBoat")

    def __init__(self, cr):
        DistributedObject.__init__(self, cr)
        self.fsm = ClassicFSM('DistributedBoat', [
            State('off', self.enterOff, self.exitOff),
            State('eastToWest', self.enterEastToWest, self.exitEastToWest),
            State('westToEast', self.enterWestToEast, self.exitWestToEast)
        ], 'off', 'off')
        self.boat = None
        self.eastPier = None
        self.eastPierPath = 'east_pier'
        self.westPier = None
        self.westPierPath = 'west_pier'
        self.pierUpP = 0.0
        self.pierDownP = -45.0
        self.fogHorn = 'phase_5/audio/sfx/SZ_DD_foghorn.ogg'
        self.shipBell = 'phase_6/audio/sfx/SZ_DD_shipbell.ogg'
        self.waterLap = 'phase_6/audio/sfx/SZ_DD_waterlap.ogg'
        self.dockCreak = 'phase_6/audio/sfx/SZ_DD_dockcreak.ogg'
        self.eastWest = 'phase_6/paths/dd-e-w.bam'
        self.westEast = 'phase_6/paths/dd-w-e.bam'
        self.boatPath = '*donalds_boat*'
        self.track = None
        self.state = None

    def __handleOnBoat(self, entry):
        base.localAvatar.b_setParent(CIGlobals.SPDonaldsBoat)
        base.playSfx(self.soundWaterLap, looping=1)

    def __handleOffBoat(self, entry):
        base.localAvatar.b_setParent(CIGlobals.SPRender)
        self.soundWaterLap.stop()

    def __pollBoat(self, task):
        try:
            self.boat = self.cr.playGame.hood.loader.geom.find('**/' +
                                                               self.boatPath)
        except:
            return task.cont
        self.generated()
        return task.done

    def generate(self):
        DistributedObject.generate(self)
        self.soundFogHorn = base.loadSfx(self.fogHorn)
        self.soundShipBell = base.loadSfx(self.shipBell)
        self.soundWaterLap = base.loadSfx(self.waterLap)
        self.soundDockCreak = base.loadSfx(self.dockCreak)
        #try:
        #    self.boat = self.cr.playGame.hood.loader.geom.find('**/' + self.boatPath)
        #except:
        #        base.taskMgr.add(self.__pollBoat, self.uniqueName('__pollBoat'))
        #        return
        self.boat = self.cr.playGame.hood.loader.geom.find('**/' +
                                                           self.boatPath)
        self.generated()

    def generated(self):
        self.eastPier = self.cr.playGame.hood.loader.geom.find(
            '**/' + self.eastPierPath)
        self.westPier = self.cr.playGame.hood.loader.geom.find(
            '**/' + self.westPierPath)
        base.cr.parentMgr.registerParent(CIGlobals.SPDonaldsBoat, self.boat)
        self.accept('enterdonalds_boat_floor', self.__handleOnBoat)
        self.accept('exitdonalds_boat_floor', self.__handleOffBoat)
        self.d_requestCurrentStateAndTimestamp()
        self.fsm.enterInitialState()

    def disable(self):
        base.taskMgr.remove(self.uniqueName('__pollBoat'))
        base.cr.parentMgr.unregisterParent(CIGlobals.SPDonaldsBoat)
        self.ignore('enterdonalds_boat_floor')
        self.ignore('exitdonalds_boat_floor')
        self.fsm.requestFinalState()
        del self.fsm
        del self.soundFogHorn
        del self.soundShipBell
        del self.soundWaterLap
        del self.soundDockCreak
        self.fogHorn = None
        self.shipBell = None
        self.waterLap = None
        self.dockCreak = None
        self.boat = None
        self.track = None
        self.pierDownP = None
        self.pierUpP = None
        self.eastPier = None
        self.eastPierPath = None
        self.westPier = None
        self.westPierPath = None
        self.boatPath = None
        self.westEast = None
        self.eastWest = None
        DistributedObject.disable(self)

    def currentStateAndTimestamp(self, state, timestamp):
        self.setState(state, timestamp)

    def d_requestCurrentStateAndTimestamp(self):
        self.sendUpdate('requestCurrentStateAndTimestamp', [])

    def setState(self, state, timestamp=None):
        if timestamp == None:
            ts = 0.0
        else:
            ts = globalClockDelta.localElapsedTime(timestamp)
        self.state = state
        if self.boat:
            self.fsm.request(state, [ts])

    def enterEastToWest(self, ts=0):
        moPath = Mopath.Mopath()
        moPath.loadFile(self.eastWest)
        moIval = MopathInterval(moPath, self.boat)

        self.track = Parallel(
            SoundInterval(self.soundShipBell, node=self.boat),
            SoundInterval(self.soundDockCreak, node=self.eastPier), moIval,
            LerpQuatInterval(self.eastPier,
                             duration=5.0,
                             quat=(90, self.pierDownP, 0),
                             startHpr=(90, self.pierUpP, 0)),
            Sequence(
                Wait(15.0),
                Parallel(
                    LerpQuatInterval(self.westPier,
                                     duration=5.0,
                                     quat=(-90, self.pierUpP, 0),
                                     startHpr=(-90, self.pierDownP, 0)),
                    Sequence(
                        Wait(2.0),
                        SoundInterval(self.soundDockCreak,
                                      node=self.westPier))),
                SoundInterval(self.soundFogHorn, node=self.boat)))
        self.track.start(ts)

    def exitEastToWest(self):
        if self.track:
            self.track.finish()
            self.track = None

    def enterWestToEast(self, ts=0):
        moPath = Mopath.Mopath()
        moPath.loadFile(self.westEast)
        moIval = MopathInterval(moPath, self.boat)

        self.track = Parallel(
            SoundInterval(self.soundShipBell, node=self.boat),
            SoundInterval(self.soundDockCreak, node=self.westPier), moIval,
            LerpQuatInterval(self.westPier,
                             duration=5.0,
                             quat=(-90, self.pierDownP, 0),
                             startHpr=(-90, self.pierUpP, 0)),
            Sequence(
                Wait(15.0),
                Parallel(
                    LerpQuatInterval(self.eastPier,
                                     duration=5.0,
                                     quat=(90, self.pierUpP, 0),
                                     startHpr=(90, self.pierDownP, 0)),
                    Sequence(
                        Wait(2.0),
                        SoundInterval(self.soundDockCreak,
                                      node=self.eastPier))),
                SoundInterval(self.soundFogHorn, node=self.boat)))
        self.track.start(ts)

    def exitWestToEast(self):
        if self.track:
            self.track.finish()
            self.track = None

    def enterOff(self):
        pass

    def exitOff(self):
        pass
Beispiel #32
0
class CogThief(DirectObject):
    notify = directNotify.newCategory('CogThief')
    DefaultSpeedWalkAnim = 4.0
    CollisionRadius = 1.25
    MaxFriendsVisible = 4
    Infinity = 100000.0
    SeparationDistance = 6.0
    MinUrgency = 0.5
    MaxUrgency = 0.75

    def __init__(self, cogIndex, suitType, game, cogSpeed):
        self.cogIndex = cogIndex
        self.suitType = suitType
        self.game = game
        self.cogSpeed = cogSpeed
        suit = Suit.Suit()
        d = SuitDNA.SuitDNA()
        d.newSuit(suitType)
        suit.setDNA(d)
        suit.pose('walk', 0)
        self.suit = suit
        self.goal = CTGG.NoGoal
        self.goalId = CTGG.InvalidGoalId
        self.lastLocalTimeStampFromAI = 0
        self.lastPosFromAI = Point3(0, 0, 0)
        self.lastThinkTime = 0
        self.doneAdjust = False
        self.barrel = CTGG.NoBarrelCarried
        self.signalledAtReturnPos = False
        self.defaultPlayRate = 1.0
        self.netTimeSentToStartByHit = 0
        self.velocity = Vec3(0, 0, 0)
        self.oldVelocity = Vec3(0, 0, 0)
        self.acceleration = Vec3(0, 0, 0)
        self.bodyLength = self.CollisionRadius * 2
        self.cruiseDistance = 2 * self.bodyLength
        self.maxVelocity = self.cogSpeed
        self.maxAcceleration = 5.0
        self.perceptionRange = 6
        self.notify.debug('cogSpeed=%s' % self.cogSpeed)
        self.kaboomSound = loader.loadSfx(
            'phase_4/audio/sfx/MG_cannon_fire_alt.mp3')
        self.kaboom = loader.loadModel(
            'phase_4/models/minigames/ice_game_kaboom')
        self.kaboom.setScale(2.0)
        self.kaboom.setBillboardPointEye()
        self.kaboom.hide()
        self.kaboomTrack = None
        splatName = 'splat-creampie'
        self.splat = globalPropPool.getProp(splatName)
        self.splat.setBillboardPointEye()
        self.splatType = globalPropPool.getPropType(splatName)
        self.pieHitSound = globalBattleSoundCache.getSound(
            'AA_wholepie_only.mp3')

    def destroy(self):
        self.ignoreAll()
        self.suit.delete()
        self.game = None

    def uniqueName(self, baseStr):
        return baseStr + '-' + str(self.game.doId)

    def handleEnterSphere(self, collEntry):
        intoNp = collEntry.getIntoNodePath()
        self.notify.debug('handleEnterSphere suit %d hit %s' %
                          (self.cogIndex, intoNp))
        if self.game:
            self.game.handleEnterSphere(collEntry)

    def gameStart(self, gameStartTime):
        self.gameStartTime = gameStartTime
        self.initCollisions()
        self.startWalkAnim()

    def gameEnd(self):
        self.moveIval.pause()
        del self.moveIval
        self.shutdownCollisions()
        self.suit.loop('neutral')

    def initCollisions(self):
        self.collSphere = CollisionSphere(0, 0, 0, 1.25)
        self.collSphere.setTangible(1)
        name = 'CogThiefSphere-%d' % self.cogIndex
        self.collSphereName = self.uniqueName(name)
        self.collNode = CollisionNode(self.collSphereName)
        self.collNode.setIntoCollideMask(CTGG.BarrelBitmask
                                         | ToontownGlobals.WallBitmask)
        self.collNode.addSolid(self.collSphere)
        self.collNodePath = self.suit.attachNewNode(self.collNode)
        self.accept('enter' + self.collSphereName, self.handleEnterSphere)
        self.pieCollSphere = CollisionTube(0, 0, 0, 0, 0, 4,
                                           self.CollisionRadius)
        self.pieCollSphere.setTangible(1)
        name = 'CogThiefPieSphere-%d' % self.cogIndex
        self.pieCollSphereName = self.uniqueName(name)
        self.pieCollNode = CollisionNode(self.pieCollSphereName)
        self.pieCollNode.setIntoCollideMask(ToontownGlobals.PieBitmask)
        self.pieCollNode.addSolid(self.pieCollSphere)
        self.pieCollNodePath = self.suit.attachNewNode(self.pieCollNode)

    def shutdownCollisions(self):
        self.ignore(self.uniqueName('enter' + self.collSphereName))
        del self.collSphere
        self.collNodePath.removeNode()
        del self.collNodePath
        del self.collNode

    def updateGoal(self, timestamp, inResponseClientStamp, goalType, goalId,
                   pos):
        self.notify.debug('self.netTimeSentToStartByHit =%s' %
                          self.netTimeSentToStartByHit)
        if not self.game:
            self.notify.debug('updateGoal self.game is None, just returning')
            return None

        if not self.suit:
            self.notify.debug('updateGoal self.suit is None, just returning')
            return None

        if self.goal == CTGG.NoGoal:
            self.startWalkAnim()

        if goalType == CTGG.NoGoal:
            self.notify.debug('updateGoal setting position to %s' % pos)
            self.suit.setPos(pos)

        self.lastThinkTime = 0
        self.velocity = Vec3(0, 0, 0)
        self.oldVelocity = Vec3(0, 0, 0)
        self.acceleration = Vec3(0, 0, 0)
        if goalType == CTGG.RunAwayGoal:
            pass
        1
        if inResponseClientStamp < self.netTimeSentToStartByHit and self.goal == CTGG.NoGoal and goalType == CTGG.RunAwayGoal:
            self.notify.warning(
                'ignoring newGoal %s as cog %d was recently hit responsetime=%s hitTime=%s'
                % (CTGG.GoalStr[goalType], self.cogIndex,
                   inResponseClientStamp, self.netTimeSentToStartByHit))
        else:
            self.lastLocalTimeStampFromAI = globalClockDelta.networkToLocalTime(
                timestamp, bits=32)
            self.goal = goalType
            self.goalId = goalId
            self.lastPosFromAI = pos
            self.doneAdjust = False
        self.signalledAtReturnPos = False

    def startWalkAnim(self):
        if self.suit:
            self.suit.loop('walk')
            speed = self.cogSpeed
            self.defaultPlayRate = float(self.cogSpeed /
                                         self.DefaultSpeedWalkAnim)
            self.suit.setPlayRate(self.defaultPlayRate, 'walk')

    def think(self):
        if self.goal == CTGG.ToonGoal:
            self.thinkAboutCatchingToon()
        elif self.goal == CTGG.BarrelGoal:
            self.thinkAboutGettingBarrel()
        elif self.goal == CTGG.RunAwayGoal:
            self.thinkAboutRunAway()

    def thinkAboutCatchingToon(self):
        if not self.game:
            return None

        av = self.game.getAvatar(self.goalId)
        if av:
            if not self.lastThinkTime:
                self.lastThinkTime = globalClock.getFrameTime()

            diffTime = globalClock.getFrameTime() - self.lastThinkTime
            avPos = av.getPos()
            myPos = self.suit.getPos()
            if not self.doneAdjust:
                myPos = self.lastPosFromAI
                self.notify.debug(
                    'thinkAboutCatchingToon not doneAdjust setting pos %s' %
                    myPos)
                self.doneAdjust = True

            self.suit.setPos(myPos)
            if self.game.isToonPlayingHitTrack(self.goalId):
                self.suit.headsUp(av)
                self.velocity = Vec3(0, 0, 0)
                self.oldVelocity = Vec3(0, 0, 0)
                self.acceleration = Vec3(0, 0, 0)
            else:
                self.commonMove()
            newPos = self.suit.getPos()
            self.adjustPlayRate(newPos, myPos, diffTime)

        self.lastThinkTime = globalClock.getFrameTime()

    def convertNetworkStampToGameTime(self, timestamp):
        localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
        gameTime = self.game.local2GameTime(localStamp)
        return gameTime

    def respondToToonHit(self, timestamp):
        localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
        if self.netTimeSentToStartByHit < timestamp:
            self.clearGoal()
            self.showKaboom()
            startPos = CTGG.CogStartingPositions[self.cogIndex]
            oldPos = self.suit.getPos()
            self.suit.setPos(startPos)
            if self.netTimeSentToStartByHit < timestamp:
                self.netTimeSentToStartByHit = timestamp

        else:
            self.notify.debug(
                'localStamp = %s, lastLocalTimeStampFromAI=%s, ignoring respondToToonHit'
                % (localStamp, self.lastLocalTimeStampFromAI))
        self.notify.debug(
            'respondToToonHit self.netTimeSentToStartByHit = %s' %
            self.netTimeSentToStartByHit)

    def clearGoal(self):
        self.goal = CTGG.NoGoal
        self.goalId = CTGG.InvalidGoalId

    def thinkAboutGettingBarrel(self):
        if not self.game:
            return None

        if not hasattr(self.game, 'barrels'):
            return None

        if self.goalId not in xrange(len(self.game.barrels)):
            return None

        if not self.lastThinkTime:
            self.lastThinkTime = globalClock.getFrameTime()

        diffTime = globalClock.getFrameTime() - self.lastThinkTime
        barrel = self.game.barrels[self.goalId]
        barrelPos = barrel.getPos()
        myPos = self.suit.getPos()
        if not self.doneAdjust:
            myPos = self.lastPosFromAI
            self.notify.debug(
                'thinkAboutGettingBarrel not doneAdjust setting position to %s'
                % myPos)
            self.suit.setPos(myPos)
            self.doneAdjust = True

        displacement = barrelPos - myPos
        distanceToToon = displacement.length()
        self.suit.headsUp(barrel)
        lengthTravelled = diffTime * self.cogSpeed
        if lengthTravelled > distanceToToon:
            lengthTravelled = distanceToToon

        displacement.normalize()
        dirVector = displacement
        dirVector *= lengthTravelled
        newPos = myPos + dirVector
        newPos.setZ(0)
        self.suit.setPos(newPos)
        self.adjustPlayRate(newPos, myPos, diffTime)
        self.lastThinkTime = globalClock.getFrameTime()

    def stopWalking(self, timestamp):
        localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
        if localStamp > self.lastLocalTimeStampFromAI:
            self.suit.loop('neutral')
            self.clearGoal()

    def thinkAboutRunAway(self):
        if not self.game:
            return None

        if not self.lastThinkTime:
            self.lastThinkTime = globalClock.getFrameTime()

        diffTime = globalClock.getFrameTime() - self.lastThinkTime
        returnPos = CTGG.CogReturnPositions[self.goalId]
        myPos = self.suit.getPos()
        if not self.doneAdjust:
            myPos = self.lastPosFromAI
            self.suit.setPos(myPos)
            self.doneAdjust = True

        displacement = returnPos - myPos
        distanceToToon = displacement.length()
        tempNp = render.attachNewNode('tempRet')
        tempNp.setPos(returnPos)
        self.suit.headsUp(tempNp)
        tempNp.removeNode()
        lengthTravelled = diffTime * self.cogSpeed
        if lengthTravelled > distanceToToon:
            lengthTravelled = distanceToToon

        displacement.normalize()
        dirVector = displacement
        dirVector *= lengthTravelled
        newPos = myPos + dirVector
        newPos.setZ(0)
        self.suit.setPos(newPos)
        self.adjustPlayRate(newPos, myPos, diffTime)
        if (self.suit.getPos() - returnPos).length() < 0.0001:
            if not (self.signalledAtReturnPos) and self.barrel >= 0:
                self.game.sendCogAtReturnPos(self.cogIndex, self.barrel)
                self.signalledAtReturnPos = True

        self.lastThinkTime = globalClock.getFrameTime()

    def makeCogCarryBarrel(self, timestamp, inResponseClientStamp, barrelModel,
                           barrelIndex, cogPos):
        if not self.game:
            return None

        localTimeStamp = globalClockDelta.networkToLocalTime(timestamp,
                                                             bits=32)
        self.lastLocalTimeStampFromAI = localTimeStamp
        inResponseGameTime = self.convertNetworkStampToGameTime(
            inResponseClientStamp)
        self.notify.debug('inResponseGameTime =%s timeSentToStart=%s' %
                          (inResponseGameTime, self.netTimeSentToStartByHit))
        if inResponseClientStamp < self.netTimeSentToStartByHit and self.goal == CTGG.NoGoal:
            self.notify.warning('ignoring makeCogCarrybarrel')
        else:
            barrelModel.setPos(0, -1.0, 1.5)
            barrelModel.reparentTo(self.suit)
            self.suit.setPos(cogPos)
            self.barrel = barrelIndex

    def makeCogDropBarrel(self, timestamp, inResponseClientStamp, barrelModel,
                          barrelIndex, barrelPos):
        localTimeStamp = globalClockDelta.networkToLocalTime(timestamp,
                                                             bits=32)
        self.lastLocalTimeStampFromAI = localTimeStamp
        barrelModel.reparentTo(render)
        barrelModel.setPos(barrelPos)
        self.barrel = CTGG.NoBarrelCarried

    def respondToPieHit(self, timestamp):
        localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
        if self.netTimeSentToStartByHit < timestamp:
            self.clearGoal()
            self.showSplat()
            startPos = CTGG.CogStartingPositions[self.cogIndex]
            oldPos = self.suit.getPos()
            self.suit.setPos(startPos)
            if self.netTimeSentToStartByHit < timestamp:
                self.netTimeSentToStartByHit = timestamp

        else:
            self.notify.debug(
                'localStamp = %s, lastLocalTimeStampFromAI=%s, ignoring respondToPieHit'
                % (localStamp, self.lastLocalTimeStampFromAI))
            self.notify.debug(
                'respondToPieHit self.netTimeSentToStartByHit = %s' %
                self.netTimeSentToStartByHit)

    def cleanup(self):
        self.clearGoal()
        self.ignoreAll()
        self.suit.delete()
        if self.kaboomTrack and self.kaboomTrack.isPlaying():
            self.kaboomTrack.finish()

        self.suit = None
        self.game = None

    def adjustPlayRate(self, newPos, oldPos, diffTime):
        lengthTravelled = (newPos - oldPos).length()
        if diffTime:
            speed = lengthTravelled / diffTime
        else:
            speed = self.cogSpeed
        rateMult = speed / self.cogSpeed
        newRate = rateMult * self.defaultPlayRate
        self.suit.setPlayRate(newRate, 'walk')

    def commonMove(self):
        if not self.lastThinkTime:
            self.lastThinkTime = globalClock.getFrameTime()

        dt = globalClock.getFrameTime() - self.lastThinkTime
        self.oldpos = self.suit.getPos()
        pos = self.suit.getPos()
        pos += self.velocity * dt
        self.suit.setPos(pos)
        self.seeFriends()
        acc = Vec3(0, 0, 0)
        self.accumulate(acc, self.getTargetVector())
        if self.numFlockmatesSeen > 0:
            keepDistanceVector = self.keepDistance()
            oldAcc = Vec3(acc)
            self.accumulate(acc, keepDistanceVector)
            if self.cogIndex == 0:
                pass

        if acc.length() > self.maxAcceleration:
            acc.normalize()
            acc *= self.maxAcceleration

        self.oldVelocity = self.velocity
        self.velocity += acc
        if self.velocity.length() > self.maxVelocity:
            self.velocity.normalize()
            self.velocity *= self.maxVelocity

        forwardVec = Vec3(1, 0, 0)
        heading = rad2Deg(math.atan2(self.velocity[1], self.velocity[0]))
        heading -= 90
        self.suit.setH(heading)

    def getTargetVector(self):
        targetPos = Point3(0, 0, 0)
        if self.goal == CTGG.ToonGoal:
            av = self.game.getAvatar(self.goalId)
            if av:
                targetPos = av.getPos()

        elif self.goal == CTGG.BarrelGoal:
            barrel = self.game.barrels[self.goalId]
            targetPos = barrel.getPos()
        elif self.goal == CTGG.RunAwayGoal:
            targetPos = CTGG.CogReturnPositions[self.goalId]

        targetPos.setZ(0)
        myPos = self.suit.getPos()
        diff = targetPos - myPos
        if diff.length() > 1.0:
            diff.normalize()
            diff *= 1.0

        return diff

    def accumulate(self, accumulator, valueToAdd):
        accumulator += valueToAdd
        return accumulator.length()

    def seeFriends(self):
        self.clearVisibleList()
        for cogIndex in self.game.cogInfo.keys():
            if cogIndex == self.cogIndex:
                continue

            if self.sameGoal(cogIndex):
                dist = self.canISee(cogIndex)
                if dist != self.Infinity:
                    self.addToVisibleList(cogIndex)
                    if dist < self.distToNearestFlockmate:
                        self.nearestFlockmate = cogIndex
                        self.distToNearestFlockmate = dist

            dist != self.Infinity

        return self.numFlockmatesSeen

    def clearVisibleList(self):
        self.visibleFriendsList = []
        self.numFlockmatesSeen = 0
        self.nearestFlockmate = None
        self.distToNearestFlockmate = self.Infinity

    def addToVisibleList(self, cogIndex):
        if self.numFlockmatesSeen < self.MaxFriendsVisible:
            self.visibleFriendsList.append(cogIndex)
            self.numFlockmatesSeen += 1
            if self.cogIndex == 0:
                pass

    def canISee(self, cogIndex):
        if self.cogIndex == cogIndex:
            return self.Infinity

        cogThief = self.game.getCogThief(cogIndex)
        distance = self.suit.getDistance(cogThief.suit)
        if distance < self.perceptionRange:
            return distance

        return self.Infinity

    def sameGoal(self, cogIndex):
        cogThief = self.game.getCogThief(cogIndex)
        if cogThief.goalId == self.goalId:
            pass
        result = cogThief.goal == self.goal
        return result

    def keepDistance(self):
        ratio = self.distToNearestFlockmate / self.SeparationDistance
        nearestThief = self.game.getCogThief(self.nearestFlockmate)
        change = nearestThief.suit.getPos() - self.suit.getPos()
        if ratio < self.MinUrgency:
            ratio = self.MinUrgency

        if ratio > self.MaxUrgency:
            ratio = self.MaxUrgency

        if self.distToNearestFlockmate < self.SeparationDistance:
            change.normalize()
            change *= -(1 - ratio)
        elif self.distToNearestFlockmate > self.SeparationDistance:
            change.normalize()
            change *= ratio
        else:
            change = Vec3(0, 0, 0)
        return change

    def showKaboom(self):
        if self.kaboomTrack and self.kaboomTrack.isPlaying():
            self.kaboomTrack.finish()

        self.kaboom.reparentTo(render)
        self.kaboom.setPos(self.suit.getPos())
        self.kaboom.setZ(3)
        self.kaboomTrack = Parallel(
            SoundInterval(self.kaboomSound, volume=0.5),
            Sequence(
                Func(self.kaboom.showThrough),
                LerpScaleInterval(self.kaboom,
                                  duration=0.5,
                                  scale=Point3(10, 10, 10),
                                  startScale=Point3(1, 1, 1),
                                  blendType='easeOut'),
                Func(self.kaboom.hide)))
        self.kaboomTrack.start()

    def showSplat(self):
        if self.kaboomTrack and self.kaboomTrack.isPlaying():
            self.kaboomTrack.finish()

        self.splat.reparentTo(render)
        self.splat.setPos(self.suit.getPos())
        self.splat.setZ(3)
        self.kaboomTrack = Parallel(
            SoundInterval(self.pieHitSound, volume=1.0),
            Sequence(
                Func(self.splat.showThrough),
                LerpScaleInterval(self.splat,
                                  duration=0.5,
                                  scale=1.75,
                                  startScale=Point3(0.10000000000000001,
                                                    0.10000000000000001,
                                                    0.10000000000000001),
                                  blendType='easeOut'), Func(self.splat.hide)))
        self.kaboomTrack.start()
class DistributedDodgeballGame(DistributedToonFPSGame, TeamMinigame):
    """The winter dodgeball minigame (client side)"""

    notify = directNotify.newCategory("DistributedDodgeballGame")

    TreeData = [['prop_snow_tree_small_ur',
                 Point3(23.23, 66.52, 7.46)],
                ['prop_snow_tree_small_ul',
                 Point3(-34.03, 88.02, 24.17)],
                ['prop_snow_tree_small_ur',
                 Point3(-54.80, 0, 4.19)],
                ['prop_snow_tree_small_ul',
                 Point3(54.80, -5, 4.19)],
                ['prop_snow_tree_small_ur',
                 Point3(62.71, 62.66, 16.80)],
                ['prop_snow_tree_small_ul',
                 Point3(-23.23, -66.52, 6)],
                ['prop_snow_tree_small_ur',
                 Point3(34.03, -88.02, 23)],
                ['prop_snow_tree_small_ul',
                 Point3(-62.71, -62.66, 16)]]

    SnowballData = [
        Point3(30, 0, 0.75),
        Point3(22.5, 0, 0.75),
        Point3(15, 0, 0.75),
        Point3(7.5, 0, 0.75),
        Point3(0, 0, 0.75),
        Point3(-7.5, 0, 0.75),
        Point3(-15, 0, 0.75),
        Point3(-22.5, 0, 0.75),
        Point3(-30, 0, 0.75)
    ]

    GameSong = "phase_4/audio/bgm/MG_Dodgeball.ogg"
    GameDesc = (
        "Welcome to the north! You have been invited to play dodgeball with the penguins!\n\n"
        "How To Play\nWASD to Move and use the mouse to aim.\nLeft click to Throw!\nThrow a "
        "snowball at a teammate to unfreeze them!\n\nThe team with the most points after {0} rounds wins!"
        .format(MaxRounds))

    InitCamTrans = [Point3(25, 45, 19.5317), Vec3(154.001, -15, 0)]

    SnowBallDmg = 25

    GetSnowBalls = "Pick up a snowball from the center!"

    Team2OtherBarrier = {BLUE: "red_barrier_coll", RED: "blue_barrier_coll"}

    def __init__(self, cr):
        try:
            self.DistributedDodgeballGame_initialized
            return
        except:
            self.DistributedDodgeballGame_initialized = 1

        DistributedToonFPSGame.__init__(self, cr)

        TeamMinigame.__init__(
            self, "BlueSnow", ('phase_4/maps/db_blue_neutral.png',
                               'phase_4/maps/db_blue_hover.png',
                               'phase_4/maps/db_blue_hover.png'), "RedIce",
            ('phase_4/maps/db_red_neutral.png',
             'phase_4/maps/db_red_hover.png', 'phase_4/maps/db_red_hover.png'))

        self.fsm.addState(
            State('chooseTeam', self.enterChooseTeam, self.exitChooseTeam,
                  ['waitForOthers']))
        self.fsm.addState(
            State('scrollBy', self.enterScrollBy, self.exitScrollBy,
                  ['countdown']))
        self.fsm.addState(
            State('countdown', self.enterCountdown, self.exitCountdown,
                  ['play']))
        self.fsm.addState(
            State('announceGameOver', self.enterAnnGameOver,
                  self.exitAnnGameOver, ['displayWinners', 'countdown']))
        self.fsm.addState(
            State('displayWinners', self.enterDisplayWinners,
                  self.exitDisplayWinners, ['gameOver']))
        self.fsm.getStateNamed('waitForOthers').addTransition('chooseTeam')
        self.fsm.getStateNamed('waitForOthers').addTransition('scrollBy')
        self.fsm.getStateNamed('play').addTransition('announceGameOver')

        self.firstPerson = DodgeballFirstPerson(self)

        self.scrollBySeq = None
        self.infoText = None

        self.redScoreLbl = None
        self.blueScoreLbl = None

        self.barrierIsStashed = False

        self.mySpawnPoint = 0

        self.infoText = getAlertText()

        self.spawnPointsByTeam = {
            BLUE: [[Point3(5, 15, 0), Vec3(180, 0, 0)],
                   [Point3(15, 15, 0), Vec3(180, 0, 0)],
                   [Point3(-5, 15, 0), Vec3(180, 0, 0)],
                   [Point3(-15, 15, 0), Vec3(180, 0, 0)]],
            RED: [[Point3(5, -15, 0), Vec3(0, 0, 0)],
                  [Point3(15, -15, 0), Vec3(0, 0, 0)],
                  [Point3(-5, -15, 0), Vec3(0, 0, 0)],
                  [Point3(-15, -15, 0), Vec3(0, 0, 0)]]
        }

        self.winnerMusic = base.loadMusic(
            'phase_9/audio/bgm/encntr_hall_of_fame.ogg')
        self.loserMusic = base.loadMusic(
            'phase_9/audio/bgm/encntr_sting_announce.ogg')
        self.danceSound = base.loadSfx('phase_3.5/audio/sfx/ENC_Win.ogg')

        # Environment vars
        self.arena = None
        self.olc = None
        self.trees = []
        self.snowballs = []

    def snowballPickupResp(self, flag, idx):
        self.firstPerson.snowballPickupResp(flag, idx)

    def roundOver(self, time=0):
        teams = [BLUE, RED]
        teams.sort(key=lambda team: self.scoreByTeam[team], reverse=True)
        self.winnerTeam = teams[0]
        base.localAvatar.disableAvatarControls()
        self.firstPerson.end()
        self.deleteTimer()
        self.fsm.request('announceGameOver', [time])

    def getTeamDNAColor(self, team):
        if team == TEAM1:
            return ToonDNA.colorDNA2color['18']
        elif team == TEAM2:
            return ToonDNA.colorDNA2color['02']

    def enterDisplayWinners(self):
        base.localAvatar.stopLookAround()
        base.localAvatar.resetHeadHpr()
        base.localAvatar.getGeomNode().show()
        camera.reparentTo(render)
        camera.setPos((-2.5, 12, 3.5))
        camera.setHpr((-175.074, -5.47218, 0))

        base.transitions.fadeIn()

        base.playSfx(self.danceSound, looping=1)

        if self.winnerTeam == self.team:
            base.playMusic(self.winnerMusic)
        else:
            base.playMusic(self.loserMusic)

        winnerPositions = [(-2, 0, 0), (2, 0, 0), (6, 0, 0), (-6, 0, 0)]
        loserPositions = [(-3.5, -10, 0), (-1.5, -15, 0), (3.0, -8, 0),
                          (5.5, -12, 0)]
        for team in [RED, BLUE]:
            for avId in self.playerListByTeam[team]:
                av = self.cr.doId2do.get(avId)
                if av:
                    av.stopSmooth()
                    av.setHpr(0, 0, 0)
                    if team == self.winnerTeam:
                        posList = winnerPositions
                        av.setAnimState("off")
                        av.stop()
                        if not self.getRemoteAvatar(avId).isFrozen:
                            av.loop("win")
                    else:
                        posList = loserPositions
                        av.setAnimState('off')
                        av.stop()
                        if not self.getRemoteAvatar(avId).isFrozen:
                            av.loop("pout")
                    pos = random.choice(posList)
                    posList.remove(pos)
                    av.setPos(pos)

        if self.winnerTeam == self.team:
            text = "YOU WIN!"
        else:
            text = "YOU LOSE!"
        self.gameOverLbl.setText(text)

        self.track = Sequence(Wait(2.0), Func(self.gameOverLbl.setScale, 0.01),
                              Func(self.gameOverLbl.show),
                              getAlertPulse(self.gameOverLbl, 0.27, 0.25))
        self.track.start()

        base.localAvatar.collisionsOff()

    def exitDisplayWinners(self):
        base.localAvatar.collisionsOn()
        base.transitions.noTransitions()
        self.danceSound.stop()
        if hasattr(self, 'track'):
            self.track.finish()
            self.track = None
        self.gameOverLbl.hide()

    def __prepareForNextRound(self):
        for av in self.remoteAvatars:
            av.unFreeze()
        pos, hpr = self.spawnPointsByTeam[self.team][self.mySpawnPoint]
        base.localAvatar.setPos(pos)
        base.localAvatar.setHpr(hpr)
        self.playMinigameMusic()
        self.fsm.request('countdown')

    def enterAnnGameOver(self, timeRanOut=0):
        self.firstPerson.vModel.hide()

        text = getGameText()
        text.setBin('gui-popup', 60)

        ival = Sequence()

        if timeRanOut:
            ival.append(Func(text.setText, "Time's Up!"))
            ival.append(getRoundIval(text))

        ival.append(
            Func(text.setText, "Round {0} Over!".format(self.getRound())))
        ival.append(getRoundIval(text))

        team = "Red"
        if self.winnerTeam == BLUE:
            team = "Blue"

        if self.round != MaxRounds:
            if self.scoreByTeam[RED] == self.scoreByTeam[BLUE]:
                ival.append(Func(text.setText, "The scores are tied!"))
            else:
                ival.append(
                    Func(text.setText, "{0} is in the lead!".format(team)))
        else:
            ival.append(Func(text.setText, "{0} wins!".format(team)))

        ival.append(getRoundIval(text))
        ival.setDoneEvent(self.uniqueName('annGameOverDone'))
        self.acceptOnce(ival.getDoneEvent(), self.__annGameOverTask)
        self.ival = ival
        self.ival.start()
        self.text = text

        base.transitions.fadeScreen()

    def __annGameOverTask(self):
        if self.round == MaxRounds:
            nextState = Func(self.fsm.request, "displayWinners")
        else:
            nextState = Func(self.__prepareForNextRound)

        self.ival = Sequence(base.transitions.getFadeOutIval(), nextState)
        self.ival.start()

    def exitAnnGameOver(self):
        if hasattr(self, 'ival'):
            self.ignore(self.ival.getDoneEvent())
            self.ival.finish()
            del self.ival
        if hasattr(self, 'text'):
            self.text.destroy()
            del self.text

    def teamWon(self, team, time=0):
        TeamMinigame.teamWon(self, team, time)
        base.localAvatar.disableAvatarControls()
        self.firstPerson.end()
        self.deleteTimer()
        self.fsm.request('announceGameOver', [time])

    def incrementTeamScore(self, team):
        TeamMinigame.incrementTeamScore(self, team)
        if team == BLUE:
            self.blueScoreLbl.setText("BLUE: " + str(self.scoreByTeam[team]))
            ToontownIntervals.start(
                ToontownIntervals.getPulseLargerIval(self.blueScoreLbl,
                                                     'blueScorePulse'))
        elif team == RED:
            self.redScoreLbl.setText("RED: " + str(self.scoreByTeam[team]))
            ToontownIntervals.start(
                ToontownIntervals.getPulseLargerIval(self.redScoreLbl,
                                                     'redScorePulse'))

    def getWinterDodgeballScoreText(self, color):
        text = OnscreenText(fg=color,
                            font=CIGlobals.getMinnieFont(),
                            scale=0.15,
                            shadow=(0, 0, 0, 1))
        return text

    def snowballHitWall(self, snowballIndex):
        snowball = self.snowballs[snowballIndex]
        snowball.handleHitWallOrPlayer()
        base.playSfx(snowball.impactSound, node=snowball, volume=1.5)

    def snowballHitGround(self, snowballIndex):
        snowball = self.snowballs[snowballIndex]
        snowball.handleHitGround()
        base.playSfx(snowball.impactSound, node=snowball, volume=1.5)

    def snowballHitPlayer(self, damagedPlayer, throwerTeam, snowballIndex):
        av = self.getRemoteAvatar(damagedPlayer)
        if av:
            if throwerTeam == av.team:
                # Someone on my team hit me. Unfreeze me if I am frozen.
                if av.unFreeze():
                    if damagedPlayer == base.localAvatar.doId:
                        self.showAlert("A team member has unfroze you!")
                        self.firstPerson.camFSM.request('unfrozen')
                        self.sendUpdate('teamMateUnfrozeMe', [self.team])
            else:
                # An enemy hit me. Become frozen if I am not already.
                if av.freeze():
                    if damagedPlayer == base.localAvatar.doId:
                        self.showAlert("You've been frozen by an enemy!")
                        self.firstPerson.camFSM.request('frozen')
                        self.sendUpdate('enemyFrozeMe',
                                        [self.team, throwerTeam])

        snowball = self.snowballs[snowballIndex]
        snowball.handleHitWallOrPlayer()
        base.playSfx(snowball.impactSound, node=snowball, volume=1.5)

    def playerCaughtSnowball(self, snowballIndex, catcherId):
        av = self.getRemoteAvatar(catcherId)
        if av:
            snowball = self.snowballs[snowballIndex]
            snowball.pauseThrowIval()
            snowball.pickup(av)

    def setupRemoteAvatar(self, avId):
        av = RemoteDodgeballAvatar(self, self.cr, avId)
        if avId == self.cr.localAvId:
            self.myRemoteAvatar = av
        self.remoteAvatars.append(av)

    def __getSnowTree(self, path):
        trees = loader.loadModel('phase_8/models/props/snow_trees.bam')
        tree = trees.find('**/' + path)
        tree.find('**/*shadow*').removeNode()
        return tree

    def load(self):
        self.setMinigameMusic(DistributedDodgeballGame.GameSong)
        self.setDescription(DistributedDodgeballGame.GameDesc)
        self.setWinnerPrize(200)
        self.setLoserPrize(0)
        self.createWorld()

        self.blueScoreLbl = self.getWinterDodgeballScoreText(VBase4(
            0, 0, 1, 1))
        self.blueScoreLbl.reparentTo(base.a2dTopLeft)
        self.blueScoreLbl['align'] = TextNode.ALeft
        self.blueScoreLbl.setText('Blue: 0')
        self.blueScoreLbl.setZ(-0.17)
        self.blueScoreLbl.setX(0.05)
        self.blueScoreLbl.hide()

        self.redScoreLbl = self.getWinterDodgeballScoreText(VBase4(1, 0, 0, 1))
        self.redScoreLbl.reparentTo(base.a2dTopLeft)
        self.redScoreLbl['align'] = TextNode.ALeft
        self.redScoreLbl.setText('Red: 0')
        self.redScoreLbl.setZ(-0.35)
        self.redScoreLbl.setX(0.05)
        self.redScoreLbl.hide()

        trans = DistributedDodgeballGame.InitCamTrans
        camera.setPos(trans[0])
        camera.setHpr(trans[1])

        DistributedToonFPSGame.load(self)

    def createWorld(self):
        self.deleteWorld()

        self.arena = loader.loadModel(
            "phase_4/models/minigames/dodgeball_arena.egg")
        self.arena.reparentTo(render)
        self.arena.setScale(0.75)
        self.arena.find('**/team_divider').setBin('ground', 18)
        self.arena.find('**/floor').setBin('ground', 18)
        self.arena.find('**/team_divider_coll').setCollideMask(
            CIGlobals.FloorBitmask)

        for data in DistributedDodgeballGame.TreeData:
            code = data[0]
            pos = data[1]
            tree = self.__getSnowTree(code)
            tree.reparentTo(self.arena)
            tree.setPos(pos)
            self.trees.append(tree)

        for i in xrange(len(DistributedDodgeballGame.SnowballData)):
            snowdata = DistributedDodgeballGame.SnowballData[i]
            snowball = Snowball(self, i)
            snowball.load()
            snowball.reparentTo(render)
            snowball.setPos(snowdata)
            self.snowballs.append(snowball)

        self.olc = ZoneUtil.getOutdoorLightingConfig(ZoneUtil.TheBrrrgh)
        self.olc.setupAndApply()

    def throw(self, snowballIndex, p):
        snowball = self.snowballs[snowballIndex]
        snowball.throw(p)

    def snowballPickup(self, snowballIndex, pickerUpperAvId):
        remoteAv = self.getRemoteAvatar(pickerUpperAvId)
        if remoteAv:
            snowball = self.snowballs[snowballIndex]
            snowball.pickup(remoteAv)

    def deleteWorld(self):
        if self.redScoreLbl:
            self.redScoreLbl.destroy()
            self.redScoreLbl = None
        if self.blueScoreLbl:
            self.blueScoreLbl.destroy()
            self.blueScoreLbl = None
        for snowball in self.snowballs:
            snowball.removeNode()
        self.snowballs = []
        for tree in self.trees:
            tree.removeNode()
        self.trees = []
        if self.olc:
            self.olc.cleanup()
            self.olc = None
        if self.arena:
            self.arena.removeNode()
            self.arena = None
        render.clearFog()

    def enterPlay(self):
        self.createTimer()
        self.redScoreLbl.show()
        self.blueScoreLbl.show()
        self.firstPerson.camFSM.request('unfrozen')

        # Stash the other team's barrier.
        if not self.barrierIsStashed:
            self.arena.find('**/' + self.Team2OtherBarrier[self.team]).stash()
            self.barrierIsStashed = True

    def exitPlay(self):
        self.firstPerson.crosshair.destroy()
        self.firstPerson.crosshair = None
        self.firstPerson.camFSM.request('off')
        DistributedToonFPSGame.exitPlay(self)

    def enterCountdown(self):
        base.transitions.noTransitions()
        self.firstPerson.start()
        self.firstPerson.disableMouse()

        self.setRound(self.getRound() + 1)

        self.infoText.setText(DistributedDodgeballGame.GetSnowBalls)

        self.countdownText = getGameText()
        self.countdownIval = Parallel(Sequence(
            Func(self.countdownText.setText,
                 "Round {0}".format(self.getRound())),
            getRoundIval(self.countdownText),
            Func(self.countdownText.setText, "5"),
            getCountdownIval(self.countdownText),
            Func(self.countdownText.setText, "4"),
            getCountdownIval(self.countdownText),
            Func(self.countdownText.setText, "3"),
            getCountdownIval(self.countdownText),
            Func(self.countdownText.setText, "2"),
            getCountdownIval(self.countdownText),
            Func(self.countdownText.setText, "1"),
            getCountdownIval(self.countdownText)),
                                      getAlertPulse(self.infoText),
                                      name="COUNTDOWNIVAL")
        self.countdownIval.setDoneEvent(self.countdownIval.getName())
        self.acceptOnce(self.countdownIval.getDoneEvent(),
                        self.__handleCountdownDone)
        self.countdownIval.start()

    def __handleCountdownDone(self):
        self.fsm.request('play')

    def exitCountdown(self):
        if hasattr(self, 'countdownIval'):
            self.ignore(self.countdownIval.getDoneEvent())
            self.countdownIval.finish()
            del self.countdownIval
        if hasattr(self, 'countdownText'):
            self.countdownText.destroy()
            del self.countdownText

    def enterScrollBy(self):
        BLUE_START_POS = Point3(-20, 0, 4)
        BLUE_END_POS = Point3(20, 0, 4)
        BLUE_HPR = Vec3(0, 0, 0)

        RED_START_POS = Point3(20, 0, 4)
        RED_END_POS = Point3(-20, 0, 4)
        RED_HPR = Vec3(180, 0, 0)

        self.playMinigameMusic()

        self.scrollBySeq = Sequence(Func(camera.setHpr, BLUE_HPR),
                                    LerpPosInterval(camera,
                                                    duration=5.0,
                                                    pos=BLUE_END_POS,
                                                    startPos=BLUE_START_POS,
                                                    blendType='easeOut'),
                                    Func(base.transitions.fadeOut, 0.4),
                                    Wait(0.5),
                                    Func(base.transitions.fadeIn, 0.4),
                                    Func(camera.setHpr, RED_HPR),
                                    LerpPosInterval(camera,
                                                    duration=5.0,
                                                    pos=RED_END_POS,
                                                    startPos=RED_START_POS,
                                                    blendType='easeOut'),
                                    name="SCROLLBYSEQ")
        self.scrollBySeq.setDoneEvent(self.scrollBySeq.getName())
        self.acceptOnce(self.scrollBySeq.getDoneEvent(),
                        self.__handleScrollByDone)
        self.scrollBySeq.start()

    def __handleScrollByDone(self):
        self.fsm.request('countdown')

    def exitScrollBy(self):
        if self.scrollBySeq:
            self.ignore(self.scrollBySeq.getDoneEvent())
            self.scrollBySeq.finish()
            self.scrollBySeq = None

    def allPlayersReady(self):
        self.fsm.request('scrollBy')

    def chooseUrTeam(self):
        # The AI has told us it's time to choose our team.
        self.fsm.request('chooseTeam')

    def enterChooseTeam(self):
        self.makeSelectionGUI()

    def acceptedIntoTeam(self, spawnPoint):
        TeamMinigame.acceptedIntoTeam(self)

        self.sendUpdate('readyToStart')
        self.fsm.request('waitForOthers')

        self.mySpawnPoint = spawnPoint

        pos, hpr = self.spawnPointsByTeam[self.team][spawnPoint]
        base.localAvatar.setPos(pos)
        base.localAvatar.setHpr(hpr)

    def exitChooseTeam(self):
        self.destroySelectionGUI()

    def announceGenerate(self):
        DistributedToonFPSGame.announceGenerate(self)
        base.camLens.setMinFov(CIGlobals.GunGameFOV / (4. / 3.))
        self.load()

    def disable(self):
        base.camLens.setMinFov(CIGlobals.DefaultCameraFov / (4. / 3.))
        self.fsm.requestFinalState()
        self.deleteWorld()
        self.trees = None
        self.snowballs = None
        self.spawnPointsByTeam = None
        if self.firstPerson:
            self.firstPerson.reallyEnd()
            self.firstPerson.cleanup()
            self.firstPerson = None
        self.scrollBySeq = None
        self.winnerMusic = None
        self.loserMusic = None
        self.danceSound = None
        self.infoText = None
        base.localAvatar.setWalkSpeedNormal()
        DistributedToonFPSGame.disable(self)
Beispiel #34
0
class Connect4:
    def __init__(self, p_base):

        self.base = p_base
        self.render = p_base.render

        # Keyboard inputs map
        self.keyMap = {"left": False, "right": False, "down": False, "drop": False}

        # Global parameters
        self.player = 1
        self.speed = 15
        self.movement_V = False
        self.movement_H = False
        self.axes_H = [-3.6, -2.4, -1.2, 0, 1.2, 2.4, 3.6]
        self.axes_V = [0.25, -1.0, -2.25, -3.5, -4.75, -6]
        self.column = 3
        self.line = 5
        self.quit_game_bool = False
        self.round = 0

        # Initialization of audio coin
        self.audio_coin = self.base.loader.loadMusic("connect4/audio/coin.ogg")

        # Initialization of the table
        self.table = self.base.loader.loadModel("connect4/models/table")
        self.table.reparentTo(self.render)
        self.table.setScale(2, 2, 2)
        self.table.setHpr(90, 0, 0)
        self.table_anim_start = self.table.posInterval(3, Point3(0, 30, -8), startPos=Point3(0, 0, -8))
        self.table_anim_end = self.table.posInterval(3, Point3(0, 0, -8), startPos=Point3(0, 30, -8))

        # Initialization of the grid
        self.grid = self.base.loader.loadModel("connect4/models/grid")
        self.grid.reparentTo(self.render)
        self.grid.setColor(0.1, 0.2, 0.8, 1.0)
        self.grid.setHpr(90, 0, 0)
        self.grid.setScale(0.6, 0.6, 0.625)
        self.grid_anim_start = self.grid.posInterval(3, Point3(3.6, 30, -6), startPos=Point3(3.6, 30, 0))
        self.grid_anim_end = self.grid.posInterval(3, Point3(3.6, 30, 0), startPos=Point3(3.6, 30, -6))
        self.gridContent = np.zeros(6 * 7)

        # Initialization of the discs
        self.discs = []
        self.nb_discs = 44
        for i in range(0, self.nb_discs):
            disc = self.base.loader.loadModel("connect4/models/disc")
            disc.reparentTo(self.render)
            if i % 2 == 0:
                color_disc = Disc(i, disc, 1.0, 0.0, 0.0)
            else:
                color_disc = Disc(i, disc, 1.0, 1.0, 0.0)
            self.discs.append(color_disc)
        self.first_disc_anim = self.discs[self.round].disc.posInterval(3, Point3(0, 30, 1.5), startPos=Point3(0, 0, 8))

        # Initialization of start sequences
        self.init_sequence = Parallel(self.table_anim_start, self.grid_anim_start, self.first_disc_anim, name="p_start")
        self.init_sequence.start()

        # Initialization of keys
        self.base.accept("arrow_left", self.updateKeyMap, ["left", True])
        self.base.accept("arrow_left-up", self.updateKeyMap, ["left", False])
        self.base.accept("arrow_right", self.updateKeyMap, ["right", True])
        self.base.accept("arrow_right-up", self.updateKeyMap, ["right", False])
        self.base.accept("arrow_down", self.updateKeyMap, ["down", True])
        self.base.accept("arrow_down-up", self.updateKeyMap, ["down", False])
        self.base.accept("space", self.updateKeyMap, ["drop", True])
        self.base.accept("space-up", self.updateKeyMap, ["drop", False])

        # Initialization of winning cases
        self.results = []
        with open("connect4/csv/cases.csv") as csvfile:
            reader = csv.reader(csvfile, quoting=csv.QUOTE_NONNUMERIC)
            for row in reader:
                self.results.append(row)

        # Initialization of fonts
        self.font = self.base.loader.loadFont("connect4/font/Roboto-Medium.ttf")
        self.font.setPixelsPerUnit(60)

        # Initialization of the victory text
        self.text_victory = OnscreenText(text='', pos=(1.4, -0.8), scale=0.1)
        self.text_victory.setFg((0, 0, 0, 1))
        self.text_victory.setBg((1, 1, 1, 0))
        self.text_victory.setShadow((0.5, 0.5, 0.5, 1))

        # Initialization of buttons
        self.load_game_button = DirectButton(text="Load", pos=(-1.5, 0, 0.75), frameSize=(-3, 3, -0.5, 1), scale=.1,
                                             text_scale=0.9, command=self.load_game)

        self.new_game_button = DirectButton(text="New game", pos=(-1.5, 0, 0.9), frameSize=(-3, 3, -0.5, 1), scale=.1,
                                            text_scale=0.9, command=self.new_game)
        self.button_changed = False

        self.save_game_button = DirectButton(text="Save", pos=(-1.5, 0, 0.6), frameSize=(-3, 3, -0.5, 1), scale=.1,
                                             text_scale=0.9, command=self.save_game)

        self.quit_game_button = DirectButton(text="Quit", pos=(-1.5, 0, -0.95), frameSize=(-3, 3, -0.5, 1), scale=.1,
                                             text_scale=0.9, command=self.quit_game)

        self.hand_control_button = DirectButton(text="Activer le contrôle \n visuel", pos=(1.5, 0, -0.9), frameSize=(-3, 3, -1, 0.8), scale=.1,
                                             text_scale=0.5, command=self.activate_hand_control)
        # Mode
        # (mode 0 : default mode)
        # (mode 1 : hand control mode)
        self.mode = 0

        self.disc_caught = -1
        self.disc_dropped = -1

        # Initialization of the right hand
        self.right_hand = self.base.loader.loadModel("connect4/models/hand")
        self.right_hand.reparentTo(self.render)
        self.right_hand.setPos(3.6, -20, 0)
        self.right_hand.setColor(0.88, 0.67, 0.41, 1.0)
        self.right_hand.setHpr(90, -90, 0)
        self.right_hand.setScale(0.2, 0.2, 0.2)

        # self.left_hand = self.base.loader.loadModel("connect4/models/hand")
        # self.left_hand.reparentTo(self.render)
        # self.left_hand.setPos(-3.6, -20, 0)
        # self.left_hand.setColor(0.88, 0.67, 0.41, 1.0)
        # self.left_hand.setHpr(90, -90, 180)
        # self.left_hand.setScale(0.2, 0.2, 0.2)

    def activate_hand_control(self):
        if self.mode == 0:
            self.mode = 1
            self.hand_control_button.setText("Désactiver le contrôle \n visuel")
            self.right_hand.setPos(3.6, 30, 0)
            self.new_game()
            # self.left_hand.setPos(-3.6, 20, 0)
        else:
            self.mode = 0
            self.hand_control_button.setText("Activer le contrôle \n visuel")
            self.right_hand.setPos(3.6, -20, 0)
            self.new_game()
            # self.left_hand.setPos(-3.6, -20, 0)

    def updateKeyMap(self, key, state):
        """ Function that updates the input map """
        self.keyMap[key] = state

    def load_game(self):
        """ Load game functions used for load game button """
        print("Connect 4 > Load a game")
        f1 = open("connect4/safeguard/safeguard.txt", "r")
        last_line = f1.readlines()[-1]
        f1.close()
        last_line_list = last_line.split(',')
        k = 0
        p = 1
        round = 0
        for i in range(0, 42):
            col = i % 7
            line = i // 7
            if last_line_list[i] == '1':
                self.discs[k].disc.setPos(self.axes_H[col], 30, self.axes_V[line])
                k += 2
                round += 1
            elif last_line_list[i] == '2':
                self.discs[p].disc.setPos(self.axes_H[col], 30, self.axes_V[line])
                p += 2
                round += 1
        self.round = round
        self.discs[self.round].disc.setPos(0, 30, 1.5)
        self.gridContent = [int(j) for j in last_line_list]

    def new_game(self):
        """ New game functions used for new game button """
        print("Connect 4 > New game")

        self.gridContent = np.zeros(6 * 7)
        self.round = 0
        self.text_victory.setText('')

        if self.mode == 0:
            for i in range(0, 42):
                self.discs[i].disc.setPos(100, 100, 100)
            self.discs[self.round].disc.setPos(0, 30, 1.5)

        elif self.mode == 1:
            pos_xr = [-8.4, -7.2, -6.0,
                      -12, -10.8, -9.6, -8.4, -7.2, -6.0,
                      -12, -10.8, -9.6, -8.4, -7.2, -6.0,
                      -12, -10.8, -9.6, -8.4, -7.2, -6.0]
            pos_xy = [8.4, 7.2, 6.0,
                      12, 10.8, 9.6, 8.4, 7.2, 6.0,
                      12, 10.8, 9.6, 8.4, 7.2, 6.0,
                      12, 10.8, 9.6, 8.4, 7.2, 6.0]
            pos_z = [-6.4, -6.4, -6.4,
                     -5.2, -5.2, -5.2, -5.2, -5.2, -5.2,
                     -4.0, -4.0, -4.0, -4.0, -4.0, -4.0,
                     -2.8, -2.8, -2.8, -2.8, -2.8, -2.8]
            n = 0
            p = 0
            for i in range(0, 42):
                if i % 2 == 0:
                    self.discs[i].disc.setPos(pos_xr[n], 30, pos_z[n])
                    n += 1
                else:
                    self.discs[i].disc.setPos(pos_xy[p], 30, pos_z[p])
                    p += 1

    def save_game(self):
        """ Save game functions used for save game button """
        print("Connect 4 > Save the game")
        grid = [int(j) for j in self.gridContent]
        grid_content_str = ','.join([str(elem) for elem in grid])
        f = open("connect4/safeguard/safeguard.txt", "a")
        f.write(grid_content_str + "\n")
        f.close()

    def quit_game(self):
        """ Quit game functions used for quit game button """
        print("Connect 4 > Quit the game")
        for i in range(0, self.nb_discs):
            self.discs[i].disc.removeNode()
        self.grid.removeNode()
        self.table.removeNode()
        self.new_game_button.destroy()
        self.save_game_button.destroy()
        self.load_game_button.destroy()
        self.quit_game_button.destroy()
        self.quit_game_bool = True

    def check_victory(self):
        """
        Function that check if there is a victory case
        @return 1 if red wins and 2 if yellow wins
        """

        if self.mode == 0:
            num_disc = self.round
        elif self.mode == 1:
            num_disc = self.disc_dropped

        if num_disc % 2 == 0:
            disc_type = 1
        else:
            disc_type = 2
        self.gridContent[7 * self.line + self.column] = disc_type

        for i in range(69):
            for j in range(4):
                if self.results[i][j] == 7 * self.line + self.column:
                    if (self.gridContent[int(self.results[i][0])] == disc_type) and (
                            self.gridContent[int(self.results[i][1])] == disc_type) and (
                            self.gridContent[int(self.results[i][2])] == disc_type) and (
                            self.gridContent[int(self.results[i][3])] == disc_type):
                        return disc_type
        return 0

    def mainloop(self):
        """ Main loop of the connect 4 game """

        # If quit_button is clicked
        if self.quit_game_bool:
            return 0

        # Get the clock
        dt = globalClock.getDt()

        # Change the button "New game" to "Restart" for the first round
        if self.round == 1 and self.button_changed == False:
            self.new_game_button["text"] = "Restart"
            self.button_changed = True
            print("Connect 4 > Main loop")

        # Default mode
        if self.mode == 0:

            # Get the position of the current disc
            pos = self.discs[self.round].disc.getPos()

            # Left click
            if self.keyMap["left"] and self.column != 0 and not self.movement_V:
                self.keyMap["left"] = False
                self.column -= 1
                self.movement_H = True

            # Right click
            if self.keyMap["right"] and self.column != 6 and not self.movement_V:
                self.keyMap["right"] = False
                self.column += 1
                self.movement_H = True

            # down clic
            if self.keyMap["down"] and self.gridContent[self.column] == 0 and not self.movement_V:
                # To have only one click
                self.keyMap["down"] = False

                # Find the final disc line
                line_fixed = 0
                self.line = 5
                while line_fixed == 0 and self.line >= 0:
                    if self.gridContent[7 * self.line + self.column] != 0:
                        self.line -= 1
                    else:
                        line_fixed = 1
                self.movement_V = True

                # check if there is a victory or not
                victory = self.check_victory()
                if victory == 1:
                    self.text_victory.setText('Red wins')
                if victory == 2:
                    self.text_victory.setText('Yellow wins')

            # Progressive vertical movement
            if self.movement_V and pos.z >= self.axes_V[self.line]:
                pos.z -= self.speed * dt
                self.discs[self.round].disc.setPos(pos)

            # Set the disc position / Prepare next disc
            if self.movement_V and pos.z <= self.axes_V[self.line]:
                pos.z = self.axes_V[self.line]
                self.discs[self.round].disc.setPos(pos)
                self.audio_coin.play()
                self.movement_V = False
                self.line = 0
                self.column = 3
                self.round += 1
                if self.round < 42 and self.mode == 0:
                    self.discs[self.round].disc.setPos(0, 30, 1.5)

            # Horizontal movement
            if self.mode == 0 and self.movement_H:
                pos.x = self.axes_H[self.column]
                self.discs[self.round].disc.setPos(pos)
                self.movement_H = False

        # Handplay mode
        if self.mode == 1:

            # Detect hand position
            if cap.isOpened():
                success, image = cap.read()

                image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)

                image.flags.writeable = False
                results = hands.process(image)

                # Draw the hand annotations on the image.
                image.flags.writeable = True
                image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

                # If a hand is detected
                if results.multi_hand_landmarks:
                    for hand_landmarks in results.multi_hand_landmarks:
                        for idx, landmark in enumerate(hand_landmarks.landmark):
                            if idx == 9:
                                x = 24 * landmark.x - 12
                                z = - 14 * landmark.y + 7
                                self.right_hand.setPos(x, 30, z)

                # If a is caught
                if self.disc_caught != -1:
                    self.discs[self.disc_caught].disc.setPos(self.right_hand.getPos().x - 0.5, 30, self.right_hand.getPos().z + 0.5)
                else:
                    for i in range(0, 42):
                        if (abs(self.right_hand.getPos().x - 0.5 - self.discs[i].disc.getPos().x) < 0.5) \
                                and abs(self.right_hand.getPos().z - self.discs[i].disc.getPos().z) < 0.5:
                            self.disc_caught = self.discs[i].id
                            print("Connect 4 > Disc n°", self.disc_caught, " is caught")
                            self.discs[self.disc_caught].disc.setPos(x - 0.5, 30, z + 0.5)

                # If space touch is pressed
                if self.keyMap["drop"]:
                    print("Connect 4 > Disc n°", self.disc_caught, " is dropped")
                    self.keyMap["drop"] = False
                    pos_x = self.discs[self.disc_caught].disc.getPos().x
                    min = 10
                    for i in range(len(self.axes_H)):
                        if abs(self.axes_H[i] - pos_x) < min:
                            self.column = i
                            min = abs(self.axes_H[i] - pos_x)

                    # Find the final disc line
                    line_fixed = 0
                    self.line = 5
                    while line_fixed == 0 and self.line >= 0:
                        if self.gridContent[7 * self.line + self.column] != 0:
                            self.line -= 1
                        else:
                            line_fixed = 1
                    self.movement_V = True

                    self.discs[self.disc_caught].disc.setPos(self.axes_H[self.column], 30, self.axes_V[0])
                    self.disc_dropped = self.disc_caught
                    self.disc_caught = -1

                    # check if there is a victory or not
                    victory = self.check_victory()
                    if victory == 1:
                        self.text_victory.setText('Red wins')
                    if victory == 2:
                        self.text_victory.setText('Yellow wins')

                # Progressive vertical movement
                pos = self.discs[self.disc_dropped].disc.getPos()
                if self.movement_V and pos.z >= self.axes_V[self.line]:
                    pos.z -= self.speed * dt
                    self.discs[self.disc_dropped].disc.setPos(pos)

                # Set the disc position
                if self.movement_V and pos.z <= self.axes_V[self.line]:
                    pos.z = self.axes_V[self.line]
                    self.discs[self.disc_dropped].disc.setPos(pos)
                    self.audio_coin.play()
                    self.movement_V = False
                    self.line = 0
                    self.column = 3
                    self.round += 1







        return 1
class PairingGameCard(PlayingCardNodePath):
    DoIntervalDefault = True
    FlipTime = 0.25
    UseDifferentCardColors = True
    CardColors = [(0.933594, 0.265625, 0.28125, 1.0),
     (0.550781, 0.824219, 0.324219, 1.0),
     (0.347656, 0.820312, 0.953125, 1.0),
     (0.460938, 0.378906, 0.824219, 1.0),
     (0.710938, 0.234375, 0.4375, 1.0),
     (0.285156, 0.328125, 0.726562, 1.0),
     (0.242188, 0.742188, 0.515625, 1.0),
     (0.96875, 0.691406, 0.699219, 1.0),
     (0.996094, 0.957031, 0.597656, 1.0),
     (0.992188, 0.480469, 0.167969, 1.0)]

    def __init__(self, value):
        style = PlayingCardGlobals.Styles[0]
        PlayingCardNodePath.__init__(self, style, value)
        self.enterCallback = None
        self.exitCallback = None
        return

    def load(self):
        oneCard = loader.loadModel('phase_4/models/minigames/garden_sign_memory')
        prop = self.attachNewNode('prop')
        PlayingCardGlobals.getImage(self.style, self.suit, self.rank).copyTo(prop)
        prop.setScale(7)
        oneCard.find('**/glow').removeNode()
        cs = oneCard.find('**/collision')
        for solidIndex in xrange(cs.node().getNumSolids()):
            cs.node().modifySolid(solidIndex).setTangible(False)

        cs.node().setName('cardCollision-%d' % self.value)
        sign = oneCard.find('**/sign1')
        if self.UseDifferentCardColors:
            index = self.rank % len(self.CardColors)
            color = self.CardColors[index]
            sign.setColorScale(*color)
        prop.setPos(0.0, 0.0, 0.08)
        prop.setP(-90)
        prop.reparentTo(oneCard)
        oneCard.reparentTo(self)
        cardBack = oneCard.find('**/sign2')
        cardBack.setColorScale(0.12, 0.35, 0.5, 1.0)
        cardModel = loader.loadModel('phase_3.5/models/gui/playingCard')
        logo = cardModel.find('**/logo')
        logo.reparentTo(self)
        logo.setScale(0.45)
        logo.setP(90)
        logo.setZ(0.025)
        logo.setX(-0.05)
        logo.setH(180)
        cardModel.removeNode()
        self.setR(0)
        self.setScale(2.5)
        self.flipIval = None
        self.turnUpSound = base.loader.loadSfx('phase_4/audio/sfx/MG_pairing_card_flip_face_up.ogg')
        self.turnDownSound = base.loader.loadSfx('phase_4/audio/sfx/MG_pairing_card_flip_face_down.ogg')
        return

    def unload(self):
        self.clearFlipIval()
        self.removeNode()
        del self.turnUpSound
        del self.turnDownSound

    def turnUp(self, doInterval = DoIntervalDefault):
        self.faceUp = 1
        if doInterval:
            self.clearFlipIval()
            self.flipIval = Parallel(LerpHprInterval(self, self.FlipTime, Vec3(0, 0, 0)), SoundInterval(self.turnUpSound, node=self, listenerNode=base.localAvatar, cutOff=240))
            self.flipIval.start()
        else:
            self.setR(0)

    def clearFlipIval(self):
        if self.flipIval:
            self.flipIval.finish()
            self.flipIval = None
        return

    def turnDown(self, doInterval = DoIntervalDefault):
        self.faceUp = 0
        if doInterval:
            self.clearFlipIval()
            self.flipIval = Parallel(LerpHprInterval(self, self.FlipTime, Vec3(0, 0, 180)), SoundInterval(self.turnDownSound, node=self, listenerNode=base.localAvatar, cutOff=240))
            self.flipIval.start()
        else:
            self.setR(180)
Beispiel #36
0
class HangUpAttack(Attack):
    notify = directNotify.newCategory('HangUpAttack')
    attack = 'hangup'

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.phone = None
        self.receiver = None
        self.collNP = None
        self.phoneSfx = None
        self.hangupSfx = None
        self.shootIval = None
        self.cord = None
        self.receiverOutCord = None
        self.phoneOutCord = None
        return

    def loadAttack(self):
        self.phone = loader.loadModel('phase_3.5/models/props/phone.bam')
        self.phone.setHpr(0, 0, 180)
        if self.suit.type == 'B':
            self.phone.setPos(0.7, 0.15, 0)
        elif self.suit.type == 'C':
            self.phone.setPos(0.25, 0, 0)
        self.receiver = loader.loadModel('phase_3.5/models/props/receiver.bam')
        self.receiver.reparentTo(self.phone)
        self.cord = Rope()
        self.cord.ropeNode.setUseVertexColor(1)
        self.cord.ropeNode.setUseVertexThickness(1)
        self.cord.setup(3, ({'node': self.phone,
          'point': (0.8, 0, 0.2),
          'color': (0, 0, 0, 1),
          'thickness': 1000}, {'node': self.phone,
          'point': (2, 0, 0),
          'color': (0, 0, 0, 1),
          'thickness': 1000}, {'node': self.receiver,
          'point': (1.1, 0.25, 0.5),
          'color': (0, 0, 0, 1),
          'thickness': 1000}), [])
        self.cord.setH(180)
        self.phoneSfx = base.audio3d.loadSfx('phase_3.5/audio/sfx/SA_hangup.mp3')
        base.audio3d.attachSoundToObject(self.phoneSfx, self.phone)
        self.hangupSfx = base.audio3d.loadSfx('phase_3.5/audio/sfx/SA_hangup_place_down.mp3')
        base.audio3d.attachSoundToObject(self.hangupSfx, self.phone)
        collSphere = CollisionSphere(0, 0, 0, 2)
        collSphere.setTangible(0)
        collNode = CollisionNode('phone_shootout')
        collNode.addSolid(collSphere)
        collNode.setCollideMask(CIGlobals.WallBitmask)
        self.collNP = self.phone.attachNewNode(collNode)

    def doAttack(self, ts = 0):
        self.loadAttack()
        if hasattr(self.suit, 'uniqueName'):
            name = self.suit.uniqueName('doHangupAttack')
        else:
            name = 'doHangupAttack'
        if self.suit.type == 'A':
            delay2playSound = 1.0
            delayAfterSoundToPlaceDownReceiver = 0.2
            delayAfterShootToIgnoreCollisions = 1.0
            delay2PickUpReceiver = 1.0
            receiverInHandPos = Point3(-0.5, 0.5, -1)
        elif self.suit.type == 'B':
            delay2playSound = 1.5
            delayAfterSoundToPlaceDownReceiver = 0.7
            delayAfterShootToIgnoreCollisions = 1.0
            delay2PickUpReceiver = 1.5
            receiverInHandPos = Point3(-0.3, 0.5, -0.8)
        elif self.suit.type == 'C':
            delay2playSound = 1.0
            delayAfterSoundToPlaceDownReceiver = 1.15
            delayAfterShootToIgnoreCollisions = 1.0
            delay2PickUpReceiver = 1.5
            receiverInHandPos = Point3(-0.3, 0.5, -0.8)
        self.suitTrack = Parallel(name=name)
        self.suitTrack.append(ActorInterval(self.suit, 'phone'))
        self.suitTrack.append(Sequence(Wait(delay2playSound), SoundInterval(self.phoneSfx, duration=2.1), Wait(delayAfterSoundToPlaceDownReceiver), Func(self.receiver.setPos, 0, 0, 0), Func(self.receiver.setH, 0.0), Func(self.receiver.reparentTo, self.phone), Func(self.acceptOnce, 'enter' + self.collNP.node().getName(), self.handleCollision), Func(self.shootOut), Parallel(SoundInterval(self.hangupSfx), Sequence(Wait(delayAfterShootToIgnoreCollisions), Func(self.ignore, 'enter' + self.collNP.node().getName())))))
        self.suitTrack.append(Sequence(Func(self.phone.reparentTo, self.suit.find('**/joint_Lhold')), Func(self.cord.reparentTo, render), Wait(delay2PickUpReceiver), Func(self.receiver.reparentTo, self.suit.find('**/joint_Rhold')), Func(self.receiver.setPos, receiverInHandPos), Func(self.receiver.setH, 270.0)))
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        self.suitTrack.delayDelete = DelayDelete.DelayDelete(self.suit, name)
        self.suitTrack.start(ts)

    def handleCollision(self, entry):
        if self.suit:
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)

    def shootOut(self):
        pathNode = NodePath('path')
        pathNode.reparentTo(self.suit)
        pathNode.setPos(0, 50, self.phone.getZ(self.suit))
        self.collNP.reparentTo(render)
        self.shootIval = LerpPosInterval(self.collNP, duration=1.0, pos=pathNode.getPos(render), startPos=self.phone.getPos(render))
        self.shootIval.start()
        pathNode.removeNode()
        del pathNode

    def cleanup(self):
        Attack.cleanup(self)
        if self.shootIval:
            self.shootIval.pause()
            self.shootIval = None
        if self.cord:
            self.cord.removeNode()
            self.cord = None
        if self.phone:
            self.phone.removeNode()
            self.phone = None
        if self.receiver:
            self.receiver.removeNode()
            self.receiver = None
        if self.collNP:
            self.collNP.node().clearSolids()
            self.collNP.removeNode()
            self.collNP = None
        if self.phoneSfx:
            self.phoneSfx.stop()
            self.phoneSfx = None
        return
class DistributedPartyGate(DistributedObject.DistributedObject):
    notify = DirectNotifyGlobal.directNotify.newCategory(
        'DistributedPartyGate')

    def __init__(self, cr):
        DistributedObject.DistributedObject.__init__(self, cr)
        self.publicPartyChooseGuiDoneEvent = 'doneChoosingPublicParty'
        self.publicPartyGui = PublicPartyGui(
            self.publicPartyChooseGuiDoneEvent)
        self.publicPartyGui.stash()
        self.loadClockSounds()
        self.hourSoundInterval = Sequence()
        self.accept('stoppedAsleep', self.handleSleep)

    def loadClockSounds(self):
        self.clockSounds = []
        for i in range(1, 13):
            if i < 10:
                si = '0%d' % i
            else:
                si = '%d' % i
            self.clockSounds.append(
                base.loadSfx('phase_4/audio/sfx/clock%s.mp3' % si))

    def generate(self):
        DistributedObject.DistributedObject.generate(self)
        loader = self.cr.playGame.hood.loader
        partyGate = loader.geom.find('**/partyGate_grp')
        if partyGate.isEmpty():
            self.notify.warning('Could not find partyGate_grp in loader.geom')
            return
        self.clockFlat = partyGate.find('**/clock_flat')
        collSphere = CollisionSphere(0, 0, 0, 6.9)
        collSphere.setTangible(1)
        self.partyGateSphere = CollisionNode('PartyGateSphere')
        self.partyGateSphere.addSolid(collSphere)
        self.partyGateCollNodePath = partyGate.find(
            '**/partyGate_stepsLocator').attachNewNode(self.partyGateSphere)
        self.__enableCollisions()
        self.toontownTimeGui = ServerTimeGui(partyGate,
                                             hourCallback=self.hourChange)
        self.toontownTimeGui.setPos(
            partyGate.find('**/clockText_locator').getPos() +
            Point3(0.0, 0.0, -0.2))
        self.toontownTimeGui.setHpr(
            partyGate.find('**/clockText_locator').getHpr())
        self.toontownTimeGui.setScale(12.0, 1.0, 26.0)
        self.toontownTimeGui.amLabel.setPos(-0.035, 0, -0.032)
        self.toontownTimeGui.amLabel.setScale(0.5)
        self.toontownTimeGui.updateTime()
        self.setupSignText()

    def setupSignText(self):
        loader = self.cr.playGame.hood.loader
        partyGate = loader.geom.find('**/partyGateSignGroup')
        if partyGate.isEmpty():
            self.notify.warning('Could not find partyGate_grp in loader.geom')
            return
        gateFont = ToontownGlobals.getMinnieFont()
        leftSign = partyGate.find('**/signTextL_locatorBack')
        signScale = 0.35
        wordWrap = 8
        leftText = DirectLabel.DirectLabel(parent=leftSign,
                                           pos=(0, 0.0, 0.0),
                                           relief=None,
                                           text=TTLocalizer.PartyGateLeftSign,
                                           text_align=TextNode.ACenter,
                                           text_font=gateFont,
                                           text_wordwrap=wordWrap,
                                           text_fg=Vec4(0.7, 0.3, 0.3, 1.0),
                                           scale=signScale)
        rightSign = partyGate.find('**/signTextR_locatorFront')
        rightText = DirectLabel.DirectLabel(
            parent=rightSign,
            pos=(0, 0.0, 0.0),
            relief=None,
            text=TTLocalizer.PartyGateRightSign,
            text_align=TextNode.ACenter,
            text_font=gateFont,
            text_wordwrap=wordWrap,
            text_fg=Vec4(0.7, 0.3, 0.3, 1.0),
            scale=signScale)
        return

    def announceGenerate(self):
        DistributedObject.DistributedObject.announceGenerate(self)
        if ToontownGlobals.dnaMap.has_key(self.zoneId):
            playground = ToontownGlobals.dnaMap[self.zoneId]
        else:
            playground = ToontownGlobals.dnaMap[2000]
        self.toontownTimeGui.hourLabel[
            'text_fg'] = PartyGlobals.PlayGroundToPartyClockColors[playground]
        self.toontownTimeGui.colonLabel[
            'text_fg'] = PartyGlobals.PlayGroundToPartyClockColors[playground]
        self.toontownTimeGui.minutesLabel[
            'text_fg'] = PartyGlobals.PlayGroundToPartyClockColors[playground]
        self.toontownTimeGui.amLabel[
            'text_fg'] = PartyGlobals.PlayGroundToPartyClockColors[playground]

    def disable(self):
        DistributedObject.DistributedObject.disable(self)
        self.__disableCollisions()
        self.toontownTimeGui.ival.finish()
        self.hourSoundInterval.finish()
        if self.publicPartyGui:
            self.publicPartyGui.stash()
            self.publicPartyGui.destroy()
            self.publicPartyGui = None
        return

    def delete(self):
        DistributedObject.DistributedObject.delete(self)
        self.toontownTimeGui.destroy()
        del self.toontownTimeGui
        self.hourSoundInterval.finish()
        del self.hourSoundInterval
        del self.clockFlat
        if self.publicPartyGui:
            self.publicPartyGui.destroy()
            del self.publicPartyGui
        self.partyGateCollNodePath.removeNode()
        del self.partyGateCollNodePath
        self.ignoreAll()

    def showMessage(self, message):
        self.messageDoneEvent = self.uniqueName('messageDoneEvent')
        self.acceptOnce(self.messageDoneEvent, self.__handleMessageDone)
        self.messageGui = TTDialog.TTGlobalDialog(
            doneEvent=self.messageDoneEvent,
            message=message,
            style=TTDialog.Acknowledge)

    def __handleMessageDone(self):
        self.ignore(self.messageDoneEvent)
        self.freeAvatar()
        self.messageGui.cleanup()
        self.messageGui = None
        return

    def __handleAskDone(self):
        DistributedPartyGate.notify.debug('__handleAskDone')
        self.ignore(self.publicPartyChooseGuiDoneEvent)
        doneStatus = self.publicPartyGui.doneStatus
        self.publicPartyGui.stash()
        if doneStatus is None:
            self.freeAvatar()
            return
        self.sendUpdate('partyChoiceRequest',
                        [base.localAvatar.doId, doneStatus[0], doneStatus[1]])
        return

    def partyRequestDenied(self, reason):
        DistributedPartyGate.notify.debug(
            'partyRequestDenied( reason=%s )' %
            PartyGlobals.PartyGateDenialReasons.getString(reason))
        if reason == PartyGlobals.PartyGateDenialReasons.Unavailable:
            self.showMessage(TTLocalizer.PartyGatePartyUnavailable)
        elif reason == PartyGlobals.PartyGateDenialReasons.Full:
            self.showMessage(TTLocalizer.PartyGatePartyFull)

    def setParty(self, partyInfoTuple):
        DistributedPartyGate.notify.debug('setParty')
        self.freeAvatar()
        if partyInfoTuple[0] == 0:
            DistributedPartyGate.notify.debug(
                'Public Party closed before toon could get to it.')
            return
        shardId, zoneId, numberOfGuests, hostName, activityIds, lane = partyInfoTuple
        if base.localAvatar.defaultShard == shardId:
            shardId = None
        base.cr.playGame.getPlace().requestLeave({
            'loader': 'safeZoneLoader',
            'where': 'party',
            'how': 'teleportIn',
            'hoodId': ToontownGlobals.PartyHood,
            'zoneId': zoneId,
            'shardId': shardId,
            'avId': -1
        })
        return

    def freeAvatar(self):
        base.localAvatar.posCamera(0, 0)
        base.cr.playGame.getPlace().setState('walk')

    def hourChange(self, currentHour):
        currentHour = currentHour % 12
        if currentHour == 0:
            currentHour = 12
        self.hourSoundInterval = Parallel()
        seq1 = Sequence()
        for i in range(currentHour):
            seq1.append(SoundInterval(self.clockSounds[i]))
            seq1.append(Wait(0.2))

        timeForEachDeformation = seq1.getDuration() / currentHour
        seq2 = Sequence()
        for i in range(currentHour):
            seq2.append(
                self.clockFlat.scaleInterval(timeForEachDeformation / 2.0,
                                             Vec3(0.9, 1.0, 1.2),
                                             blendType='easeInOut'))
            seq2.append(
                self.clockFlat.scaleInterval(timeForEachDeformation / 2.0,
                                             Vec3(1.2, 1.0, 0.9),
                                             blendType='easeInOut'))

        seq2.append(
            self.clockFlat.scaleInterval(timeForEachDeformation / 2.0,
                                         Vec3(1.0, 1.0, 1.0),
                                         blendType='easeInOut'))
        self.hourSoundInterval.append(seq1)
        self.hourSoundInterval.append(seq2)
        self.hourSoundInterval.start()

    def handleEnterGateSphere(self, collEntry):
        self.notify.debug('Entering steps Sphere....')
        base.cr.playGame.getPlace().fsm.request('stopped')
        self.sendUpdate('getPartyList', [base.localAvatar.doId])

    def listAllPublicParties(self, publicPartyInfo):
        self.notify.debug('listAllPublicParties : publicPartyInfo = %s' %
                          publicPartyInfo)
        self.acceptOnce(self.publicPartyChooseGuiDoneEvent,
                        self.__handleAskDone)
        self.publicPartyGui.refresh(publicPartyInfo)
        self.publicPartyGui.unstash()

    def __enableCollisions(self):
        self.accept('enterPartyGateSphere', self.handleEnterGateSphere)
        self.partyGateSphere.setCollideMask(OTPGlobals.WallBitmask)

    def __disableCollisions(self):
        self.ignore('enterPartyGateSphere')
        self.partyGateSphere.setCollideMask(BitMask32(0))

    def handleSleep(self):
        if hasattr(self, 'messageGui') and self.messageGui:
            self.__handleMessageDone()
Beispiel #38
0
class DistributedPartyGate(DistributedObject.DistributedObject):

    notify = DirectNotifyGlobal.directNotify.newCategory(
        "DistributedPartyGate")

    def __init__(self, cr):
        """__init__(cr)
        """
        DistributedObject.DistributedObject.__init__(self, cr)
        self.publicPartyChooseGuiDoneEvent = "doneChoosingPublicParty"
        self.publicPartyGui = PublicPartyGui(
            self.publicPartyChooseGuiDoneEvent)
        self.publicPartyGui.stash()
        self.loadClockSounds()
        self.hourSoundInterval = Sequence()
        self.accept('stoppedAsleep', self.handleSleep)

    def loadClockSounds(self):
        self.clockSounds = []
        for i in range(1, 13):
            if i < 10:
                si = "0%d" % i
            else:
                si = "%d" % i
            self.clockSounds.append(
                base.loader.loadSfx("phase_4/audio/sfx/clock%s.mp3" % si))

    def generate(self):
        """generate(self)
        This method is called when the DistributedObject is reintroduced
        to the world, either for the first time or from the cache.
        """
        DistributedObject.DistributedObject.generate(self)

        loader = self.cr.playGame.hood.loader
        partyGate = loader.geom.find('**/partyGate_grp')
        if partyGate.isEmpty():
            self.notify.warning('Could not find partyGate_grp in loader.geom')
            return
        self.clockFlat = partyGate.find("**/clock_flat")
        collSphere = CollisionSphere(0, 0, 0, 6.9)
        collSphere.setTangible(1)
        self.partyGateSphere = CollisionNode("PartyGateSphere")
        self.partyGateSphere.addSolid(collSphere)
        self.partyGateCollNodePath = partyGate.find(
            "**/partyGate_stepsLocator").attachNewNode(self.partyGateSphere)
        self.__enableCollisions()
        #        self.tunnelOrigin = NodePath("PartyGateTunnelOrigin")
        #        self.tunnelOrigin.reparentTo(partyGate)
        #        self.tunnelOrigin.setPos(partyGate.find("**/clockText_locator").getPos() + Point3(0.0, 0.0, -12.0))

        self.toontownTimeGui = ServerTimeGui(partyGate,
                                             hourCallback=self.hourChange)
        self.toontownTimeGui.setPos(
            partyGate.find("**/clockText_locator").getPos() +
            Point3(0.0, 0.0, -0.2))
        self.toontownTimeGui.setHpr(
            partyGate.find("**/clockText_locator").getHpr())
        self.toontownTimeGui.setScale(12.0, 1.0, 26.0)
        self.toontownTimeGui.amLabel.setPos(-0.035, 0, -0.032)
        self.toontownTimeGui.amLabel.setScale(0.5)
        self.toontownTimeGui.updateTime()
        self.setupSignText()

    def setupSignText(self):
        """Attach text to the left and right signs"""
        loader = self.cr.playGame.hood.loader
        partyGate = loader.geom.find('**/partyGateSignGroup')
        if partyGate.isEmpty():
            self.notify.warning('Could not find partyGate_grp in loader.geom')
            return
        gateFont = ToontownGlobals.getMinnieFont()
        leftSign = partyGate.find("**/signTextL_locatorBack")
        signScale = 0.35
        wordWrap = 8
        leftText = DirectLabel.DirectLabel(
            parent=leftSign,
            pos=(0, 0.0, 0.0),
            relief=None,
            text=TTLocalizer.PartyGateLeftSign,
            text_align=TextNode.ACenter,
            text_font=gateFont,
            text_wordwrap=wordWrap,
            text_fg=Vec4(0.7, 0.3, 0.3, 1.0),
            scale=signScale,
        )
        rightSign = partyGate.find("**/signTextR_locatorFront")
        rightText = DirectLabel.DirectLabel(
            parent=rightSign,
            pos=(0, 0.0, 0.0),
            relief=None,
            text=TTLocalizer.PartyGateRightSign,
            text_align=TextNode.ACenter,
            text_font=gateFont,
            text_wordwrap=wordWrap,
            text_fg=Vec4(0.7, 0.3, 0.3, 1.0),
            scale=signScale,
        )

    def announceGenerate(self):
        DistributedObject.DistributedObject.announceGenerate(self)
        if self.zoneId in ToontownGlobals.dnaMap:
            playground = ToontownGlobals.dnaMap[self.zoneId]
        else:
            playground = ToontownGlobals.dnaMap[2000]
        self.toontownTimeGui.hourLabel[
            "text_fg"] = PartyGlobals.PlayGroundToPartyClockColors[playground]
        self.toontownTimeGui.colonLabel[
            "text_fg"] = PartyGlobals.PlayGroundToPartyClockColors[playground]
        self.toontownTimeGui.minutesLabel[
            "text_fg"] = PartyGlobals.PlayGroundToPartyClockColors[playground]
        self.toontownTimeGui.amLabel[
            "text_fg"] = PartyGlobals.PlayGroundToPartyClockColors[playground]

    def disable(self):
        DistributedObject.DistributedObject.disable(self)
        self.__disableCollisions()
        self.toontownTimeGui.ival.finish()
        self.hourSoundInterval.finish()
        if self.publicPartyGui:
            self.publicPartyGui.stash()
            self.publicPartyGui.destroy()
            self.publicPartyGui = None

    def delete(self):
        DistributedObject.DistributedObject.delete(self)
        self.toontownTimeGui.destroy()
        del self.toontownTimeGui
        self.hourSoundInterval.finish()
        del self.hourSoundInterval
        del self.clockFlat
        if self.publicPartyGui:
            self.publicPartyGui.destroy()
            del self.publicPartyGui
        self.partyGateCollNodePath.removeNode()
        del self.partyGateCollNodePath
        self.ignoreAll()

    def showMessage(self, message):
        self.messageDoneEvent = self.uniqueName("messageDoneEvent")
        self.acceptOnce(self.messageDoneEvent, self.__handleMessageDone)
        self.messageGui = TTDialog.TTGlobalDialog(
            doneEvent=self.messageDoneEvent,
            message=message,
            style=TTDialog.Acknowledge,
        )

    def __handleMessageDone(self):
        self.ignore(self.messageDoneEvent)
        self.freeAvatar()
        self.messageGui.cleanup()
        self.messageGui = None

    def __handleAskDone(self):
        DistributedPartyGate.notify.debug("__handleAskDone")
        self.ignore(self.publicPartyChooseGuiDoneEvent)
        doneStatus = self.publicPartyGui.doneStatus
        self.publicPartyGui.stash()
        if doneStatus is None:
            # They don't want to party... just let them walk away from the hat
            self.freeAvatar()
            return
        self.sendUpdate("partyChoiceRequest",
                        [base.localAvatar.doId, doneStatus[0], doneStatus[1]])

    def partyRequestDenied(self, reason):
        """
        Called by the AI when the player's request to join a public party was denied.
        """
        DistributedPartyGate.notify.debug(
            "partyRequestDenied( reason=%s )" %
            PartyGlobals.PartyGateDenialReasons.getString(reason))
        # let the local toon know that they were denied
        # TODO-parties: tell player through gui
        if reason == PartyGlobals.PartyGateDenialReasons.Unavailable:
            self.showMessage(TTLocalizer.PartyGatePartyUnavailable)
        elif reason == PartyGlobals.PartyGateDenialReasons.Full:
            self.showMessage(TTLocalizer.PartyGatePartyFull)

    def setParty(self, partyInfoTuple):
        """
        Gets called by the AI server with the approved partyId.
        """
        DistributedPartyGate.notify.debug("setParty")

        self.freeAvatar()
        if partyInfoTuple[0] == 0:
            DistributedPartyGate.notify.debug(
                "Public Party closed before toon could get to it.")
            return

        # We now need to enter the party with the given partyId, that is, move
        # our toon toward the hat entrance and do the appropriate state transition
        shardId, zoneId, numberOfGuests, hostName, activityIds, lane = partyInfoTuple
        if base.localAvatar.defaultShard == shardId:
            shardId = None
        base.cr.playGame.getPlace().requestLeave({
            "loader": "safeZoneLoader",
            "where": "party",
            "how": "teleportIn",
            "hoodId": ToontownGlobals.PartyHood,
            "zoneId": zoneId,
            "shardId": shardId,
            "avId": -1,
            #            "partyHat" : True,
            #            "tunnelOrigin" : self.tunnelOrigin,
        })

    def freeAvatar(self):
        base.localAvatar.posCamera(0, 0)
        base.cr.playGame.getPlace().setState("walk")

    def hourChange(self, currentHour):
        currentHour = currentHour % 12
        if currentHour == 0:
            currentHour = 12
        self.hourSoundInterval = Parallel()
        # Make a sequence with all the clock sounds
        seq1 = Sequence()
        for i in range(currentHour):
            seq1.append(SoundInterval(self.clockSounds[i]))
            seq1.append(Wait(0.2))
        # Now make a sequence that will deform the clock face
        timeForEachDeformation = seq1.getDuration() / currentHour
        seq2 = Sequence()
        for i in range(currentHour):
            seq2.append(
                self.clockFlat.scaleInterval(timeForEachDeformation / 2.0,
                                             Vec3(0.9, 1.0, 1.2),
                                             blendType='easeInOut'))
            seq2.append(
                self.clockFlat.scaleInterval(timeForEachDeformation / 2.0,
                                             Vec3(1.2, 1.0, 0.9),
                                             blendType='easeInOut'))
        seq2.append(
            self.clockFlat.scaleInterval(timeForEachDeformation / 2.0,
                                         Vec3(1.0, 1.0, 1.0),
                                         blendType='easeInOut'))
        # Now parallel the two together
        self.hourSoundInterval.append(seq1)
        self.hourSoundInterval.append(seq2)
        self.hourSoundInterval.start()

    def handleEnterGateSphere(self, collEntry):
        self.notify.debug("Entering steps Sphere....")
        # Freeze the toon, don't let him walk away...
        base.cr.playGame.getPlace().fsm.request('stopped')
        self.sendUpdate("getPartyList", [base.localAvatar.doId])

    def listAllPublicParties(self, publicPartyInfo):
        """
        Called from DistributedPartyGateAI with a tuple of all the public party
        information as told to it by the DistributedPartyManagerAI in order of
        newest party to oldest party.
        ( shardId, zoneId, numberOfGuests, hostName, activityIds, minLeft )
        """
        self.notify.debug("listAllPublicParties : publicPartyInfo = %s" %
                          publicPartyInfo)
        self.acceptOnce(self.publicPartyChooseGuiDoneEvent,
                        self.__handleAskDone)
        self.publicPartyGui.refresh(publicPartyInfo)
        self.publicPartyGui.unstash()

    def __enableCollisions(self):
        # start listening for toons to enter.
        self.accept('enterPartyGateSphere', self.handleEnterGateSphere)
        self.partyGateSphere.setCollideMask(OTPGlobals.WallBitmask)

    def __disableCollisions(self):
        # stop listening for toons.
        self.ignore('enterPartyGateSphere')
        self.partyGateSphere.setCollideMask(BitMask32(0))

    def handleSleep(self):
        if hasattr(self, 'messageGui'):
            self.__handleMessageDone()
Beispiel #39
0
class DistributedPairingGame(DistributedMinigame):
    TOON_SPEED = 11
    MAX_FRAME_MOVE = 1
    MAX_FACE_UP_CARDS = 2
    notify = directNotify.newCategory('DistributedPairingGame')
    bonusGlowTime = 0.5
    EndGameTaskName = 'endPairingGame'
    xCardInc = 4
    cardsPerRow = 8
    cardsPerCol = 5

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedPairingGame', [
            State.State('off', self.enterOff, self.exitOff, ['play']),
            State.State('play', self.enterPlay, self.exitPlay, ['cleanup']),
            State.State('cleanup', self.enterCleanup, self.exitCleanup, [])
        ], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.cameraTopView = (17.6, 6.18756, 43.9956, 0, -89, 0)
        self.cameraThreeQuarterView = (14.0, -8.93352, 33.4497, 0, -62.89, 0)
        self.deckSeed = 0
        self.faceUpList = []
        self.localFaceUpList = []
        self.inList = []
        self.inactiveList = []
        self.points = 0
        self.flips = 0
        self.matches = 0
        self.yCardInc = 4
        self.startingPositions = [
            (0, 0, 0, -45),
            ((self.cardsPerRow - 1) * self.xCardInc,
             (self.cardsPerCol - 1) * self.yCardInc, 0, 135),
            ((self.cardsPerRow - 1) * self.xCardInc, 0, 0, 45),
            (0, (self.cardsPerCol - 1) * self.yCardInc, 0, -135)
        ]
        self.stageMin = Point2(0, 0)
        self.stageMax = Point2((self.cardsPerRow - 1) * self.xCardInc,
                               (self.cardsPerCol - 1) * self.yCardInc)
        self.gameDuration = PairingGameGlobals.EasiestGameDuration

    def moveCameraToTop(self):
        camera.reparentTo(render)
        p = self.cameraThreeQuarterView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])

    def getTitle(self):
        return TTLocalizer.PairingGameTitle

    def getInstructions(self):
        if self.numPlayers > 1:
            return TTLocalizer.PairingGameInstructionsMulti
        else:
            return TTLocalizer.PairingGameInstructions

    def getMaxDuration(self):
        return 0

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.gameDuration = PairingGameGlobals.calcGameDuration(
            self.getDifficulty())
        self.gameBoard = loader.loadModel(
            'phase_4/models/minigames/memory_room')
        self.gameBoard.setPosHpr(0.5, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        self.deck = PairingGameGlobals.createDeck(self.deckSeed,
                                                  self.numPlayers)
        self.notify.debug('%s' % self.deck.cards)
        testCard = self.getDeckOrderIndex(self.cardsPerCol - 1, 0)
        if not testCard > -1:
            self.yCardInc *= 1.25
        self.cards = []
        for index in xrange(len(self.deck.cards)):
            cardValue = self.deck.cards[index]
            oneCard = PairingGameCard.PairingGameCard(cardValue)
            oneCard.load()
            xPos, yPos = self.getCardPos(index)
            oneCard.setPos(xPos, yPos, 0)
            oneCard.reparentTo(render)
            self.notify.debug('%s' % oneCard.getPos())
            self.notify.debug('suit %s rank %s value %s' %
                              (oneCard.suit, oneCard.rank, oneCard.value))
            self.accept('entercardCollision-%d' % oneCard.value,
                        self.enterCard)
            self.accept('exitcardCollision-%d' % oneCard.value, self.exitCard)
            oneCard.turnDown(doInterval=False)
            self.cards.append(oneCard)

        self.bonusTraversal = range(len(self.cards))
        self.bonusGlow = render.attachNewNode('bonusGlow')
        sign = loader.loadModel('phase_4/models/minigames/garden_sign_memory')
        sign.find('**/sign1').removeNode()
        sign.find('**/sign2').removeNode()
        sign.find('**/collision').removeNode()
        sign.setPos(0, 0, 0.05)
        sign.reparentTo(self.bonusGlow)
        self.bonusGlow.setScale(2.5)
        self.pointsFrame = DirectFrame(relief=None,
                                       geom=DGG.getDefaultDialogGeom(),
                                       geom_color=GlobalDialogColor,
                                       geom_scale=(4, 1, 1),
                                       pos=(-0.33, 0, 0.9),
                                       scale=0.1,
                                       text=TTLocalizer.PairingGamePoints,
                                       text_align=TextNode.ALeft,
                                       text_scale=TTLocalizer.DPGpointsFrame,
                                       text_pos=(-1.94, -0.1, 0.0))
        self.pointsLabel = DirectLabel(parent=self.pointsFrame,
                                       relief=None,
                                       text='0',
                                       text_fg=VBase4(0, 0.5, 0, 1),
                                       text_align=TextNode.ARight,
                                       text_scale=0.7,
                                       pos=(1.82, 0, -0.15))
        self.flipsFrame = DirectFrame(relief=None,
                                      geom=DGG.getDefaultDialogGeom(),
                                      geom_color=GlobalDialogColor,
                                      geom_scale=(4, 1, 1),
                                      pos=(0.33, 0, 0.9),
                                      scale=0.1,
                                      text=TTLocalizer.PairingGameFlips,
                                      text_align=TextNode.ALeft,
                                      text_scale=TTLocalizer.DPGflipsFrame,
                                      text_pos=(-1.94, -0.1, 0.0))
        self.flipsLabel = DirectLabel(parent=self.flipsFrame,
                                      relief=None,
                                      text='0',
                                      text_fg=VBase4(0, 1.0, 0, 1),
                                      text_align=TextNode.ARight,
                                      text_scale=0.7,
                                      pos=(1.82, 0, -0.15))
        self.__textGen = TextNode('ringGame')
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        self.sndPerfect = base.loadSfx(
            'phase_4/audio/sfx/MG_pairing_all_matched.ogg')
        self.calcBonusTraversal()
        self.music = base.loadMusic('phase_4/audio/bgm/MG_Pairing.ogg')
        self.matchSfx = base.loadSfx('phase_4/audio/sfx/MG_pairing_match.ogg')
        self.matchWithBonusSfx = base.loadSfx(
            'phase_4/audio/sfx/MG_pairing_match_bonus_both.ogg')
        self.signalSfx = []
        for i in range(4):
            self.signalSfx.append(
                base.loadSfx(
                    'phase_4/audio/sfx/MG_pairing_jumping_signal.ogg'))

        self.bonusMovesSfx = base.loadSfx(
            'phase_4/audio/sfx/MG_pairing_bonus_moves.ogg')
        return

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM
        self.gameBoard.removeNode()
        del self.gameBoard
        for card in self.cards:
            card.unload()
            del card

        self.cards = []
        self.pointsFrame.removeNode()
        del self.pointsFrame
        self.flipsFrame.removeNode()
        del self.flipsFrame
        del self.__textGen
        del self.sndPerfect
        self.bonusGlow.removeNode()
        del self.bonusGlow
        del self.music
        del self.matchSfx
        del self.matchWithBonusSfx
        for i in range(4):
            del self.signalSfx[0]

        self.signalSfx = []
        del self.bonusMovesSfx

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.gameBoard.reparentTo(render)
        for card in self.cards:
            card.reparentTo(render)

        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        self.moveCameraToTop()

    def offstage(self):
        self.notify.debug('offstage')
        self.gameBoard.hide()
        for card in self.cards:
            card.hide()

        DistributedMinigame.offstage(self)

    def handleDisabledAvatar(self, avId):
        self.notify.debug('handleDisabledAvatar')
        self.notify.debug('avatar ' + str(avId) + ' disabled')
        DistributedMinigame.handleDisabledAvatar(self, avId)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        for index in xrange(self.numPlayers):
            avId = self.avIdList[index]
            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)
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.stopLookAround()

        self.gameFSM.request('play')

    def isInPlayState(self):
        if not self.gameFSM.getCurrentState():
            return False
        if not self.gameFSM.getCurrentState().getName() == 'play':
            return False
        return True

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

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        base.playMusic(self.music, looping=1, volume=0.9)
        orthoDrive = OrthoDrive(
            self.TOON_SPEED,
            maxFrameMove=self.MAX_FRAME_MOVE,
            customCollisionCallback=self.__doPairingGameCollisions)
        self.orthoWalk = OrthoWalk(orthoDrive,
                                   broadcast=not self.isSinglePlayer())
        self.orthoWalk.start()
        self.accept('insert', self.__flipKeyPressed)
        self.accept('delete', self.__flipKeyPressed)
        self.accept('time-control', self.__beginSignal)
        self.accept('time-control-up', self.__endSignal)
        self.bonusGlowIndex = 0
        self.bonusGlowCard = self.bonusTraversal[self.bonusGlowIndex]
        self.startBonusTask()
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(self.gameDuration)
        self.timer.countdown(self.gameDuration, self.timerExpired)
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.stop()

    def exitPlay(self):
        self.music.stop()
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        self.bonusGlow.hide()
        self.stopBonusTask()
        self.timer.stop()
        self.timer.destroy()
        del self.timer
        self.ignoreAll()
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.start()
        if hasattr(self, 'perfectIval'):
            self.perfectIval.pause()
            del self.perfectIval
        taskMgr.remove(self.EndGameTaskName)
        taskMgr.remove('pairGameContinueSignal')

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

    def exitCleanup(self):
        pass

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if self.numPlayers == 1:
            toon.setPos(0, 0, 0)
            toon.setHpr(0, 0, 0)
        else:
            posIndex = self.avIdList.index(avId)
            pos = self.startingPositions[posIndex]
            toon.setPos(pos[0], pos[1], pos[2])
            toon.setHpr(pos[3], 0, 0)

    def __doPairingGameCollisions(self, oldPos, newPos):
        x = bound(newPos[0], self.stageMin[0], self.stageMax[0])
        y = bound(newPos[1], self.stageMin[1], self.stageMax[1])
        newPos.setX(x)
        newPos.setY(y)
        if self.inList:
            newPos.setZ(0.15)
        else:
            newPos.setZ(0.0)
        if not oldPos == newPos:
            taskMgr.remove('pairGameContinueSignal')
        return newPos

    def getDeckOrderFromValue(self, value):
        for index in xrange(len(self.cards)):
            if self.cards[index].value == value:
                return index

        return -1

    def getDeckOrderFromPairingGameCard(self, into):
        try:
            index = self.cards.index(into)
        except ValueError:
            index = -1

        return index

    def enterCard(self, colEntry):
        intoName = colEntry.getIntoNodePath().getName()
        parts = intoName.split('-')
        value = int(parts[1])
        self.notify.debug('entered cardValue %d' % value)
        deckOrder = self.getDeckOrderFromValue(value)
        if deckOrder not in self.inList:
            self.inList.append(deckOrder)

    def exitCard(self, colEntry):
        intoName = colEntry.getIntoNodePath().getName()
        parts = intoName.split('-')
        value = int(parts[1])
        self.notify.debug('exited cardValue %d' % value)
        deckOrder = self.getDeckOrderFromValue(value)
        if deckOrder in self.inList:
            self.inList.remove(deckOrder)

    def handleMatch(self, cardA, cardB, withBonus):
        self.notify.debug('we got a match %d %d' % (cardA, cardB))
        self.matches += 1
        if cardA in self.faceUpList:
            self.faceUpList.remove(cardA)
        if cardB in self.faceUpList:
            self.faceUpList.remove(cardB)
        self.inactiveList.append(cardA)
        self.inactiveList.append(cardB)
        matchIval = Parallel()
        for card in [cardA, cardB]:
            self.cards[card].setTransparency(1)
            cardSeq = Sequence(
                LerpColorScaleInterval(self.cards[card],
                                       duration=1,
                                       colorScale=Vec4(1.0, 1.0, 1.0, 0.0)),
                Func(self.cards[card].hide))
            matchIval.append(cardSeq)

        if withBonus:
            matchIval.append(
                SoundInterval(self.matchWithBonusSfx,
                              node=self.cards[card],
                              listenerNode=base.localAvatar,
                              cutOff=240))
        else:
            matchIval.append(
                SoundInterval(self.matchSfx,
                              node=self.cards[card],
                              listenerNode=base.localAvatar,
                              cutOff=240))
        matchIval.start()
        if len(self.inactiveList) == len(self.cards):
            self.sendUpdate('reportDone')

    def turnUpCard(self, deckOrder):
        self.cards[deckOrder].turnUp()
        self.faceUpList.append(deckOrder)

    def turnDownCard(self, deckOrder):
        self.cards[deckOrder].turnDown()
        if deckOrder in self.faceUpList:
            self.faceUpList.remove(deckOrder)

    def __flipKeyPressed(self):
        if self.inList:
            shortestDistance = 10000
            cardToFlip = -1
            for deckOrder in self.inList:
                dist = base.localAvatar.getDistance(self.cards[deckOrder])
                if dist < shortestDistance:
                    shortestDistance = dist
                    cardToFlip = deckOrder

            deckOrderIndex = cardToFlip
            card = self.cards[deckOrderIndex]
            if card.isFaceDown() and deckOrderIndex not in self.inactiveList:
                self.sendUpdate('openCardRequest',
                                [deckOrderIndex, self.bonusGlowCard])
            elif card.isFaceUp() and deckOrderIndex in self.faceUpList:
                pass

    def moveBonusGlowTask(self, task):
        if len(self.cards) == 0:
            return Task.done
        curT = self.getCurrentGameTime()
        intTime = int(curT / self.bonusGlowTime)
        newIndex = intTime % len(self.cards)
        if not newIndex == self.bonusGlowIndex:
            self.bonusGlowIndex = newIndex
            self.bonusGlowCard = self.bonusTraversal[self.bonusGlowIndex]
            card = self.cards[self.bonusGlowCard]
            self.bonusGlow.setPos(card.getPos())
            base.playSfx(self.bonusMovesSfx, node=card, volume=0.25)
        return Task.cont

    def timerExpired(self):
        self.sendUpdate('reportDone')

    def setDeckSeed(self, deckSeed):
        if not self.hasLocalToon:
            return
        self.deckSeed = deckSeed

    def updateFlipText(self):
        self.flipsLabel['text'] = str(self.flips)
        lowFlipModifier = PairingGameGlobals.calcLowFlipModifier(
            self.matches, self.flips)
        red = 1.0 - lowFlipModifier
        green = lowFlipModifier
        self.flipsLabel['text_fg'] = Vec4(red, green, 0, 1.0)

    def openCardResult(self, cardToTurnUp, avId, matchingCard, points,
                       cardsToTurnDown):
        if not self.hasLocalToon:
            return
        if not self.isInPlayState():
            return
        if avId == base.localAvatar.doId:
            self.localFaceUpList.append(cardToTurnUp)
        self.turnUpCard(cardToTurnUp)
        gotBonus = False
        if points - self.points > 1:
            gotBonus = True
        if matchingCard > -1:
            self.handleMatch(cardToTurnUp, matchingCard, gotBonus)
        self.flips += 1
        self.updateFlipText()
        self.points = points
        self.pointsLabel['text'] = str(self.points)
        for card in cardsToTurnDown:
            self.turnDownCard(card)

    def startBonusTask(self):
        taskMgr.add(self.moveBonusGlowTask, self.taskName('moveBonusGlowTask'))

    def stopBonusTask(self):
        taskMgr.remove(self.taskName('moveBonusGlowTask'))

    def setEveryoneDone(self):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring setEveryoneDone msg')
            return
        self.notify.debug('setEveryoneDone')

        def endGame(task, self=self):
            if not PairingGameGlobals.EndlessGame:
                self.gameOver()
            return Task.done

        self.timer.hide()
        self.bonusGlow.hide()
        if len(self.inactiveList) == len(self.cards):
            self.notify.debug('perfect game!')
            perfectTextSubnode = hidden.attachNewNode(
                self.__genText(TTLocalizer.PairingGamePerfect))
            perfectText = hidden.attachNewNode('perfectText')
            perfectTextSubnode.reparentTo(perfectText)
            frame = self.__textGen.getCardActual()
            offsetY = -abs(frame[2] + frame[3]) / 2.0
            perfectTextSubnode.setPos(0, 0, offsetY)
            perfectText.setColor(1, 0.1, 0.1, 1)

            def fadeFunc(t, text=perfectText):
                text.setColorScale(1, 1, 1, t)

            def destroyText(text=perfectText):
                text.removeNode()

            textTrack = Sequence(
                Func(perfectText.reparentTo, aspect2d),
                Parallel(
                    LerpScaleInterval(perfectText,
                                      duration=0.5,
                                      scale=0.3,
                                      startScale=0.0),
                    LerpFunctionInterval(fadeFunc,
                                         fromData=0.0,
                                         toData=1.0,
                                         duration=0.5)), Wait(2.0),
                Parallel(
                    LerpScaleInterval(perfectText, duration=0.5, scale=1.0),
                    LerpFunctionInterval(fadeFunc,
                                         fromData=1.0,
                                         toData=0.0,
                                         duration=0.5,
                                         blendType='easeIn')),
                Func(destroyText), WaitInterval(0.5), Func(endGame, None))
            soundTrack = SoundInterval(self.sndPerfect)
            self.perfectIval = Parallel(textTrack, soundTrack)
            self.perfectIval.start()
        else:
            taskMgr.doMethodLater(1, endGame, self.EndGameTaskName)
        return

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def b_setSignaling(self, avId):
        self.setSignaling(avId)
        self.sendUpdate('setSignaling', [self.localAvId])

    def setSignaling(self, avId):
        if not self.hasLocalToon:
            return
        avIndex = self.avIdList.index(avId)
        av = base.cr.doId2do.get(avId)
        if av and avIndex >= 0 and hasattr(self,
                                           'signalSfx') and self.signalSfx:
            base.playSfx(self.signalSfx[avIndex], node=av)

    def __beginSignal(self, mouseParam):
        self.notify.debug('beginSignal')
        base.localAvatar.b_setEmoteState(1, 1.0)
        self.b_setSignaling(self.localAvId)
        taskMgr.doMethodLater(1.67, self.__continueSignal,
                              'pairGameContinueSignal')

    def __endSignal(self, mouseParam):
        self.notify.debug('endSignal')
        base.localAvatar.b_setEmoteState(-1, 1.0)
        taskMgr.remove('pairGameContinueSignal')

    def __continueSignal(self, task):
        base.localAvatar.b_setEmoteState(1, 1.0)
        self.b_setSignaling(self.localAvId)
        taskMgr.doMethodLater(1.67, self.__continueSignal,
                              'pairGameContinueSignal')

    def getCardPos(self, deckOrderIndex):
        col = deckOrderIndex % self.cardsPerRow
        row = deckOrderIndex / self.cardsPerRow
        x = col * self.xCardInc
        y = row * self.yCardInc
        return (x, y)

    def getDeckOrderIndex(self, row, col):
        retval = row * self.cardsPerRow
        retval += col
        if retval >= len(self.deck.cards):
            retval = -1
        return retval

    def calcBonusTraversal(self):
        self.bonusTraversal = []
        halfRow = self.cardsPerRow / 2
        if self.cardsPerRow % 2:
            halfRow += 1
        for i in xrange(halfRow):
            for j in xrange(2):
                col = i + j * halfRow
                for row in xrange(self.cardsPerCol):
                    card = self.getDeckOrderIndex(row, col)
                    if card > -1:
                        self.bonusTraversal.append(card)
class DistributedPieTurret(DistributedAvatar, DistributedSmoothNode):
    notify = directNotify.newCategory('DistributedPieTurret')

    def __init__(self, cr):
        DistributedAvatar.__init__(self, cr)
        DistributedSmoothNode.__init__(self, cr)
        self.fsm = ClassicFSM(
            'DistributedPieTurret',
            [
                State('off', self.enterOff, self.exitOff),
                State('scan', self.enterScan, self.exitScan),
                State('shoot', self.enterShoot, self.exitShoot)
             ],
             'off', 'off'
         )
        self.fsm.enterInitialState()
        self.reloadTime = 0.25
        self.cannon = None
        self.track = None
        self.owner = None
        self.gag = None
        self.readyGag = None
        self.hitGag = None
        self.explosion = None
        self.wallCollNode = None
        self.eventCollNode = None
        self.event = None
        self.suit = None
        self.eventId = None
        self.entities = []
        self.upgradeID = None
        self.deathEvent = None

    def setOwner(self, avatar):
        self.owner = avatar

    def getOwner(self):
        return self.owner

    def setGag(self, upgradeId):
        gags = {0 : CIGlobals.WholeCreamPie, 1 : CIGlobals.WholeFruitPie, 2 : CIGlobals.BirthdayCake, 3 : CIGlobals.WeddingCake}
        self.gag = gags.get(upgradeId)
        if not self.readyGag:
            self.loadGagInTurret()

    def b_setGag(self, upgradeId):
        self.sendUpdate('setGag', [upgradeId])
        self.setGag(upgradeId)
        self.upgradeID = upgradeId

    def getGag(self):
        return self.gag

    def getGagID(self):
        return self.upgradeID

    def generate(self):
        DistributedAvatar.generate(self)
        DistributedSmoothNode.generate(self)

    def announceGenerate(self):
        DistributedAvatar.announceGenerate(self)
        DistributedSmoothNode.announceGenerate(self)
        self.healthLabel.setScale(1.1)
        self.deathEvent = self.uniqueName('DistributedPieTurret-death')
        self.makeTurret()

    def disable(self):
        self.fsm.requestFinalState()
        del self.fsm

        # This should fix crashes related to Sequences.
        if self.track:
            self.track.pause()
            self.track = None

        # Cleanup entities.
        for ent in self.entities:
            ent.cleanup()
        self.entities = None

        # Get rid of explosions.
        if self.explosion:
            self.explosion.removeNode()
            self.explosion = None

        self.removeTurret()
        DistributedSmoothNode.disable(self)
        DistributedAvatar.disable(self)

    def showAndMoveHealthLabel(self):
        self.unstashHpLabel()
        self.stopMovingHealthLabel()
        moveTrack = LerpPosInterval(self.healthLabel,
                                duration = 0.5,
                                pos = Point3(0, 0, 5),
                                startPos = Point3(0, 0, 0),
                                blendType = 'easeOut')
        self.healthLabelTrack = Sequence(moveTrack, Wait(1.0), Func(self.stashHpLabel))
        self.healthLabelTrack.start()

    # BEGIN STATES

    def enterShoot(self, suitId):
        if self.cannon:
            smoke = loader.loadModel("phase_4/models/props/test_clouds.bam")
            smoke.setBillboardPointEye()
            smoke.reparentTo(self.cannon.find('**/cannon'))
            smoke.setPos(0, 6, -3)
            smoke.setScale(0.5)
            smoke.wrtReparentTo(render)
            self.suit = self.cr.doId2do.get(suitId)
            self.cannon.find('**/cannon').lookAt(self.suit.find('**/joint_head'))
            self.cannon.find('**/square_drop_shadow').headsUp(self.suit.find('**/joint_head'))
            self.track = Sequence(Parallel(LerpScaleInterval(smoke, 0.5, 3), LerpColorScaleInterval(smoke, 0.5, Vec4(2, 2, 2, 0))), Func(smoke.removeNode))
            self.track.start()
            self.createAndShootGag()

    def exitShoot(self):
        if hasattr(self, 'suit'):
            del self.suit

    def shoot(self, suitId):
        self.fsm.request('shoot', [suitId])

    def scan(self, timestamp = None, afterShooting = 0):
        if timestamp == None:
            ts = 0.0
        else:
            ts = globalClockDelta.localElapsedTime(timestamp)

        self.fsm.request('scan', [ts, afterShooting])
        
    def buildScanTrack(self, ts = None):
        if self.track:
            self.track.pause()
            self.track = None
        self.track = Parallel(
            Sequence(
                LerpQuatInterval(self.cannon.find('**/cannon'), duration = 3, quat = (60, 0, 0),
                    startHpr = Vec3(-60, 0, 0), blendType = 'easeInOut'),
                LerpQuatInterval(self.cannon.find('**/cannon'), duration = 3, quat = (-60, 0, 0),
                    startHpr = Vec3(60, 0, 0), blendType = 'easeInOut'),
            ),
            Sequence(
                LerpQuatInterval(self.cannon.find('**/square_drop_shadow'), duration = 3, quat = (60, 0, 0),
                    startHpr = Vec3(-60, 0, 0), blendType = 'easeInOut'),
                LerpQuatInterval(self.cannon.find('**/square_drop_shadow'), duration = 3, quat = (-60, 0, 0),
                    startHpr = Vec3(60, 0, 0), blendType = 'easeInOut'),
            )
        )
        if ts:
            self.track.loop(ts)
        else:
            self.track.loop()

    def enterScan(self, ts = 0, afterShooting = 0):
        if afterShooting:
            self.track = Parallel(
                LerpQuatInterval(self.cannon.find('**/cannon'), duration = 3, quat = (-60, 0, 0),
                    startHpr = self.cannon.find('**/cannon').getHpr(), blendType = 'easeInOut'),
                LerpQuatInterval(self.cannon.find('**/square_drop_shadow'), duration = 3, quat = (-60, 0, 0),
                    startHpr = self.cannon.find('**/square_drop_shadow').getHpr(), blendType = 'easeInOut'),
                name = "afterShootTrack" + str(id(self))
            )
            self.track.setDoneEvent(self.track.getName())
            self.acceptOnce(self.track.getDoneEvent(), self._afterShootTrackDone)
            self.track.start(ts)
        else:
            self.buildScanTrack(ts)

    def exitScan(self):
        if self.track:
            self.ignore(self.track.getDoneEvent())
            self.track.finish()
            self.track = None

    def enterOff(self):
        pass

    def exitOff(self):
        pass

    # END STATES

    def _afterShootTrackDone(self):
        self.buildScanTrack()

    def makeTurret(self):
        self.cannon = loader.loadModel('phase_4/models/minigames/toon_cannon.bam')
        self.cannon.reparentTo(self)
        self.loadGagInTurret()
        self.setupWallSphere()
        if self.isLocal():
            self.setupEventSphere()

    def removeTurret(self):
        self.removeWallSphere()
        self.removeGagInTurret()
        if self.cannon:
            self.cannon.removeNode()
            self.cannon = None

    def getCannon(self):
        return self.cannon.find('**/cannon')

    def setupWallSphere(self):
        sphere = CollisionSphere(0.0, 0.0, 0.0, 3.0)
        node = CollisionNode('DistributedPieTurret.WallSphere')
        node.addSolid(sphere)
        node.setCollideMask(CIGlobals.WallBitmask)
        self.wallCollNode = self.cannon.attachNewNode(node)
        self.wallCollNode.setZ(2)
        self.wallCollNode.setY(1.0)

    def removeWallSphere(self):
        if self.wallCollNode:
            self.wallCollNode.removeNode()
            self.wallCollNode = None

    def createAndShootGag(self):
        if not self.readyGag:
            self.loadGagInTurret()
        if self.readyGag:
            self.readyGag.shoot(Point3(0, 200, -90))
            self.entities.append(self.readyGag)
            collideEventName = self.readyGag.getCollideEventName()
            self.readyGag = None
            if self.isLocal():
                self.acceptOnce(collideEventName, self.handleGagCollision)
        Sequence(Wait(self.reloadTime), Func(self.loadGagInTurret)).start()

    def loadGagInTurret(self):
        if self.cannon and self.gag:
            self.removeGagInTurret()
            self.eventId = random.uniform(0, 100000000)
            self.readyGag = TurretGag(self, self.uniqueName('pieTurretCollision') + str(self.eventId), self.gag)
            self.readyGag.build()

    def removeGagInTurret(self):
        if self.readyGag:
            self.readyGag.cleanup()
            self.readyGag = None

    def makeSplat(self, index, pos):
        if index >= len(self.entities):
            return
        ent = self.entities[index]
        gagClass = ent.gagClass
        splat = gagClass.buildSplat(gagClass.splatScale, gagClass.splatColor)
        base.audio3d.attachSoundToObject(gagClass.hitSfx, splat)
        splat.reparentTo(render)
        splat.setPos(pos[0], pos[1], pos[2])
        gagClass.hitSfx.play()
        Sequence(Wait(0.5), Func(splat.cleanup)).start()
        self.hitGag = None

    def d_makeSplat(self, index, pos):
        self.sendUpdate('makeSplat', [index, pos])

    def b_makeSplat(self, index, pos):
        self.d_makeSplat(index, pos)
        self.makeSplat(index, pos)

    def handleGagCollision(self, entry, ent):
        x, y, z = ent.getGag().getPos(render)
        self.b_makeSplat(self.entities.index(ent), [x, y, z])
        if self.isLocal():
            intoNP = entry.getIntoNodePath()
            avNP = intoNP.getParent()
            for key in self.cr.doId2do.keys():
                obj = self.cr.doId2do[key]
                if obj.__class__.__name__ == 'DistributedSuit':
                    if obj.getKey() == avNP.getKey():
                        if obj.getHealth() > 0:
                            obj.sendUpdate('hitByGag', [ent.getID()])
        ent.cleanup()

    def setHealth(self, hp):
        DistributedAvatar.setHealth(self, hp)
        if self.isLocal():
            base.localAvatar.getMyBattle().getTurretManager().updateTurretGui()

    def die(self):
        self.fsm.requestFinalState()
        turretPos = self.cannon.getPos(render)
        self.removeTurret()
        self.explosion = loader.loadModel("phase_3.5/models/props/explosion.bam")
        self.explosion.setScale(0.5)
        self.explosion.reparentTo(render)
        self.explosion.setBillboardPointEye()
        self.explosion.setPos(turretPos + (0, 0, 5))
        sfx = base.audio3d.loadSfx("phase_3.5/audio/sfx/ENC_cogfall_apart.ogg")
        base.audio3d.attachSoundToObject(sfx, self)
        base.playSfx(sfx)
        messenger.send(self.deathEvent)

    def isLocal(self):
        return self.getOwner() == base.localAvatar.doId

    def getDeathEvent(self):
        return self.deathEvent
class TestCutscene:
    def __init__(self, dcs):
        self.dcs = dcs
        self.ival = None

    def run(self):

        boss = base.bspLoader.getPyEntityByTargetName("groom_boss_suit")
        boss.show()
        boss.cleanupPropeller()
        boss.animFSM.request('neutral')
        camera.reparentTo(boss)

        onBtn = base.bspLoader.getPyEntityByTargetName("powerovrd_on_button")

        # press animation (fingerwag): 1-31 32-1

        buttonPos = (-1105 / 16.0, 2683 / 16.0, 76 / 16.0)
        camBtnPosHpr = (-1027 / 16.0, 2638 / 16.0, 96 / 16.0, 135 - 90, 30, 0)
        camBtnPosHpr2 = (-1009 / 16.0, 2764 / 16.0, 169 / 16.0, 232 - 90, 0, 0)

        def __pingpongboss_speak():
            boss.pingpong("speak", fromFrame=95, toFrame=100)

        def __boss_pressseq():
            return Sequence(
                ActorInterval(boss, "fingerwag", startFrame=1, endFrame=31),
                Func(onBtn.d_requestPress),
                ActorInterval(boss, "fingerwag", startFrame=32, endFrame=1))

        def __localavplay_duck():
            base.localAvatar.setAnimState('off')
            base.localAvatar.play("duck", fromFrame=19)

        numGoons = 6

        goonWakeup = Parallel()
        for i in xrange(numGoons):
            delay = random.uniform(0.0, 0.5)
            goon = base.bspLoader.getPyEntityByTargetName(
                "groom_goon_{0}".format(i))
            goonWakeup.append(Sequence(Wait(delay), Func(goon.wakeup)))

        bdoor = base.bspLoader.getPyEntityByTargetName("to_groom_botdoor")
        tdoor = base.bspLoader.getPyEntityByTargetName("to_groom_topdoor")

        oldHpr = boss.getHpr(render)

        bossTrack = Parallel(
            Sequence(
                Wait(10.0),
                Func(boss.setHpr, oldHpr),
                ActorInterval(boss,
                              "speak",
                              startFrame=55,
                              endFrame=105,
                              playRate=1.5),
                #Func(__pingpongboss_speak),
                Wait(2.0),
                Func(boss.loop, "neutral")),
            Sequence(
                Func(boss.play, "speak"),
                Func(boss.setChat,
                     "What's this?! How did a Toon get down here?"), Wait(2.5),
                Func(
                    boss.setChat,
                    "The only place you'll be going is back to the playground."
                ), Func(boss.loop, "walk"), Func(boss.headsUp, *buttonPos),
                LerpPosInterval(boss, 2.5, buttonPos), __boss_pressseq(),
                Wait(2.5), Func(boss.setChat, "Goons, ATTACK!!!")))
        camTrack = Sequence(
            Func(camera.setPos, 2.5, 14, 7), Func(camera.lookAt, boss, 0, 0,
                                                  4), Wait(2.5),
            Func(camera.reparentTo, render),
            Func(camera.setPosHpr, *camBtnPosHpr), Wait(2.0),
            Func(camera.setPosHpr, *camBtnPosHpr2), Wait(0.5), Wait(3.0),
            Func(camera.setPosHpr, 4.351, 126.686, 5.5, -154.16, 16.11, 0),
            Wait(0.3), Func(bdoor.request, 'Closing'),
            Func(tdoor.request, 'Closing'), Func(__localavplay_duck),
            Wait(0.9), Func(base.doCamShake, 0.5, 0.35), Wait(0.9),
            Func(camera.reparentTo, boss), Func(camera.setPos, 2.5, 17, 7),
            Func(camera.lookAt, boss, 0, 0, 4),
            LerpPosHprInterval(camera,
                               duration=0.75,
                               blendType='easeOut',
                               pos=(1, 5, 4),
                               hpr=(165, 20, 0)), Wait(1.75),
            Func(camera.reparentTo, render),
            Func(camera.setPosHpr, 88 / 16.0, 2746 / 16.0, 127 / 16.0, 71 - 90,
                 -10, 0))
        goonTrack = Sequence(Wait(12.6), goonWakeup)

        self.ival = Parallel(camTrack, bossTrack, goonTrack)
        self.ival.start()

    def stop(self):
        if self.ival:
            self.ival.pause()
            self.ival = None

    def cleanup(self):
        self.dcs = None
Beispiel #42
0
class DistributedPartyCatchActivity(DistributedPartyActivity, DistributedPartyCatchActivityBase):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyCatchActivity')
    DropTaskName = 'dropSomething'
    DropObjectPlurals = {'apple': TTLocalizer.PartyCatchActivityApples,
     'orange': TTLocalizer.PartyCatchActivityOranges,
     'pear': TTLocalizer.PartyCatchActivityPears,
     'coconut': TTLocalizer.PartyCatchActivityCoconuts,
     'watermelon': TTLocalizer.PartyCatchActivityWatermelons,
     'pineapple': TTLocalizer.PartyCatchActivityPineapples,
     'anvil': TTLocalizer.PartyCatchActivityAnvils}

    class Generation:

        def __init__(self, generation, startTime, startNetworkTime, numPlayers):
            self.generation = generation
            self.startTime = startTime
            self.startNetworkTime = startNetworkTime
            self.numPlayers = numPlayers
            self.hasBeenScheduled = False
            self.droppedObjNames = []
            self.dropSchedule = []
            self.numItemsDropped = 0
            self.droppedObjCaught = {}

    def __init__(self, cr):
        DistributedPartyActivity.__init__(self, cr, PartyGlobals.ActivityIds.PartyCatch, PartyGlobals.ActivityTypes.HostInitiated, wantRewardGui=True)
        self.setUsesSmoothing()
        self.setUsesLookAround()
        self._sNumGen = SerialNumGen()

    def getTitle(self):
        return TTLocalizer.PartyCatchActivityTitle

    def getInstructions(self):
        return TTLocalizer.PartyCatchActivityInstructions % {'badThing': self.DropObjectPlurals['anvil']}

    def generate(self):
        DistributedPartyActivity.generate(self)
        self.notify.info('localAvatar doId: %s' % base.localAvatar.doId)
        self.notify.info('generate()')
        self._generateFrame = globalClock.getFrameCount()
        self._id2gen = {}
        self._orderedGenerations = []
        self._orderedGenerationIndex = None
        rng = RandomNumGen(self.doId)
        self._generationSeedBase = rng.randrange(1000)
        self._lastDropTime = 0.0
        return

    def getCurGeneration(self):
        if self._orderedGenerationIndex is None:
            return
        return self._orderedGenerations[self._orderedGenerationIndex]

    def _addGeneration(self, generation, startTime, startNetworkTime, numPlayers):
        self._id2gen[generation] = self.Generation(generation, startTime, startNetworkTime, numPlayers)
        i = 0
        while 1:
            if i >= len(self._orderedGenerations):
                break
            gen = self._orderedGenerations[i]
            startNetT = self._id2gen[gen].startTime
            genId = self._id2gen[gen].generation
            if startNetT > startNetworkTime:
                break
            if startNetT == startNetworkTime and genId > generation:
                break
            i += 1
        self._orderedGenerations = self._orderedGenerations[:i] + [generation] + self._orderedGenerations[i:]
        if self._orderedGenerationIndex is not None:
            if self._orderedGenerationIndex >= i:
                self._orderedGenerationIndex += 1

    def _removeGeneration(self, generation):
        del self._id2gen[generation]
        i = self._orderedGenerations.index(generation)
        self._orderedGenerations = self._orderedGenerations[:i] + self._orderedGenerations[i + 1:]
        if self._orderedGenerationIndex is not None:
            if len(self._orderedGenerations):
                if self._orderedGenerationIndex >= i:
                    self._orderedGenerationIndex -= 1
            else:
                self._orderedGenerationIndex = None
        return

    def announceGenerate(self):
        self.notify.info('announceGenerate()')
        self.catchTreeZoneEvent = 'fence_floor'
        DistributedPartyActivity.announceGenerate(self)

    def load(self, loadModels = 1, arenaModel = 'partyCatchTree'):
        self.notify.info('load()')
        DistributedPartyCatchActivity.notify.debug('PartyCatch: load')
        self.activityFSM = CatchActivityFSM(self)
        if __dev__:
            for o in xrange(3):
                print {0: 'SPOTS PER PLAYER',
                 1: 'DROPS PER MINUTE PER SPOT DURING NORMAL DROP PERIOD',
                 2: 'DROPS PER MINUTE PER PLAYER DURING NORMAL DROP PERIOD'}[o]
                for i in xrange(1, self.FallRateCap_Players + 10):
                    self.defineConstants(forceNumPlayers=i)
                    numDropLocations = self.DropRows * self.DropColumns
                    numDropsPerMin = 60.0 / self.DropPeriod
                    if o == 0:
                        spotsPerPlayer = numDropLocations / float(i)
                        print '%2d PLAYERS: %s' % (i, spotsPerPlayer)
                    elif o == 1:
                        numDropsPerMinPerSpot = numDropsPerMin / numDropLocations
                        print '%2d PLAYERS: %s' % (i, numDropsPerMinPerSpot)
                    elif i > 0:
                        numDropsPerMinPerPlayer = numDropsPerMin / i
                        print '%2d PLAYERS: %s' % (i, numDropsPerMinPerPlayer)

        self.defineConstants()
        self.treesAndFence = loader.loadModel('phase_13/models/parties/%s' % arenaModel)
        self.treesAndFence.setScale(0.9)
        self.treesAndFence.find('**/fence_floor').setPos(0.0, 0.0, 0.1)
        self.treesAndFence.reparentTo(self.root)
        ground = self.treesAndFence.find('**/groundPlane')
        ground.setBin('ground', 1)
        DistributedPartyActivity.load(self)
        exitText = TextNode('PartyCatchExitText')
        exitText.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
        exitText.setCardDecal(True)
        exitText.setCardColor(1.0, 1.0, 1.0, 0.0)
        exitText.setText(TTLocalizer.PartyCatchActivityExit)
        exitText.setTextColor(0.0, 8.0, 0.0, 0.9)
        exitText.setAlign(exitText.ACenter)
        exitText.setFont(ToontownGlobals.getBuildingNametagFont())
        exitText.setShadowColor(0, 0, 0, 1)
        exitText.setBin('fixed')
        if TTLocalizer.BuildingNametagShadow:
            exitText.setShadow(*TTLocalizer.BuildingNametagShadow)
        exitTextLoc = self.treesAndFence.find('**/loc_exitSignText')
        exitTextNp = exitTextLoc.attachNewNode(exitText)
        exitTextNp.setDepthWrite(0)
        exitTextNp.setScale(4)
        exitTextNp.setZ(-.5)
        self.sign.reparentTo(self.treesAndFence.find('**/loc_eventSign'))
        self.sign.wrtReparentTo(self.root)
        self.avatarNodePath = NodePath('PartyCatchAvatarNodePath')
        self.avatarNodePath.reparentTo(self.root)
        self._avatarNodePathParentToken = 3
        base.cr.parentMgr.registerParent(self._avatarNodePathParentToken, self.avatarNodePath)
        self.toonSDs = {}
        self.dropShadow = loader.loadModelOnce('phase_3/models/props/drop_shadow')
        self.dropObjModels = {}
        if loadModels:
            self.__loadDropModels()
        self.sndGoodCatch = base.loadSfx('phase_4/audio/sfx/SZ_DD_treasure.ogg')
        self.sndOof = base.loadSfx('phase_4/audio/sfx/MG_cannon_hit_dirt.ogg')
        self.sndAnvilLand = base.loadSfx('phase_4/audio/sfx/AA_drop_anvil_miss.ogg')
        self.sndPerfect = base.loadSfx('phase_4/audio/sfx/ring_perfect.ogg')
        self.__textGen = TextNode('partyCatchActivity')
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        self.activityFSM.request('Idle')

    def __loadDropModels(self):
        for objType in PartyGlobals.DropObjectTypes:
            model = loader.loadModel(objType.modelPath)
            self.dropObjModels[objType.name] = model
            modelScales = {'apple': 0.7,
             'orange': 0.7,
             'pear': 0.5,
             'coconut': 0.7,
             'watermelon': 0.6,
             'pineapple': 0.45}
            if objType.name in modelScales:
                model.setScale(modelScales[objType.name])
            if objType == PartyGlobals.Name2DropObjectType['pear']:
                model.setZ(-.6)
            if objType == PartyGlobals.Name2DropObjectType['coconut']:
                model.setP(180)
            if objType == PartyGlobals.Name2DropObjectType['watermelon']:
                model.setH(135)
                model.setZ(-.5)
            if objType == PartyGlobals.Name2DropObjectType['pineapple']:
                model.setZ(-1.7)
            if objType == PartyGlobals.Name2DropObjectType['anvil']:
                model.setZ(-self.ObjRadius)
            model.flattenStrong()

    def unload(self):
        DistributedPartyCatchActivity.notify.debug('unload')
        self.finishAllDropIntervals()
        self.destroyOrthoWalk()
        DistributedPartyActivity.unload(self)
        self.stopDropTask()
        del self.activityFSM
        del self.__textGen
        for avId in self.toonSDs.keys():
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.unload()

        del self.toonSDs
        self.treesAndFence.removeNode()
        del self.treesAndFence
        self.dropShadow.removeNode()
        del self.dropShadow
        base.cr.parentMgr.unregisterParent(self._avatarNodePathParentToken)
        for model in self.dropObjModels.values():
            model.removeNode()

        del self.dropObjModels
        del self.sndGoodCatch
        del self.sndOof
        del self.sndAnvilLand
        del self.sndPerfect

    def setStartTimestamp(self, timestamp32):
        self.notify.info('setStartTimestamp(%s)' % (timestamp32,))
        self._startTimestamp = globalClockDelta.networkToLocalTime(timestamp32, bits=32)

    def getCurrentCatchActivityTime(self):
        return globalClock.getFrameTime() - self._startTimestamp

    def getObjModel(self, objName):
        return self.dropObjModels[objName].copyTo(hidden)

    def joinRequestDenied(self, reason):
        DistributedPartyActivity.joinRequestDenied(self, reason)
        base.cr.playGame.getPlace().fsm.request('walk')

    def handleToonJoined(self, toonId):
        if toonId not in self.toonSDs:
            toonSD = PartyCatchActivityToonSD(toonId, self)
            self.toonSDs[toonId] = toonSD
            toonSD.load()
        self.notify.debug('handleToonJoined : currentState = %s' % self.activityFSM.state)
        self.cr.doId2do[toonId].useLOD(500)
        if self.activityFSM.state == 'Active':
            if toonId in self.toonSDs:
                self.toonSDs[toonId].enter()
            if base.localAvatar.doId == toonId:
                base.localAvatar.b_setParent(self._avatarNodePathParentToken)
                self.putLocalAvatarInActivity()
            if toonId in self.toonSDs:
                self.toonSDs[toonId].fsm.request('rules')

    def handleToonExited(self, toonId):
        self.notify.debug('handleToonExited( toonId=%s )' % toonId)
        if toonId in self.cr.doId2do:
            self.cr.doId2do[toonId].resetLOD()
            if toonId in self.toonSDs:
                self.toonSDs[toonId].fsm.request('notPlaying')
                self.toonSDs[toonId].exit()
                self.toonSDs[toonId].unload()
                del self.toonSDs[toonId]
            if base.localAvatar.doId == toonId:
                base.localAvatar.b_setParent(ToontownGlobals.SPRender)

    def takeLocalAvatarOutOfActivity(self):
        self.notify.debug('localToon has left the circle')
        base.camera.reparentTo(base.localAvatar)
        base.localAvatar.startUpdateSmartCamera()
        base.localAvatar.enableSmartCameraViews()
        base.localAvatar.setCameraPositionByIndex(base.localAvatar.cameraIndex)
        DistributedSmoothNode.activateSmoothing(1, 0)

    def _enableCollisions(self):
        DistributedPartyActivity._enableCollisions(self)
        self._enteredTree = False
        self.accept('enter' + self.catchTreeZoneEvent, self._toonMayHaveEnteredTree)
        self.accept('again' + self.catchTreeZoneEvent, self._toonMayHaveEnteredTree)
        self.accept('exit' + self.catchTreeZoneEvent, self._toonExitedTree)
        self.accept(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT, self._handleCannonLanded)

    def _disableCollisions(self):
        self.ignore(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT)
        self.ignore('enter' + self.catchTreeZoneEvent)
        self.ignore('again' + self.catchTreeZoneEvent)
        self.ignore('exit' + self.catchTreeZoneEvent)
        DistributedPartyActivity._disableCollisions(self)

    def _handleCannonLanded(self):
        x = base.localAvatar.getX()
        y = base.localAvatar.getY()
        if x > self.x - self.StageHalfWidth and x < self.x + self.StageHalfWidth and y > self.y - self.StageHalfHeight and y < self.y + self.StageHalfHeight:
            self._toonEnteredTree(None)
        return

    def _toonMayHaveEnteredTree(self, collEntry):
        if self._enteredTree:
            return
        if base.localAvatar.controlManager.currentControls.getIsAirborne():
            return
        self._toonEnteredTree(collEntry)

    def _toonEnteredTree(self, collEntry):
        self.notify.debug('_toonEnteredTree : avid = %s' % base.localAvatar.doId)
        self.notify.debug('_toonEnteredTree : currentState = %s' % self.activityFSM.state)
        if self.isLocalToonInActivity():
            return
        if self.activityFSM.state == 'Active':
            base.cr.playGame.getPlace().fsm.request('activity')
            self.d_toonJoinRequest()
        elif self.activityFSM.state == 'Idle':
            base.cr.playGame.getPlace().fsm.request('activity')
            self.d_toonJoinRequest()
        self._enteredTree = True

    def _toonExitedTree(self, collEntry):
        self.notify.debug('_toonExitedTree : avid = %s' % base.localAvatar.doId)
        self._enteredTree = False
        if hasattr(base.cr.playGame.getPlace(), 'fsm') and self.activityFSM.state == 'Active' and self.isLocalToonInActivity():
            if base.localAvatar.doId in self.toonSDs:
                self.takeLocalAvatarOutOfActivity()
                self.toonSDs[base.localAvatar.doId].fsm.request('notPlaying')
            self.d_toonExitDemand()

    def setToonsPlaying(self, toonIds):
        self.notify.info('setToonsPlaying(%s)' % (toonIds,))
        DistributedPartyActivity.setToonsPlaying(self, toonIds)
        if self.isLocalToonInActivity() and base.localAvatar.doId not in toonIds:
            if base.localAvatar.doId in self.toonSDs:
                self.takeLocalAvatarOutOfActivity()
                self.toonSDs[base.localAvatar.doId].fsm.request('notPlaying')

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def getNumPlayers(self):
        return len(self.toonIds)

    def defineConstants(self, forceNumPlayers = None):
        DistributedPartyCatchActivity.notify.debug('defineConstants')
        self.ShowObjSpheres = 0
        self.ShowToonSpheres = 0
        self.useGravity = True
        self.trickShadows = True
        if forceNumPlayers is None:
            numPlayers = self.getNumPlayers()
        else:
            numPlayers = forceNumPlayers
        self.calcDifficultyConstants(numPlayers)
        DistributedPartyCatchActivity.notify.debug('ToonSpeed: %s' % self.ToonSpeed)
        DistributedPartyCatchActivity.notify.debug('total drops: %s' % self.totalDrops)
        DistributedPartyCatchActivity.notify.debug('numFruits: %s' % self.numFruits)
        DistributedPartyCatchActivity.notify.debug('numAnvils: %s' % self.numAnvils)
        self.ObjRadius = 1.0
        dropRegionTable = PartyRegionDropPlacer.getDropRegionTable(numPlayers)
        self.DropRows, self.DropColumns = len(dropRegionTable), len(dropRegionTable[0])
        for objType in PartyGlobals.DropObjectTypes:
            DistributedPartyCatchActivity.notify.debug('*** Object Type: %s' % objType.name)
            objType.onscreenDuration = objType.onscreenDurMult * self.BaselineOnscreenDropDuration
            DistributedPartyCatchActivity.notify.debug('onscreenDuration=%s' % objType.onscreenDuration)
            v_0 = 0.0
            t = objType.onscreenDuration
            x_0 = self.MinOffscreenHeight
            x = 0.0
            g = 2.0 * (x - x_0 - v_0 * t) / (t * t)
            DistributedPartyCatchActivity.notify.debug('gravity=%s' % g)
            objType.trajectory = Trajectory(0, Vec3(0, 0, x_0), Vec3(0, 0, v_0), gravMult=abs(g / Trajectory.gravity))
            objType.fallDuration = objType.onscreenDuration + self.OffscreenTime

        return

    def grid2world(self, column, row):
        x = column / float(self.DropColumns - 1)
        y = row / float(self.DropRows - 1)
        x = x * 2.0 - 1.0
        y = y * 2.0 - 1.0
        x *= self.StageHalfWidth
        y *= self.StageHalfHeight
        return (x, y)

    def showPosts(self):
        self.hidePosts()
        self.posts = [Toon.Toon(),
         Toon.Toon(),
         Toon.Toon(),
         Toon.Toon()]
        for i in xrange(len(self.posts)):
            tree = self.posts[i]
            tree.reparentTo(render)
            x = self.StageHalfWidth
            y = self.StageHalfHeight
            if i > 1:
                x = -x
            if i % 2:
                y = -y
            tree.setPos(x + self.x, y + self.y, 0)

    def hidePosts(self):
        if hasattr(self, 'posts'):
            for tree in self.posts:
                tree.removeNode()

            del self.posts

    def showDropGrid(self):
        self.hideDropGrid()
        self.dropMarkers = []
        for row in xrange(self.DropRows):
            self.dropMarkers.append([])
            rowList = self.dropMarkers[row]
            for column in xrange(self.DropColumns):
                toon = Toon.Toon()
                toon.setDNA(base.localAvatar.getStyle())
                toon.reparentTo(self.root)
                toon.setScale(1.0 / 3)
                x, y = self.grid2world(column, row)
                toon.setPos(x, y, 0)
                rowList.append(toon)

    def hideDropGrid(self):
        if hasattr(self, 'dropMarkers'):
            for row in self.dropMarkers:
                for marker in row:
                    marker.removeNode()

            del self.dropMarkers

    def handleToonDisabled(self, avId):
        DistributedPartyCatchActivity.notify.debug('handleToonDisabled')
        DistributedPartyCatchActivity.notify.debug('avatar ' + str(avId) + ' disabled')
        if avId in self.toonSDs:
            self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]

    def turnOffSmoothingOnGuests(self):
        pass

    def setState(self, newState, timestamp):
        self.notify.info('setState(%s, %s)' % (newState, timestamp))
        DistributedPartyCatchActivity.notify.debug('setState( newState=%s, ... )' % newState)
        DistributedPartyActivity.setState(self, newState, timestamp)
        self.activityFSM.request(newState)
        if newState == 'Active':
            if base.localAvatar.doId != self.party.partyInfo.hostId:
                if globalClock.getFrameCount() > self._generateFrame:
                    if base.localAvatar.getX() > self.x - self.StageHalfWidth and base.localAvatar.getX() < self.x + self.StageHalfWidth and base.localAvatar.getY() > self.y - self.StageHalfHeight and base.localAvatar.getY() < self.y + self.StageHalfHeight:
                        self._toonEnteredTree(None)
        return

    def putLocalAvatarInActivity(self):
        if base.cr.playGame.getPlace() and hasattr(base.cr.playGame.getPlace(), 'fsm'):
            base.cr.playGame.getPlace().fsm.request('activity', [False])
        else:
            self.notify.info("Avoided crash: toontown.parties.DistributedPartyCatchActivity:632, toontown.parties.DistributedPartyCatchActivity:1198, toontown.parties.activityFSMMixins:49, direct.fsm.FSM:423, AttributeError: 'NoneType' object has no attribute 'fsm'")
        base.localAvatar.stopUpdateSmartCamera()
        base.camera.reparentTo(self.treesAndFence)
        base.camera.setPosHpr(0.0, -63.0, 30.0, 0.0, -20.0, 0.0)
        if not hasattr(self, 'ltLegsCollNode'):
            self.createCatchCollisions()

    def createCatchCollisions(self):
        radius = 0.7
        handler = CollisionHandlerEvent()
        handler.setInPattern('ltCatch%in')
        self.ltLegsCollNode = CollisionNode('catchLegsCollNode')
        self.ltLegsCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltHeadCollNode = CollisionNode('catchHeadCollNode')
        self.ltHeadCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltLHandCollNode = CollisionNode('catchLHandCollNode')
        self.ltLHandCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltRHandCollNode = CollisionNode('catchRHandCollNode')
        self.ltRHandCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        legsCollNodepath = base.localAvatar.attachNewNode(self.ltLegsCollNode)
        legsCollNodepath.hide()
        head = base.localAvatar.getHeadParts().getPath(2)
        headCollNodepath = head.attachNewNode(self.ltHeadCollNode)
        headCollNodepath.hide()
        lHand = base.localAvatar.getLeftHands()[0]
        lHandCollNodepath = lHand.attachNewNode(self.ltLHandCollNode)
        lHandCollNodepath.hide()
        rHand = base.localAvatar.getRightHands()[0]
        rHandCollNodepath = rHand.attachNewNode(self.ltRHandCollNode)
        rHandCollNodepath.hide()
        base.localAvatar.cTrav.addCollider(legsCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(headCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(lHandCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(lHandCollNodepath, handler)
        if self.ShowToonSpheres:
            legsCollNodepath.show()
            headCollNodepath.show()
            lHandCollNodepath.show()
            rHandCollNodepath.show()
        self.ltLegsCollNode.addSolid(CollisionSphere(0, 0, radius, radius))
        self.ltHeadCollNode.addSolid(CollisionSphere(0, 0, 0, radius))
        self.ltLHandCollNode.addSolid(CollisionSphere(0, 0, 0, 2 * radius / 3.0))
        self.ltRHandCollNode.addSolid(CollisionSphere(0, 0, 0, 2 * radius / 3.0))
        self.toonCollNodes = [legsCollNodepath,
         headCollNodepath,
         lHandCollNodepath,
         rHandCollNodepath]

    def destroyCatchCollisions(self):
        if not hasattr(self, 'ltLegsCollNode'):
            return
        for collNode in self.toonCollNodes:
            while collNode.node().getNumSolids():
                collNode.node().removeSolid(0)

            base.localAvatar.cTrav.removeCollider(collNode)

        del self.toonCollNodes
        del self.ltLegsCollNode
        del self.ltHeadCollNode
        del self.ltLHandCollNode
        del self.ltRHandCollNode

    def timerExpired(self):
        pass

    def __handleCatch(self, generation, objNum):
        DistributedPartyCatchActivity.notify.debug('catch: %s' % [generation, objNum])
        if base.localAvatar.doId not in self.toonIds:
            return
        self.showCatch(base.localAvatar.doId, generation, objNum)
        objName = self._id2gen[generation].droppedObjNames[objNum]
        objTypeId = PartyGlobals.Name2DOTypeId[objName]
        self.sendUpdate('claimCatch', [generation, objNum, objTypeId])
        self.finishDropInterval(generation, objNum)

    def showCatch(self, avId, generation, objNum):
        if avId not in self.toonSDs:
            return
        isLocal = avId == base.localAvatar.doId
        if generation not in self._id2gen:
            return
        if not self._id2gen[generation].hasBeenScheduled:
            return
        objName = self._id2gen[generation].droppedObjNames[objNum]
        objType = PartyGlobals.Name2DropObjectType[objName]
        if objType.good:
            if objNum not in self._id2gen[generation].droppedObjCaught:
                if isLocal:
                    base.playSfx(self.sndGoodCatch)
                fruit = self.getObjModel(objName)
                toon = self.getAvatar(avId)
                rHand = toon.getRightHands()[1]
                self.toonSDs[avId].eatFruit(fruit, rHand)
        else:
            self.toonSDs[avId].fsm.request('fallForward')
        self._id2gen[generation].droppedObjCaught[objNum] = 1

    def setObjectCaught(self, avId, generation, objNum):
        self.notify.info('setObjectCaught(%s, %s, %s)' % (avId, generation, objNum))
        if self.activityFSM.state != 'Active':
            DistributedPartyCatchActivity.notify.warning('ignoring msg: object %s caught by %s' % (objNum, avId))
            return
        isLocal = avId == base.localAvatar.doId
        if not isLocal:
            DistributedPartyCatchActivity.notify.debug('AI: avatar %s caught %s' % (avId, objNum))
            self.finishDropInterval(generation, objNum)
            self.showCatch(avId, generation, objNum)
        self._scheduleGenerations()
        gen = self._id2gen[generation]
        if gen.hasBeenScheduled:
            objName = gen.droppedObjNames[objNum]
            if PartyGlobals.Name2DropObjectType[objName].good:
                if hasattr(self, 'fruitsCaught'):
                    self.fruitsCaught += 1

    def finishDropInterval(self, generation, objNum):
        if hasattr(self, 'dropIntervals'):
            if (generation, objNum) in self.dropIntervals:
                self.dropIntervals[generation, objNum].finish()

    def finishAllDropIntervals(self):
        if hasattr(self, 'dropIntervals'):
            for dropInterval in self.dropIntervals.values():
                dropInterval.finish()

    def setGenerations(self, generations):
        self.notify.info('setGenerations(%s)' % (generations,))
        gen2t = {}
        gen2nt = {}
        gen2np = {}
        for id, timestamp32, numPlayers in generations:
            gen2t[id] = globalClockDelta.networkToLocalTime(timestamp32, bits=32) - self._startTimestamp
            gen2nt[id] = timestamp32
            gen2np[id] = numPlayers

        ids = self._id2gen.keys()
        for id in ids:
            if id not in gen2t:
                self._removeGeneration(id)

        for id in gen2t:
            if id not in self._id2gen:
                self._addGeneration(id, gen2t[id], gen2nt[id], gen2np[id])

    def scheduleDrops(self, genId = None):
        if genId is None:
            genId = self.getCurGeneration()
        gen = self._id2gen[genId]
        if gen.hasBeenScheduled:
            return
        fruitIndex = int((gen.startTime + 0.5 * self.DropPeriod) / PartyGlobals.CatchActivityDuration)
        fruitNames = ['apple',
         'orange',
         'pear',
         'coconut',
         'watermelon',
         'pineapple']
        fruitName = fruitNames[fruitIndex % len(fruitNames)]
        rng = RandomNumGen(genId + self._generationSeedBase)
        gen.droppedObjNames = [fruitName] * self.numFruits + ['anvil'] * self.numAnvils
        rng.shuffle(gen.droppedObjNames)
        dropPlacer = PartyRegionDropPlacer(self, gen.numPlayers, genId, gen.droppedObjNames, startTime=gen.startTime)
        gen.numItemsDropped = 0
        tIndex = gen.startTime % PartyGlobals.CatchActivityDuration
        tPercent = float(tIndex) / PartyGlobals.CatchActivityDuration
        gen.numItemsDropped += dropPlacer.skipPercent(tPercent)
        while not dropPlacer.doneDropping(continuous=True):
            nextDrop = dropPlacer.getNextDrop()
            gen.dropSchedule.append(nextDrop)

        gen.hasBeenScheduled = True
        return

    def startDropTask(self):
        taskMgr.add(self.dropTask, self.DropTaskName)

    def stopDropTask(self):
        taskMgr.remove(self.DropTaskName)

    def _scheduleGenerations(self):
        curT = self.getCurrentCatchActivityTime()
        genIndex = self._orderedGenerationIndex
        newGenIndex = genIndex
        while genIndex is None or genIndex < len(self._orderedGenerations) - 1:
            if genIndex is None:
                nextGenIndex = 0
            else:
                nextGenIndex = genIndex + 1
            nextGenId = self._orderedGenerations[nextGenIndex]
            nextGen = self._id2gen[nextGenId]
            startT = nextGen.startTime
            if curT >= startT:
                newGenIndex = nextGenIndex
            if not nextGen.hasBeenScheduled:
                self.defineConstants(forceNumPlayers=nextGen.numPlayers)
                self.scheduleDrops(genId=self._orderedGenerations[nextGenIndex])
            genIndex = nextGenIndex

        self._orderedGenerationIndex = newGenIndex
        return

    def dropTask(self, task):
        self._scheduleGenerations()
        curT = self.getCurrentCatchActivityTime()
        if self._orderedGenerationIndex is not None:
            i = self._orderedGenerationIndex
            genIndex = self._orderedGenerations[i]
            gen = self._id2gen[genIndex]
            while len(gen.dropSchedule) > 0 and gen.dropSchedule[0][0] < curT:
                drop = gen.dropSchedule[0]
                gen.dropSchedule = gen.dropSchedule[1:]
                dropTime, objName, dropCoords = drop
                objNum = gen.numItemsDropped
                x, y = self.grid2world(*dropCoords)
                dropIval = self.getDropIval(x, y, objName, genIndex, objNum)

                def cleanup(generation, objNum, self = self):
                    del self.dropIntervals[generation, objNum]

                dropIval.append(Func(Functor(cleanup, genIndex, objNum)))
                self.dropIntervals[genIndex, objNum] = dropIval
                gen.numItemsDropped += 1
                dropIval.start(curT - dropTime)
                self._lastDropTime = dropTime

        return Task.cont

    def getDropIval(self, x, y, dropObjName, generation, num):
        objType = PartyGlobals.Name2DropObjectType[dropObjName]
        id = (generation, num)
        dropNode = hidden.attachNewNode('catchDropNode%s' % (id,))
        dropNode.setPos(x, y, 0)
        shadow = self.dropShadow.copyTo(dropNode)
        shadow.setZ(PartyGlobals.CatchDropShadowHeight)
        shadow.setColor(1, 1, 1, 1)
        object = self.getObjModel(dropObjName)
        object.reparentTo(hidden)
        if dropObjName in ['watermelon', 'anvil']:
            objH = object.getH()
            absDelta = {'watermelon': 12,
             'anvil': 15}[dropObjName]
            delta = (self.randomNumGen.random() * 2.0 - 1.0) * absDelta
            newH = objH + delta
        else:
            newH = self.randomNumGen.random() * 360.0
        object.setH(newH)
        sphereName = 'FallObj%s' % (id,)
        radius = self.ObjRadius
        if objType.good:
            radius *= lerp(1.0, 1.3, 0.5)
        collSphere = CollisionSphere(0, 0, 0, radius)
        collSphere.setTangible(0)
        collNode = CollisionNode(sphereName)
        collNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        collNode.addSolid(collSphere)
        collNodePath = object.attachNewNode(collNode)
        collNodePath.hide()
        if self.ShowObjSpheres:
            collNodePath.show()
        catchEventName = 'ltCatch' + sphereName

        def eatCollEntry(forward, collEntry):
            forward()

        self.accept(catchEventName, Functor(eatCollEntry, Functor(self.__handleCatch, id[0], id[1])))

        def cleanup(self = self, dropNode = dropNode, id = id, event = catchEventName):
            self.ignore(event)
            dropNode.removeNode()

        duration = objType.fallDuration
        onscreenDuration = objType.onscreenDuration
        targetShadowScale = 0.3
        if self.trickShadows:
            intermedScale = targetShadowScale * (self.OffscreenTime / self.BaselineDropDuration)
            shadowScaleIval = Sequence(LerpScaleInterval(shadow, self.OffscreenTime, intermedScale, startScale=0))
            shadowScaleIval.append(LerpScaleInterval(shadow, duration - self.OffscreenTime, targetShadowScale, startScale=intermedScale))
        else:
            shadowScaleIval = LerpScaleInterval(shadow, duration, targetShadowScale, startScale=0)
        targetShadowAlpha = 0.4
        shadowAlphaIval = LerpColorScaleInterval(shadow, self.OffscreenTime, Point4(1, 1, 1, targetShadowAlpha), startColorScale=Point4(1, 1, 1, 0))
        shadowIval = Parallel(shadowScaleIval, shadowAlphaIval)
        if self.useGravity:

            def setObjPos(t, objType = objType, object = object):
                z = objType.trajectory.calcZ(t)
                object.setZ(z)

            setObjPos(0)
            dropIval = LerpFunctionInterval(setObjPos, fromData=0, toData=onscreenDuration, duration=onscreenDuration)
        else:
            startPos = Point3(0, 0, self.MinOffscreenHeight)
            object.setPos(startPos)
            dropIval = LerpPosInterval(object, onscreenDuration, Point3(0, 0, 0), startPos=startPos, blendType='easeIn')
        ival = Sequence(Func(Functor(dropNode.reparentTo, self.root)), Parallel(Sequence(WaitInterval(self.OffscreenTime), Func(Functor(object.reparentTo, dropNode)), dropIval), shadowIval), Func(cleanup), name='drop%s' % (id,))
        if objType == PartyGlobals.Name2DropObjectType['anvil']:
            ival.append(Func(self.playAnvil))
        return ival

    def playAnvil(self):
        if base.localAvatar.doId in self.toonIds:
            base.playSfx(self.sndAnvilLand)

    def initOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug('startOrthoWalk')

        def doCollisions(oldPos, newPos, self = self):
            x = bound(newPos[0], self.StageHalfWidth, -self.StageHalfWidth)
            y = bound(newPos[1], self.StageHalfHeight, -self.StageHalfHeight)
            newPos.setX(x)
            newPos.setY(y)
            return newPos

        orthoDrive = OrthoDrive(self.ToonSpeed, instantTurn=True)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)

    def destroyOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug('destroyOrthoWalk')
        if hasattr(self, 'orthoWalk'):
            self.orthoWalk.stop()
            self.orthoWalk.destroy()
            del self.orthoWalk

    def startIdle(self):
        DistributedPartyCatchActivity.notify.debug('startIdle')

    def finishIdle(self):
        DistributedPartyCatchActivity.notify.debug('finishIdle')

    def startActive(self):
        DistributedPartyCatchActivity.notify.debug('startActive')
        for avId in self.toonIds:
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.enter()
                toonSD.fsm.request('normal')

        self.fruitsCaught = 0
        self.dropIntervals = {}
        self.startDropTask()
        if base.localAvatar.doId in self.toonIds:
            self.putLocalAvatarInActivity()

    def finishActive(self):
        DistributedPartyCatchActivity.notify.debug('finishActive')
        self.stopDropTask()
        if hasattr(self, 'finishIval'):
            self.finishIval.pause()
            del self.finishIval
        if base.localAvatar.doId in self.toonIds:
            self.takeLocalAvatarOutOfActivity()
        for ival in self.dropIntervals.values():
            ival.finish()

        del self.dropIntervals

    def startConclusion(self):
        DistributedPartyCatchActivity.notify.debug('startConclusion')
        for avId in self.toonIds:
            if avId in self.toonSDs:
                toonSD = self.toonSDs[avId]
                toonSD.fsm.request('notPlaying')

        self.destroyCatchCollisions()
        if base.localAvatar.doId not in self.toonIds:
            return
        else:
            self.localToonExiting()
        if self.fruitsCaught >= self.numFruits:
            finishText = TTLocalizer.PartyCatchActivityFinishPerfect
        else:
            finishText = TTLocalizer.PartyCatchActivityFinish
        perfectTextSubnode = hidden.attachNewNode(self.__genText(finishText))
        perfectText = hidden.attachNewNode('perfectText')
        perfectTextSubnode.reparentTo(perfectText)
        frame = self.__textGen.getCardActual()
        offsetY = -abs(frame[2] + frame[3]) / 2.0
        perfectTextSubnode.setPos(0, 0, offsetY)
        perfectText.setColor(1, 0.1, 0.1, 1)

        def fadeFunc(t, text = perfectText):
            text.setColorScale(1, 1, 1, t)

        def destroyText(text = perfectText):
            text.removeNode()

        textTrack = Sequence(Func(perfectText.reparentTo, aspect2d), Parallel(LerpScaleInterval(perfectText, duration=0.5, scale=0.3, startScale=0.0), LerpFunctionInterval(fadeFunc, fromData=0.0, toData=1.0, duration=0.5)), Wait(2.0), Parallel(LerpScaleInterval(perfectText, duration=0.5, scale=1.0), LerpFunctionInterval(fadeFunc, fromData=1.0, toData=0.0, duration=0.5, blendType='easeIn')), Func(destroyText), WaitInterval(0.5))
        soundTrack = SoundInterval(self.sndPerfect)
        self.finishIval = Parallel(textTrack, soundTrack)
        self.finishIval.start()

    def finishConclusion(self):
        DistributedPartyCatchActivity.notify.debug('finishConclusion')
        if base.localAvatar.doId in self.toonIds:
            self.takeLocalAvatarOutOfActivity()
            base.cr.playGame.getPlace().fsm.request('walk')

    def showJellybeanReward(self, earnedAmount, jarAmount, message):
        if earnedAmount > 0:
            DistributedPartyActivity.showJellybeanReward(self, earnedAmount, jarAmount, message)
        else:
            base.cr.playGame.getPlace().fsm.request('walk')
class DistributedNPCToon(DistributedToon):
    notify = directNotify.newCategory('DistributedNPCToon')

    def __init__(self, cr):
        DistributedToon.__init__(self, cr)
        self.collisionNodePath = None
        self.cameraTrack = None
        self.originIndex = None
        self.npcId = None
        self.currentChatIndex = 0
        self.chatArray = None
        return

    def setLoadout(self, foo):
        pass

    def lookAtAvatar(self, avId):
        av = self.cr.doId2do.get(avId)
        if av:
            self.headsUp(av)

    def setNpcId(self, id):
        self.npcId = id

    def getNpcId(self):
        return self.npcId

    def setOriginIndex(self, index):
        self.originIndex = index

    def getOriginIndex(self):
        return self.originIndex

    def __setupCollisions(self):
        sphere = CollisionSphere(0, 0, 0, 4)
        sphere.setTangible(0)
        collisionNode = CollisionNode(self.uniqueName('NPCToonSphere'))
        collisionNode.addSolid(sphere)
        collisionNode.setCollideMask(CIGlobals.WallBitmask)
        self.collisionNodePath = self.attachNewNode(collisionNode)
        self.collisionNodePath.setY(1.5)

    def __removeCollisions(self):
        if self.collisionNodePath:
            self.collisionNodePath.removeNode()
            self.collisionNodePath = None
        return

    def handleEnterCollision(self, entry):
        self.cr.playGame.getPlace().fsm.request('stop')
        base.localAvatar.stopSmartCamera()
        self.sendUpdate('requestEnter', [])

    def doCameraNPCInteraction(self):
        currCamPos = camera.getPos()
        currCamHpr = camera.getHpr()
        camera.setX(camera.getX() + 5)
        camera.setY(camera.getY() + 5)
        camera.headsUp(self)
        newCamPos = camera.getPos()
        newCamHpr = camera.getHpr()
        camera.setPos(currCamPos)
        camera.setHpr(currCamHpr)
        self.cameraTrack = Parallel(
            LerpPosInterval(camera,
                            duration=1.0,
                            pos=newCamPos,
                            startPos=currCamPos,
                            blendType='easeOut'),
            LerpQuatInterval(camera,
                             duration=1.0,
                             quat=newCamHpr,
                             startHpr=currCamHpr,
                             blendType='easeOut'))
        self.cameraTrack.start()

    def stopCameraTrack(self):
        if self.cameraTrack:
            self.cameraTrack.finish()
            self.cameraTrack = None
        return

    def oneChatThenExit(self):
        self.acceptOnce('mouse1-up', self.d_requestExit)

    def enterAccepted(self):
        self.doCameraNPCInteraction()
        questData = base.localAvatar.questManager.getQuestAndIdWhereCurrentObjectiveIsToVisit(
            self.npcId)
        if questData:
            quest = questData[1]
            self.currentQuestObjective = quest.currentObjectiveIndex
            self.currentQuestId = questData[0]
            self.currentChatIndex = 0
            if CIGlobals.NPCToonDict[self.npcId][3] == CIGlobals.NPC_HQ:
                if not quest.isComplete():
                    self.doNPCChat(array=Quests.QuestHQOfficerDialogue)
            if CIGlobals.NPCToonDict[self.npcId][3] == CIGlobals.NPC_REGULAR:
                self.doNPCChat(array=Quests.QuestNPCDialogue)

    def doNPCChat(self, array=Quests.QuestNPCDialogue, chat=None):
        if array and not chat:
            self.chatArray = array
            self.b_setChat(array[self.currentQuestId][
                self.currentQuestObjective][self.currentChatIndex])
            self.currentChatIndex += 1
            Sequence(Wait(0.1),
                     Func(self.acceptOnce, 'mouse1-up',
                          self.doNextNPCChat)).start()
        else:
            if chat and not array:
                self.b_setChat(chat)
                Sequence(
                    Wait(0.1),
                    Func(self.acceptOnce, 'mouse1-up',
                         self.d_requestExit)).start()

    def d_requestExit(self):
        self.sendUpdate('requestExit', [])

    def doNextNPCChat(self):
        if self.currentChatIndex >= len(self.chatArray[self.currentQuestId][
                self.currentQuestObjective]):
            self.chatArray = None
            self.d_requestExit()
        else:
            self.doNPCChat(self.chatArray)
        return

    def rejectEnter(self):
        self.exitAccepted()

    def exitAccepted(self):
        self.stopCameraTrack()
        self.cr.playGame.getPlace().fsm.request('walk')
        self.acceptCollisions()

    def acceptCollisions(self):
        self.acceptOnce('enter' + self.uniqueName('NPCToonSphere'),
                        self.handleEnterCollision)

    def ignoreCollisions(self):
        self.ignore('enter' + self.uniqueName('NPCToonSphere'))

    def __npcOriginPoll(self, task):
        if task.time > 4.0:
            self.notify.warning(
                'Giving up waiting for npc origin after %d seconds. Will parent to render.'
                % task.time)
            self.reparentTo(render)
            return task.done
        npcOrigin = render.find('**/npc_origin_' + str(self.originIndex))
        if not npcOrigin.isEmpty():
            self.reparentTo(npcOrigin)
            return task.done
        return task.cont

    def startNPCOriginPoll(self):
        base.taskMgr.add(self.__npcOriginPoll,
                         self.uniqueName('NPCOriginPoll'))

    def stopNPCOriginPoll(self):
        base.taskMgr.remove(self.uniqueName('NPCOriginPoll'))

    def setupNameTag(self, tempName=None):
        DistributedToon.setupNameTag(self, tempName)
        self.nametag.setNametagColor(
            NametagGlobals.NametagColors[NametagGlobals.CCNPC])
        self.nametag.setActive(0)
        self.nametag.updateAll()

    def announceGenerate(self):
        DistributedToon.announceGenerate(self)
        self.startLookAround()
        self.__setupCollisions()
        npcOrigin = render.find('**/npc_origin_' + str(self.originIndex))
        if not npcOrigin.isEmpty():
            self.reparentTo(npcOrigin)
        else:
            self.startNPCOriginPoll()
        self.acceptCollisions()

    def disable(self):
        self.ignore('mouse1-up')
        self.stopLookAround()
        self.stopNPCOriginPoll()
        self.chatArray = None
        self.originIndex = None
        self.npcId = None
        self.stopCameraTrack()
        self.ignoreCollisions()
        self.__removeCollisions()
        DistributedToon.disable(self)
        return
class BambooCane(ToonUpGag):
    def __init__(self):
        ToonUpGag.__init__(self, CIGlobals.BambooCane,
                           'phase_5/models/props/cane.bam', 40, 45, 100,
                           GagGlobals.BAMBOO_CANE_SFX, 1)
        self.setImage('phase_3.5/maps/bamboo-cane.png')
        self.hatPath = 'phase_5/models/props/hat.bam'
        self.hat = None
        self.track = None
        self.scaleDuration = 0.5
        self.scaleUp = None
        self.scaleDown = None
        self.soundInterval = None
        self.timeout = 5.0

    def buildHat(self):
        self.cleanupHat()
        self.hat = loader.loadModel(self.hatPath)

    def cleanupHat(self):
        if self.hat:
            self.hat.removeNode()

    def buildScaleTracks(self):
        props = []
        props.append(self.hat)
        props.append(self.gag)
        self.scaleUp = self.getScaleTrack(props, self.scaleDuration,
                                          self.PNTNEARZERO, self.PNTNORMAL)
        self.scaleDown = self.getScaleTrack(props, self.scaleDuration,
                                            self.PNTNORMAL, self.PNTNEARZERO)

    def start(self):
        super(BambooCane, self).start()
        if not self.hat:
            self.buildHat()
        if not self.gag:
            self.build()
        self.setupHandJoints()
        if not self.scaleUp:
            self.buildScaleTracks()
        self.placeProp(self.handJoint, self.hat, Point3(0.23, 0.09, 0.69),
                       Point3(180, 0, 0))
        self.placeProp(self.lHandJoint, self.gag, Point3(-0.28, 0.0, 0.14),
                       Point3(0.0, 0.0, -150.0))
        self.soundInterval = self.getSoundTrack(0.2, self.gag, 6.4)
        propInterval = Sequence()
        propInterval.append(self.scaleUp)
        propInterval.append(
            Wait(base.localAvatar.getDuration('happy-dance') - 2))
        if self.avatar == base.localAvatar:
            propInterval.append(Func(self.setHealAmount))
            propInterval.append(Func(self.healNearbyAvatars, 25))
        propInterval.append(self.scaleDown)
        propInterval.append(Func(self.cleanupGag))
        propInterval.append(Func(self.reset))
        self.track = Parallel(propInterval,
                              ActorInterval(self.avatar, 'happy-dance'),
                              Func(self.soundInterval.start))
        self.track.start()

    def equip(self):
        # self.gag returns the cane object.
        super(BambooCane, self).equip()
        self.build()
        self.buildHat()
        self.buildScaleTracks()

    def unEquip(self):
        if self.track:
            self.soundInterval.finish()
            self.track.finish()
            self.track = None
        self.reset()
        if self.scaleDown:
            Sequence(self.scaleDown, Func(self.cleanupGag)).start()

    def cleanupGag(self):
        if self.gag:
            self.gag.removeNode()
        self.cleanupHat()
Beispiel #45
0
class PairingGameCard(PlayingCardNodePath):
    """
    The specifc class used for the pairing game
    """
    DoIntervalDefault = True
    FlipTime = 0.25
    UseDifferentCardColors = True

    # these color values were taken from ToonDNA.py
    CardColors = [
        (0.933594, 0.265625, 0.28125, 1.0),  # bright red
        (0.550781, 0.824219, 0.324219, 1.0),  # light green
        (0.347656, 0.820312, 0.953125, 1.0),  # light blue
        (0.460938, 0.378906, 0.824219, 1.0),  # purple blue
        (0.710938, 0.234375, 0.4375, 1.0),  # plum
        (0.285156, 0.328125, 0.726562, 1.0),  # blue
        (0.242188, 0.742188, 0.515625, 1.0),  # seafoam
        (0.96875, 0.691406, 0.699219, 1.0),  # light pink
        (0.996094, 0.957031, 0.597656, 1.0),  # light yellow
        (0.992188, 0.480469, 0.167969, 1.0),  # orange
    ]

    def __init__(self, value):
        """Constructor, value should be [0..51]."""
        style = PlayingCardGlobals.Styles[0]
        PlayingCardNodePath.__init__(self, style, value)
        self.enterCallback = None
        self.exitCallback = None

    def load(self):
        """Load the assets."""
        # these are just temp assets
        oneCard = loader.loadModel(
            "phase_4/models/minigames/garden_sign_memory")

        # grab the gag icon
        prop = self.attachNewNode('prop')
        PlayingCardGlobals.getImage(self.style, self.suit,
                                    self.rank).copyTo(prop)
        prop.setScale(7)

        # remove the bits we don't want
        oneCard.find('**/glow').removeNode()
        #oneCard.find('**/shadow').removeNode()
        # munge the collision to fit just the sign
        cs = oneCard.find('**/collision')
        #cs.setScale(1, 1.0, 0.5)
        #cs.setPos(0,0, 0.9)
        for solidIndex in range(cs.node().getNumSolids()):
            cs.node().modifySolid(solidIndex).setTangible(False)
        cs.node().setName('cardCollision-%d' % self.value)

        # munge the sign to fit the rank
        sign = oneCard.find('**/sign1')
        if self.UseDifferentCardColors:
            index = self.rank % len(self.CardColors)
            color = self.CardColors[index]
            sign.setColorScale(*color)

        # set up the prop that shows which tree it is
        prop.setPos(0.0, 0.0, 0.08)
        prop.setP(-90)
        prop.reparentTo(oneCard)

        oneCard.reparentTo(self)

        #set up the back of the card
        cardBack = oneCard.find('**/sign2')
        cardBack.setColorScale(0.12, 0.35, 0.5, 1.0)
        cardModel = loader.loadModel('phase_3.5/models/gui/playingCard')
        logo = cardModel.find('**/logo')
        logo.reparentTo(self)
        logo.setScale(0.45)
        logo.setP(90)
        logo.setZ(0.025)
        logo.setX(-0.05)
        logo.setH(180)

        cardModel.remove()

        self.setR(0)  # the default value is face Up
        self.setScale(2.5)

        self.flipIval = None

        self.turnUpSound = base.loadSfx(
            "phase_4/audio/sfx/MG_pairing_card_flip_face_up.mp3")
        self.turnDownSound = base.loadSfx(
            "phase_4/audio/sfx/MG_pairing_card_flip_face_down.mp3")

    def unload(self):
        """Unload the assets."""
        self.clearFlipIval()
        self.removeNode()
        del self.turnUpSound
        del self.turnDownSound

    def turnUp(self, doInterval=DoIntervalDefault):
        """Turn up the card.

        doInterval -- if true do a sound and flip up animation
        
        """
        assert self.value != PlayingCardGlobals.Unknown
        self.faceUp = 1
        if doInterval:
            self.clearFlipIval()
            self.flipIval = Parallel(
                LerpHprInterval(self, self.FlipTime, Vec3(0, 0, 0)),
                SoundInterval(self.turnUpSound,
                              node=self,
                              listenerNode=base.localAvatar,
                              cutOff=240))
            self.flipIval.start()
        else:
            self.setR(0)

    def clearFlipIval(self):
        """Clear any flip intervals on this card."""
        if self.flipIval:
            self.flipIval.finish()
            self.flipIval = None

    def turnDown(self, doInterval=DoIntervalDefault):
        """Turn up the card.

        doInterval -- if true do a sound and flip up animation
        
        """
        self.faceUp = 0
        if doInterval:
            self.clearFlipIval()
            self.flipIval = Parallel(
                LerpHprInterval(self, self.FlipTime, Vec3(0, 0, 180)),
                SoundInterval(self.turnDownSound,
                              node=self,
                              listenerNode=base.localAvatar,
                              cutOff=240))
            self.flipIval.start()
        else:
            self.setR(180)
class DistributedBoat(DistributedObject):
    notify = directNotify.newCategory("DistributedBoat")

    def __init__(self, cr):
        DistributedObject.__init__(self, cr)
        self.fsm = ClassicFSM('DistributedBoat', [
            State('off', self.enterOff, self.exitOff),
            State('eastToWest', self.enterEastToWest, self.exitEastToWest),
            State('westToEast', self.enterWestToEast, self.exitWestToEast)
        ], 'off', 'off')
        self.boat = None
        self.eastPier = None
        self.eastPierPath = 'east_pier'
        self.westPier = None
        self.westPierPath = 'west_pier'
        self.pierUpP = 0.0
        self.pierDownP = -45.0
        self.fogHorn = 'phase_5/audio/sfx/SZ_DD_foghorn.ogg'
        self.shipBell = 'phase_6/audio/sfx/SZ_DD_shipbell.ogg'
        self.waterLap = 'phase_6/audio/sfx/SZ_DD_waterlap.ogg'
        self.dockCreak = 'phase_6/audio/sfx/SZ_DD_dockcreak.ogg'
        self.eastWest = 'phase_6/paths/dd-e-w.bam'
        self.westEast = 'phase_6/paths/dd-w-e.bam'
        self.track = None
        self.state = None
        self.animBoat1Track = None
        self.animBoatTrack = None

        # Variables that handle the winter collision node.
        self.crashColl = None
        self.crashCollNP = None

    def __handleOnBoat(self, entry):
        base.localAvatar.b_setParent(CIGlobals.SPDonaldsBoat)

    def __handleOffBoat(self, entry):
        base.localAvatar.b_setParent(CIGlobals.SPRender)
        base.localAvatar.setR(0)
        base.localAvatar.setP(0)

    def generate(self):
        DistributedObject.generate(self)
        self.soundFogHorn = base.audio3d.loadSfx(self.fogHorn)
        self.soundShipBell = base.audio3d.loadSfx(self.shipBell)
        self.soundWaterLap = base.audio3d.loadSfx(self.waterLap)
        self.soundDockCreak = base.audio3d.loadSfx(self.dockCreak)

        geom = self.cr.playGame.hood.loader.geom
        self.boatMdl = geom.find('**/*donalds_boat*')
        self.boat = geom.find("**/ddBoatRoot")
        self.boatMdl1 = geom.find("**/ddBoatMdl1")

        base.audio3d.attachSoundToObject(self.soundFogHorn, self.boat)
        base.audio3d.attachSoundToObject(self.soundShipBell, self.boat)
        base.audio3d.attachSoundToObject(self.soundWaterLap, self.boat)
        base.audio3d.attachSoundToObject(self.soundDockCreak, self.boat)

        self.soundWaterLap.setLoop(True)
        self.soundWaterLap.play()

        self.generated()

    def generated(self):
        self.eastPier = self.cr.playGame.hood.loader.geom.find(
            '**/' + self.eastPierPath)
        self.westPier = self.cr.playGame.hood.loader.geom.find(
            '**/' + self.westPierPath)
        base.cr.parentMgr.registerParent(CIGlobals.SPDonaldsBoat, self.boatMdl)
        self.accept('enterdonalds_boat_floor', self.__handleOnBoat)
        self.accept('exitdonalds_boat_floor', self.__handleOffBoat)
        self.d_requestCurrentStateAndTimestamp()
        self.fsm.enterInitialState()

        speedFactor = 2

        self.animBoatTrack = Sequence(
            LerpHprInterval(self.boatMdl,
                            duration=1 * speedFactor,
                            hpr=(0, 0, -1),
                            startHpr=(0, 0, 1),
                            blendType='easeInOut'),
            LerpHprInterval(self.boatMdl,
                            duration=1 * speedFactor,
                            hpr=(0, 0, 1),
                            startHpr=(0, 0, -1),
                            blendType='easeInOut'))

        import math

        self.animBoat1Track = Sequence(
            LerpHprInterval(self.boatMdl1,
                            duration=math.pi * speedFactor,
                            hpr=(0, 1, 0),
                            startHpr=(0, -1, 0),
                            blendType='easeInOut'),
            LerpHprInterval(self.boatMdl1,
                            duration=math.pi * speedFactor,
                            hpr=(0, -1, 0),
                            startHpr=(0, 1, 0),
                            blendType='easeInOut'))
        self.animBoat1Track.loop()
        self.animBoatTrack.loop()

        if base.cr.holidayManager.getHoliday() == HolidayType.CHRISTMAS:
            self.boat.setPosHpr(12.73, -1.6, -4.7, 341.57, 350.0, 26.5)
            self.fsm.request('off')

            self.crashColl = CollisionSphere(0, 0, 0, 15)
            self.crashCollNP = self.boat.attachNewNode(
                CollisionNode('crashed_boat_collision'))
            self.crashCollNP.node().addSolid(self.crashColl)
            self.crashCollNP.node().setCollideMask(CIGlobals.WallBitmask)
            self.crashCollNP.setSz(2)
            self.crashCollNP.setSx(0.75)
            self.crashCollNP.setSy(1.25)
            self.crashCollNP.setPosHpr(2.05, 3.21, 1.66, 8.44, 6.93, 332.61)

    def disable(self):
        base.taskMgr.remove(self.uniqueName('__pollBoat'))
        base.cr.parentMgr.unregisterParent(CIGlobals.SPDonaldsBoat)
        self.ignore('enterdonalds_boat_floor')
        self.ignore('exitdonalds_boat_floor')
        self.fsm.requestFinalState()

        if self.animBoat1Track:
            self.animBoat1Track.finish()
            self.animBoat1Track = None
        if self.animBoatTrack:
            self.animBoatTrack.finish()
            self.animBoatTrack = None

        if self.crashCollNP:
            self.crashCollNP.removeNode()
            del self.crashCollNP
            del self.crashColl

        del self.fsm
        del self.soundFogHorn
        del self.soundShipBell
        self.soundWaterLap.stop()
        del self.soundWaterLap
        del self.soundDockCreak
        self.fogHorn = None
        self.shipBell = None
        self.waterLap = None
        self.dockCreak = None
        self.boat = None
        self.boatMdl = None
        self.boatMdl1 = None
        self.track = None
        self.pierDownP = None
        self.pierUpP = None
        self.eastPier = None
        self.eastPierPath = None
        self.westPier = None
        self.westPierPath = None
        self.boatPath = None
        self.westEast = None
        self.eastWest = None
        DistributedObject.disable(self)

    def currentStateAndTimestamp(self, state, timestamp):
        self.setState(state, timestamp)

    def d_requestCurrentStateAndTimestamp(self):
        self.sendUpdate('requestCurrentStateAndTimestamp', [])

    def setState(self, state, timestamp=None):
        if timestamp is None:
            ts = 0.0
        else:
            ts = globalClockDelta.localElapsedTime(timestamp)
        self.state = state
        if self.boat and base.cr.holidayManager.getHoliday(
        ) != HolidayType.CHRISTMAS:
            self.fsm.request(state, [ts])

    def enterEastToWest(self, ts=0):
        moPath = Mopath.Mopath()
        moPath.loadFile(self.eastWest)
        moIval = MopathInterval(moPath, self.boat, blendType='easeInOut')

        self.track = Parallel(
            SoundInterval(self.soundShipBell, node=self.boat),
            SoundInterval(self.soundDockCreak, node=self.eastPier), moIval,
            LerpQuatInterval(self.eastPier,
                             duration=5.0,
                             quat=(90, self.pierDownP, 0),
                             startHpr=(90, self.pierUpP, 0),
                             blendType='easeInOut'),
            Sequence(
                Wait(15.0),
                Parallel(
                    LerpQuatInterval(self.westPier,
                                     duration=5.0,
                                     quat=(-90, self.pierUpP, 0),
                                     startHpr=(-90, self.pierDownP, 0),
                                     blendType='easeInOut'),
                    Sequence(
                        Wait(2.0),
                        SoundInterval(self.soundDockCreak,
                                      node=self.westPier))),
                SoundInterval(self.soundFogHorn, node=self.boat)))
        self.track.start(ts)

    def exitEastToWest(self):
        if self.track:
            self.track.finish()
            self.track = None

    def enterWestToEast(self, ts=0):
        moPath = Mopath.Mopath()
        moPath.loadFile(self.westEast)
        moIval = MopathInterval(moPath, self.boat, blendType='easeInOut')

        self.track = Parallel(
            SoundInterval(self.soundShipBell, node=self.boat),
            SoundInterval(self.soundDockCreak, node=self.westPier), moIval,
            LerpQuatInterval(self.westPier,
                             duration=5.0,
                             quat=(-90, self.pierDownP, 0),
                             startHpr=(-90, self.pierUpP, 0),
                             blendType='easeInOut'),
            Sequence(
                Wait(15.0),
                Parallel(
                    LerpQuatInterval(self.eastPier,
                                     duration=5.0,
                                     quat=(90, self.pierUpP, 0),
                                     startHpr=(90, self.pierDownP, 0),
                                     blendType='easeInOut'),
                    Sequence(
                        Wait(2.0),
                        SoundInterval(self.soundDockCreak,
                                      node=self.eastPier))),
                SoundInterval(self.soundFogHorn, node=self.boat)))
        self.track.start(ts)

    def exitWestToEast(self):
        if self.track:
            self.track.finish()
            self.track = None

    def enterOff(self):
        pass

    def exitOff(self):
        pass
Beispiel #47
0
class FountainPenAttack(Attack):
    notify = directNotify.newCategory('FountainPenAttack')
    attack = 'fountainpen'

    def __init__(self, attacksClass, suit):
        Attack.__init__(self, attacksClass, suit)
        self.pen = None
        self.spray = None
        self.splat = None
        self.spraySfx = None
        self.sprayParticle = None
        self.sprayScaleIval = None
        self.wsnp = None
        return

    def loadAttack(self):
        self.pen = loader.loadModel('phase_5/models/props/pen.bam')
        self.pen.reparentTo(self.suit.find('**/joint_Rhold'))
        self.sprayParticle = ParticleLoader.loadParticleEffect('phase_5/etc/penSpill.ptf')
        self.spray = loader.loadModel('phase_3.5/models/props/spray.bam')
        self.spray.setColor(VBase4(0, 0, 0, 1))
        self.splat = Actor('phase_3.5/models/props/splat-mod.bam', {'chan': 'phase_3.5/models/props/splat-chan.bam'})
        self.splat.setColor(VBase4(0, 0, 0, 1))
        self.sprayScaleIval = LerpScaleInterval(self.spray, duration=0.3, scale=(1, 20, 1), startScale=(1, 1, 1))
        sphere = CollisionSphere(0, 0, 0, 0.5)
        sphere.setTangible(0)
        if hasattr(self.suit, 'uniqueName'):
            collName = self.suit.uniqueName('fountainPenCollNode')
        else:
            collName = 'fountainPenCollNode'
        collNode = CollisionNode(collName)
        collNode.addSolid(sphere)
        collNode.setCollideMask(CIGlobals.WallBitmask)
        self.wsnp = self.spray.attachNewNode(collNode)
        self.wsnp.setY(1)

    def doAttack(self, ts = 0):
        self.loadAttack()
        if hasattr(self.suit, 'uniqueName'):
            name = self.suit.uniqueName('doFountainPenAttack')
        else:
            name = 'doFountainPenAttack'
        self.suitTrack = Parallel(name=name)
        self.suitTrack.append(ActorInterval(self.suit, 'fountainpen'))
        self.suitTrack.append(Sequence(Wait(1.2), Func(self.acceptOnce, 'enter' + self.wsnp.node().getName(), self.handleSprayCollision), Func(self.playWeaponSound), Func(self.attachSpray), Func(self.sprayParticle.start, self.pen.find('**/joint_toSpray'), self.pen.find('**/joint_toSpray')), self.sprayScaleIval, Wait(0.5), Func(self.sprayParticle.cleanup), Func(self.spray.setScale, 1), Func(self.spray.reparentTo, hidden), Func(self.ignore, 'enter' + self.wsnp.node().getName())))
        self.suitTrack.setDoneEvent(self.suitTrack.getName())
        self.acceptOnce(self.suitTrack.getDoneEvent(), self.finishedAttack)
        self.suitTrack.delayDelete = DelayDelete.DelayDelete(self.suit, name)
        self.suitTrack.start(ts)

    def attachSpray(self):
        self.spray.reparentTo(self.pen.find('**/joint_toSpray'))
        pos = self.spray.getPos(render)
        hpr = self.spray.getHpr(render)
        self.spray.reparentTo(render)
        self.spray.setPos(pos)
        self.spray.setHpr(hpr)
        self.spray.setP(0)
        if self.suit.type == 'C':
            self.spray.setH(self.spray.getH() + 7.5)
        self.spray.setTwoSided(True)

    def handleSprayCollision(self, entry):
        if self.suit:
            self.suit.sendUpdate('toonHitByWeapon', [self.getAttackId(self.attack), base.localAvatar.doId])
            base.localAvatar.b_handleSuitAttack(self.getAttackId(self.attack), self.suit.doId)
        self.sprayScaleIval.pause()

    def playWeaponSound(self):
        self.spraySfx = base.audio3d.loadSfx('phase_5/audio/sfx/SA_fountain_pen.mp3')
        base.audio3d.attachSoundToObject(self.spraySfx, self.pen)
        self.spraySfx.play()

    def cleanup(self):
        Attack.cleanup(self)
        if self.wsnp:
            self.wsnp.node().clearSolids()
            self.wsnp.removeNode()
            self.wsnp = None
        if self.pen:
            self.pen.removeNode()
            self.pen = None
        if self.sprayParticle:
            self.sprayParticle.cleanup()
            self.sprayParticle = None
        if self.spray:
            self.spray.removeNode()
            self.spray = None
        if self.splat:
            self.splat.cleanup()
            self.splat = None
        if self.sprayScaleIval:
            self.sprayScaleIval.pause()
            self.sprayScaleIval = None
        self.spraySfx = None
        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.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)
Beispiel #49
0
class DodgeballFirstPerson(FirstPerson):
    """The first person controls for the local player in Winter Dodgeball"""

    notify = directNotify.newCategory("DodgeballFirstPerson")

    MaxPickupDistance = 5.0

    def __init__(self, mg):
        self.mg = mg
        self.crosshair = None
        self.soundCatch = None
        self.vModelRoot = None
        self.vModel = None
        self.ival = None
        self.soundPickup = base.loadSfx(
            'phase_4/audio/sfx/MG_snowball_pickup.wav')
        self.fakeSnowball = loader.loadModel(
            "phase_5/models/props/snowball.bam")
        self.hasSnowball = False
        self.mySnowball = None
        self.fsm = ClassicFSM.ClassicFSM("DodgeballFirstPerson", [
            State.State("off", self.enterOff, self.exitOff),
            State.State("hold", self.enterHold, self.exitHold),
            State.State("catch", self.enterCatch, self.exitCatch),
            State.State("throw", self.enterThrow, self.exitThrow)
        ], "off", "off")
        self.fsm.enterInitialState()

        FirstPerson.__init__(self)

    def enterOff(self):
        if self.vModel:
            self.vModel.hide()

    def exitOff(self):
        if self.vModel:
            self.vModel.show()

    def enterHold(self):
        self.ival = Sequence(ActorInterval(self.vModel, "hold-start"),
                             Func(self.vModel.loop, "hold"))
        self.ival.start()

    def exitHold(self):
        if self.ival:
            self.ival.finish()
            self.ival = None
        self.vModel.stop()

    def enterThrow(self):
        self.ival = Parallel(
            Sequence(Wait(0.4), Func(self.mySnowball.b_throw)),
            Sequence(ActorInterval(self.vModel, "throw"),
                     Func(self.fsm.request, 'off')))
        self.ival.start()

    def exitThrow(self):
        if self.ival:
            self.ival.pause()
            self.ival = None
        self.vModel.stop()

    def enterCatch(self):
        self.ival = Parallel(
            Sequence(Wait(0.2), Func(self.__tryToCatchOrGrab)),
            Sequence(ActorInterval(self.vModel, "catch"),
                     Func(self.__maybeHold)))
        self.ival.start()

    def __maybeHold(self):
        if self.hasSnowball:
            self.fsm.request('hold')
        else:
            self.fsm.request('off')

    def __tryToCatchOrGrab(self):
        snowballs = list(self.mg.snowballs)
        snowballs.sort(
            key=lambda snowball: snowball.getDistance(base.localAvatar))
        for i in xrange(len(snowballs)):
            snowball = snowballs[i]
            if (not snowball.hasOwner() and not snowball.isAirborne
                    and snowball.getDistance(base.localAvatar) <=
                    DodgeballFirstPerson.MaxPickupDistance):
                snowball.b_pickup()
                self.mySnowball = snowball
                self.fakeSnowball.setPosHpr(0, 0.73, 0, 0, 0, 0)
                self.fakeSnowball.reparentTo(
                    self.vModel.exposeJoint(None, "modelRoot", "Bone.011"))
                base.playSfx(self.soundPickup)
                self.hasSnowball = True
                break

    def exitCatch(self):
        self.vModel.stop()
        if self.ival:
            self.ival.pause()
            self.ival = None

    def start(self):
        # Black crosshair because basically the entire arena is white.
        self.crosshair = getCrosshair(color=(0, 0, 0, 1), hidden=False)

        self.soundCatch = base.loadSfx(
            "phase_4/audio/sfx/MG_sfx_vine_game_catch.ogg")

        self.vModelRoot = camera.attachNewNode('vModelRoot')
        self.vModelRoot.setPos(-0.09, 1.38, -2.48)

        self.vModel = Actor(
            "phase_4/models/minigames/v_dgm.egg", {
                "hold": "phase_4/models/minigames/v_dgm-ball-hold.egg",
                "hold-start":
                "phase_4/models/minigames/v_dgm-ball-hold-start.egg",
                "throw": "phase_4/models/minigames/v_dgm-ball-throw.egg",
                "catch": "phase_4/models/minigames/v_dgm-ball-catch.egg"
            })
        self.vModel.setBlend(frameBlend=True)
        self.vModel.reparentTo(self.vModelRoot)
        self.vModel.setBin("fixed", 40)
        self.vModel.setDepthTest(False)
        self.vModel.setDepthWrite(False)
        self.vModel.hide()

        base.localAvatar.walkControls.setWalkSpeed(ToonForwardSpeed,
                                                   ToonJumpForce,
                                                   ToonReverseSpeed,
                                                   ToonRotateSpeed)

        FirstPerson.start(self)

    def reallyStart(self):
        FirstPerson.reallyStart(self)
        base.localAvatar.startTrackAnimToSpeed()

        self.accept('mouse3', self.__handleCatchOrGrabButton)
        self.accept('mouse1', self.__handleThrowButton)

    def __handleThrowButton(self):
        if self.hasSnowball and self.mySnowball and self.fsm.getCurrentState(
        ).getName() == 'hold':
            self.fakeSnowball.reparentTo(hidden)
            self.fsm.request('throw')

    def __handleCatchOrGrabButton(self):
        if not self.hasSnowball and not self.mySnowball and self.fsm.getCurrentState(
        ).getName() == 'off':
            self.fsm.request('catch')

    def reallyEnd(self):
        base.localAvatar.setWalkSpeedNormal()
        FirstPerson.reallyEnd(self)
Beispiel #50
0
class CogThief(DirectObject):
    """This represents a single cog thief in the cog thief game"""
    notify = directNotify.newCategory("CogThief")
    DefaultSpeedWalkAnim = 4.
    CollisionRadius = 1.25
    MaxFriendsVisible = 4
    Infinity = 100000.0  # just a really big number
    SeparationDistance = 6.0
    MinUrgency = 0.5
    MaxUrgency = 0.75

    def __init__(self, cogIndex, suitType, game, cogSpeed):
        self.cogIndex = cogIndex
        self.suitType = suitType
        self.game = game
        self.cogSpeed = cogSpeed
        suit = Suit.Suit()
        d = SuitDNA.SuitDNA()
        d.newSuit(suitType)
        suit.setDNA(d)
        # cache the walk anim
        suit.pose('walk', 0)
        self.suit = suit
        self.goal = CTGG.NoGoal
        self.goalId = CTGG.InvalidGoalId
        self.lastLocalTimeStampFromAI = 0
        self.lastPosFromAI = Point3(0, 0, 0)
        self.lastThinkTime = 0
        self.doneAdjust = False
        self.barrel = CTGG.NoBarrelCarried
        self.signalledAtReturnPos = False
        self.defaultPlayRate = 1.0
        self.netTimeSentToStartByHit = 0

        # steering loosely based on boid code game programming gems #1
        # "Portions Copyright (C) Steven Woodcock, 2000"
        self.velocity = Vec3(0, 0, 0)
        self.oldVelocity = Vec3(0, 0, 0)
        self.acceleration = Vec3(0, 0, 0)
        self.bodyLength = self.CollisionRadius * 2
        # Desired distance from closest neighbor when flying.
        self.cruiseDistance = 2 * self.bodyLength
        self.maxVelocity = self.cogSpeed
        # Maximum magnitude of acceleration as a fraction of maxSpeed.
        self.maxAcceleration = 5.0
        self.perceptionRange = 6
        self.notify.debug('cogSpeed=%s' % self.cogSpeed)

        self.kaboomSound = loader.loadSfx(
            "phase_4/audio/sfx/MG_cannon_fire_alt.mp3")
        self.kaboom = loader.loadModel(
            'phase_4/models/minigames/ice_game_kaboom')
        self.kaboom.setScale(2.0)
        self.kaboom.setBillboardPointEye()
        self.kaboom.hide()
        self.kaboomTrack = None

        splatName = 'splat-creampie'
        self.splat = globalPropPool.getProp(splatName)
        self.splat.setBillboardPointEye()
        self.splatType = globalPropPool.getPropType(splatName)

        self.pieHitSound = globalBattleSoundCache.getSound(
            'AA_wholepie_only.mp3')

    def destroy(self):
        self.ignoreAll()
        self.suit.delete()
        self.game = None

    def uniqueName(self, baseStr):
        return baseStr + '-' + str(self.game.doId)

    def handleEnterSphere(self, collEntry):
        """Handle the suit colliding with localToon."""
        #assert self.notify.debugStateCall(self)
        intoNp = collEntry.getIntoNodePath()
        self.notify.debug('handleEnterSphere suit %d hit %s' %
                          (self.cogIndex, intoNp))
        if self.game:
            self.game.handleEnterSphere(collEntry)

    def gameStart(self, gameStartTime):
        self.gameStartTime = gameStartTime

        self.initCollisions()
        self.startWalkAnim()

    def gameEnd(self):
        self.moveIval.pause()
        del self.moveIval

        self.shutdownCollisions()

        # keep the suits from walking in place
        self.suit.loop('neutral')

    def initCollisions(self):
        # Make a sphere, give it a unique name, and parent it
        # to the suit.
        self.collSphere = CollisionSphere(0, 0, 0, 1.25)
        # Make he sphere intangible
        self.collSphere.setTangible(1)
        name = "CogThiefSphere-%d" % self.cogIndex
        self.collSphereName = self.uniqueName(name)
        self.collNode = CollisionNode(self.collSphereName)
        self.collNode.setIntoCollideMask(CTGG.BarrelBitmask
                                         | ToontownGlobals.WallBitmask)
        self.collNode.addSolid(self.collSphere)
        self.collNodePath = self.suit.attachNewNode(self.collNode)
        #self.collNodePath.hide()

        # Add a hook looking for collisions with localToon
        self.accept('enter' + self.collSphereName, self.handleEnterSphere)

        # we need a taller collision tube to collide against for pie
        self.pieCollSphere = CollisionTube(0, 0, 0, 0, 0, 4,
                                           self.CollisionRadius)
        # Make he sphere intangible
        self.pieCollSphere.setTangible(1)
        name = "CogThiefPieSphere-%d" % self.cogIndex
        self.pieCollSphereName = self.uniqueName(name)
        self.pieCollNode = CollisionNode(self.pieCollSphereName)
        self.pieCollNode.setIntoCollideMask(ToontownGlobals.PieBitmask)
        self.pieCollNode.addSolid(self.pieCollSphere)
        self.pieCollNodePath = self.suit.attachNewNode(self.pieCollNode)
        #self.pieCollNodePath.show()

        # Add a hook looking for collisions with localToon
        #self.accept('enter' + self.pieCollSphereName,
        #            self.handleEnter)

    def shutdownCollisions(self):
        self.ignore(self.uniqueName('enter' + self.collSphereName))

        del self.collSphere
        self.collNodePath.removeNode()
        del self.collNodePath
        del self.collNode

    def updateGoal(self, timestamp, inResponseClientStamp, goalType, goalId,
                   pos):
        """Update our goal and position."""
        assert self.notify.debugStateCall(self)
        self.notify.debug('self.netTimeSentToStartByHit =%s' %
                          self.netTimeSentToStartByHit)
        if not self.game:
            self.notify.debug('updateGoal self.game is None, just returning')
            return
        if not self.suit:
            self.notify.debug('updateGoal self.suit is None, just returning')
            return
        if self.goal == CTGG.NoGoal:
            self.startWalkAnim()

        if goalType == CTGG.NoGoal:
            self.notify.debug('updateGoal setting position to %s' % pos)
            self.suit.setPos(pos)

        self.lastThinkTime = 0
        self.velocity = Vec3(0, 0, 0)
        self.oldVelocity = Vec3(0, 0, 0)
        self.acceleration = Vec3(0, 0, 0)

        if goalType == CTGG.RunAwayGoal:
            #import pdb; pdb.set_trace()
            pass

        if inResponseClientStamp < self.netTimeSentToStartByHit and \
           self.goal == CTGG.NoGoal and \
           goalType == CTGG.RunAwayGoal:
            #import pdb; pdb.set_trace()
            self.notify.warning(
                'ignoring newGoal %s as cog %d was recently hit responsetime=%s hitTime=%s'
                % (CTGG.GoalStr[goalType], self.cogIndex,
                   inResponseClientStamp, self.netTimeSentToStartByHit))
        else:
            self.lastLocalTimeStampFromAI = globalClockDelta.networkToLocalTime(
                timestamp, bits=32)
            self.goal = goalType
            self.goalId = goalId
            self.lastPosFromAI = pos
            self.doneAdjust = False
        self.signalledAtReturnPos = False
        # TODO move the suit to where we expect him to be given the time difference

    def startWalkAnim(self):
        if self.suit:
            self.suit.loop('walk')
            speed = self.cogSpeed  # float(MazeData.CELL_WIDTH) / self.cellWalkDuration
            self.defaultPlayRate = float(self.cogSpeed /
                                         self.DefaultSpeedWalkAnim)
            self.suit.setPlayRate(self.defaultPlayRate, 'walk')

    def think(self):
        """Calculate where we should go."""
        if self.goal == CTGG.ToonGoal:
            self.thinkAboutCatchingToon()
        elif self.goal == CTGG.BarrelGoal:
            self.thinkAboutGettingBarrel()
        elif self.goal == CTGG.RunAwayGoal:
            self.thinkAboutRunAway()

    def thinkAboutCatchingToon(self):
        if not self.game:
            return

        av = self.game.getAvatar(self.goalId)
        if av:
            if not self.lastThinkTime:
                self.lastThinkTime = globalClock.getFrameTime()
            diffTime = globalClock.getFrameTime() - self.lastThinkTime
            avPos = av.getPos()
            myPos = self.suit.getPos()

            if not self.doneAdjust:
                myPos = self.lastPosFromAI
                self.notify.debug(
                    'thinkAboutCatchingToon not doneAdjust setting pos %s' %
                    myPos)
                self.doneAdjust = True

            self.suit.setPos(myPos)

            if self.game.isToonPlayingHitTrack(self.goalId):
                # do nothing, just look at toon
                self.suit.headsUp(av)
                self.velocity = Vec3(0, 0, 0)
                self.oldVelocity = Vec3(0, 0, 0)
                self.acceleration = Vec3(0, 0, 0)
            else:
                self.commonMove()

            newPos = self.suit.getPos()
            self.adjustPlayRate(newPos, myPos, diffTime)

        self.lastThinkTime = globalClock.getFrameTime()

    def convertNetworkStampToGameTime(self, timestamp):
        """Convert a network timestamp to game time."""
        localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
        gameTime = self.game.local2GameTime(localStamp)
        return gameTime

    def respondToToonHit(self, timestamp):
        """The toon hit us, react appropriately."""
        assert self.notify.debugStateCall(self)
        localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
        # using 1.0 sec as fudge
        #if localStamp > self.lastLocalTimeStampFromAI:
        if self.netTimeSentToStartByHit < timestamp:
            self.clearGoal()
            self.showKaboom()
            # move him to his starting postion
            startPos = CTGG.CogStartingPositions[self.cogIndex]
            oldPos = self.suit.getPos()
            self.suit.setPos(startPos)
            if self.netTimeSentToStartByHit < timestamp:
                self.netTimeSentToStartByHit = timestamp
        else:
            self.notify.debug(
                'localStamp = %s, lastLocalTimeStampFromAI=%s, ignoring respondToToonHit'
                % (localStamp, self.lastLocalTimeStampFromAI))
        self.notify.debug(
            'respondToToonHit self.netTimeSentToStartByHit = %s' %
            self.netTimeSentToStartByHit)

    def clearGoal(self):
        """Clear goal and goal id."""
        self.goal = CTGG.NoGoal
        self.goalId = CTGG.InvalidGoalId

    def thinkAboutGettingBarrel(self):
        """Go for  a barrel."""
        if not self.game:
            return

        if not hasattr(self.game, 'barrels'):
            return
        if not self.goalId in xrange(len(self.game.barrels)):
            return

        if not self.lastThinkTime:
            self.lastThinkTime = globalClock.getFrameTime()
        diffTime = globalClock.getFrameTime() - self.lastThinkTime
        barrel = self.game.barrels[self.goalId]
        barrelPos = barrel.getPos()
        myPos = self.suit.getPos()
        if not self.doneAdjust:
            myPos = self.lastPosFromAI
            self.notify.debug(
                'thinkAboutGettingBarrel not doneAdjust setting position to %s'
                % myPos)
            self.suit.setPos(myPos)
            """
            diffTime = globalClock.getFrameTime()- self.lastLocalTimeStampFromAI
            self.notify.debug('doing adjust, diffTime = %s' % diffTime)
            if diffTime < 0:
                # it just looks really weird when it moves backwards
                diffTime = 0
                self.notify.debug('forcing diffTime to %s' % diffTime)
            """
            self.doneAdjust = True
        displacement = barrelPos - myPos
        distanceToToon = displacement.length()
        #self.notify.debug('diffTime = %s' % diffTime)
        self.suit.headsUp(barrel)
        lengthTravelled = diffTime * self.cogSpeed
        #self.notify.debug('lengthTravelled = %s' % lengthTravelled)
        # don't overshoot our target
        if lengthTravelled > distanceToToon:
            lengthTravelled = distanceToToon
            #self.notify.debug('overshooting lengthTravelled = %s' % lengthTravelled)
        displacement.normalize()
        dirVector = displacement
        dirVector *= lengthTravelled
        newPos = myPos + dirVector
        # always keep them grounded
        newPos.setZ(0)
        self.suit.setPos(newPos)
        self.adjustPlayRate(newPos, myPos, diffTime)

        self.lastThinkTime = globalClock.getFrameTime()

    def stopWalking(self, timestamp):
        """Stop the cog from walking."""
        localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
        if localStamp > self.lastLocalTimeStampFromAI:
            self.suit.loop('neutral')
            self.clearGoal()

    def thinkAboutRunAway(self):
        """Go for  a barrel."""
        if not self.game:
            return
        if not self.lastThinkTime:
            self.lastThinkTime = globalClock.getFrameTime()
        diffTime = globalClock.getFrameTime() - self.lastThinkTime

        returnPos = CTGG.CogReturnPositions[self.goalId]
        myPos = self.suit.getPos()
        if not self.doneAdjust:
            myPos = self.lastPosFromAI
            self.suit.setPos(myPos)
            """
            diffTime = globalClock.getFrameTime()- self.lastLocalTimeStampFromAI
            self.notify.debug('run away doing adjust, diffTime = %s' % diffTime)
            if diffTime < 0:
                # it just looks really weird when it moves backwards
                diffTime = 0
                self.notify.debug('forcing diffTime to %s' % diffTime)
            """
            self.doneAdjust = True
        displacement = returnPos - myPos
        distanceToToon = displacement.length()
        #self.notify.debug('diffTime = %s' % diffTime)
        tempNp = render.attachNewNode('tempRet')
        tempNp.setPos(returnPos)
        self.suit.headsUp(tempNp)
        tempNp.removeNode()
        lengthTravelled = diffTime * self.cogSpeed
        #self.notify.debug('lengthTravelled = %s' % lengthTravelled)
        # don't overshoot our target
        if lengthTravelled > distanceToToon:
            lengthTravelled = distanceToToon
            #self.notify.debug('overshooting lengthTravelled = %s' % lengthTravelled)
        displacement.normalize()
        dirVector = displacement
        dirVector *= lengthTravelled
        newPos = myPos + dirVector
        # always keep them grounded
        newPos.setZ(0)
        self.suit.setPos(newPos)
        self.adjustPlayRate(newPos, myPos, diffTime)

        if (self.suit.getPos() - returnPos).length() < 0.0001:
            if not self.signalledAtReturnPos and self.barrel >= 0:
                # tell the AI we're at return Pos
                self.game.sendCogAtReturnPos(self.cogIndex, self.barrel)
                self.signalledAtReturnPos = True

        self.lastThinkTime = globalClock.getFrameTime()

    def makeCogCarryBarrel(self, timestamp, inResponseClientStamp, barrelModel,
                           barrelIndex, cogPos):
        """Handle the AI telling us the barrel is attached to a cog."""
        #assert self.notify.debugStateCall(self)
        if not self.game:
            return
        localTimeStamp = globalClockDelta.networkToLocalTime(timestamp,
                                                             bits=32)
        # TODO validate time?
        self.lastLocalTimeStampFromAI = localTimeStamp
        inResponseGameTime = self.convertNetworkStampToGameTime(
            inResponseClientStamp)

        self.notify.debug('inResponseGameTime =%s timeSentToStart=%s' %
                          (inResponseGameTime, self.netTimeSentToStartByHit))
        if inResponseClientStamp  < self.netTimeSentToStartByHit and \
           self.goal == CTGG.NoGoal:
            self.notify.warning('ignoring makeCogCarrybarrel')
        else:
            barrelModel.setPos(0, -1.0, 1.5)
            barrelModel.reparentTo(self.suit)
            self.suit.setPos(cogPos)
            self.barrel = barrelIndex

    def makeCogDropBarrel(self, timestamp, inResponseClientStamp, barrelModel,
                          barrelIndex, barrelPos):
        """Handle the AI telling us the barrel is attached to a cog."""
        #assert self.notify.debugStateCall(self)
        localTimeStamp = globalClockDelta.networkToLocalTime(timestamp,
                                                             bits=32)
        # TODO validate time?
        self.lastLocalTimeStampFromAI = localTimeStamp

        barrelModel.reparentTo(render)
        barrelModel.setPos(barrelPos)

        self.barrel = CTGG.NoBarrelCarried

        #
        #self.suit.setPos(cogPos)

    def respondToPieHit(self, timestamp):
        """The toon hit us, react appropriately."""
        assert self.notify.debugStateCall(self)
        localStamp = globalClockDelta.networkToLocalTime(timestamp, bits=32)
        # argh using 1.0 sec as fudge
        #if localStamp  > self.lastLocalTimeStampFromAI:
        if self.netTimeSentToStartByHit < timestamp:
            self.clearGoal()
            self.showSplat()
            # move him to his starting postion
            startPos = CTGG.CogStartingPositions[self.cogIndex]
            oldPos = self.suit.getPos()
            self.suit.setPos(startPos)
            if self.netTimeSentToStartByHit < timestamp:
                self.netTimeSentToStartByHit = timestamp
        else:
            self.notify.debug(
                'localStamp = %s, lastLocalTimeStampFromAI=%s, ignoring respondToPieHit'
                % (localStamp, self.lastLocalTimeStampFromAI))
            self.notify.debug(
                'respondToPieHit self.netTimeSentToStartByHit = %s' %
                self.netTimeSentToStartByHit)

    def cleanup(self):
        """Do whatever is necessary to cleanup properly."""
        self.clearGoal()
        self.ignoreAll()
        self.suit.delete()
        if self.kaboomTrack and self.kaboomTrack.isPlaying():
            self.kaboomTrack.finish()
        self.suit = None
        self.game = None

    def adjustPlayRate(self, newPos, oldPos, diffTime):
        """Adjust animation rate based on how far he's moved."""
        # lets slowdown playrate if they're not moving much
        lengthTravelled = (newPos - oldPos).length()
        if diffTime:
            speed = lengthTravelled / diffTime
        else:
            speed = self.cogSpeed
        rateMult = speed / self.cogSpeed
        newRate = rateMult * self.defaultPlayRate
        self.suit.setPlayRate(newRate, 'walk')

    def commonMove(self):
        """Move the cog thief. Common for all 3 behaviors """
        if not self.lastThinkTime:
            self.lastThinkTime = globalClock.getFrameTime()
        dt = globalClock.getFrameTime() - self.lastThinkTime

        # Step 1:  Update our position.
        # Update our position based on the velocity
        # vector we computed last time around.

        self.oldpos = self.suit.getPos()
        # save off our previous position

        pos = self.suit.getPos()
        pos += self.velocity * dt
        # apply velocities.
        self.suit.setPos(pos)

        # Step 2:  SeeFriends.
        # Determine if we can see any of our flockmates.

        self.seeFriends()

        acc = Vec3(0, 0, 0)

        # well first off we want to move to our target
        self.accumulate(acc, self.getTargetVector())

        # Step 3:  Flocking behavior.
        # Do we see any of our flockmates?  If yes, it's time to implement
        # the first Three Rules (they don't matter if we can't see anybody)

        if self.numFlockmatesSeen > 0:
            #if hasattr(base,'doDebug') and base.doDebug:
            #    import pdb; pdb.set_trace()
            keepDistanceVector = self.keepDistance()
            oldAcc = Vec3(acc)
            self.accumulate(acc, keepDistanceVector)
            if self.cogIndex == 0:
                #self.notify.debug('oldAcc=%s, keepDist=%s newAcc=%s' %
                #                  (oldAcc,keepDistanceVector, acc))
                pass

        # Step 8:  Constrain acceleration
        # If our acceleration change is more than we allow, constrain it

        if (acc.length() > self.maxAcceleration):
            # definitely too much...constrain to maximum change
            acc.normalize()
            acc *= self.maxAcceleration

        # Step 9:  Implementation.
        # Here's where we apply our newly computed acceleration vector
        # to create a new velocity vector to use next update cycle.

        self.oldVelocity = self.velocity
        # save off our previous velocity

        # now add in the acceleration

        self.velocity += acc

        # Step 10:  constraint Y velocity changes.
        # Attempt to restrict flight straight up/down by damping out Y axis velocity.
        # This isn't strictly necessary, but does lead to more realistic looking flight.

        # Step 11:  Constrain our speed.
        # If we're moving faster than we're allowed to move, constrain our velocity.
        if self.velocity.length() > self.maxVelocity:
            self.velocity.normalize()
            self.velocity *= self.maxVelocity

        # Step 12:  Compute roll/pitch/yaw.
        # Compute our orientation after all this speed adjustment nonsense.
        # bah no need, we turn on a dime towards our velocity
        forwardVec = Vec3(1, 0, 0)
        heading = rad2Deg(math.atan2(self.velocity[1], self.velocity[0]))
        heading -= 90
        self.suit.setH(heading)

    def getTargetVector(self):
        """Return a vector to my goal."""
        targetPos = Point3(0, 0, 0)
        if self.goal == CTGG.ToonGoal:
            av = self.game.getAvatar(self.goalId)
            if av:
                targetPos = av.getPos()
        elif self.goal == CTGG.BarrelGoal:
            barrel = self.game.barrels[self.goalId]
            targetPos = barrel.getPos()
        elif self.goal == CTGG.RunAwayGoal:
            targetPos = CTGG.CogReturnPositions[self.goalId]
        targetPos.setZ(0)
        myPos = self.suit.getPos()
        diff = targetPos - myPos
        if diff.length() > 1.0:
            diff.normalize()
            diff *= 1.0

        return diff

    def accumulate(self, accumulator, valueToAdd):
        """Return the magnitude of the accumulated vector."""

        accumulator += valueToAdd

        return accumulator.length()

    def seeFriends(self):
        """Determines which flockmates a given flock boid can see."""
        # clear the existing visibility list of any holdover from last round

        self.clearVisibleList()

        for cogIndex in self.game.cogInfo.keys():
            if cogIndex == self.cogIndex:
                continue

            if self.sameGoal(cogIndex):
                dist = self.canISee(cogIndex)
                if dist != self.Infinity:
                    self.addToVisibleList(cogIndex)
                    if dist < self.distToNearestFlockmate:
                        self.nearestFlockmate = cogIndex
                        self.distToNearestFlockmate = dist

        return self.numFlockmatesSeen

    def clearVisibleList(self):
        """Clears the visibility list and associated fields."""
        self.visibleFriendsList = []
        self.numFlockmatesSeen = 0
        self.nearestFlockmate = None
        self.distToNearestFlockmate = self.Infinity

    def addToVisibleList(self, cogIndex):
        """Add the cog to the visible list."""
        # test:  do we see enough buddies already?
        if self.numFlockmatesSeen < self.MaxFriendsVisible:
            #nope--we can add to this one to the list
            self.visibleFriendsList.append(cogIndex)
            self.numFlockmatesSeen += 1
            if self.cogIndex == 0:
                #self.notify.debug('self.numFlockmatesSeen = %s' % self.numFlockmatesSeen)
                pass

    def canISee(self, cogIndex):
        """Return distance if I can see the other cog, infinity otherwise"""

        if self.cogIndex == cogIndex:
            # well we should never see ourself
            return self.Infinity

        cogThief = self.game.getCogThief(cogIndex)
        distance = self.suit.getDistance(cogThief.suit)

        if distance < self.perceptionRange:
            #self.notify.debug('%s can see %s' % (self.cogIndex, cogIndex))
            return distance

        # fell through; can not see it
        return self.Infinity

    def sameGoal(self, cogIndex):
        """Return true if we have the same goal."""
        cogThief = self.game.getCogThief(cogIndex)
        result = (cogThief.goalId == self.goalId) and (cogThief.goal
                                                       == self.goal)
        return result

    def keepDistance(self):
        """Generates a vector for a flock boid to maintain his
        desired separation distance from the nearest flockmate he sees.
        """
        ratio = self.distToNearestFlockmate / self.SeparationDistance
        nearestThief = self.game.getCogThief(self.nearestFlockmate)
        change = nearestThief.suit.getPos() - self.suit.getPos()

        if ratio < self.MinUrgency:
            ratio = self.MinUrgency
        if ratio > self.MaxUrgency:
            ratio = self.MaxUrgency

        # test:  are we too close to our nearest flockmate?
        if self.distToNearestFlockmate < self.SeparationDistance:
            #self.notify.debug('%d is too close to %d' % (self.cogIndex, self.nearestFlockmate))

            # too close...move away from our neighbor
            change.normalize()
            change *= -(1 - ratio
                        )  # the close we are the more we are pushed away
        elif self.distToNearestFlockmate > self.SeparationDistance:
            # too far away move towards our neighbor
            change.normalize()
            change *= ratio
        else:
            # in the UNLIKELY event we're exactly the right distance away, do nothing
            change = Vec3(0, 0, 0)

        return change

    def showKaboom(self):
        """Show the kaboom graphic and sound."""
        if self.kaboomTrack and self.kaboomTrack.isPlaying():
            self.kaboomTrack.finish()
        self.kaboom.reparentTo(render)
        self.kaboom.setPos(self.suit.getPos())
        self.kaboom.setZ(3)

        self.kaboomTrack = Parallel(
            SoundInterval(self.kaboomSound, volume=0.5),
            Sequence(
                Func(self.kaboom.showThrough),
                LerpScaleInterval(self.kaboom,
                                  duration=0.5,
                                  scale=Point3(10, 10, 10),
                                  startScale=Point3(1, 1, 1),
                                  blendType='easeOut'),
                Func(self.kaboom.hide),
            ))
        self.kaboomTrack.start()

    def showSplat(self):
        """Show the splat graphic and sound."""
        if self.kaboomTrack and self.kaboomTrack.isPlaying():
            self.kaboomTrack.finish()
        self.splat.reparentTo(render)
        self.splat.setPos(self.suit.getPos())
        self.splat.setZ(3)

        self.kaboomTrack = Parallel(
            SoundInterval(self.pieHitSound, volume=1.0),
            Sequence(
                Func(self.splat.showThrough),
                LerpScaleInterval(self.splat,
                                  duration=0.5,
                                  scale=1.75,
                                  startScale=Point3(0.1, 0.1, 0.1),
                                  blendType='easeOut'),
                Func(self.splat.hide),
            ))
        self.kaboomTrack.start()
class DistributedPartyCatchActivity(DistributedPartyActivity, DistributedPartyCatchActivityBase):
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedPartyCatchActivity')
    DropTaskName = 'dropSomething'
    DropObjectPlurals = {'apple': TTLocalizer.PartyCatchActivityApples,
     'orange': TTLocalizer.PartyCatchActivityOranges,
     'pear': TTLocalizer.PartyCatchActivityPears,
     'coconut': TTLocalizer.PartyCatchActivityCoconuts,
     'watermelon': TTLocalizer.PartyCatchActivityWatermelons,
     'pineapple': TTLocalizer.PartyCatchActivityPineapples,
     'anvil': TTLocalizer.PartyCatchActivityAnvils}

    class Generation:

        def __init__(self, generation, startTime, startNetworkTime, numPlayers):
            self.generation = generation
            self.startTime = startTime
            self.startNetworkTime = startNetworkTime
            self.numPlayers = numPlayers
            self.hasBeenScheduled = False
            self.droppedObjNames = []
            self.dropSchedule = []
            self.numItemsDropped = 0
            self.droppedObjCaught = {}

    def __init__(self, cr):
        DistributedPartyActivity.__init__(self, cr, PartyGlobals.ActivityIds.PartyCatch, PartyGlobals.ActivityTypes.HostInitiated, wantRewardGui=True)
        self.setUsesSmoothing()
        self.setUsesLookAround()
        self._sNumGen = SerialNumGen()

    def getTitle(self):
        return TTLocalizer.PartyCatchActivityTitle

    def getInstructions(self):
        return TTLocalizer.PartyCatchActivityInstructions % {'badThing': self.DropObjectPlurals['anvil']}

    def generate(self):
        DistributedPartyActivity.generate(self)
        self.notify.info('localAvatar doId: %s' % base.localAvatar.doId)
        self.notify.info('generate()')
        self._generateFrame = globalClock.getFrameCount()
        self._id2gen = {}
        self._orderedGenerations = []
        self._orderedGenerationIndex = None
        rng = RandomNumGen(self.doId)
        self._generationSeedBase = rng.randrange(1000)
        self._lastDropTime = 0.0
        return

    def getCurGeneration(self):
        if self._orderedGenerationIndex is None:
            return
        return self._orderedGenerations[self._orderedGenerationIndex]

    def _addGeneration(self, generation, startTime, startNetworkTime, numPlayers):
        self._id2gen[generation] = self.Generation(generation, startTime, startNetworkTime, numPlayers)
        i = 0
        while 1:
            if i >= len(self._orderedGenerations):
                break
            gen = self._orderedGenerations[i]
            startNetT = self._id2gen[gen].startTime
            genId = self._id2gen[gen].generation
            if startNetT > startNetworkTime:
                break
            if startNetT == startNetworkTime and genId > generation:
                break
            i += 1
        self._orderedGenerations = self._orderedGenerations[:i] + [generation] + self._orderedGenerations[i:]
        if self._orderedGenerationIndex is not None:
            if self._orderedGenerationIndex >= i:
                self._orderedGenerationIndex += 1

    def _removeGeneration(self, generation):
        del self._id2gen[generation]
        i = self._orderedGenerations.index(generation)
        self._orderedGenerations = self._orderedGenerations[:i] + self._orderedGenerations[i + 1:]
        if self._orderedGenerationIndex is not None:
            if len(self._orderedGenerations):
                if self._orderedGenerationIndex >= i:
                    self._orderedGenerationIndex -= 1
            else:
                self._orderedGenerationIndex = None
        return

    def announceGenerate(self):
        self.notify.info('announceGenerate()')
        self.catchTreeZoneEvent = 'fence_floor'
        DistributedPartyActivity.announceGenerate(self)

    def load(self, loadModels = 1, arenaModel = 'partyCatchTree'):
        self.notify.info('load()')
        DistributedPartyCatchActivity.notify.debug('PartyCatch: load')
        self.activityFSM = CatchActivityFSM(self)
        if __dev__:
            for o in xrange(3):
                print {0: 'SPOTS PER PLAYER',
                 1: 'DROPS PER MINUTE PER SPOT DURING NORMAL DROP PERIOD',
                 2: 'DROPS PER MINUTE PER PLAYER DURING NORMAL DROP PERIOD'}[o]
                for i in xrange(1, self.FallRateCap_Players + 10):
                    self.defineConstants(forceNumPlayers=i)
                    numDropLocations = self.DropRows * self.DropColumns
                    numDropsPerMin = 60.0 / self.DropPeriod
                    if o == 0:
                        spotsPerPlayer = numDropLocations / float(i)
                        print '%2d PLAYERS: %s' % (i, spotsPerPlayer)
                    elif o == 1:
                        numDropsPerMinPerSpot = numDropsPerMin / numDropLocations
                        print '%2d PLAYERS: %s' % (i, numDropsPerMinPerSpot)
                    elif i > 0:
                        numDropsPerMinPerPlayer = numDropsPerMin / i
                        print '%2d PLAYERS: %s' % (i, numDropsPerMinPerPlayer)

        self.defineConstants()
        self.treesAndFence = loader.loadModel('phase_13/models/parties/%s' % arenaModel)
        self.treesAndFence.setScale(0.9)
        self.treesAndFence.find('**/fence_floor').setPos(0.0, 0.0, 0.1)
        self.treesAndFence.reparentTo(self.root)
        ground = self.treesAndFence.find('**/groundPlane')
        ground.setBin('ground', 1)
        DistributedPartyActivity.load(self)
        exitText = TextNode('PartyCatchExitText')
        exitText.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
        exitText.setCardDecal(True)
        exitText.setCardColor(1.0, 1.0, 1.0, 0.0)
        exitText.setText(TTLocalizer.PartyCatchActivityExit)
        exitText.setTextColor(0.0, 8.0, 0.0, 0.9)
        exitText.setAlign(exitText.ACenter)
        exitText.setFont(ToontownGlobals.getBuildingNametagFont())
        exitText.setShadowColor(0, 0, 0, 1)
        exitText.setBin('fixed')
        if TTLocalizer.BuildingNametagShadow:
            exitText.setShadow(*TTLocalizer.BuildingNametagShadow)
        exitTextLoc = self.treesAndFence.find('**/loc_exitSignText')
        exitTextNp = exitTextLoc.attachNewNode(exitText)
        exitTextNp.setDepthWrite(0)
        exitTextNp.setScale(4)
        exitTextNp.setZ(-.5)
        self.sign.reparentTo(self.treesAndFence.find('**/loc_eventSign'))
        self.sign.wrtReparentTo(self.root)
        self.avatarNodePath = NodePath('PartyCatchAvatarNodePath')
        self.avatarNodePath.reparentTo(self.root)
        self._avatarNodePathParentToken = 3
        base.cr.parentMgr.registerParent(self._avatarNodePathParentToken, self.avatarNodePath)
        self.toonSDs = {}
        self.dropShadow = loader.loadModelOnce('phase_3/models/props/drop_shadow')
        self.dropObjModels = {}
        if loadModels:
            self.__loadDropModels()
        self.sndGoodCatch = base.loadSfx('phase_4/audio/sfx/SZ_DD_treasure.ogg')
        self.sndOof = base.loadSfx('phase_4/audio/sfx/MG_cannon_hit_dirt.ogg')
        self.sndAnvilLand = base.loadSfx('phase_4/audio/sfx/AA_drop_anvil_miss.ogg')
        self.sndPerfect = base.loadSfx('phase_4/audio/sfx/ring_perfect.ogg')
        self.__textGen = TextNode('partyCatchActivity')
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        self.activityFSM.request('Idle')

    def __loadDropModels(self):
        for objType in PartyGlobals.DropObjectTypes:
            model = loader.loadModel(objType.modelPath)
            self.dropObjModels[objType.name] = model
            modelScales = {'apple': 0.7,
             'orange': 0.7,
             'pear': 0.5,
             'coconut': 0.7,
             'watermelon': 0.6,
             'pineapple': 0.45}
            if modelScales.has_key(objType.name):
                model.setScale(modelScales[objType.name])
            if objType == PartyGlobals.Name2DropObjectType['pear']:
                model.setZ(-.6)
            if objType == PartyGlobals.Name2DropObjectType['coconut']:
                model.setP(180)
            if objType == PartyGlobals.Name2DropObjectType['watermelon']:
                model.setH(135)
                model.setZ(-.5)
            if objType == PartyGlobals.Name2DropObjectType['pineapple']:
                model.setZ(-1.7)
            if objType == PartyGlobals.Name2DropObjectType['anvil']:
                model.setZ(-self.ObjRadius)
            model.flattenStrong()

    def unload(self):
        DistributedPartyCatchActivity.notify.debug('unload')
        self.finishAllDropIntervals()
        self.destroyOrthoWalk()
        DistributedPartyActivity.unload(self)
        self.stopDropTask()
        del self.activityFSM
        del self.__textGen
        for avId in self.toonSDs.keys():
            if self.toonSDs.has_key(avId):
                toonSD = self.toonSDs[avId]
                toonSD.unload()

        del self.toonSDs
        self.treesAndFence.removeNode()
        del self.treesAndFence
        self.dropShadow.removeNode()
        del self.dropShadow
        base.cr.parentMgr.unregisterParent(self._avatarNodePathParentToken)
        for model in self.dropObjModels.values():
            model.removeNode()

        del self.dropObjModels
        del self.sndGoodCatch
        del self.sndOof
        del self.sndAnvilLand
        del self.sndPerfect

    def setStartTimestamp(self, timestamp32):
        self.notify.info('setStartTimestamp(%s)' % (timestamp32,))
        self._startTimestamp = globalClockDelta.networkToLocalTime(timestamp32, bits=32)

    def getCurrentCatchActivityTime(self):
        return globalClock.getFrameTime() - self._startTimestamp

    def getObjModel(self, objName):
        return self.dropObjModels[objName].copyTo(hidden)

    def joinRequestDenied(self, reason):
        DistributedPartyActivity.joinRequestDenied(self, reason)
        base.cr.playGame.getPlace().fsm.request('walk')

    def handleToonJoined(self, toonId):
        if not self.toonSDs.has_key(toonId):
            toonSD = PartyCatchActivityToonSD(toonId, self)
            self.toonSDs[toonId] = toonSD
            toonSD.load()
        self.notify.debug('handleToonJoined : currentState = %s' % self.activityFSM.state)
        self.cr.doId2do[toonId].useLOD(500)
        if self.activityFSM.state == 'Active':
            if self.toonSDs.has_key(toonId):
                self.toonSDs[toonId].enter()
            if base.localAvatar.doId == toonId:
                base.localAvatar.b_setParent(self._avatarNodePathParentToken)
                self.putLocalAvatarInActivity()
            if self.toonSDs.has_key(toonId):
                self.toonSDs[toonId].fsm.request('rules')

    def handleToonExited(self, toonId):
        self.notify.debug('handleToonExited( toonId=%s )' % toonId)
        if self.cr.doId2do.has_key(toonId):
            self.cr.doId2do[toonId].resetLOD()
            if self.toonSDs.has_key(toonId):
                self.toonSDs[toonId].fsm.request('notPlaying')
                self.toonSDs[toonId].exit()
                self.toonSDs[toonId].unload()
                del self.toonSDs[toonId]
            if base.localAvatar.doId == toonId:
                base.localAvatar.b_setParent(ToontownGlobals.SPRender)

    def takeLocalAvatarOutOfActivity(self):
        self.notify.debug('localToon has left the circle')
        camera.reparentTo(base.localAvatar)
        base.localAvatar.startUpdateSmartCamera()
        base.localAvatar.enableSmartCameraViews()
        base.localAvatar.setCameraPositionByIndex(base.localAvatar.cameraIndex)
        DistributedSmoothNode.activateSmoothing(1, 0)

    def _enableCollisions(self):
        DistributedPartyActivity._enableCollisions(self)
        self._enteredTree = False
        self.accept('enter' + self.catchTreeZoneEvent, self._toonMayHaveEnteredTree)
        self.accept('again' + self.catchTreeZoneEvent, self._toonMayHaveEnteredTree)
        self.accept('exit' + self.catchTreeZoneEvent, self._toonExitedTree)
        self.accept(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT, self._handleCannonLanded)

    def _disableCollisions(self):
        self.ignore(DistributedPartyCannonActivity.LOCAL_TOON_LANDED_EVENT)
        self.ignore('enter' + self.catchTreeZoneEvent)
        self.ignore('again' + self.catchTreeZoneEvent)
        self.ignore('exit' + self.catchTreeZoneEvent)
        DistributedPartyActivity._disableCollisions(self)

    def _handleCannonLanded(self):
        x = base.localAvatar.getX()
        y = base.localAvatar.getY()
        if x > self.x - self.StageHalfWidth and x < self.x + self.StageHalfWidth and y > self.y - self.StageHalfHeight and y < self.y + self.StageHalfHeight:
            self._toonEnteredTree(None)
        return

    def _toonMayHaveEnteredTree(self, collEntry):
        if self._enteredTree:
            return
        if base.localAvatar.controlManager.currentControls.getIsAirborne():
            return
        self._toonEnteredTree(collEntry)

    def _toonEnteredTree(self, collEntry):
        self.notify.debug('_toonEnteredTree : avid = %s' % base.localAvatar.doId)
        self.notify.debug('_toonEnteredTree : currentState = %s' % self.activityFSM.state)
        if self.isLocalToonInActivity():
            return
        if self.activityFSM.state == 'Active':
            base.cr.playGame.getPlace().fsm.request('activity')
            self.d_toonJoinRequest()
        elif self.activityFSM.state == 'Idle':
            base.cr.playGame.getPlace().fsm.request('activity')
            self.d_toonJoinRequest()
        self._enteredTree = True

    def _toonExitedTree(self, collEntry):
        self.notify.debug('_toonExitedTree : avid = %s' % base.localAvatar.doId)
        self._enteredTree = False
        if hasattr(base.cr.playGame.getPlace(), 'fsm') and self.activityFSM.state == 'Active' and self.isLocalToonInActivity():
            if self.toonSDs.has_key(base.localAvatar.doId):
                self.takeLocalAvatarOutOfActivity()
                self.toonSDs[base.localAvatar.doId].fsm.request('notPlaying')
            self.d_toonExitDemand()

    def setToonsPlaying(self, toonIds):
        self.notify.info('setToonsPlaying(%s)' % (toonIds,))
        DistributedPartyActivity.setToonsPlaying(self, toonIds)
        if self.isLocalToonInActivity() and base.localAvatar.doId not in toonIds:
            if self.toonSDs.has_key(base.localAvatar.doId):
                self.takeLocalAvatarOutOfActivity()
                self.toonSDs[base.localAvatar.doId].fsm.request('notPlaying')

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def getNumPlayers(self):
        return len(self.toonIds)

    def defineConstants(self, forceNumPlayers = None):
        DistributedPartyCatchActivity.notify.debug('defineConstants')
        self.ShowObjSpheres = 0
        self.ShowToonSpheres = 0
        self.useGravity = True
        self.trickShadows = True
        if forceNumPlayers is None:
            numPlayers = self.getNumPlayers()
        else:
            numPlayers = forceNumPlayers
        self.calcDifficultyConstants(numPlayers)
        DistributedPartyCatchActivity.notify.debug('ToonSpeed: %s' % self.ToonSpeed)
        DistributedPartyCatchActivity.notify.debug('total drops: %s' % self.totalDrops)
        DistributedPartyCatchActivity.notify.debug('numFruits: %s' % self.numFruits)
        DistributedPartyCatchActivity.notify.debug('numAnvils: %s' % self.numAnvils)
        self.ObjRadius = 1.0
        dropRegionTable = PartyRegionDropPlacer.getDropRegionTable(numPlayers)
        self.DropRows, self.DropColumns = len(dropRegionTable), len(dropRegionTable[0])
        for objType in PartyGlobals.DropObjectTypes:
            DistributedPartyCatchActivity.notify.debug('*** Object Type: %s' % objType.name)
            objType.onscreenDuration = objType.onscreenDurMult * self.BaselineOnscreenDropDuration
            DistributedPartyCatchActivity.notify.debug('onscreenDuration=%s' % objType.onscreenDuration)
            v_0 = 0.0
            t = objType.onscreenDuration
            x_0 = self.MinOffscreenHeight
            x = 0.0
            g = 2.0 * (x - x_0 - v_0 * t) / (t * t)
            DistributedPartyCatchActivity.notify.debug('gravity=%s' % g)
            objType.trajectory = Trajectory(0, Vec3(0, 0, x_0), Vec3(0, 0, v_0), gravMult=abs(g / Trajectory.gravity))
            objType.fallDuration = objType.onscreenDuration + self.OffscreenTime

        return

    def grid2world(self, column, row):
        x = column / float(self.DropColumns - 1)
        y = row / float(self.DropRows - 1)
        x = x * 2.0 - 1.0
        y = y * 2.0 - 1.0
        x *= self.StageHalfWidth
        y *= self.StageHalfHeight
        return (x, y)

    def showPosts(self):
        self.hidePosts()
        self.posts = [Toon.Toon(),
         Toon.Toon(),
         Toon.Toon(),
         Toon.Toon()]
        for i in xrange(len(self.posts)):
            tree = self.posts[i]
            tree.reparentTo(render)
            x = self.StageHalfWidth
            y = self.StageHalfHeight
            if i > 1:
                x = -x
            if i % 2:
                y = -y
            tree.setPos(x + self.x, y + self.y, 0)

    def hidePosts(self):
        if hasattr(self, 'posts'):
            for tree in self.posts:
                tree.removeNode()

            del self.posts

    def showDropGrid(self):
        self.hideDropGrid()
        self.dropMarkers = []
        for row in xrange(self.DropRows):
            self.dropMarkers.append([])
            rowList = self.dropMarkers[row]
            for column in xrange(self.DropColumns):
                toon = Toon.Toon()
                toon.setDNA(base.localAvatar.getStyle())
                toon.reparentTo(self.root)
                toon.setScale(1.0 / 3)
                x, y = self.grid2world(column, row)
                toon.setPos(x, y, 0)
                rowList.append(toon)

    def hideDropGrid(self):
        if hasattr(self, 'dropMarkers'):
            for row in self.dropMarkers:
                for marker in row:
                    marker.removeNode()

            del self.dropMarkers

    def handleToonDisabled(self, avId):
        DistributedPartyCatchActivity.notify.debug('handleToonDisabled')
        DistributedPartyCatchActivity.notify.debug('avatar ' + str(avId) + ' disabled')
        if self.toonSDs.has_key(avId):
            self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]

    def turnOffSmoothingOnGuests(self):
        pass

    def setState(self, newState, timestamp):
        self.notify.info('setState(%s, %s)' % (newState, timestamp))
        DistributedPartyCatchActivity.notify.debug('setState( newState=%s, ... )' % newState)
        DistributedPartyActivity.setState(self, newState, timestamp)
        self.activityFSM.request(newState)
        if newState == 'Active':
            if base.localAvatar.doId != self.party.partyInfo.hostId:
                if globalClock.getFrameCount() > self._generateFrame:
                    if base.localAvatar.getX() > self.x - self.StageHalfWidth and base.localAvatar.getX() < self.x + self.StageHalfWidth and base.localAvatar.getY() > self.y - self.StageHalfHeight and base.localAvatar.getY() < self.y + self.StageHalfHeight:
                        self._toonEnteredTree(None)
        return

    def putLocalAvatarInActivity(self):
        if base.cr.playGame.getPlace() and hasattr(base.cr.playGame.getPlace(), 'fsm'):
            base.cr.playGame.getPlace().fsm.request('activity', [False])
        else:
            self.notify.info("Avoided crash: toontown.parties.DistributedPartyCatchActivity:632, toontown.parties.DistributedPartyCatchActivity:1198, toontown.parties.activityFSMMixins:49, direct.fsm.FSM:423, AttributeError: 'NoneType' object has no attribute 'fsm'")
        base.localAvatar.stopUpdateSmartCamera()
        camera.reparentTo(self.treesAndFence)
        camera.setPosHpr(0.0, -63.0, 30.0, 0.0, -20.0, 0.0)
        if not hasattr(self, 'ltLegsCollNode'):
            self.createCatchCollisions()

    def createCatchCollisions(self):
        radius = 0.7
        handler = CollisionHandlerEvent()
        handler.setInPattern('ltCatch%in')
        self.ltLegsCollNode = CollisionNode('catchLegsCollNode')
        self.ltLegsCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltHeadCollNode = CollisionNode('catchHeadCollNode')
        self.ltHeadCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltLHandCollNode = CollisionNode('catchLHandCollNode')
        self.ltLHandCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        self.ltRHandCollNode = CollisionNode('catchRHandCollNode')
        self.ltRHandCollNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        legsCollNodepath = base.localAvatar.attachNewNode(self.ltLegsCollNode)
        legsCollNodepath.hide()
        head = base.localAvatar.getHeadParts().getPath(2)
        headCollNodepath = head.attachNewNode(self.ltHeadCollNode)
        headCollNodepath.hide()
        lHand = base.localAvatar.getLeftHands()[0]
        lHandCollNodepath = lHand.attachNewNode(self.ltLHandCollNode)
        lHandCollNodepath.hide()
        rHand = base.localAvatar.getRightHands()[0]
        rHandCollNodepath = rHand.attachNewNode(self.ltRHandCollNode)
        rHandCollNodepath.hide()
        base.localAvatar.cTrav.addCollider(legsCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(headCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(lHandCollNodepath, handler)
        base.localAvatar.cTrav.addCollider(lHandCollNodepath, handler)
        if self.ShowToonSpheres:
            legsCollNodepath.show()
            headCollNodepath.show()
            lHandCollNodepath.show()
            rHandCollNodepath.show()
        self.ltLegsCollNode.addSolid(CollisionSphere(0, 0, radius, radius))
        self.ltHeadCollNode.addSolid(CollisionSphere(0, 0, 0, radius))
        self.ltLHandCollNode.addSolid(CollisionSphere(0, 0, 0, 2 * radius / 3.0))
        self.ltRHandCollNode.addSolid(CollisionSphere(0, 0, 0, 2 * radius / 3.0))
        self.toonCollNodes = [legsCollNodepath,
         headCollNodepath,
         lHandCollNodepath,
         rHandCollNodepath]

    def destroyCatchCollisions(self):
        if not hasattr(self, 'ltLegsCollNode'):
            return
        for collNode in self.toonCollNodes:
            while collNode.node().getNumSolids():
                collNode.node().removeSolid(0)

            base.localAvatar.cTrav.removeCollider(collNode)

        del self.toonCollNodes
        del self.ltLegsCollNode
        del self.ltHeadCollNode
        del self.ltLHandCollNode
        del self.ltRHandCollNode

    def timerExpired(self):
        pass

    def __handleCatch(self, generation, objNum):
        DistributedPartyCatchActivity.notify.debug('catch: %s' % [generation, objNum])
        if base.localAvatar.doId not in self.toonIds:
            return
        self.showCatch(base.localAvatar.doId, generation, objNum)
        objName = self._id2gen[generation].droppedObjNames[objNum]
        objTypeId = PartyGlobals.Name2DOTypeId[objName]
        self.sendUpdate('claimCatch', [generation, objNum, objTypeId])
        self.finishDropInterval(generation, objNum)

    def showCatch(self, avId, generation, objNum):
        if not self.toonSDs.has_key(avId):
            return
        isLocal = avId == base.localAvatar.doId
        if generation not in self._id2gen:
            return
        if not self._id2gen[generation].hasBeenScheduled:
            return
        objName = self._id2gen[generation].droppedObjNames[objNum]
        objType = PartyGlobals.Name2DropObjectType[objName]
        if objType.good:
            if not self._id2gen[generation].droppedObjCaught.has_key(objNum):
                if isLocal:
                    base.playSfx(self.sndGoodCatch)
                fruit = self.getObjModel(objName)
                toon = self.getAvatar(avId)
                rHand = toon.getRightHands()[1]
                self.toonSDs[avId].eatFruit(fruit, rHand)
        else:
            self.toonSDs[avId].fsm.request('fallForward')
        self._id2gen[generation].droppedObjCaught[objNum] = 1

    def setObjectCaught(self, avId, generation, objNum):
        self.notify.info('setObjectCaught(%s, %s, %s)' % (avId, generation, objNum))
        if self.activityFSM.state != 'Active':
            DistributedPartyCatchActivity.notify.warning('ignoring msg: object %s caught by %s' % (objNum, avId))
            return
        isLocal = avId == base.localAvatar.doId
        if not isLocal:
            DistributedPartyCatchActivity.notify.debug('AI: avatar %s caught %s' % (avId, objNum))
            self.finishDropInterval(generation, objNum)
            self.showCatch(avId, generation, objNum)
        self._scheduleGenerations()
        gen = self._id2gen[generation]
        if gen.hasBeenScheduled:
            objName = gen.droppedObjNames[objNum]
            if PartyGlobals.Name2DropObjectType[objName].good:
                if hasattr(self, 'fruitsCaught'):
                    self.fruitsCaught += 1

    def finishDropInterval(self, generation, objNum):
        if hasattr(self, 'dropIntervals'):
            if self.dropIntervals.has_key((generation, objNum)):
                self.dropIntervals[generation, objNum].finish()

    def finishAllDropIntervals(self):
        if hasattr(self, 'dropIntervals'):
            for dropInterval in self.dropIntervals.values():
                dropInterval.finish()

    def setGenerations(self, generations):
        self.notify.info('setGenerations(%s)' % (generations,))
        gen2t = {}
        gen2nt = {}
        gen2np = {}
        for id, timestamp32, numPlayers in generations:
            gen2t[id] = globalClockDelta.networkToLocalTime(timestamp32, bits=32) - self._startTimestamp
            gen2nt[id] = timestamp32
            gen2np[id] = numPlayers

        ids = self._id2gen.keys()
        for id in ids:
            if id not in gen2t:
                self._removeGeneration(id)

        for id in gen2t:
            if id not in self._id2gen:
                self._addGeneration(id, gen2t[id], gen2nt[id], gen2np[id])

    def scheduleDrops(self, genId = None):
        if genId is None:
            genId = self.getCurGeneration()
        gen = self._id2gen[genId]
        if gen.hasBeenScheduled:
            return
        fruitIndex = int((gen.startTime + 0.5 * self.DropPeriod) / PartyGlobals.CatchActivityDuration)
        fruitNames = ['apple',
         'orange',
         'pear',
         'coconut',
         'watermelon',
         'pineapple']
        fruitName = fruitNames[fruitIndex % len(fruitNames)]
        rng = RandomNumGen(genId + self._generationSeedBase)
        gen.droppedObjNames = [fruitName] * self.numFruits + ['anvil'] * self.numAnvils
        rng.shuffle(gen.droppedObjNames)
        dropPlacer = PartyRegionDropPlacer(self, gen.numPlayers, genId, gen.droppedObjNames, startTime=gen.startTime)
        gen.numItemsDropped = 0
        tIndex = gen.startTime % PartyGlobals.CatchActivityDuration
        tPercent = float(tIndex) / PartyGlobals.CatchActivityDuration
        gen.numItemsDropped += dropPlacer.skipPercent(tPercent)
        while not dropPlacer.doneDropping(continuous=True):
            nextDrop = dropPlacer.getNextDrop()
            gen.dropSchedule.append(nextDrop)

        gen.hasBeenScheduled = True
        return

    def startDropTask(self):
        taskMgr.add(self.dropTask, self.DropTaskName)

    def stopDropTask(self):
        taskMgr.remove(self.DropTaskName)

    def _scheduleGenerations(self):
        curT = self.getCurrentCatchActivityTime()
        genIndex = self._orderedGenerationIndex
        newGenIndex = genIndex
        while genIndex is None or genIndex < len(self._orderedGenerations) - 1:
            if genIndex is None:
                nextGenIndex = 0
            else:
                nextGenIndex = genIndex + 1
            nextGenId = self._orderedGenerations[nextGenIndex]
            nextGen = self._id2gen[nextGenId]
            startT = nextGen.startTime
            if curT >= startT:
                newGenIndex = nextGenIndex
            if not nextGen.hasBeenScheduled:
                self.defineConstants(forceNumPlayers=nextGen.numPlayers)
                self.scheduleDrops(genId=self._orderedGenerations[nextGenIndex])
            genIndex = nextGenIndex

        self._orderedGenerationIndex = newGenIndex
        return

    def dropTask(self, task):
        self._scheduleGenerations()
        curT = self.getCurrentCatchActivityTime()
        if self._orderedGenerationIndex is not None:
            i = self._orderedGenerationIndex
            genIndex = self._orderedGenerations[i]
            gen = self._id2gen[genIndex]
            while len(gen.dropSchedule) > 0 and gen.dropSchedule[0][0] < curT:
                drop = gen.dropSchedule[0]
                gen.dropSchedule = gen.dropSchedule[1:]
                dropTime, objName, dropCoords = drop
                objNum = gen.numItemsDropped
                x, y = self.grid2world(*dropCoords)
                dropIval = self.getDropIval(x, y, objName, genIndex, objNum)

                def cleanup(generation, objNum, self = self):
                    del self.dropIntervals[generation, objNum]

                dropIval.append(Func(Functor(cleanup, genIndex, objNum)))
                self.dropIntervals[genIndex, objNum] = dropIval
                gen.numItemsDropped += 1
                dropIval.start(curT - dropTime)
                self._lastDropTime = dropTime

        return Task.cont

    def getDropIval(self, x, y, dropObjName, generation, num):
        objType = PartyGlobals.Name2DropObjectType[dropObjName]
        id = (generation, num)
        dropNode = hidden.attachNewNode('catchDropNode%s' % (id,))
        dropNode.setPos(x, y, 0)
        shadow = self.dropShadow.copyTo(dropNode)
        shadow.setZ(PartyGlobals.CatchDropShadowHeight)
        shadow.setColor(1, 1, 1, 1)
        object = self.getObjModel(dropObjName)
        object.reparentTo(hidden)
        if dropObjName in ['watermelon', 'anvil']:
            objH = object.getH()
            absDelta = {'watermelon': 12,
             'anvil': 15}[dropObjName]
            delta = (self.randomNumGen.random() * 2.0 - 1.0) * absDelta
            newH = objH + delta
        else:
            newH = self.randomNumGen.random() * 360.0
        object.setH(newH)
        sphereName = 'FallObj%s' % (id,)
        radius = self.ObjRadius
        if objType.good:
            radius *= lerp(1.0, 1.3, 0.5)
        collSphere = CollisionSphere(0, 0, 0, radius)
        collSphere.setTangible(0)
        collNode = CollisionNode(sphereName)
        collNode.setCollideMask(PartyGlobals.CatchActivityBitmask)
        collNode.addSolid(collSphere)
        collNodePath = object.attachNewNode(collNode)
        collNodePath.hide()
        if self.ShowObjSpheres:
            collNodePath.show()
        catchEventName = 'ltCatch' + sphereName

        def eatCollEntry(forward, collEntry):
            forward()

        self.accept(catchEventName, Functor(eatCollEntry, Functor(self.__handleCatch, id[0], id[1])))

        def cleanup(self = self, dropNode = dropNode, id = id, event = catchEventName):
            self.ignore(event)
            dropNode.removeNode()

        duration = objType.fallDuration
        onscreenDuration = objType.onscreenDuration
        targetShadowScale = 0.3
        if self.trickShadows:
            intermedScale = targetShadowScale * (self.OffscreenTime / self.BaselineDropDuration)
            shadowScaleIval = Sequence(LerpScaleInterval(shadow, self.OffscreenTime, intermedScale, startScale=0))
            shadowScaleIval.append(LerpScaleInterval(shadow, duration - self.OffscreenTime, targetShadowScale, startScale=intermedScale))
        else:
            shadowScaleIval = LerpScaleInterval(shadow, duration, targetShadowScale, startScale=0)
        targetShadowAlpha = 0.4
        shadowAlphaIval = LerpColorScaleInterval(shadow, self.OffscreenTime, Point4(1, 1, 1, targetShadowAlpha), startColorScale=Point4(1, 1, 1, 0))
        shadowIval = Parallel(shadowScaleIval, shadowAlphaIval)
        if self.useGravity:

            def setObjPos(t, objType = objType, object = object):
                z = objType.trajectory.calcZ(t)
                object.setZ(z)

            setObjPos(0)
            dropIval = LerpFunctionInterval(setObjPos, fromData=0, toData=onscreenDuration, duration=onscreenDuration)
        else:
            startPos = Point3(0, 0, self.MinOffscreenHeight)
            object.setPos(startPos)
            dropIval = LerpPosInterval(object, onscreenDuration, Point3(0, 0, 0), startPos=startPos, blendType='easeIn')
        ival = Sequence(Func(Functor(dropNode.reparentTo, self.root)), Parallel(Sequence(WaitInterval(self.OffscreenTime), Func(Functor(object.reparentTo, dropNode)), dropIval), shadowIval), Func(cleanup), name='drop%s' % (id,))
        if objType == PartyGlobals.Name2DropObjectType['anvil']:
            ival.append(Func(self.playAnvil))
        return ival

    def playAnvil(self):
        if base.localAvatar.doId in self.toonIds:
            base.playSfx(self.sndAnvilLand)

    def initOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug('startOrthoWalk')

        def doCollisions(oldPos, newPos, self = self):
            x = bound(newPos[0], self.StageHalfWidth, -self.StageHalfWidth)
            y = bound(newPos[1], self.StageHalfHeight, -self.StageHalfHeight)
            newPos.setX(x)
            newPos.setY(y)
            return newPos

        orthoDrive = OrthoDrive(self.ToonSpeed, instantTurn=True)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=True)

    def destroyOrthoWalk(self):
        DistributedPartyCatchActivity.notify.debug('destroyOrthoWalk')
        if hasattr(self, 'orthoWalk'):
            self.orthoWalk.stop()
            self.orthoWalk.destroy()
            del self.orthoWalk

    def startIdle(self):
        DistributedPartyCatchActivity.notify.debug('startIdle')

    def finishIdle(self):
        DistributedPartyCatchActivity.notify.debug('finishIdle')

    def startActive(self):
        DistributedPartyCatchActivity.notify.debug('startActive')
        for avId in self.toonIds:
            if self.toonSDs.has_key(avId):
                toonSD = self.toonSDs[avId]
                toonSD.enter()
                toonSD.fsm.request('normal')

        self.fruitsCaught = 0
        self.dropIntervals = {}
        self.startDropTask()
        if base.localAvatar.doId in self.toonIds:
            self.putLocalAvatarInActivity()

    def finishActive(self):
        DistributedPartyCatchActivity.notify.debug('finishActive')
        self.stopDropTask()
        if hasattr(self, 'finishIval'):
            self.finishIval.pause()
            del self.finishIval
        if base.localAvatar.doId in self.toonIds:
            self.takeLocalAvatarOutOfActivity()
        for ival in self.dropIntervals.values():
            ival.finish()

        del self.dropIntervals

    def startConclusion(self):
        DistributedPartyCatchActivity.notify.debug('startConclusion')
        for avId in self.toonIds:
            if self.toonSDs.has_key(avId):
                toonSD = self.toonSDs[avId]
                toonSD.fsm.request('notPlaying')

        self.destroyCatchCollisions()
        if base.localAvatar.doId not in self.toonIds:
            return
        else:
            self.localToonExiting()
        if self.fruitsCaught >= self.numFruits:
            finishText = TTLocalizer.PartyCatchActivityFinishPerfect
        else:
            finishText = TTLocalizer.PartyCatchActivityFinish
        perfectTextSubnode = hidden.attachNewNode(self.__genText(finishText))
        perfectText = hidden.attachNewNode('perfectText')
        perfectTextSubnode.reparentTo(perfectText)
        frame = self.__textGen.getCardActual()
        offsetY = -abs(frame[2] + frame[3]) / 2.0
        perfectTextSubnode.setPos(0, 0, offsetY)
        perfectText.setColor(1, 0.1, 0.1, 1)

        def fadeFunc(t, text = perfectText):
            text.setColorScale(1, 1, 1, t)

        def destroyText(text = perfectText):
            text.removeNode()

        textTrack = Sequence(Func(perfectText.reparentTo, aspect2d), Parallel(LerpScaleInterval(perfectText, duration=0.5, scale=0.3, startScale=0.0), LerpFunctionInterval(fadeFunc, fromData=0.0, toData=1.0, duration=0.5)), Wait(2.0), Parallel(LerpScaleInterval(perfectText, duration=0.5, scale=1.0), LerpFunctionInterval(fadeFunc, fromData=1.0, toData=0.0, duration=0.5, blendType='easeIn')), Func(destroyText), WaitInterval(0.5))
        soundTrack = SoundInterval(self.sndPerfect)
        self.finishIval = Parallel(textTrack, soundTrack)
        self.finishIval.start()

    def finishConclusion(self):
        DistributedPartyCatchActivity.notify.debug('finishConclusion')
        if base.localAvatar.doId in self.toonIds:
            self.takeLocalAvatarOutOfActivity()
            base.cr.playGame.getPlace().fsm.request('walk')

    def showJellybeanReward(self, earnedAmount, jarAmount, message):
        if earnedAmount > 0:
            DistributedPartyActivity.showJellybeanReward(self, earnedAmount, jarAmount, message)
        else:
            base.cr.playGame.getPlace().fsm.request('walk')
class DistributedIceGame(DistributedMinigame.DistributedMinigame, DistributedIceWorld.DistributedIceWorld):
    notify = directNotify.newCategory("DistributedIceGame")
    MaxLocalForce = 100
    MaxPhysicsForce = 25000

    def __init__(self, cr):
        DistributedMinigame.DistributedMinigame.__init__(self, cr)
        DistributedIceWorld.DistributedIceWorld.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM(
            "DistributedIceGame",
            [
                State.State("off", self.enterOff, self.exitOff, ["inputChoice"]),
                State.State(
                    "inputChoice",
                    self.enterInputChoice,
                    self.exitInputChoice,
                    ["waitServerChoices", "moveTires", "displayVotes", "cleanup"],
                ),
                State.State(
                    "waitServerChoices",
                    self.enterWaitServerChoices,
                    self.exitWaitServerChoices,
                    ["moveTires", "cleanup"],
                ),
                State.State("moveTires", self.enterMoveTires, self.exitMoveTires, ["synch", "cleanup"]),
                State.State("synch", self.enterSynch, self.exitSynch, ["inputChoice", "scoring", "cleanup"]),
                State.State("scoring", self.enterScoring, self.exitScoring, ["cleanup", "finalResults", "inputChoice"]),
                State.State("finalResults", self.enterFinalResults, self.exitFinalResults, ["cleanup"]),
                State.State("cleanup", self.enterCleanup, self.exitCleanup, []),
            ],
            "off",
            "cleanup",
        )
        self.addChildGameFSM(self.gameFSM)
        self.cameraThreeQuarterView = (0, -22, 45, 0, -62.89, 0)
        self.tireDict = {}
        self.forceArrowDict = {}
        self.canDrive = False
        self.timer = None
        self.timerStartTime = None
        self.curForce = 0
        self.curHeading = 0
        self.headingMomentum = 0.0
        self.forceMomentum = 0.0
        self.allTireInputs = None
        self.curRound = 0
        self.curMatch = 0
        self.controlKeyWarningLabel = DirectLabel(
            text=TTLocalizer.IceGameControlKeyWarning,
            text_fg=VBase4(1, 0, 0, 1),
            relief=None,
            pos=(0.0, 0, 0),
            scale=0.15,
        )
        self.controlKeyWarningLabel.hide()
        self.waitingMoveLabel = DirectLabel(
            text=TTLocalizer.IceGameWaitingForPlayersToFinishMove,
            text_fg=VBase4(1, 1, 1, 1),
            relief=None,
            pos=(-0.6, 0, -0.75),
            scale=0.075,
        )
        self.waitingMoveLabel.hide()
        self.waitingSyncLabel = DirectLabel(
            text=TTLocalizer.IceGameWaitingForAISync,
            text_fg=VBase4(1, 1, 1, 1),
            relief=None,
            pos=(-0.6, 0, -0.75),
            scale=0.075,
        )
        self.waitingSyncLabel.hide()
        self.infoLabel = DirectLabel(text="", text_fg=VBase4(0, 0, 0, 1), relief=None, pos=(0.0, 0, 0.7), scale=0.075)
        self.updateInfoLabel()
        self.lastForceArrowUpdateTime = 0
        self.sendForceArrowUpdateAsap = False
        self.treasures = []
        self.penalties = []
        self.obstacles = []
        self.controlKeyPressed = False
        self.controlKeyWarningIval = None
        return

    def delete(self):
        DistributedIceWorld.DistributedIceWorld.delete(self)
        DistributedMinigame.DistributedMinigame.delete(self)
        if self.controlKeyWarningIval:
            self.controlKeyWarningIval.finish()
            self.controlKeyWarningIval = None
        self.controlKeyWarningLabel.destroy()
        del self.controlKeyWarningLabel
        self.waitingMoveLabel.destroy()
        del self.waitingMoveLabel
        self.waitingSyncLabel.destroy()
        del self.waitingSyncLabel
        self.infoLabel.destroy()
        del self.infoLabel
        for treasure in self.treasures:
            treasure.destroy()

        del self.treasures
        for penalty in self.penalties:
            penalty.destroy()

        del self.penalties
        for obstacle in self.obstacles:
            obstacle.removeNode()

        del self.obstacles
        del self.gameFSM
        return

    def announceGenerate(self):
        DistributedMinigame.DistributedMinigame.announceGenerate(self)
        DistributedIceWorld.DistributedIceWorld.announceGenerate(self)
        self.debugTaskName = self.uniqueName("debugTask")

    def getTitle(self):
        return TTLocalizer.IceGameTitle

    def getInstructions(self):
        szId = self.getSafezoneId()
        numPenalties = IceGameGlobals.NumPenalties[szId]
        result = TTLocalizer.IceGameInstructions
        if numPenalties == 0:
            result = TTLocalizer.IceGameInstructionsNoTnt
        return result

    def getMaxDuration(self):
        return 0

    def load(self):
        self.notify.debug("load")
        DistributedMinigame.DistributedMinigame.load(self)
        self.music = base.loadMusic("phase_4/audio/bgm/MG_IceGame.ogg")
        self.gameBoard = loader.loadModel("phase_4/models/minigames/ice_game_icerink")
        background = loader.loadModel("phase_4/models/minigames/ice_game_2d")
        backgroundWide = loader.loadModel("phase_4/models/minigames/iceslide_ground")
        background.reparentTo(self.gameBoard)
        backgroundWide.reparentTo(self.gameBoard)
        backgroundWide.setPos(0, -0.3, -0.5)
        self.gameBoard.setPosHpr(0, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        self.setupSimulation()
        index = 0
        for avId in self.avIdList:
            self.setupTire(avId, index)
            self.setupForceArrow(avId)
            index += 1

        for index in xrange(len(self.avIdList), 4):
            self.setupTire(-index, index)
            self.setupForceArrow(-index)

        self.showForceArrows(realPlayersOnly=True)
        self.westWallModel = NodePath()
        if not self.westWallModel.isEmpty():
            self.westWallModel.reparentTo(self.gameBoard)
            self.westWallModel.setPos(IceGameGlobals.MinWall[0], IceGameGlobals.MinWall[1], 0)
            self.westWallModel.setScale(4)
        self.eastWallModel = NodePath()
        if not self.eastWallModel.isEmpty():
            self.eastWallModel.reparentTo(self.gameBoard)
            self.eastWallModel.setPos(IceGameGlobals.MaxWall[0], IceGameGlobals.MaxWall[1], 0)
            self.eastWallModel.setScale(4)
            self.eastWallModel.setH(180)
        self.arrowKeys = ArrowKeys.ArrowKeys()
        self.target = loader.loadModel("phase_3/models/misc/sphere")
        self.target.setScale(0.01)
        self.target.reparentTo(self.gameBoard)
        self.target.setPos(0, 0, 0)
        self.scoreCircle = loader.loadModel("phase_4/models/minigames/ice_game_score_circle")
        self.scoreCircle.setScale(0.01)
        self.scoreCircle.reparentTo(self.gameBoard)
        self.scoreCircle.setZ(IceGameGlobals.TireRadius / 2.0)
        self.scoreCircle.setAlphaScale(0.5)
        self.scoreCircle.setTransparency(1)
        self.scoreCircle.hide()
        self.treasureModel = loader.loadModel("phase_4/models/minigames/ice_game_barrel")
        self.penaltyModel = loader.loadModel("phase_4/models/minigames/ice_game_tnt2")
        self.penaltyModel.setScale(0.75, 0.75, 0.7)
        szId = self.getSafezoneId()
        obstacles = IceGameGlobals.Obstacles[szId]
        index = 0
        cubicObstacle = IceGameGlobals.ObstacleShapes[szId]
        for pos in obstacles:
            newPos = Point3(pos[0], pos[1], IceGameGlobals.TireRadius)
            newObstacle = self.createObstacle(newPos, index, cubicObstacle)
            self.obstacles.append(newObstacle)
            index += 1

        self.countSound = loader.loadSfx("phase_3.5/audio/sfx/tick_counter.ogg")
        self.treasureGrabSound = loader.loadSfx("phase_4/audio/sfx/MG_sfx_vine_game_bananas.ogg")
        self.penaltyGrabSound = loader.loadSfx("phase_4/audio/sfx/MG_cannon_fire_alt.ogg")
        self.tireSounds = []
        for tireIndex in xrange(4):
            tireHit = loader.loadSfx("phase_4/audio/sfx/Golf_Hit_Barrier_1.ogg")
            wallHit = loader.loadSfx("phase_4/audio/sfx/MG_maze_pickup.ogg")
            obstacleHit = loader.loadSfx("phase_4/audio/sfx/Golf_Hit_Barrier_2.ogg")
            self.tireSounds.append({"tireHit": tireHit, "wallHit": wallHit, "obstacleHit": obstacleHit})

        self.arrowRotateSound = loader.loadSfx("phase_4/audio/sfx/MG_sfx_ice_force_rotate.ogg")
        self.arrowUpSound = loader.loadSfx("phase_4/audio/sfx/MG_sfx_ice_force_increase_3sec.ogg")
        self.arrowDownSound = loader.loadSfx("phase_4/audio/sfx/MG_sfx_ice_force_decrease_3sec.ogg")
        self.scoreCircleSound = loader.loadSfx("phase_4/audio/sfx/MG_sfx_ice_scoring_1.ogg")

    def unload(self):
        self.notify.debug("unload")
        DistributedMinigame.DistributedMinigame.unload(self)
        del self.music
        self.gameBoard.removeNode()
        del self.gameBoard
        for forceArrow in self.forceArrowDict.values():
            forceArrow.removeNode()

        del self.forceArrowDict
        self.scoreCircle.removeNode()
        del self.scoreCircle
        del self.countSound

    def onstage(self):
        self.notify.debug("onstage")
        DistributedMinigame.DistributedMinigame.onstage(self)
        self.gameBoard.reparentTo(render)
        self.__placeToon(self.localAvId)
        self.moveCameraToTop()
        self.scorePanels = []
        base.playMusic(self.music, looping=1, volume=0.8)

    def offstage(self):
        self.notify.debug("offstage")
        self.music.stop()
        self.gameBoard.hide()
        self.infoLabel.hide()
        for avId in self.tireDict:
            self.tireDict[avId]["tireNodePath"].hide()

        for panel in self.scorePanels:
            panel.cleanup()

        del self.scorePanels
        for obstacle in self.obstacles:
            obstacle.hide()

        for treasure in self.treasures:
            treasure.nodePath.hide()

        for penalty in self.penalties:
            penalty.nodePath.hide()

        for avId in self.avIdList:
            av = self.getAvatar(avId)
            if av:
                av.dropShadow.show()
                av.resetLOD()

        taskMgr.remove(self.uniqueName("aimtask"))
        self.arrowKeys.destroy()
        del self.arrowKeys
        DistributedMinigame.DistributedMinigame.offstage(self)

    def handleDisabledAvatar(self, avId):
        self.notify.debug("handleDisabledAvatar")
        self.notify.debug("avatar " + str(avId) + " disabled")
        DistributedMinigame.DistributedMinigame.handleDisabledAvatar(self, avId)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug("setGameReady")
        if DistributedMinigame.DistributedMinigame.setGameReady(self):
            return
        for index in xrange(self.numPlayers):
            avId = self.avIdList[index]
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self.__placeToon(avId)
                toon.forwardSpeed = 0
                toon.rotateSpeed = False
                toon.dropShadow.hide()
                toon.setAnimState("Sit")
                if avId in self.tireDict:
                    tireNp = self.tireDict[avId]["tireNodePath"]
                    toon.reparentTo(tireNp)
                    toon.setY(1.0)
                    toon.setZ(-3)
                toon.startLookAround()

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

        self.scores = [0] * self.numPlayers
        spacing = 0.4
        for i in xrange(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = MinigameAvatarScorePanel.MinigameAvatarScorePanel(avId, avName)
            scorePanel.setScale(0.9)
            scorePanel.setPos(-0.583 - spacing * (self.numPlayers - 1 - i), 0.0, -0.15)
            scorePanel.reparentTo(base.a2dTopRight)
            scorePanel.makeTransparent(0.75)
            self.scorePanels.append(scorePanel)

        self.arrowKeys.setPressHandlers(
            [
                self.__upArrowPressed,
                self.__downArrowPressed,
                self.__leftArrowPressed,
                self.__rightArrowPressed,
                self.__controlPressed,
            ]
        )

    def isInPlayState(self):
        if not self.gameFSM.getCurrentState():
            return False
        if not self.gameFSM.getCurrentState().getName() == "play":
            return False
        return True

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

    def exitOff(self):
        pass

    def enterInputChoice(self):
        self.notify.debug("enterInputChoice")
        self.forceLocalToonToTire()
        self.controlKeyPressed = False
        if self.curRound == 0:
            self.setupStartOfMatch()
        else:
            self.notify.debug("self.curRound = %s" % self.curRound)
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.hide()
        if self.timerStartTime != None:
            self.startTimer()
        self.showForceArrows(realPlayersOnly=True)
        self.localForceArrow().setPosHpr(0, 0, -1.0, 0, 0, 0)
        self.localForceArrow().reparentTo(self.localTireNp())
        self.localForceArrow().setY(IceGameGlobals.TireRadius)
        self.localTireNp().headsUp(self.target)
        self.notify.debug("self.localForceArrow() heading = %s" % self.localForceArrow().getH())
        self.curHeading = self.localTireNp().getH()
        self.curForce = 25
        self.updateLocalForceArrow()
        for avId in self.forceArrowDict:
            forceArrow = self.forceArrowDict[avId]
            forceArrow.setPosHpr(0, 0, -1.0, 0, 0, 0)
            tireNp = self.tireDict[avId]["tireNodePath"]
            forceArrow.reparentTo(tireNp)
            forceArrow.setY(IceGameGlobals.TireRadius)
            tireNp.headsUp(self.target)
            self.updateForceArrow(avId, tireNp.getH(), 25)

        taskMgr.add(self.__aimTask, self.uniqueName("aimtask"))
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.stop()
        self.sendForceArrowUpdateAsap = False
        return

    def exitInputChoice(self):
        if not self.controlKeyPressed:
            if self.controlKeyWarningIval:
                self.controlKeyWarningIval.finish()
                self.controlKeyWarningIval = None
            self.controlKeyWarningIval = Sequence(
                Func(self.controlKeyWarningLabel.show),
                self.controlKeyWarningLabel.colorScaleInterval(
                    10, VBase4(1, 1, 1, 0), startColorScale=VBase4(1, 1, 1, 1)
                ),
                Func(self.controlKeyWarningLabel.hide),
            )
            self.controlKeyWarningIval.start()
        if self.timer != None:
            self.timer.destroy()
            self.timer = None
        self.timerStartTime = None
        self.hideForceArrows()
        self.arrowRotateSound.stop()
        self.arrowUpSound.stop()
        self.arrowDownSound.stop()
        taskMgr.remove(self.uniqueName("aimtask"))
        return

    def enterWaitServerChoices(self):
        self.waitingMoveLabel.show()
        self.showForceArrows(True)

    def exitWaitServerChoices(self):
        self.waitingMoveLabel.hide()
        self.hideForceArrows()

    def enterMoveTires(self):
        for key in self.tireDict:
            body = self.tireDict[key]["tireBody"]
            body.setAngularVel(0, 0, 0)
            body.setLinearVel(0, 0, 0)

        for index in xrange(len(self.allTireInputs)):
            input = self.allTireInputs[index]
            avId = self.avIdList[index]
            body = self.getTireBody(avId)
            degs = input[1] + 90
            tireNp = self.getTireNp(avId)
            tireH = tireNp.getH()
            self.notify.debug("tireH = %s" % tireH)
            radAngle = deg2Rad(degs)
            foo = NodePath("foo")
            dirVector = Vec3(math.cos(radAngle), math.sin(radAngle), 0)
            self.notify.debug("dirVector is now=%s" % dirVector)
            inputForce = input[0]
            inputForce /= self.MaxLocalForce
            inputForce *= self.MaxPhysicsForce
            force = dirVector * inputForce
            self.notify.debug("adding force %s to %d" % (force, avId))
            body.addForce(force)

        self.enableAllTireBodies()
        self.totalPhysicsSteps = 0
        self.startSim()
        taskMgr.add(self.__moveTiresTask, self.uniqueName("moveTiresTtask"))

    def exitMoveTires(self):
        self.forceLocalToonToTire()
        self.disableAllTireBodies()
        self.stopSim()
        self.notify.debug("total Physics steps = %d" % self.totalPhysicsSteps)
        taskMgr.remove(self.uniqueName("moveTiresTtask"))

    def enterSynch(self):
        self.waitingSyncLabel.show()

    def exitSynch(self):
        self.waitingSyncLabel.hide()

    def enterScoring(self):
        sortedByDistance = []
        for avId in self.avIdList:
            np = self.getTireNp(avId)
            pos = np.getPos()
            pos.setZ(0)
            sortedByDistance.append((avId, pos.length()))

        def compareDistance(x, y):
            if x[1] - y[1] > 0:
                return 1
            elif x[1] - y[1] < 0:
                return -1
            else:
                return 0

        sortedByDistance.sort(cmp=compareDistance)
        self.scoreMovie = Sequence()
        curScale = 0.01
        curTime = 0
        self.scoreCircle.setScale(0.01)
        self.scoreCircle.show()
        self.notify.debug("newScores = %s" % self.newScores)
        circleStartTime = 0
        for index in xrange(len(sortedByDistance)):
            distance = sortedByDistance[index][1]
            avId = sortedByDistance[index][0]
            scorePanelIndex = self.avIdList.index(avId)
            time = (distance - curScale) / IceGameGlobals.ExpandFeetPerSec
            if time < 0:
                time = 0.01
            scaleXY = distance + IceGameGlobals.TireRadius
            self.notify.debug("circleStartTime = %s" % circleStartTime)
            self.scoreMovie.append(
                Parallel(
                    LerpScaleInterval(self.scoreCircle, time, Point3(scaleXY, scaleXY, 1.0)),
                    SoundInterval(self.scoreCircleSound, duration=time, startTime=circleStartTime),
                )
            )
            circleStartTime += time
            startScore = self.scorePanels[scorePanelIndex].getScore()
            destScore = self.newScores[scorePanelIndex]
            self.notify.debug("for avId %d, startScore=%d, newScores=%d" % (avId, startScore, destScore))

            def increaseScores(t, scorePanelIndex=scorePanelIndex, startScore=startScore, destScore=destScore):
                oldScore = self.scorePanels[scorePanelIndex].getScore()
                diff = destScore - startScore
                newScore = int(startScore + diff * t)
                if newScore > oldScore:
                    base.playSfx(self.countSound)
                self.scorePanels[scorePanelIndex].setScore(newScore)
                self.scores[scorePanelIndex] = newScore

            duration = (destScore - startScore) * IceGameGlobals.ScoreCountUpRate
            tireNp = self.tireDict[avId]["tireNodePath"]
            self.scoreMovie.append(
                Parallel(
                    LerpFunctionInterval(increaseScores, duration),
                    Sequence(
                        LerpColorScaleInterval(tireNp, duration / 6.0, VBase4(1, 0, 0, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0, VBase4(1, 1, 1, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0, VBase4(1, 0, 0, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0, VBase4(1, 1, 1, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0, VBase4(1, 0, 0, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0, VBase4(1, 1, 1, 1)),
                    ),
                )
            )
            curScale += distance

        self.scoreMovie.append(Func(self.sendUpdate, "reportScoringMovieDone", []))
        self.scoreMovie.start()

    def exitScoring(self):
        self.scoreMovie.finish()
        self.scoreMovie = None
        self.scoreCircle.hide()
        return

    def enterFinalResults(self):
        lerpTrack = Parallel()
        lerpDur = 0.5
        tY = 0.6
        bY = -0.05
        lX = -0.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(IceGameGlobals.ShowScoresDuration), Func(self.gameOver))
        )
        self.showScoreTrack.start()

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

    def enterCleanup(self):
        self.notify.debug("enterCleanup")
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.start()

    def exitCleanup(self):
        pass

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if toon:
            toon.setPos(0, 0, 0)
            toon.setHpr(0, 0, 0)

    def moveCameraToTop(self):
        camera.reparentTo(render)
        p = self.cameraThreeQuarterView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])

    def setupTire(self, avId, index):
        tireNp, tireBody, tireOdeGeom = self.createTire(index)
        self.tireDict[avId] = {"tireNodePath": tireNp, "tireBody": tireBody, "tireOdeGeom": tireOdeGeom}
        if avId <= 0:
            tireBlocker = tireNp.find("**/tireblockermesh")
            if not tireBlocker.isEmpty():
                tireBlocker.hide()
        if avId == self.localAvId:
            tireNp = self.tireDict[avId]["tireNodePath"]
            self.treasureSphereName = "treasureCollider"
            self.treasureCollSphere = CollisionSphere(0, 0, 0, IceGameGlobals.TireRadius)
            self.treasureCollSphere.setTangible(0)
            self.treasureCollNode = CollisionNode(self.treasureSphereName)
            self.treasureCollNode.setFromCollideMask(ToontownGlobals.PieBitmask)
            self.treasureCollNode.addSolid(self.treasureCollSphere)
            self.treasureCollNodePath = tireNp.attachNewNode(self.treasureCollNode)
            self.treasureHandler = CollisionHandlerEvent()
            self.treasureHandler.addInPattern("%fn-intoTreasure")
            base.cTrav.addCollider(self.treasureCollNodePath, self.treasureHandler)
            eventName = "%s-intoTreasure" % self.treasureCollNodePath.getName()
            self.notify.debug("eventName = %s" % eventName)
            self.accept(eventName, self.toonHitSomething)

    def setupForceArrow(self, avId):
        arrow = loader.loadModel("phase_4/models/minigames/ice_game_arrow")
        priority = 0
        if avId < 0:
            priority = -avId
        else:
            priority = self.avIdList.index(avId)
            if avId == self.localAvId:
                priority = 10
        self.forceArrowDict[avId] = arrow

    def hideForceArrows(self):
        for forceArrow in self.forceArrowDict.values():
            forceArrow.hide()

    def showForceArrows(self, realPlayersOnly=True):
        for avId in self.forceArrowDict:
            if realPlayersOnly:
                if avId > 0:
                    self.forceArrowDict[avId].show()
                else:
                    self.forceArrowDict[avId].hide()
            else:
                self.forceArrowDict[avId].show()

    def localForceArrow(self):
        if self.localAvId in self.forceArrowDict:
            return self.forceArrowDict[self.localAvId]
        else:
            return None
        return None

    def setChoices(self, input0, input1, input2, input3):
        pass

    def startDebugTask(self):
        taskMgr.add(self.debugTask, self.debugTaskName)

    def stopDebugTask(self):
        taskMgr.remove(self.debugTaskName)

    def debugTask(self, task):
        if self.canDrive and self.tireDict.has_key(localAvatar.doId):
            dt = globalClock.getDt()
            forceMove = 25000
            forceMoveDt = forceMove
            tireBody = self.tireDict[localAvatar.doId]["tireBody"]
            if self.arrowKeys.upPressed() and not tireBody.isEnabled():
                x = 0
                y = 1
                tireBody.enable()
                tireBody.addForce(Vec3(x * forceMoveDt, y * forceMoveDt, 0))
            if self.arrowKeys.downPressed() and not tireBody.isEnabled():
                x = 0
                y = -1
                tireBody.enable()
                tireBody.addForce(Vec3(x * forceMoveDt, y * forceMoveDt, 0))
            if self.arrowKeys.leftPressed() and not tireBody.isEnabled():
                x = -1
                y = 0
                tireBody.enable()
                tireBody.addForce(Vec3(x * forceMoveDt, y * forceMoveDt, 0))
            if self.arrowKeys.rightPressed() and not tireBody.isEnabled():
                x = 1
                y = 0
                tireBody.enable()
                tireBody.addForce(Vec3(x * forceMoveDt, y * forceMoveDt, 0))
        return task.cont

    def __upArrowPressed(self):
        pass

    def __downArrowPressed(self):
        pass

    def __leftArrowPressed(self):
        pass

    def __rightArrowPressed(self):
        pass

    def __controlPressed(self):
        if self.gameFSM.getCurrentState().getName() == "inputChoice":
            self.sendForceArrowUpdateAsap = True
            self.updateLocalForceArrow()
            self.controlKeyPressed = True
            self.sendUpdate("setAvatarChoice", [self.curForce, self.curHeading])
            self.gameFSM.request("waitServerChoices")

    def startTimer(self):
        now = globalClock.getFrameTime()
        elapsed = now - self.timerStartTime
        self.timer.posInTopRightCorner()
        self.timer.setTime(IceGameGlobals.InputTimeout)
        self.timer.countdown(IceGameGlobals.InputTimeout - elapsed, self.handleChoiceTimeout)
        self.timer.show()

    def setTimerStartTime(self, timestamp):
        if not self.hasLocalToon:
            return
        self.timerStartTime = globalClockDelta.networkToLocalTime(timestamp)
        if self.timer != None:
            self.startTimer()
        return

    def handleChoiceTimeout(self):
        self.sendUpdate("setAvatarChoice", [0, 0])
        self.gameFSM.request("waitServerChoices")

    def localTireNp(self):
        ret = None
        if self.localAvId in self.tireDict:
            ret = self.tireDict[self.localAvId]["tireNodePath"]
        return ret

    def localTireBody(self):
        ret = None
        if self.localAvId in self.tireDict:
            ret = self.tireDict[self.localAvId]["tireBody"]
        return ret

    def getTireBody(self, avId):
        ret = None
        if avId in self.tireDict:
            ret = self.tireDict[avId]["tireBody"]
        return ret

    def getTireNp(self, avId):
        ret = None
        if avId in self.tireDict:
            ret = self.tireDict[avId]["tireNodePath"]
        return ret

    def updateForceArrow(self, avId, curHeading, curForce):
        forceArrow = self.forceArrowDict[avId]
        tireNp = self.tireDict[avId]["tireNodePath"]
        tireNp.setH(curHeading)
        tireBody = self.tireDict[avId]["tireBody"]
        tireBody.setQuaternion(tireNp.getQuat())
        self.notify.debug("curHeading = %s" % curHeading)
        yScale = curForce / 100.0
        yScale *= 1
        headY = yScale * 15
        xScale = (yScale - 1) / 2.0 + 1.0
        shaft = forceArrow.find("**/arrow_shaft")
        head = forceArrow.find("**/arrow_head")
        shaft.setScale(xScale, yScale, 1)
        head.setPos(0, headY, 0)
        head.setScale(xScale, xScale, 1)

    def updateLocalForceArrow(self):
        avId = self.localAvId
        self.b_setForceArrowInfo(avId, self.curHeading, self.curForce)

    def __aimTask(self, task):
        if not hasattr(self, "arrowKeys"):
            return task.done
        dt = globalClock.getDt()
        headingMomentumChange = dt * 60.0
        forceMomentumChange = dt * 160.0
        arrowUpdate = False
        arrowRotating = False
        arrowUp = False
        arrowDown = False
        if self.arrowKeys.upPressed() and not self.arrowKeys.downPressed():
            self.forceMomentum += forceMomentumChange
            if self.forceMomentum < 0:
                self.forceMomentum = 0
            if self.forceMomentum > 50:
                self.forceMomentum = 50
            oldForce = self.curForce
            self.curForce += self.forceMomentum * dt
            arrowUpdate = True
            if oldForce < self.MaxLocalForce:
                arrowUp = True
        elif self.arrowKeys.downPressed() and not self.arrowKeys.upPressed():
            self.forceMomentum += forceMomentumChange
            if self.forceMomentum < 0:
                self.forceMomentum = 0
            if self.forceMomentum > 50:
                self.forceMomentum = 50
            oldForce = self.curForce
            self.curForce -= self.forceMomentum * dt
            arrowUpdate = True
            if oldForce > 0.01:
                arrowDown = True
        else:
            self.forceMomentum = 0
        if self.arrowKeys.leftPressed() and not self.arrowKeys.rightPressed():
            self.headingMomentum += headingMomentumChange
            if self.headingMomentum < 0:
                self.headingMomentum = 0
            if self.headingMomentum > 50:
                self.headingMomentum = 50
            self.curHeading += self.headingMomentum * dt
            arrowUpdate = True
            arrowRotating = True
        elif self.arrowKeys.rightPressed() and not self.arrowKeys.leftPressed():
            self.headingMomentum += headingMomentumChange
            if self.headingMomentum < 0:
                self.headingMomentum = 0
            if self.headingMomentum > 50:
                self.headingMomentum = 50
            self.curHeading -= self.headingMomentum * dt
            arrowUpdate = True
            arrowRotating = True
        else:
            self.headingMomentum = 0
        if arrowUpdate:
            self.normalizeHeadingAndForce()
            self.updateLocalForceArrow()
        if arrowRotating:
            if not self.arrowRotateSound.status() == self.arrowRotateSound.PLAYING:
                base.playSfx(self.arrowRotateSound, looping=True)
        else:
            self.arrowRotateSound.stop()
        if arrowUp:
            if not self.arrowUpSound.status() == self.arrowUpSound.PLAYING:
                base.playSfx(self.arrowUpSound, looping=False)
        else:
            self.arrowUpSound.stop()
        if arrowDown:
            if not self.arrowDownSound.status() == self.arrowDownSound.PLAYING:
                base.playSfx(self.arrowDownSound, looping=False)
        else:
            self.arrowDownSound.stop()
        return task.cont

    def normalizeHeadingAndForce(self):
        if self.curForce > self.MaxLocalForce:
            self.curForce = self.MaxLocalForce
        if self.curForce < 0.01:
            self.curForce = 0.01

    def setTireInputs(self, tireInputs):
        if not self.hasLocalToon:
            return
        self.allTireInputs = tireInputs
        self.gameFSM.request("moveTires")

    def enableAllTireBodies(self):
        for avId in self.tireDict.keys():
            self.tireDict[avId]["tireBody"].enable()

    def disableAllTireBodies(self):
        for avId in self.tireDict.keys():
            self.tireDict[avId]["tireBody"].disable()

    def areAllTiresDisabled(self):
        for avId in self.tireDict.keys():
            if self.tireDict[avId]["tireBody"].isEnabled():
                return False

        return True

    def __moveTiresTask(self, task):
        if self.areAllTiresDisabled():
            self.sendTirePositions()
            self.gameFSM.request("synch")
            return task.done
        return task.cont

    def sendTirePositions(self):
        tirePositions = []
        for index in xrange(len(self.avIdList)):
            avId = self.avIdList[index]
            tire = self.getTireBody(avId)
            pos = Point3(tire.getPosition())
            tirePositions.append([pos[0], pos[1], pos[2]])

        for index in xrange(len(self.avIdList), 4):
            avId = -index
            tire = self.getTireBody(avId)
            pos = Point3(tire.getPosition())
            tirePositions.append([pos[0], pos[1], pos[2]])

        self.sendUpdate("endingPositions", [tirePositions])

    def setFinalPositions(self, finalPos):
        if not self.hasLocalToon:
            return
        for index in xrange(len(self.avIdList)):
            avId = self.avIdList[index]
            tire = self.getTireBody(avId)
            np = self.getTireNp(avId)
            pos = finalPos[index]
            tire.setPosition(pos[0], pos[1], pos[2])
            np.setPos(pos[0], pos[1], pos[2])

        for index in xrange(len(self.avIdList), 4):
            avId = -index
            tire = self.getTireBody(avId)
            np = self.getTireNp(avId)
            pos = finalPos[index]
            tire.setPosition(pos[0], pos[1], pos[2])
            np.setPos(pos[0], pos[1], pos[2])

    def updateInfoLabel(self):
        self.infoLabel["text"] = TTLocalizer.IceGameInfo % {
            "curMatch": self.curMatch + 1,
            "numMatch": IceGameGlobals.NumMatches,
            "curRound": self.curRound + 1,
            "numRound": IceGameGlobals.NumRounds,
        }

    def setMatchAndRound(self, match, round):
        if not self.hasLocalToon:
            return
        self.curMatch = match
        self.curRound = round
        self.updateInfoLabel()

    def setScores(self, match, round, scores):
        if not self.hasLocalToon:
            return
        self.newMatch = match
        self.newRound = round
        self.newScores = scores

    def setNewState(self, state):
        if not self.hasLocalToon:
            return
        self.notify.debug("setNewState gameFSM=%s newState=%s" % (self.gameFSM, state))
        self.gameFSM.request(state)

    def putAllTiresInStartingPositions(self):
        for index in xrange(len(self.avIdList)):
            avId = self.avIdList[index]
            np = self.tireDict[avId]["tireNodePath"]
            np.setPos(IceGameGlobals.StartingPositions[index])
            self.notify.debug("avId=%s newPos=%s" % (avId, np.getPos))
            np.setHpr(0, 0, 0)
            quat = np.getQuat()
            body = self.tireDict[avId]["tireBody"]
            body.setPosition(IceGameGlobals.StartingPositions[index])
            body.setQuaternion(quat)

        for index in xrange(len(self.avIdList), 4):
            avId = -index
            np = self.tireDict[avId]["tireNodePath"]
            np.setPos(IceGameGlobals.StartingPositions[index])
            self.notify.debug("avId=%s newPos=%s" % (avId, np.getPos))
            np.setHpr(0, 0, 0)
            quat = np.getQuat()
            body = self.tireDict[avId]["tireBody"]
            body.setPosition(IceGameGlobals.StartingPositions[index])
            body.setQuaternion(quat)

    def b_setForceArrowInfo(self, avId, force, heading):
        self.setForceArrowInfo(avId, force, heading)
        self.d_setForceArrowInfo(avId, force, heading)

    def d_setForceArrowInfo(self, avId, force, heading):
        sendIt = False
        curTime = self.getCurrentGameTime()
        if self.sendForceArrowUpdateAsap:
            sendIt = True
        elif curTime - self.lastForceArrowUpdateTime > 0.2:
            sendIt = True
        if sendIt:
            self.sendUpdate("setForceArrowInfo", [avId, force, heading])
            self.sendForceArrowUpdateAsap = False
            self.lastForceArrowUpdateTime = self.getCurrentGameTime()

    def setForceArrowInfo(self, avId, force, heading):
        if not self.hasLocalToon:
            return
        self.updateForceArrow(avId, force, heading)

    def setupStartOfMatch(self):
        self.putAllTiresInStartingPositions()
        szId = self.getSafezoneId()
        self.numTreasures = IceGameGlobals.NumTreasures[szId]
        if self.treasures:
            for treasure in self.treasures:
                treasure.destroy()

            self.treasures = []
        index = 0
        treasureMargin = IceGameGlobals.TireRadius + 1.0
        while len(self.treasures) < self.numTreasures:
            xPos = self.randomNumGen.randrange(IceGameGlobals.MinWall[0] + 5, IceGameGlobals.MaxWall[0] - 5)
            yPos = self.randomNumGen.randrange(IceGameGlobals.MinWall[1] + 5, IceGameGlobals.MaxWall[1] - 5)
            self.notify.debug("yPos=%s" % yPos)
            pos = Point3(xPos, yPos, IceGameGlobals.TireRadius)
            newTreasure = IceTreasure.IceTreasure(self.treasureModel, pos, index, self.doId, penalty=False)
            goodSpot = True
            for obstacle in self.obstacles:
                if newTreasure.nodePath.getDistance(obstacle) < treasureMargin:
                    goodSpot = False
                    break

            if goodSpot:
                for treasure in self.treasures:
                    if newTreasure.nodePath.getDistance(treasure.nodePath) < treasureMargin:
                        goodSpot = False
                        break

            if goodSpot:
                self.treasures.append(newTreasure)
                index += 1
            else:
                newTreasure.destroy()

        self.numPenalties = IceGameGlobals.NumPenalties[szId]
        if self.penalties:
            for penalty in self.penalties:
                penalty.destroy()

            self.penalties = []
        index = 0
        while len(self.penalties) < self.numPenalties:
            xPos = self.randomNumGen.randrange(IceGameGlobals.MinWall[0] + 5, IceGameGlobals.MaxWall[0] - 5)
            yPos = self.randomNumGen.randrange(IceGameGlobals.MinWall[1] + 5, IceGameGlobals.MaxWall[1] - 5)
            self.notify.debug("yPos=%s" % yPos)
            pos = Point3(xPos, yPos, IceGameGlobals.TireRadius)
            newPenalty = IceTreasure.IceTreasure(self.penaltyModel, pos, index, self.doId, penalty=True)
            goodSpot = True
            for obstacle in self.obstacles:
                if newPenalty.nodePath.getDistance(obstacle) < treasureMargin:
                    goodSpot = False
                    break

            if goodSpot:
                for treasure in self.treasures:
                    if newPenalty.nodePath.getDistance(treasure.nodePath) < treasureMargin:
                        goodSpot = False
                        break

            if goodSpot:
                for penalty in self.penalties:
                    if newPenalty.nodePath.getDistance(penalty.nodePath) < treasureMargin:
                        goodSpot = False
                        break

            if goodSpot:
                self.penalties.append(newPenalty)
                index += 1
            else:
                newPenalty.destroy()

    def toonHitSomething(self, entry):
        self.notify.debug("---- treasure Enter ---- ")
        self.notify.debug("%s" % entry)
        name = entry.getIntoNodePath().getName()
        parts = name.split("-")
        if len(parts) < 3:
            self.notify.debug("collided with %s, but returning" % name)
            return
        if not int(parts[1]) == self.doId:
            self.notify.debug("collided with %s, but doId doesn't match" % name)
            return
        treasureNum = int(parts[2])
        if "penalty" in parts[0]:
            self.__penaltyGrabbed(treasureNum)
        else:
            self.__treasureGrabbed(treasureNum)

    def __treasureGrabbed(self, treasureNum):
        self.treasures[treasureNum].showGrab()
        self.treasureGrabSound.play()
        self.sendUpdate("claimTreasure", [treasureNum])

    def setTreasureGrabbed(self, avId, treasureNum):
        if not self.hasLocalToon:
            return
        self.notify.debug("treasure %s grabbed by %s" % (treasureNum, avId))
        if avId != self.localAvId:
            self.treasures[treasureNum].showGrab()
        i = self.avIdList.index(avId)
        self.scores[i] += 1
        self.scorePanels[i].setScore(self.scores[i])

    def __penaltyGrabbed(self, penaltyNum):
        self.penalties[penaltyNum].showGrab()
        self.sendUpdate("claimPenalty", [penaltyNum])

    def setPenaltyGrabbed(self, avId, penaltyNum):
        if not self.hasLocalToon:
            return
        self.notify.debug("penalty %s grabbed by %s" % (penaltyNum, avId))
        if avId != self.localAvId:
            self.penalties[penaltyNum].showGrab()
        i = self.avIdList.index(avId)
        self.scores[i] -= 1
        self.scorePanels[i].setScore(self.scores[i])

    def postStep(self):
        DistributedIceWorld.DistributedIceWorld.postStep(self)
        if not self.colCount:
            return
        for count in xrange(self.colCount):
            c0, c1 = self.getOrderedContacts(count)
            if c1 in self.tireCollideIds:
                tireIndex = self.tireCollideIds.index(c1)
                if c0 in self.tireCollideIds:
                    self.tireSounds[tireIndex]["tireHit"].play()
                elif c0 == self.wallCollideId:
                    self.tireSounds[tireIndex]["wallHit"].play()
                elif c0 == self.obstacleCollideId:
                    self.tireSounds[tireIndex]["obstacleHit"].play()

    def forceLocalToonToTire(self):
        toon = localAvatar
        if toon and self.localAvId in self.tireDict:
            tireNp = self.tireDict[self.localAvId]["tireNodePath"]
            toon.reparentTo(tireNp)
            toon.setPosHpr(0, 0, 0, 0, 0, 0)
            toon.setY(1.0)
            toon.setZ(-3)
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)
Beispiel #54
0
class DistributedPairingGame(DistributedMinigame):

    # define constants that you won't want to tweak here
    TOON_SPEED = 11  #8
    MAX_FRAME_MOVE = 1  # maximum movement in one frame
    MAX_FACE_UP_CARDS = 2

    notify = directNotify.newCategory("DistributedPairingGame")
    bonusGlowTime = 0.5  # how many seconds does bonus stay on a card

    EndGameTaskName = 'endPairingGame'

    xCardInc = 4

    cardsPerRow = 8
    cardsPerCol = 5

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)

        self.gameFSM = ClassicFSM.ClassicFSM(
            'DistributedPairingGame',
            [
                State.State('off', self.enterOff, self.exitOff, ['play']),
                State.State('play', self.enterPlay, self.exitPlay,
                            ['cleanup']),
                State.State('cleanup', self.enterCleanup, self.exitCleanup,
                            []),
            ],
            # Initial State
            'off',
            # Final State
            'cleanup',
        )

        # it's important for the final state to do cleanup;
        # on disconnect, the ClassicFSM will be forced into the
        # final state. All states (except 'off') should
        # be prepared to transition to 'cleanup' at any time.

        # Add our game ClassicFSM to the framework ClassicFSM
        self.addChildGameFSM(self.gameFSM)

        self.cameraTopView = (17.6, 6.18756, 43.9956, 0, -89, 0)
        #self.cameraThreeQuarterView = (13.8234, -8.93352, 33.4497, 0, -62.89, 0)
        self.cameraThreeQuarterView = (14.0, -8.93352, 33.4497, 0, -62.89, 0)

        self.deckSeed = 0
        self.faceUpList = []  # which cards are face up
        self.localFaceUpList = []  # which cards did this local toon turn up

        self.inList = []  # which cards is the local toon in
        self.inactiveList = []  # which cards are out of play

        self.points = 0
        self.flips = 0
        self.matches = 0

        self.yCardInc = 4

        self.startingPositions = [
            (0, 0, 0, -45),
            ((self.cardsPerRow - 1) * self.xCardInc,
             (self.cardsPerCol - 1) * self.yCardInc, 0, 135),
            ((self.cardsPerRow - 1) * self.xCardInc, 0, 0, 45),
            (0, (self.cardsPerCol - 1) * self.yCardInc, 0, -135),
        ]

        self.stageMin = Point2(0, 0)
        self.stageMax = Point2((self.cardsPerRow - 1) * self.xCardInc,
                               (self.cardsPerCol - 1) * self.yCardInc)
        self.gameDuration = PairingGameGlobals.EasiestGameDuration

    def moveCameraToTop(self):
        camera.reparentTo(render)
        #p = self.cameraTopView
        p = self.cameraThreeQuarterView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])

    def getTitle(self):
        return TTLocalizer.PairingGameTitle

    def getInstructions(self):
        if self.numPlayers > 1:
            return TTLocalizer.PairingGameInstructionsMulti
        else:
            return TTLocalizer.PairingGameInstructions

    def getMaxDuration(self):
        # how many seconds can this minigame possibly last (within reason)?
        # this is for debugging only
        return 0

    def load(self):
        # load resources and create objects here
        self.notify.debug("load")
        DistributedMinigame.load(self)

        self.gameDuration = PairingGameGlobals.calcGameDuration(
            self.getDifficulty())

        #self.gameBoard = loader.loadModel("phase_4/models/minigames/toon_cannon_gameground")
        self.gameBoard = loader.loadModel(
            "phase_4/models/minigames/memory_room")
        self.gameBoard.setPosHpr(0.5, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        #self.gameBoard.find('**/tree_ring').removeNode()

        #self.debugAxis = loader.loadModel('models/misc/xyzAxis')
        #self.debugAxis.reparentTo(self.gameBoard)
        self.deck = PairingGameGlobals.createDeck(self.deckSeed,
                                                  self.numPlayers)
        self.notify.debug('%s' % self.deck.cards)

        testCard = self.getDeckOrderIndex(self.cardsPerCol - 1, 0)
        if not testCard > -1:
            self.yCardInc *= 1.25

        self.cards = []

        for index in xrange(len(self.deck.cards)):
            cardValue = self.deck.cards[index]
            oneCard = PairingGameCard.PairingGameCard(cardValue)
            oneCard.load()
            xPos, yPos = self.getCardPos(index)
            oneCard.setPos(xPos, yPos, 0)
            oneCard.reparentTo(render)
            self.notify.debug('%s' % oneCard.getPos())
            self.notify.debug('suit %s rank %s value %s' %
                              (oneCard.suit, oneCard.rank, oneCard.value))
            self.accept('entercardCollision-%d' % oneCard.value,
                        self.enterCard)
            self.accept('exitcardCollision-%d' % oneCard.value, self.exitCard)
            oneCard.turnDown(doInterval=False)
            self.cards.append(oneCard)

        self.bonusTraversal = range(len(self.cards))

        self.bonusGlow = render.attachNewNode('bonusGlow')

        sign = loader.loadModel("phase_4/models/minigames/garden_sign_memory")
        # remove the bits we don't want
        sign.find('**/sign1').removeNode()
        sign.find('**/sign2').removeNode()
        sign.find('**/collision').removeNode()
        #sign.setTransparency(1)
        #sign.setColorScale(1,1,0,0.5)
        sign.setPos(0, 0, 0.05)
        sign.reparentTo(self.bonusGlow)

        self.bonusGlow.setScale(2.5)

        self.pointsFrame = DirectFrame(
            #parent = self.gui,
            relief=None,
            geom=DGG.getDefaultDialogGeom(),
            geom_color=GlobalDialogColor,
            geom_scale=(4, 1, 1),
            pos=(-0.33, 0, 0.9),
            scale=0.1,
            text=TTLocalizer.PairingGamePoints,
            text_align=TextNode.ALeft,
            text_scale=TTLocalizer.DPGpointsFrame,
            text_pos=(-1.94, -0.1, 0.0))
        self.pointsLabel = DirectLabel(
            parent=self.pointsFrame,
            relief=None,
            text='0',
            text_fg=VBase4(0, 0.5, 0, 1),
            text_align=TextNode.ARight,
            text_scale=0.7,
            pos=(1.82, 0, -0.15),
        )

        self.flipsFrame = DirectFrame(
            #parent = self.gui,
            relief=None,
            geom=DGG.getDefaultDialogGeom(),
            geom_color=GlobalDialogColor,
            geom_scale=(4, 1, 1),
            pos=(0.33, 0, 0.9),
            scale=0.1,
            text=TTLocalizer.PairingGameFlips,
            text_align=TextNode.ALeft,
            text_scale=TTLocalizer.DPGflipsFrame,
            text_pos=(-1.94, -0.1, 0.0))
        self.flipsLabel = DirectLabel(
            parent=self.flipsFrame,
            relief=None,
            text='0',
            text_fg=VBase4(0, 1.0, 0, 1),
            text_align=TextNode.ARight,
            text_scale=0.7,
            pos=(1.82, 0, -0.15),
        )

        # this will be used to generate textnodes
        self.__textGen = TextNode("ringGame")
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)

        self.sndPerfect = base.loadSfx(
            "phase_4/audio/sfx/MG_pairing_all_matched.mp3")

        self.calcBonusTraversal()

        self.music = base.loadMusic("phase_4/audio/bgm/MG_Pairing.mid")
        self.matchSfx = base.loadSfx("phase_4/audio/sfx/MG_pairing_match.mp3")
        self.matchWithBonusSfx = base.loadSfx(
            "phase_4/audio/sfx/MG_pairing_match_bonus_both.mp3")
        self.signalSfx = []
        for i in range(4):
            self.signalSfx.append(
                base.loadSfx(
                    "phase_4/audio/sfx/MG_pairing_jumping_signal.mp3"))
        self.bonusMovesSfx = base.loadSfx(
            "phase_4/audio/sfx/MG_pairing_bonus_moves.mp3")

        # WARNING DEBUG only, remove or else it will leak
        # base.minigame = self

    def unload(self):
        self.notify.debug("unload")
        DistributedMinigame.unload(self)
        # unload resources and delete objects from load() here
        # remove our game ClassicFSM from the framework ClassicFSM
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM

        self.gameBoard.removeNode()
        del self.gameBoard

        for card in self.cards:
            card.unload()
            del card
        self.cards = []

        self.pointsFrame.removeNode()
        del self.pointsFrame
        self.flipsFrame.removeNode()
        del self.flipsFrame

        del self.__textGen
        del self.sndPerfect

        self.bonusGlow.removeNode()
        del self.bonusGlow

        del self.music
        del self.matchSfx
        del self.matchWithBonusSfx
        for i in range(4):
            del self.signalSfx[0]
        self.signalSfx = []
        del self.bonusMovesSfx

    def onstage(self):
        self.notify.debug("onstage")
        DistributedMinigame.onstage(self)
        # start up the minigame; parent things to render, start playing
        # music...
        # at this point we cannot yet show the remote players' toons
        self.gameBoard.reparentTo(render)

        for card in self.cards:
            card.reparentTo(render)

        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)

        self.moveCameraToTop()

    def offstage(self):
        self.notify.debug("offstage")
        # stop the minigame; parent things to hidden, stop the
        # music...

        self.gameBoard.hide()

        for card in self.cards:
            card.hide()

        # the base class parents the toons to hidden, so consider
        # calling it last
        DistributedMinigame.offstage(self)

    def handleDisabledAvatar(self, avId):
        """This will be called if an avatar exits unexpectedly"""
        self.notify.debug("handleDisabledAvatar")
        self.notify.debug("avatar " + str(avId) + " disabled")
        # clean up any references to the disabled avatar before he disappears

        # then call the base class
        DistributedMinigame.handleDisabledAvatar(self, avId)

    def setGameReady(self):
        if not self.hasLocalToon: return
        self.notify.debug("setGameReady")
        if DistributedMinigame.setGameReady(self):
            return
        # all of the remote toons have joined the game;
        # it's safe to show them now.
        for index in xrange(self.numPlayers):
            avId = self.avIdList[index]
            # Find the actual avatar in the cr
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self.__placeToon(avId)
                toon.setAnimState('Happy', 1.0)
                # Start the smoothing task.
                toon.startSmooth()
                toon.startLookAround()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon: return
        self.notify.debug("setGameStart")
        # base class will cause gameFSM to enter initial state
        DistributedMinigame.setGameStart(self, timestamp)
        # all players have finished reading the rules,
        # and are ready to start playing.

        # make the remote toons stop looking around
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.stopLookAround()

        # transition to the appropriate state
        self.gameFSM.request("play")

    # these are enter and exit functions for the game's
    # fsm (finite state machine)

    def isInPlayState(self):
        """Return true if we are in the play state."""
        if not self.gameFSM.getCurrentState():
            return False
        if not self.gameFSM.getCurrentState().getName() == 'play':
            return False
        return True

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

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug("enterPlay")

        # Start music
        base.playMusic(self.music, looping=1, volume=0.9)

        orthoDrive = OrthoDrive(
            self.TOON_SPEED,
            maxFrameMove=self.MAX_FRAME_MOVE,
            customCollisionCallback=self.
            __doPairingGameCollisions  # self.__doMazeCollisions,
        )
        self.orthoWalk = OrthoWalk(orthoDrive,
                                   broadcast=not self.isSinglePlayer())
        self.orthoWalk.start()

        # listen for key presses
        # We used to use the insert key for tossing pies.
        # Nowadays we use the delete key instead, for better
        # consistency with Macs (which lack an insert key).
        self.accept('insert', self.__flipKeyPressed)
        self.accept('delete', self.__flipKeyPressed)

        self.accept('time-control', self.__beginSignal)
        self.accept('time-control-up', self.__endSignal)

        self.bonusGlowIndex = 0
        self.bonusGlowCard = self.bonusTraversal[self.bonusGlowIndex]
        self.startBonusTask()

        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(self.gameDuration)
        self.timer.countdown(self.gameDuration, self.timerExpired)

        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.stop()

        # when the game is done, call gameOver()
        # self.gameOver()

    def exitPlay(self):
        # Stop music
        self.music.stop()

        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        self.bonusGlow.hide()

        self.stopBonusTask()

        self.timer.stop()
        self.timer.destroy()
        del self.timer

        self.ignoreAll()

        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.start()

        if hasattr(self, 'perfectIval'):
            self.perfectIval.pause()
            del self.perfectIval

        taskMgr.remove(self.EndGameTaskName)
        taskMgr.remove('pairGameContinueSignal')

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

    def exitCleanup(self):
        pass

    def __placeToon(self, avId):
        """ places a toon in its starting position """
        toon = self.getAvatar(avId)
        if self.numPlayers == 1:
            toon.setPos(0, 0, 0)
            toon.setHpr(0, 0, 0)
        else:
            posIndex = self.avIdList.index(avId)
            pos = self.startingPositions[posIndex]
            toon.setPos(pos[0], pos[1], pos[2])
            toon.setHpr(pos[3], 0, 0)

    def __doPairingGameCollisions(self, oldPos, newPos):
        x = bound(newPos[0], self.stageMin[0], self.stageMax[0])
        y = bound(newPos[1], self.stageMin[1], self.stageMax[1])
        newPos.setX(x)
        newPos.setY(y)

        if self.inList:
            newPos.setZ(0.15)
        else:
            newPos.setZ(0.0)

        #if we're moving also cancel out the signaling task
        if not oldPos == newPos:
            taskMgr.remove('pairGameContinueSignal')
        return newPos

    def getDeckOrderFromValue(self, value):
        for index in xrange(len(self.cards)):
            if self.cards[index].value == value:
                return index
        return -1

    def getDeckOrderFromPairingGameCard(self, into):
        try:
            index = self.cards.index(into)
        except ValueError:
            index = -1
        return index

    def enterCard(self, colEntry):
        intoName = colEntry.getIntoNodePath().getName()
        parts = intoName.split('-')
        value = int(parts[1])

        self.notify.debug('entered cardValue %d' % value)
        deckOrder = self.getDeckOrderFromValue(value)

        if not deckOrder in self.inList:
            self.inList.append(deckOrder)

    def exitCard(self, colEntry):
        intoName = colEntry.getIntoNodePath().getName()
        parts = intoName.split('-')
        value = int(parts[1])
        self.notify.debug('exited cardValue %d' % value)
        deckOrder = self.getDeckOrderFromValue(value)
        if deckOrder in self.inList:
            self.inList.remove(deckOrder)

    def handleMatch(self, cardA, cardB, withBonus):
        self.notify.debug('we got a match %d %d' % (cardA, cardB))
        self.matches += 1

        if cardA in self.faceUpList:
            self.faceUpList.remove(cardA)
        if cardB in self.faceUpList:
            self.faceUpList.remove(cardB)

        self.inactiveList.append(cardA)
        self.inactiveList.append(cardB)

        matchIval = Parallel()
        for card in [cardA, cardB]:
            self.cards[card].setTransparency(1)
            cardSeq = Sequence(
                LerpColorScaleInterval(self.cards[card],
                                       duration=1,
                                       colorScale=Vec4(1.0, 1.0, 1.0, 0.0)),
                Func(self.cards[card].hide))
            matchIval.append(cardSeq)

        if withBonus:
            matchIval.append(
                SoundInterval(self.matchWithBonusSfx,
                              node=self.cards[card],
                              listenerNode=base.localAvatar,
                              cutOff=240))
        else:
            matchIval.append(
                SoundInterval(self.matchSfx,
                              node=self.cards[card],
                              listenerNode=base.localAvatar,
                              cutOff=240))
        matchIval.start()

        # report we're done if all cards are matched
        if len(self.inactiveList) == len(self.cards):
            self.sendUpdate('reportDone')

    def turnUpCard(self, deckOrder):
        self.cards[deckOrder].turnUp()
        self.faceUpList.append(deckOrder)

    def turnDownCard(self, deckOrder):
        self.cards[deckOrder].turnDown()
        if deckOrder in self.faceUpList:
            self.faceUpList.remove(deckOrder)

    def __flipKeyPressed(self):
        if self.inList:
            shortestDistance = 10000
            cardToFlip = -1
            for deckOrder in self.inList:
                dist = base.localAvatar.getDistance(self.cards[deckOrder])
                if dist < shortestDistance:
                    shortestDistance = dist
                    cardToFlip = deckOrder
            deckOrderIndex = cardToFlip  # self.inList[-1]
            card = self.cards[deckOrderIndex]
            if card.isFaceDown() and not deckOrderIndex in self.inactiveList:
                #self.turnUpCard(deckOrderIndex)
                self.sendUpdate('openCardRequest',
                                [deckOrderIndex, self.bonusGlowCard])
            elif card.isFaceUp() and deckOrderIndex in self.faceUpList:
                pass
                #make sure this is the latest card
                #self.faceUpList.remove(deckOrderIndex)
                #self.faceUpList.append(deckOrderIndex)

    def moveBonusGlowTask(self, task):
        """
        move the bonus glow based on the game time to keep 2 clients in sync
        """
        if len(self.cards) == 0:
            return Task.done

        curT = self.getCurrentGameTime()
        intTime = int(curT / self.bonusGlowTime)
        newIndex = intTime % len(self.cards)
        if not newIndex == self.bonusGlowIndex:
            self.bonusGlowIndex = newIndex
            self.bonusGlowCard = self.bonusTraversal[self.bonusGlowIndex]
            card = self.cards[self.bonusGlowCard]
            self.bonusGlow.setPos(card.getPos())
            base.playSfx(self.bonusMovesSfx, node=card, volume=0.25)
        return Task.cont

    def timerExpired(self):
        self.sendUpdate('reportDone')
        pass

    def setDeckSeed(self, deckSeed):
        if not self.hasLocalToon: return
        self.deckSeed = deckSeed

    def updateFlipText(self):
        self.flipsLabel['text'] = str(self.flips)
        lowFlipModifier = PairingGameGlobals.calcLowFlipModifier(
            self.matches, self.flips)
        red = 1.0 - lowFlipModifier
        green = lowFlipModifier
        self.flipsLabel['text_fg'] = Vec4(red, green, 0, 1.0)

    def openCardResult(self, cardToTurnUp, avId, matchingCard, points,
                       cardsToTurnDown):
        if not self.hasLocalToon: return
        assert self.notify.debugStateCall()

        if not self.isInPlayState():
            return

        if avId == base.localAvatar.doId:
            self.localFaceUpList.append(cardToTurnUp)

        self.turnUpCard(cardToTurnUp)

        gotBonus = False
        if points - self.points > 1:
            gotBonus = True

        if matchingCard > -1:
            self.handleMatch(cardToTurnUp, matchingCard, gotBonus)

        self.flips += 1
        self.updateFlipText()

        self.points = points
        self.pointsLabel['text'] = str(self.points)

        for card in cardsToTurnDown:
            self.turnDownCard(card)

    def startBonusTask(self):
        taskMgr.add(self.moveBonusGlowTask, self.taskName("moveBonusGlowTask"))

    def stopBonusTask(self):
        taskMgr.remove(self.taskName("moveBonusGlowTask"))

    def setEveryoneDone(self):
        if not self.hasLocalToon: return
        if self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring setEveryoneDone msg')
            return

        self.notify.debug('setEveryoneDone')

        def endGame(task, self=self):
            if not PairingGameGlobals.EndlessGame:
                self.gameOver()
            return Task.done

        # hide the timer
        self.timer.hide()

        # hide the bonus
        self.bonusGlow.hide()

        # if it was a perfect game, let the players know
        if len(self.inactiveList) == len(self.cards):
            self.notify.debug("perfect game!")

            perfectTextSubnode = hidden.attachNewNode(
                self.__genText(TTLocalizer.PairingGamePerfect))
            perfectText = hidden.attachNewNode('perfectText')
            perfectTextSubnode.reparentTo(perfectText)
            # offset the subnode so that the text is centered on both axes
            # we need the parent node so that the text will scale correctly
            frame = self.__textGen.getCardActual()
            offsetY = -abs(frame[2] + frame[3]) / 2.
            perfectTextSubnode.setPos(0, 0, offsetY)

            perfectText.setColor(1, .1, .1, 1)

            def fadeFunc(t, text=perfectText):
                text.setColorScale(1, 1, 1, t)

            def destroyText(text=perfectText):
                text.removeNode()

            textTrack = Sequence(
                Func(perfectText.reparentTo, aspect2d),
                Parallel(
                    LerpScaleInterval(perfectText,
                                      duration=.5,
                                      scale=.3,
                                      startScale=0.),
                    LerpFunctionInterval(
                        fadeFunc,
                        fromData=0.,
                        toData=1.,
                        duration=.5,
                    )),
                Wait(2.),
                Parallel(
                    LerpScaleInterval(perfectText, duration=.5, scale=1.),
                    LerpFunctionInterval(fadeFunc,
                                         fromData=1.,
                                         toData=0.,
                                         duration=.5,
                                         blendType="easeIn"),
                ),
                Func(destroyText),
                WaitInterval(.5),
                Func(endGame, None),
            )

            soundTrack = SoundInterval(self.sndPerfect)

            self.perfectIval = Parallel(textTrack, soundTrack)
            self.perfectIval.start()
        else:
            taskMgr.doMethodLater(1, endGame, self.EndGameTaskName)

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def b_setSignaling(self, avId):
        self.setSignaling(avId)
        self.sendUpdate('setSignaling', [self.localAvId])

    def setSignaling(self, avId):
        if not self.hasLocalToon: return
        avIndex = self.avIdList.index(avId)
        av = base.cr.doId2do.get(avId)
        if av and (avIndex >= 0) and hasattr(self,
                                             'signalSfx') and self.signalSfx:
            base.playSfx(self.signalSfx[avIndex], node=av)

    def __beginSignal(self, mouseParam):
        self.notify.debug('beginSignal')
        base.localAvatar.b_setEmoteState(1, 1.0)
        self.b_setSignaling(self.localAvId)

        # we get the time of the jump animation  from base.localAvatar.animPanel()
        taskMgr.doMethodLater(1.67, self.__continueSignal,
                              'pairGameContinueSignal')

    def __endSignal(self, mouseParam):
        self.notify.debug('endSignal')
        base.localAvatar.b_setEmoteState(-1, 1.0)
        taskMgr.remove('pairGameContinueSignal')

    def __continueSignal(self, task):
        base.localAvatar.b_setEmoteState(1, 1.0)
        self.b_setSignaling(self.localAvId)
        taskMgr.doMethodLater(1.67, self.__continueSignal,
                              'pairGameContinueSignal')

    def getCardPos(self, deckOrderIndex):
        col = deckOrderIndex % self.cardsPerRow
        row = deckOrderIndex / self.cardsPerRow
        x = col * self.xCardInc
        y = row * self.yCardInc
        return x, y

    def getDeckOrderIndex(self, row, col):
        """
        returns the card at a given row, and column, or -1 if a card is not there
        """
        retval = row * self.cardsPerRow
        retval += col
        if retval >= len(self.deck.cards):
            retval = -1
        return retval

    def calcBonusTraversal(self):
        self.bonusTraversal = []
        halfRow = self.cardsPerRow / 2
        if self.cardsPerRow % 2:
            halfRow += 1
        for i in xrange(halfRow):
            for j in xrange(2):
                col = i + j * halfRow
                for row in xrange(self.cardsPerCol):
                    card = self.getDeckOrderIndex(row, col)
                    if card > -1:
                        self.bonusTraversal.append(card)
Beispiel #55
0
class DistributedCogThiefGame(DistributedMinigame):
    notify = directNotify.newCategory('DistributedCogThiefGame')
    ToonSpeed = CTGG.ToonSpeed
    StageHalfWidth = 200.0
    StageHalfHeight = 100.0
    BarrelScale = 0.3
    TOON_Z = 0
    UPDATE_SUITS_TASK = 'CogThiefGameUpdateSuitsTask'
    REWARD_COUNTDOWN_TASK = 'cogThiefGameRewardCountdown'
    ControlKeyLimitTime = 1.0

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedCogThiefGame', [
            State.State('off', self.enterOff, self.exitOff, ['play']),
            State.State('play', self.enterPlay, self.exitPlay, ['cleanup']),
            State.State('cleanup', self.enterCleanup, self.exitCleanup, [])
        ], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        toon = base.localAvatar
        camera.reparentTo(toon)
        camera.setPos(0, -15, 5)
        camera.setHpr(0, -5, 0)
        self.barrels = []
        self.cogInfo = {}
        self.lastTimeControlPressed = 0
        self.stolenBarrels = []
        self.useOrthoWalk = config.GetBool('cog-thief-ortho', 0)
        self.resultIval = None
        self.gameIsEnding = False
        self.__textGen = TextNode('cogThiefGame')
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        return

    def getTitle(self):
        return TTLocalizer.CogThiefGameTitle

    def getInstructions(self):
        return TTLocalizer.CogThiefGameInstructions

    def getMaxDuration(self):
        return 0

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.music = base.loader.loadMusic('phase_4/audio/bgm/MG_CogThief.ogg')
        self.initCogInfo()
        for barrelIndex in range(CTGG.NumBarrels):
            barrel = loader.loadModel(
                'phase_4/models/minigames/cogthief_game_gagTank')
            barrel.setPos(CTGG.BarrelStartingPositions[barrelIndex])
            barrel.setScale(self.BarrelScale)
            barrel.reparentTo(render)
            barrel.setTag('barrelIndex', str(barrelIndex))
            collSphere = CollisionSphere(0, 0, 0, 4)
            collSphere.setTangible(0)
            name = 'BarrelSphere-%d' % barrelIndex
            collSphereName = self.uniqueName(name)
            collNode = CollisionNode(collSphereName)
            collNode.setFromCollideMask(CTGG.BarrelBitmask)
            collNode.addSolid(collSphere)
            colNp = barrel.attachNewNode(collNode)
            handler = CollisionHandlerEvent()
            handler.setInPattern('barrelHit-%fn')
            base.cTrav.addCollider(colNp, handler)
            self.accept('barrelHit-' + collSphereName, self.handleEnterBarrel)
            nodeToHide = '**/gagMoneyTen'
            if barrelIndex % 2:
                nodeToHide = '**/gagMoneyFive'
            iconToHide = barrel.find(nodeToHide)
            if not iconToHide.isEmpty():
                iconToHide.hide()
            self.barrels.append(barrel)

        self.gameBoard = loader.loadModel(
            'phase_8/models/minigames/tag_arena_DG')
        self.sky = loader.loadModel('phase_3.5/models/props/TT_sky')
        self.gameBoard.setPosHpr(0, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        self.sky.setPosHpr(0, 0, -47, 0, 0, 0)
        self.sky.setScale(1.0)
        self.toonSDs = {}
        avId = self.localAvId
        toonSD = CogThiefGameToonSD.CogThiefGameToonSD(avId, self)
        self.toonSDs[avId] = toonSD
        toonSD.load()
        self.loadCogs()
        self.toonHitTracks = {}
        self.toonPieTracks = {}
        self.sndOof = base.loader.loadSfx(
            'phase_4/audio/sfx/MG_cannon_hit_dirt.ogg')
        self.sndRewardTick = base.loader.loadSfx(
            'phase_3.5/audio/sfx/tick_counter.ogg')
        self.sndPerfect = base.loader.loadSfx(
            'phase_4/audio/sfx/ring_perfect.ogg')
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.hide()
        purchaseModels = loader.loadModel('phase_4/models/gui/purchase_gui')
        self.jarImage = purchaseModels.find('**/Jar')
        self.jarImage.reparentTo(hidden)
        self.rewardPanel = DirectLabel(parent=hidden,
                                       relief=None,
                                       pos=(-0.173, -1.2, -0.55),
                                       scale=0.65,
                                       text='',
                                       text_scale=0.2,
                                       text_fg=(0.95, 0.95, 0, 1),
                                       text_pos=(0, -0.13),
                                       text_font=ToontownGlobals.getSignFont(),
                                       image=self.jarImage)
        self.rewardPanelTitle = DirectLabel(parent=self.rewardPanel,
                                            relief=None,
                                            pos=(0, 0, 0.06),
                                            scale=0.08,
                                            text=TTLocalizer.CannonGameReward,
                                            text_fg=(0.95, 0.95, 0, 1),
                                            text_shadow=(0, 0, 0, 1))
        return

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        del self.music
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM
        self.gameBoard.removeNode()
        self.sky.removeNode()
        del self.gameBoard
        for barrel in self.barrels:
            barrel.removeNode()

        del self.barrels
        for avId in self.toonSDs.keys():
            toonSD = self.toonSDs[avId]
            toonSD.unload()

        del self.toonSDs
        self.timer.destroy()
        del self.timer
        self.rewardPanel.destroy()
        del self.rewardPanel
        self.jarImage.removeNode()
        del self.jarImage
        del self.sndRewardTick

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.gameBoard.reparentTo(render)
        self.sky.reparentTo(render)
        lt = base.localAvatar
        lt.reparentTo(render)
        self.__placeToon(self.localAvId)
        lt.setSpeed(0, 0)
        toonSD = self.toonSDs[self.localAvId]
        toonSD.enter()
        toonSD.fsm.request('normal')
        self.stopGameWalk()
        for cogIndex in xrange(self.getNumCogs()):
            suit = self.cogInfo[cogIndex]['suit'].suit
            pos = self.cogInfo[cogIndex]['pos']
            suit.reparentTo(self.gameBoard)
            suit.setPos(pos)

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

        self.toonRNGs = []
        for i in xrange(self.numPlayers):
            self.toonRNGs.append(RandomNumGen.RandomNumGen(self.randomNumGen))

        self.sndTable = {
            'hitBySuit': [None] * self.numPlayers,
            'falling': [None] * self.numPlayers
        }
        for i in xrange(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')

        base.playMusic(self.music, looping=1, volume=0.8)
        return

    def offstage(self):
        self.notify.debug('offstage')
        self.gameBoard.hide()
        self.music.stop()
        for barrel in self.barrels:
            barrel.hide()

        for avId in self.toonSDs.keys():
            self.toonSDs[avId].exit()

        for avId in self.avIdList:
            av = self.getAvatar(avId)
            if av:
                av.resetLOD()

        self.timer.reparentTo(hidden)
        self.rewardPanel.reparentTo(hidden)
        DistributedMinigame.offstage(self)

    def handleDisabledAvatar(self, avId):
        self.notify.debug('handleDisabledAvatar')
        self.notify.debug('avatar ' + str(avId) + ' disabled')
        self.toonSDs[avId].exit(unexpectedExit=True)
        del self.toonSDs[avId]
        DistributedMinigame.handleDisabledAvatar(self, avId)

    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.useLOD(1000)
                toonSD = CogThiefGameToonSD.CogThiefGameToonSD(avId, self)
                self.toonSDs[avId] = toonSD
                toonSD.load()
                toonSD.enter()
                toonSD.fsm.request('normal')
                toon.startSmooth()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        if not config.GetBool('cog-thief-endless', 0):
            self.timer.show()
            self.timer.countdown(CTGG.GameTime, self.__gameTimerExpired)
        self.clockStopTime = None
        self.rewardPanel.reparentTo(base.a2dTopRight)
        self.scoreMult = MinigameGlobals.getScoreMult(self.cr.playGame.hood.id)
        self.__startRewardCountdown()
        self.gameFSM.request('play')
        return

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

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        self.startGameWalk()
        self.spawnUpdateSuitsTask()
        self.accept('delete', self.controlKeyPressed)
        self.accept('insert', self.controlKeyPressed)
        self.accept('alt', self.controlKeyPressed)
        self.pieHandler = CollisionHandlerEvent()
        self.pieHandler.setInPattern('pieHit-%fn')

    def exitPlay(self):
        self.ignore('control')
        if self.resultIval and self.resultIval.isPlaying():
            self.resultIval.finish()
            self.resultIval = None
        return

    def enterCleanup(self):
        self.__killRewardCountdown()
        if hasattr(self, 'jarIval'):
            self.jarIval.finish()
            del self.jarIval
        for key in self.toonHitTracks:
            ival = self.toonHitTracks[key]
            if ival.isPlaying():
                ival.finish()

        self.toonHitTracks = {}
        for key in self.toonPieTracks:
            ival = self.toonPieTracks[key]
            if ival.isPlaying():
                ival.finish()

        self.toonPieTracks = {}
        for key in self.cogInfo:
            cogThief = self.cogInfo[key]['suit']
            cogThief.cleanup()

        self.removeUpdateSuitsTask()
        self.notify.debug('enterCleanup')

    def exitCleanup(self):
        pass

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if toon:
            index = self.avIdList.index(avId)
            toon.setPos(CTGG.ToonStartingPositions[index])
            toon.setHpr(CTGG.ToonStartingRotations[index])

    def moveCameraToTop(self):
        camera.reparentTo(render)
        p = self.cameraTopView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])
        camera.setZ(camera.getZ() +
                    config.GetFloat('cog-thief-z-camera-adjust', 0.0))

    def destroyGameWalk(self):
        self.notify.debug('destroyOrthoWalk')
        if self.useOrthoWalk:
            self.gameWalk.destroy()
            del self.gameWalk
        else:
            self.notify.debug('TODO destroyGameWalk')

    def initGameWalk(self):
        self.notify.debug('startOrthoWalk')
        if self.useOrthoWalk:

            def doCollisions(oldPos, newPos, self=self):
                x = bound(newPos[0], CTGG.StageHalfWidth, -CTGG.StageHalfWidth)
                y = bound(newPos[1], CTGG.StageHalfHeight,
                          -CTGG.StageHalfHeight)
                newPos.setX(x)
                newPos.setY(y)
                return newPos

            orthoDrive = OrthoDrive(self.ToonSpeed,
                                    customCollisionCallback=doCollisions,
                                    instantTurn=True)
            self.gameWalk = OrthoWalk(orthoDrive,
                                      broadcast=not self.isSinglePlayer())
        else:
            self.gameWalk = CogThiefWalk.CogThiefWalk('walkDone')
            forwardSpeed = self.ToonSpeed / 2.0
            base.mouseInterfaceNode.setForwardSpeed(forwardSpeed)
            multiplier = forwardSpeed / ToontownGlobals.ToonForwardSpeed
            base.mouseInterfaceNode.setRotateSpeed(
                ToontownGlobals.ToonRotateSpeed * 4)

    def initCogInfo(self):
        for cogIndex in xrange(self.getNumCogs()):
            self.cogInfo[cogIndex] = {
                'pos': Point3(CTGG.CogStartingPositions[cogIndex]),
                'goal': CTGG.NoGoal,
                'goalId': CTGG.InvalidGoalId,
                'suit': None
            }

        return

    def loadCogs(self):
        suitTypes = ['ds', 'ac', 'bc', 'ms']
        for suitIndex in xrange(self.getNumCogs()):
            st = base.cr.newsManager.getInvadingSuit()
            if not st:
                st = self.randomNumGen.choice(suitTypes)
            suit = CogThief.CogThief(suitIndex, st, self, self.getCogSpeed())
            self.cogInfo[suitIndex]['suit'] = suit

    def handleEnterSphere(self, colEntry):
        if self.gameIsEnding:
            return
        intoName = colEntry.getIntoNodePath().getName()
        fromName = colEntry.getFromNodePath().getName()
        debugInto = intoName.split('/')
        debugFrom = fromName.split('/')
        self.notify.debug(
            'handleEnterSphere gametime=%s %s into %s' %
            (self.getCurrentGameTime(), debugFrom[(-1)], debugInto[(-1)]))
        intoName = colEntry.getIntoNodePath().getName()
        if 'CogThiefSphere' in intoName:
            parts = intoName.split('-')
            suitNum = int(parts[1])
            self.localToonHitBySuit(suitNum)

    def localToonHitBySuit(self, suitNum):
        self.notify.debug('localToonHitBySuit %d' % suitNum)
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime(), bits=32)
        pos = self.cogInfo[suitNum]['suit'].suit.getPos()
        self.sendUpdate(
            'hitBySuit',
            [self.localAvId, timestamp, suitNum, pos[0], pos[1], pos[2]])
        self.showToonHitBySuit(self.localAvId, timestamp)
        self.makeSuitRespondToToonHit(timestamp, suitNum)

    def hitBySuit(self, avId, timestamp, suitNum, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ('play', ):
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        if self.gameIsEnding:
            return
        self.notify.debug('avatar ' + ` avId ` + ' hit by a suit')
        if avId != self.localAvId:
            self.showToonHitBySuit(avId, timestamp)
            self.makeSuitRespondToToonHit(timestamp, suitNum)

    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)
        oldFlyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        trajectory = Trajectory.Trajectory(0,
                                           Point3(0, 0, 0),
                                           Point3(0, 0, 40),
                                           gravMult=1.0)
        flyDur = trajectory.calcTimeOfImpactOnPlane(0.0)
        avIndex = self.avIdList.index(avId)
        endPos = CTGG.ToonStartingPositions[avIndex]

        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'))
        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.stopGameWalk()
            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, 'gameWalk'):
                    toon = base.localAvatar
                    toon.setSpeed(0, 0)
                    self.startGameWalk()
            dropShadow.removeNode()
            del dropShadow
            toon = self.getAvatar(avId)
            if toon:
                toon.dropShadow.show()
                geomNode = toon.getGeomNode()
                rotNode = geomNode.getParent()
                baseNode = rotNode.getParent()
                geomNode.reparentTo(baseNode)
                rotNode.removeNode()
                del rotNode
                geomNode.setZ(oldGeomNodeZ)
            if toon:
                toon.reparentTo(render)
                toon.setPos(endPos)
            parentNode.removeNode()
            del parentNode
            if avId != self.localAvId:
                if toon:
                    toon.startSmooth()

        preFunc()
        slipBack = Parallel(
            Sequence(ActorInterval(toon, 'slip-backward', endFrame=24),
                     ActorInterval(toon, 'slip-backward', startFrame=24)))
        if toon.doId == self.localAvId:
            slipBack.append(SoundInterval(self.sndOof))
        hitTrack = Sequence(Parallel(flyTrack, spinHTrack, spinPTrack,
                                     soundTrack),
                            slipBack,
                            Func(postFunc),
                            name=toon.uniqueName('hitBySuit'))
        self.notify.debug('hitTrack duration = %s' % hitTrack.getDuration())
        self.toonHitTracks[avId] = hitTrack
        hitTrack.start(globalClockDelta.localElapsedTime(timestamp))
        return

    def updateSuitGoal(self, timestamp, inResponseToClientStamp, suitNum,
                       goalType, goalId, x, y, z):
        if not self.hasLocalToon:
            return
        self.notify.debug(
            'updateSuitGoal gameTime=%s timeStamp=%s cog=%s goal=%s goalId=%s (%.1f, %.1f,%.1f)'
            % (self.getCurrentGameTime(), timestamp, suitNum,
               CTGG.GoalStr[goalType], goalId, x, y, z))
        cog = self.cogInfo[suitNum]
        cog['goal'] = goalType
        cog['goalId'] = goalId
        newPos = Point3(x, y, z)
        cog['pos'] = newPos
        suit = cog['suit']
        suit.updateGoal(timestamp, inResponseToClientStamp, goalType, goalId,
                        newPos)

    def spawnUpdateSuitsTask(self):
        self.notify.debug('spawnUpdateSuitsTask')
        for cogIndex in self.cogInfo:
            suit = self.cogInfo[cogIndex]['suit']
            suit.gameStart(self.gameStartTime)

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

    def removeUpdateSuitsTask(self):
        taskMgr.remove(self.UPDATE_SUITS_TASK)

    def updateSuitsTask(self, task):
        if self.gameIsEnding:
            return task.done
        for cogIndex in self.cogInfo:
            suit = self.cogInfo[cogIndex]['suit']
            suit.think()

        return task.cont

    def makeSuitRespondToToonHit(self, timestamp, suitNum):
        cog = self.cogInfo[suitNum]['suit']
        cog.respondToToonHit(timestamp)

    def handleEnterBarrel(self, colEntry):
        if self.gameIsEnding:
            return
        intoName = colEntry.getIntoNodePath().getName()
        fromName = colEntry.getFromNodePath().getName()
        debugInto = intoName.split('/')
        debugFrom = fromName.split('/')
        self.notify.debug(
            'handleEnterBarrel gameTime=%s %s into %s' %
            (self.getCurrentGameTime(), debugFrom[(-1)], debugInto[(-1)]))
        if 'CogThiefSphere' in intoName:
            parts = intoName.split('-')
            cogIndex = int(parts[1])
            barrelName = colEntry.getFromNodePath().getName()
            barrelParts = barrelName.split('-')
            barrelIndex = int(barrelParts[1])
            cog = self.cogInfo[cogIndex]['suit']
            if cog.barrel == CTGG.NoBarrelCarried and barrelIndex not in self.stolenBarrels:
                timestamp = globalClockDelta.localToNetworkTime(
                    globalClock.getFrameTime(), bits=32)
                if cog.suit:
                    cogPos = cog.suit.getPos()
                    collisionPos = colEntry.getContactPos(render)
                    if (cogPos - collisionPos).length() > 4:
                        import pdb
                        pdb.set_trace()
                    self.sendUpdate('cogHitBarrel', [
                        timestamp, cogIndex, barrelIndex, cogPos[0], cogPos[1],
                        cogPos[2]
                    ])

    def makeCogCarryBarrel(self, timestamp, inResponseToClientStamp, cogIndex,
                           barrelIndex, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameIsEnding:
            return
        self.notify.debug(
            'makeCogCarryBarrel gameTime=%s timeStamp=%s cog=%s barrel=%s (%.1f, %.1f,%.1f)'
            % (self.getCurrentGameTime(), timestamp, cogIndex, barrelIndex, x,
               y, z))
        barrel = self.barrels[barrelIndex]
        self.notify.debug('barrelPos= %s' % barrel.getPos())
        cog = self.cogInfo[cogIndex]['suit']
        cogPos = Point3(x, y, z)
        cog.makeCogCarryBarrel(timestamp, inResponseToClientStamp, barrel,
                               barrelIndex, cogPos)

    def makeCogDropBarrel(self, timestamp, inResponseToClientStamp, cogIndex,
                          barrelIndex, x, y, z):
        if not self.hasLocalToon:
            return
        self.notify.debug(
            'makeCogDropBarrel gameTime=%s timeStamp=%s cog=%s barrel=%s (%.1f, %.1f,%.1f)'
            % (self.getCurrentGameTime(), timestamp, cogIndex, barrelIndex, x,
               y, z))
        barrel = self.barrels[barrelIndex]
        self.notify.debug('barrelPos= %s' % barrel.getPos())
        cog = self.cogInfo[cogIndex]['suit']
        cogPos = Point3(x, y, z)
        cog.makeCogDropBarrel(timestamp, inResponseToClientStamp, barrel,
                              barrelIndex, cogPos)

    def controlKeyPressed(self):
        if self.isToonPlayingHitTrack(self.localAvId):
            return
        if self.gameIsEnding:
            return
        if self.getCurrentGameTime(
        ) - self.lastTimeControlPressed > self.ControlKeyLimitTime:
            self.lastTimeControlPressed = self.getCurrentGameTime()
            self.notify.debug('controlKeyPressed')
            toonSD = self.toonSDs[self.localAvId]
            curState = toonSD.fsm.getCurrentState().getName()
            toon = self.getAvatar(self.localAvId)
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime(), bits=32)
            pos = toon.getPos()
            heading = toon.getH()
            self.sendUpdate(
                'throwingPie',
                [self.localAvId, timestamp, heading, pos[0], pos[1], pos[2]])
            self.showToonThrowingPie(self.localAvId, timestamp, heading, pos)

    def throwingPie(self, avId, timestamp, heading, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ('play', ):
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        self.notify.debug('avatar ' + ` avId ` + ' throwing pie')
        if avId != self.localAvId:
            pos = Point3(x, y, z)
            self.showToonThrowingPie(avId, timestamp, heading, pos)

    def showToonThrowingPie(self, avId, timestamp, heading, pos):
        toon = self.getAvatar(avId)
        if toon:
            tossTrack, pieTrack, flyPie = self.getTossPieInterval(
                toon, pos[0], pos[1], pos[2], heading, 0, 0, 0)

            def removePieFromTraverser(flyPie=flyPie):
                if base.cTrav:
                    if flyPie:
                        base.cTrav.removeCollider(flyPie)

            if avId == self.localAvId:
                flyPie.setTag('throwerId', str(avId))
                collSphere = CollisionSphere(0, 0, 0, 0.5)
                collSphere.setTangible(0)
                name = 'PieSphere-%d' % avId
                collSphereName = self.uniqueName(name)
                collNode = CollisionNode(collSphereName)
                collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
                collNode.addSolid(collSphere)
                colNp = flyPie.attachNewNode(collNode)
                colNp.show()
                base.cTrav.addCollider(colNp, self.pieHandler)
                self.accept('pieHit-' + collSphereName, self.handlePieHitting)

            def matchRunningAnim(toon=toon):
                toon.playingAnim = None
                toon.setSpeed(toon.forwardSpeed, toon.rotateSpeed)
                return

            newTossTrack = Sequence(tossTrack, Func(matchRunningAnim))
            pieTrack = Parallel(newTossTrack, pieTrack)
            elapsedTime = globalClockDelta.localElapsedTime(timestamp)
            if elapsedTime < 16.0 / 24.0:
                elapsedTime = 16.0 / 24.0
            pieTrack.start(elapsedTime)
            self.toonPieTracks[avId] = pieTrack

    def getTossPieInterval(self,
                           toon,
                           x,
                           y,
                           z,
                           h,
                           p,
                           r,
                           power,
                           beginFlyIval=Sequence()):
        from toontown.toonbase import ToontownBattleGlobals
        from toontown.battle import BattleProps
        pie = toon.getPieModel()
        pie.setScale(0.9)
        flyPie = pie.copyTo(NodePath('a'))
        pieName = ToontownBattleGlobals.pieNames[toon.pieType]
        pieType = BattleProps.globalPropPool.getPropType(pieName)
        animPie = Sequence()
        if pieType == 'actor':
            animPie = ActorInterval(pie, pieName, startFrame=48)
        sound = loader.loadSfx('phase_3.5/audio/sfx/AA_pie_throw_only.ogg')
        t = power / 100.0
        dist = 100 - 70 * t
        time = 1 + 0.5 * t
        proj = ProjectileInterval(None,
                                  startPos=Point3(0, 0, 0),
                                  endPos=Point3(0, dist, 0),
                                  duration=time)
        relVel = proj.startVel

        def getVelocity(toon=toon, relVel=relVel):
            return render.getRelativeVector(toon, relVel) * 0.6

        toss = Track(
            (0,
             Sequence(
                 Func(toon.setPosHpr, x, y, z, h, p, r),
                 Func(pie.reparentTo, toon.rightHand),
                 Func(pie.setPosHpr, 0, 0, 0, 0, 0, 0),
                 Parallel(
                     ActorInterval(
                         toon, 'throw', startFrame=48, partName='torso'),
                     animPie), Func(toon.loop, 'neutral'))),
            (16.0 / 24.0, Func(pie.detachNode)))
        fly = Track(
            (14.0 / 24.0, SoundInterval(sound, node=toon)),
            (16.0 / 24.0,
             Sequence(
                 Func(flyPie.reparentTo, render),
                 Func(flyPie.setPosHpr, toon, 0.52, 0.97, 2.24, 0, -45, 0),
                 beginFlyIval,
                 ProjectileInterval(flyPie, startVel=getVelocity, duration=6),
                 Func(flyPie.detachNode))))
        return (toss, fly, flyPie)

    def handlePieHitting(self, colEntry):
        if self.gameIsEnding:
            return
        into = colEntry.getIntoNodePath()
        intoName = into.getName()
        if 'CogThiefPieSphere' in intoName:
            timestamp = globalClockDelta.localToNetworkTime(
                globalClock.getFrameTime(), bits=32)
            parts = intoName.split('-')
            suitNum = int(parts[1])
            pos = self.cogInfo[suitNum]['suit'].suit.getPos()
            if pos in CTGG.CogStartingPositions:
                self.notify.debug('Cog %d hit at starting pos %s, ignoring' %
                                  (suitNum, pos))
            else:
                self.sendUpdate('pieHitSuit', [
                    self.localAvId, timestamp, suitNum, pos[0], pos[1], pos[2]
                ])
                self.makeSuitRespondToPieHit(timestamp, suitNum)

    def pieHitSuit(self, avId, timestamp, suitNum, x, y, z):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() not in ('play', ):
            self.notify.warning('ignoring msg: av %s hit by suit' % avId)
            return
        if self.gameIsEnding:
            return
        self.notify.debug('avatar ' + ` avId ` + ' hit by a suit')
        if avId != self.localAvId:
            self.makeSuitRespondToPieHit(timestamp, suitNum)

    def makeSuitRespondToPieHit(self, timestamp, suitNum):
        cog = self.cogInfo[suitNum]['suit']
        cog.respondToPieHit(timestamp)

    def sendCogAtReturnPos(self, cogIndex, barrelIndex):
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime(), bits=32)
        self.sendUpdate('cogAtReturnPos', [timestamp, cogIndex, barrelIndex])

    def markBarrelStolen(self, timestamp, inResponseToClientStamp,
                         barrelIndex):
        if not self.hasLocalToon:
            return
        if barrelIndex not in self.stolenBarrels:
            self.stolenBarrels.append(barrelIndex)
            barrel = self.barrels[barrelIndex]
            barrel.hide()
        if config.GetBool('cog-thief-check-barrels', 1):
            if not config.GetBool('cog-thief-endless', 0):
                if len(self.stolenBarrels) == len(self.barrels):
                    localStamp = globalClockDelta.networkToLocalTime(timestamp,
                                                                     bits=32)
                    gameTime = self.local2GameTime(localStamp)
                    self.clockStopTime = gameTime
                    self.notify.debug('clockStopTime = %s' % gameTime)
                    score = int(self.scoreMult * CTGG.calcScore(gameTime) +
                                0.5)
                    self.rewardPanel['text'] = str(score)
                    self.showResults()

    def __gameTimerExpired(self):
        self.notify.debug('game timer expired')
        self.showResults()

    def __startRewardCountdown(self):
        taskMgr.remove(self.REWARD_COUNTDOWN_TASK)
        taskMgr.add(self.__updateRewardCountdown, self.REWARD_COUNTDOWN_TASK)

    def __killRewardCountdown(self):
        taskMgr.remove(self.REWARD_COUNTDOWN_TASK)

    def __updateRewardCountdown(self, task):
        curTime = self.getCurrentGameTime()
        if self.clockStopTime is not None:
            if self.clockStopTime < curTime:
                self.notify.debug('self.clockStopTime < curTime %s %s' %
                                  (self.clockStopTime, curTime))
                self.__killRewardCountdown()
                curTime = self.clockStopTime
        if curTime > CTGG.GameTime:
            curTime = CTGG.GameTime
        score = int(self.scoreMult * CTGG.calcScore(curTime) + 0.5)
        if not hasattr(task, 'curScore'):
            task.curScore = score
        result = Task.cont
        if hasattr(self, 'rewardPanel'):
            self.rewardPanel['text'] = str(score)
            if task.curScore != score:
                if hasattr(self, 'jarIval'):
                    self.jarIval.finish()
                s = self.rewardPanel.getScale()
                self.jarIval = Parallel(Sequence(
                    self.rewardPanel.scaleInterval(0.15,
                                                   s * 3.0 / 4.0,
                                                   blendType='easeOut'),
                    self.rewardPanel.scaleInterval(0.15, s,
                                                   blendType='easeIn')),
                                        SoundInterval(self.sndRewardTick),
                                        name='cogThiefGameRewardJarThrob')
                self.jarIval.start()
            task.curScore = score
        else:
            result = Task.done
        return result

    def startGameWalk(self):
        if self.useOrthoWalk:
            self.gameWalk.start()
        else:
            self.gameWalk.enter()
            self.gameWalk.fsm.request('walking')

    def stopGameWalk(self):
        if self.useOrthoWalk:
            self.gameWalk.stop()
        else:
            self.gameWalk.exit()

    def getCogThief(self, cogIndex):
        return self.cogInfo[cogIndex]['suit']

    def isToonPlayingHitTrack(self, avId):
        if avId in self.toonHitTracks:
            track = self.toonHitTracks[avId]
            if track.isPlaying():
                return True
        return False

    def getNumCogs(self):
        result = config.GetInt('cog-thief-num-cogs', 0)
        if not result:
            safezone = self.getSafezoneId()
            result = CTGG.calculateCogs(self.numPlayers, safezone)
        return result

    def getCogSpeed(self):
        result = 6.0
        safezone = self.getSafezoneId()
        result = CTGG.calculateCogSpeed(self.numPlayers, safezone)
        return result

    def showResults(self):
        if not self.gameIsEnding:
            self.gameIsEnding = True
            for barrel in self.barrels:
                barrel.wrtReparentTo(render)

            for key in self.cogInfo:
                thief = self.cogInfo[key]['suit']
                thief.suit.setPos(100, 0, 0)
                thief.suit.hide()

            self.__killRewardCountdown()
            self.stopGameWalk()
            numBarrelsSaved = len(self.barrels) - len(self.stolenBarrels)
            resultStr = ''
            if numBarrelsSaved == len(self.barrels):
                resultStr = TTLocalizer.CogThiefPerfect
            else:
                if numBarrelsSaved > 1:
                    resultStr = TTLocalizer.CogThiefBarrelsSaved % {
                        'num': numBarrelsSaved
                    }
                else:
                    if numBarrelsSaved == 1:
                        resultStr = TTLocalizer.CogThiefBarrelSaved % {
                            'num': numBarrelsSaved
                        }
                    else:
                        resultStr = TTLocalizer.CogThiefNoBarrelsSaved
            perfectTextSubnode = hidden.attachNewNode(
                self.__genText(resultStr))
            perfectText = hidden.attachNewNode('perfectText')
            perfectTextSubnode.reparentTo(perfectText)
            frame = self.__textGen.getCardActual()
            offsetY = -abs(frame[2] + frame[3]) / 2.0
            perfectTextSubnode.setPos(0, 0, offsetY)
            perfectText.setColor(1, 0.1, 0.1, 1)

            def fadeFunc(t, text=perfectText):
                text.setColorScale(1, 1, 1, t)

            def destroyText(text=perfectText):
                text.removeNode()

            def safeGameOver(self=self):
                if not self.frameworkFSM.isInternalStateInFlux():
                    self.gameOver()

            textTrack = Sequence(
                Func(perfectText.reparentTo, aspect2d),
                Parallel(
                    LerpScaleInterval(perfectText,
                                      duration=0.5,
                                      scale=0.3,
                                      startScale=0.0),
                    LerpFunctionInterval(fadeFunc,
                                         fromData=0.0,
                                         toData=1.0,
                                         duration=0.5)), Wait(2.0),
                Parallel(
                    LerpScaleInterval(perfectText, duration=0.5, scale=1.0),
                    LerpFunctionInterval(fadeFunc,
                                         fromData=1.0,
                                         toData=0.0,
                                         duration=0.5,
                                         blendType='easeIn')),
                Func(destroyText), WaitInterval(0.5), Func(safeGameOver))
            if numBarrelsSaved == len(self.barrels):
                soundTrack = SoundInterval(self.sndPerfect)
            else:
                soundTrack = Sequence()
            self.resultIval = Parallel(textTrack, soundTrack)
            self.resultIval.start()
            if config.GetBool('want-blueprint4-ARG', False):
                MinigameGlobals.generateDebugARGPhrase()

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def getIntroTrack(self):
        base.camera.setPosHpr(0, -13.66, 13.59, 0, -51.6, 0)
        result = Sequence(
            Wait(2),
            LerpPosHprInterval(base.camera,
                               13,
                               Point3(self.cameraTopView[0],
                                      self.cameraTopView[1],
                                      self.cameraTopView[2]),
                               Point3(self.cameraTopView[3],
                                      self.cameraTopView[4],
                                      self.cameraTopView[5]),
                               blendType='easeIn'))
        return result
Beispiel #56
0
class IceTreasure(DirectObject):
    notify = DirectNotifyGlobal.directNotify.newCategory('IceTreasure')
    RADIUS = 1.0

    def __init__(self, model, pos, serialNum, gameId, penalty=False):
        self.serialNum = serialNum
        self.penalty = penalty
        center = model.getBounds().getCenter()
        center = Point3(0, 0, 0)
        self.nodePath = model.copyTo(render)
        self.nodePath.setPos(pos[0] - center[0], pos[1] - center[1],
                             pos[2] - center[2])
        self.nodePath.setZ(0)
        self.notify.debug('newPos = %s' % self.nodePath.getPos())
        if self.penalty:
            self.sphereName = 'penaltySphere-%s-%s' % (gameId, self.serialNum)
        else:
            self.sphereName = 'treasureSphere-%s-%s' % (gameId, self.serialNum)
        self.collSphere = CollisionSphere(center[0], center[1], center[2],
                                          self.RADIUS)
        self.collSphere.setTangible(0)
        self.collNode = CollisionNode(self.sphereName)
        self.collNode.setIntoCollideMask(ToontownGlobals.PieBitmask)
        self.collNode.addSolid(self.collSphere)
        self.collNodePath = render.attachNewNode(self.collNode)
        self.collNodePath.setPos(pos[0] - center[0], pos[1] - center[1],
                                 pos[2] - center[2])
        self.collNodePath.hide()
        self.track = None
        if self.penalty:
            self.tip = self.nodePath.find('**/fusetip')
            sparks = BattleParticles.createParticleEffect(file='icetnt')
            self.sparksEffect = sparks
            sparks.start(self.tip)
            self.penaltyGrabSound = loader.loadSfx(
                'phase_4/audio/sfx/MG_cannon_fire_alt.mp3')
            self.penaltyGrabSound.setVolume(0.75)
            kaboomAttachPoint = self.nodePath.attachNewNode('kaboomAttach')
            kaboomAttachPoint.setZ(3)
            self.kaboom = loader.loadModel(
                'phase_4/models/minigames/ice_game_kaboom')
            self.kaboom.reparentTo(kaboomAttachPoint)
            self.kaboom.setScale(2.0)
            self.kaboom.setBillboardPointEye()

    def destroy(self):
        self.ignoreAll()
        if self.penalty:
            self.sparksEffect.cleanup()
            if self.track:
                self.track.finish()

        self.nodePath.removeNode()
        del self.nodePath
        del self.collSphere
        self.collNodePath.removeNode()
        del self.collNodePath
        del self.collNode

    def showGrab(self):
        self.nodePath.hide()
        self.collNodePath.hide()
        self.collNode.setIntoCollideMask(BitMask32(0))
        if self.penalty:
            self.track = Parallel(
                SoundInterval(self.penaltyGrabSound),
                Sequence(
                    Func(self.kaboom.showThrough),
                    LerpScaleInterval(self.kaboom,
                                      duration=0.5,
                                      scale=Point3(10, 10, 10),
                                      blendType='easeOut'),
                    Func(self.kaboom.hide)))
            self.track.start()
Beispiel #57
0
class DistributedIceGame(DistributedMinigame.DistributedMinigame,
                         DistributedIceWorld.DistributedIceWorld):
    notify = directNotify.newCategory('DistributedIceGame')
    MaxLocalForce = 100
    MaxPhysicsForce = 25000

    def __init__(self, cr):
        DistributedMinigame.DistributedMinigame.__init__(self, cr)
        DistributedIceWorld.DistributedIceWorld.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedIceGame', [
            State.State('off', self.enterOff, self.exitOff, ['inputChoice']),
            State.State(
                'inputChoice', self.enterInputChoice, self.exitInputChoice,
                ['waitServerChoices', 'moveTires', 'displayVotes', 'cleanup']),
            State.State('waitServerChoices', self.enterWaitServerChoices,
                        self.exitWaitServerChoices, ['moveTires', 'cleanup']),
            State.State('moveTires', self.enterMoveTires, self.exitMoveTires,
                        ['synch', 'cleanup']),
            State.State('synch', self.enterSynch, self.exitSynch,
                        ['inputChoice', 'scoring', 'cleanup']),
            State.State('scoring', self.enterScoring, self.exitScoring,
                        ['cleanup', 'finalResults', 'inputChoice']),
            State.State('finalResults', self.enterFinalResults,
                        self.exitFinalResults, ['cleanup']),
            State.State('cleanup', self.enterCleanup, self.exitCleanup, [])
        ], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.cameraThreeQuarterView = (0, -22, 45, 0, -62.890000000000001, 0)
        self.tireDict = {}
        self.forceArrowDict = {}
        self.canDrive = False
        self.timer = None
        self.timerStartTime = None
        self.curForce = 0
        self.curHeading = 0
        self.headingMomentum = 0.0
        self.forceMomentum = 0.0
        self.allTireInputs = None
        self.curRound = 0
        self.curMatch = 0
        self.controlKeyWarningLabel = DirectLabel(
            text=TTLocalizer.IceGameControlKeyWarning,
            text_fg=VBase4(1, 0, 0, 1),
            relief=None,
            pos=(0.0, 0, 0),
            scale=0.14999999999999999)
        self.controlKeyWarningLabel.hide()
        self.waitingMoveLabel = DirectLabel(
            text=TTLocalizer.IceGameWaitingForPlayersToFinishMove,
            text_fg=VBase4(1, 1, 1, 1),
            relief=None,
            pos=(-0.59999999999999998, 0, -0.75),
            scale=0.074999999999999997)
        self.waitingMoveLabel.hide()
        self.waitingSyncLabel = DirectLabel(
            text=TTLocalizer.IceGameWaitingForAISync,
            text_fg=VBase4(1, 1, 1, 1),
            relief=None,
            pos=(-0.59999999999999998, 0, -0.75),
            scale=0.074999999999999997)
        self.waitingSyncLabel.hide()
        self.infoLabel = DirectLabel(text='',
                                     text_fg=VBase4(0, 0, 0, 1),
                                     relief=None,
                                     pos=(0.0, 0, 0.69999999999999996),
                                     scale=0.074999999999999997)
        self.updateInfoLabel()
        self.lastForceArrowUpdateTime = 0
        self.sendForceArrowUpdateAsap = False
        self.treasures = []
        self.penalties = []
        self.obstacles = []
        self.controlKeyPressed = False
        self.controlKeyWarningIval = None

    def delete(self):
        DistributedIceWorld.DistributedIceWorld.delete(self)
        DistributedMinigame.DistributedMinigame.delete(self)
        if self.controlKeyWarningIval:
            self.controlKeyWarningIval.finish()
            self.controlKeyWarningIval = None

        self.controlKeyWarningLabel.destroy()
        del self.controlKeyWarningLabel
        self.waitingMoveLabel.destroy()
        del self.waitingMoveLabel
        self.waitingSyncLabel.destroy()
        del self.waitingSyncLabel
        self.infoLabel.destroy()
        del self.infoLabel
        for treasure in self.treasures:
            treasure.destroy()

        del self.treasures
        for penalty in self.penalties:
            penalty.destroy()

        del self.penalties
        for obstacle in self.obstacles:
            obstacle.removeNode()

        del self.obstacles
        del self.gameFSM

    def announceGenerate(self):
        DistributedMinigame.DistributedMinigame.announceGenerate(self)
        DistributedIceWorld.DistributedIceWorld.announceGenerate(self)
        self.debugTaskName = self.uniqueName('debugTask')

    def getTitle(self):
        return TTLocalizer.IceGameTitle

    def getInstructions(self):
        szId = self.getSafezoneId()
        numPenalties = IceGameGlobals.NumPenalties[szId]
        result = TTLocalizer.IceGameInstructions
        if numPenalties == 0:
            result = TTLocalizer.IceGameInstructionsNoTnt

        return result

    def getMaxDuration(self):
        return 0

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.DistributedMinigame.load(self)
        self.music = base.loadMusic('phase_4/audio/bgm/MG_IceGame.mid')
        self.gameBoard = loader.loadModel(
            'phase_4/models/minigames/ice_game_icerink')
        background = loader.loadModel('phase_4/models/minigames/ice_game_2d')
        background.reparentTo(self.gameBoard)
        self.gameBoard.setPosHpr(0, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        self.setupSimulation()
        index = 0
        for avId in self.avIdList:
            self.setupTire(avId, index)
            self.setupForceArrow(avId)
            index += 1

        for index in xrange(len(self.avIdList), 4):
            self.setupTire(-index, index)
            self.setupForceArrow(-index)

        self.showForceArrows(realPlayersOnly=True)
        self.westWallModel = NodePath()
        if not self.westWallModel.isEmpty():
            self.westWallModel.reparentTo(self.gameBoard)
            self.westWallModel.setPos(IceGameGlobals.MinWall[0],
                                      IceGameGlobals.MinWall[1], 0)
            self.westWallModel.setScale(4)

        self.eastWallModel = NodePath()
        if not self.eastWallModel.isEmpty():
            self.eastWallModel.reparentTo(self.gameBoard)
            self.eastWallModel.setPos(IceGameGlobals.MaxWall[0],
                                      IceGameGlobals.MaxWall[1], 0)
            self.eastWallModel.setScale(4)
            self.eastWallModel.setH(180)

        self.arrowKeys = ArrowKeys.ArrowKeys()
        self.target = loader.loadModel('phase_3/models/misc/sphere')
        self.target.setScale(0.01)
        self.target.reparentTo(self.gameBoard)
        self.target.setPos(0, 0, 0)
        self.scoreCircle = loader.loadModel(
            'phase_4/models/minigames/ice_game_score_circle')
        self.scoreCircle.setScale(0.01)
        self.scoreCircle.reparentTo(self.gameBoard)
        self.scoreCircle.setZ(IceGameGlobals.TireRadius / 2.0)
        self.scoreCircle.setAlphaScale(0.5)
        self.scoreCircle.setTransparency(1)
        self.scoreCircle.hide()
        self.treasureModel = loader.loadModel(
            'phase_4/models/minigames/ice_game_barrel')
        self.penaltyModel = loader.loadModel(
            'phase_4/models/minigames/ice_game_tnt2')
        self.penaltyModel.setScale(0.75, 0.75, 0.69999999999999996)
        szId = self.getSafezoneId()
        obstacles = IceGameGlobals.Obstacles[szId]
        index = 0
        cubicObstacle = IceGameGlobals.ObstacleShapes[szId]
        for pos in obstacles:
            newPos = Point3(pos[0], pos[1], IceGameGlobals.TireRadius)
            newObstacle = self.createObstacle(newPos, index, cubicObstacle)
            self.obstacles.append(newObstacle)
            index += 1

        self.countSound = loader.loadSfx(
            'phase_3.5/audio/sfx/tick_counter.mp3')
        self.treasureGrabSound = loader.loadSfx(
            'phase_4/audio/sfx/MG_sfx_vine_game_bananas.mp3')
        self.penaltyGrabSound = loader.loadSfx(
            'phase_4/audio/sfx/MG_cannon_fire_alt.mp3')
        self.tireSounds = []
        for tireIndex in xrange(4):
            tireHit = loader.loadSfx(
                'phase_4/audio/sfx/Golf_Hit_Barrier_1.mp3')
            wallHit = loader.loadSfx('phase_4/audio/sfx/MG_maze_pickup.mp3')
            obstacleHit = loader.loadSfx(
                'phase_4/audio/sfx/Golf_Hit_Barrier_2.mp3')
            self.tireSounds.append({
                'tireHit': tireHit,
                'wallHit': wallHit,
                'obstacleHit': obstacleHit
            })

        self.arrowRotateSound = loader.loadSfx(
            'phase_4/audio/sfx/MG_sfx_ice_force_rotate.wav')
        self.arrowUpSound = loader.loadSfx(
            'phase_4/audio/sfx/MG_sfx_ice_force_increase_3sec.mp3')
        self.arrowDownSound = loader.loadSfx(
            'phase_4/audio/sfx/MG_sfx_ice_force_decrease_3sec.mp3')
        self.scoreCircleSound = loader.loadSfx(
            'phase_4/audio/sfx/MG_sfx_ice_scoring_1.mp3')

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.DistributedMinigame.unload(self)
        del self.music
        self.gameBoard.removeNode()
        del self.gameBoard
        for forceArrow in self.forceArrowDict.values():
            forceArrow.removeNode()

        del self.forceArrowDict
        self.scoreCircle.removeNode()
        del self.scoreCircle
        del self.countSound

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.DistributedMinigame.onstage(self)
        self.gameBoard.reparentTo(render)
        self._DistributedIceGame__placeToon(self.localAvId)
        self.moveCameraToTop()
        self.scorePanels = []
        base.playMusic(self.music, looping=1, volume=0.80000000000000004)

    def offstage(self):
        self.notify.debug('offstage')
        self.music.stop()
        self.gameBoard.hide()
        self.infoLabel.hide()
        for avId in self.tireDict:
            self.tireDict[avId]['tireNodePath'].hide()

        for panel in self.scorePanels:
            panel.cleanup()

        del self.scorePanels
        for obstacle in self.obstacles:
            obstacle.hide()

        for treasure in self.treasures:
            treasure.nodePath.hide()

        for penalty in self.penalties:
            penalty.nodePath.hide()

        for avId in self.avIdList:
            av = self.getAvatar(avId)
            if av:
                av.dropShadow.show()
                av.resetLOD()
                continue

        taskMgr.remove(self.uniqueName('aimtask'))
        self.arrowKeys.destroy()
        del self.arrowKeys
        DistributedMinigame.DistributedMinigame.offstage(self)

    def handleDisabledAvatar(self, avId):
        self.notify.debug('handleDisabledAvatar')
        self.notify.debug('avatar ' + str(avId) + ' disabled')
        DistributedMinigame.DistributedMinigame.handleDisabledAvatar(
            self, avId)

    def setGameReady(self):
        if not self.hasLocalToon:
            return None

        self.notify.debug('setGameReady')
        if DistributedMinigame.DistributedMinigame.setGameReady(self):
            return None

        for index in xrange(self.numPlayers):
            avId = self.avIdList[index]
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self._DistributedIceGame__placeToon(avId)
                toon.forwardSpeed = 0
                toon.rotateSpeed = False
                toon.dropShadow.hide()
                toon.setAnimState('Sit')
                if avId in self.tireDict:
                    tireNp = self.tireDict[avId]['tireNodePath']
                    toon.reparentTo(tireNp)
                    toon.setY(1.0)
                    toon.setZ(-3)

                toon.startLookAround()
                continue

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return None

        self.notify.debug('setGameStart')
        DistributedMinigame.DistributedMinigame.setGameStart(self, timestamp)
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.stopLookAround()
                continue

        self.scores = [0] * self.numPlayers
        spacing = 0.40000000000000002
        for i in xrange(self.numPlayers):
            avId = self.avIdList[i]
            avName = self.getAvatarName(avId)
            scorePanel = MinigameAvatarScorePanel.MinigameAvatarScorePanel(
                avId, avName)
            scorePanel.setScale(0.90000000000000002)
            scorePanel.setPos(0.75 - spacing * (self.numPlayers - 1 - i), 0.0,
                              0.875)
            scorePanel.makeTransparent(0.75)
            self.scorePanels.append(scorePanel)

        self.arrowKeys.setPressHandlers([
            self._DistributedIceGame__upArrowPressed,
            self._DistributedIceGame__downArrowPressed,
            self._DistributedIceGame__leftArrowPressed,
            self._DistributedIceGame__rightArrowPressed,
            self._DistributedIceGame__controlPressed
        ])

    def isInPlayState(self):
        if not self.gameFSM.getCurrentState():
            return False

        if not self.gameFSM.getCurrentState().getName() == 'play':
            return False

        return True

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

    def exitOff(self):
        pass

    def enterInputChoice(self):
        self.notify.debug('enterInputChoice')
        self.forceLocalToonToTire()
        self.controlKeyPressed = False
        if self.curRound == 0:
            self.setupStartOfMatch()
        else:
            self.notify.debug('self.curRound = %s' % self.curRound)
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.hide()
        if self.timerStartTime != None:
            self.startTimer()

        self.showForceArrows(realPlayersOnly=True)
        self.localForceArrow().setPosHpr(0, 0, -1.0, 0, 0, 0)
        self.localForceArrow().reparentTo(self.localTireNp())
        self.localForceArrow().setY(IceGameGlobals.TireRadius)
        self.localTireNp().headsUp(self.target)
        self.notify.debug('self.localForceArrow() heading = %s' %
                          self.localForceArrow().getH())
        self.curHeading = self.localTireNp().getH()
        self.curForce = 25
        self.updateLocalForceArrow()
        for avId in self.forceArrowDict:
            forceArrow = self.forceArrowDict[avId]
            forceArrow.setPosHpr(0, 0, -1.0, 0, 0, 0)
            tireNp = self.tireDict[avId]['tireNodePath']
            forceArrow.reparentTo(tireNp)
            forceArrow.setY(IceGameGlobals.TireRadius)
            tireNp.headsUp(self.target)
            self.updateForceArrow(avId, tireNp.getH(), 25)

        taskMgr.add(self._DistributedIceGame__aimTask,
                    self.uniqueName('aimtask'))
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.stop()

        self.sendForceArrowUpdateAsap = False

    def exitInputChoice(self):
        if not self.controlKeyPressed:
            if self.controlKeyWarningIval:
                self.controlKeyWarningIval.finish()
                self.controlKeyWarningIval = None

            self.controlKeyWarningIval = Sequence(
                Func(self.controlKeyWarningLabel.show),
                self.controlKeyWarningLabel.colorScaleInterval(
                    10, VBase4(1, 1, 1, 0), startColorScale=VBase4(1, 1, 1,
                                                                   1)),
                Func(self.controlKeyWarningLabel.hide))
            self.controlKeyWarningIval.start()

        if self.timer != None:
            self.timer.destroy()
            self.timer = None

        self.timerStartTime = None
        self.hideForceArrows()
        self.arrowRotateSound.stop()
        self.arrowUpSound.stop()
        self.arrowDownSound.stop()
        taskMgr.remove(self.uniqueName('aimtask'))

    def enterWaitServerChoices(self):
        self.waitingMoveLabel.show()
        self.showForceArrows(True)

    def exitWaitServerChoices(self):
        self.waitingMoveLabel.hide()
        self.hideForceArrows()

    def enterMoveTires(self):
        for key in self.tireDict:
            body = self.tireDict[key]['tireBody']
            body.setAngularVel(0, 0, 0)
            body.setLinearVel(0, 0, 0)

        for index in xrange(len(self.allTireInputs)):
            input = self.allTireInputs[index]
            avId = self.avIdList[index]
            body = self.getTireBody(avId)
            degs = input[1] + 90
            tireNp = self.getTireNp(avId)
            tireH = tireNp.getH()
            self.notify.debug('tireH = %s' % tireH)
            radAngle = deg2Rad(degs)
            foo = NodePath('foo')
            dirVector = Vec3(math.cos(radAngle), math.sin(radAngle), 0)
            self.notify.debug('dirVector is now=%s' % dirVector)
            inputForce = input[0]
            inputForce /= self.MaxLocalForce
            inputForce *= self.MaxPhysicsForce
            force = dirVector * inputForce
            self.notify.debug('adding force %s to %d' % (force, avId))
            body.addForce(force)

        self.enableAllTireBodies()
        self.totalPhysicsSteps = 0
        self.startSim()
        taskMgr.add(self._DistributedIceGame__moveTiresTask,
                    self.uniqueName('moveTiresTtask'))

    def exitMoveTires(self):
        self.forceLocalToonToTire()
        self.disableAllTireBodies()
        self.stopSim()
        self.notify.debug('total Physics steps = %d' % self.totalPhysicsSteps)
        taskMgr.remove(self.uniqueName('moveTiresTtask'))

    def enterSynch(self):
        self.waitingSyncLabel.show()

    def exitSynch(self):
        self.waitingSyncLabel.hide()

    def enterScoring(self):
        sortedByDistance = []
        for avId in self.avIdList:
            np = self.getTireNp(avId)
            pos = np.getPos()
            pos.setZ(0)
            sortedByDistance.append((avId, pos.length()))

        def compareDistance(x, y):
            if x[1] - y[1] > 0:
                return 1
            elif x[1] - y[1] < 0:
                return -1
            else:
                return 0

        sortedByDistance.sort(cmp=compareDistance)
        self.scoreMovie = Sequence()
        curScale = 0.01
        curTime = 0
        self.scoreCircle.setScale(0.01)
        self.scoreCircle.show()
        self.notify.debug('newScores = %s' % self.newScores)
        circleStartTime = 0
        for index in xrange(len(sortedByDistance)):
            distance = sortedByDistance[index][1]
            avId = sortedByDistance[index][0]
            scorePanelIndex = self.avIdList.index(avId)
            time = (distance - curScale) / IceGameGlobals.ExpandFeetPerSec
            if time < 0:
                time = 0.01

            scaleXY = distance + IceGameGlobals.TireRadius
            self.notify.debug('circleStartTime = %s' % circleStartTime)
            self.scoreMovie.append(
                Parallel(
                    LerpScaleInterval(self.scoreCircle, time,
                                      Point3(scaleXY, scaleXY, 1.0)),
                    SoundInterval(self.scoreCircleSound,
                                  duration=time,
                                  startTime=circleStartTime)))
            circleStartTime += time
            startScore = self.scorePanels[scorePanelIndex].getScore()
            destScore = self.newScores[scorePanelIndex]
            self.notify.debug('for avId %d, startScore=%d, newScores=%d' %
                              (avId, startScore, destScore))

            def increaseScores(t,
                               scorePanelIndex=scorePanelIndex,
                               startScore=startScore,
                               destScore=destScore):
                oldScore = self.scorePanels[scorePanelIndex].getScore()
                diff = destScore - startScore
                newScore = int(startScore + diff * t)
                if newScore > oldScore:
                    base.playSfx(self.countSound)

                self.scorePanels[scorePanelIndex].setScore(newScore)
                self.scores[scorePanelIndex] = newScore

            duration = (destScore -
                        startScore) * IceGameGlobals.ScoreCountUpRate
            tireNp = self.tireDict[avId]['tireNodePath']
            self.scoreMovie.append(
                Parallel(
                    LerpFunctionInterval(increaseScores, duration),
                    Sequence(
                        LerpColorScaleInterval(tireNp, duration / 6.0,
                                               VBase4(1, 0, 0, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0,
                                               VBase4(1, 1, 1, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0,
                                               VBase4(1, 0, 0, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0,
                                               VBase4(1, 1, 1, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0,
                                               VBase4(1, 0, 0, 1)),
                        LerpColorScaleInterval(tireNp, duration / 6.0,
                                               VBase4(1, 1, 1, 1)))))
            curScale += distance

        self.scoreMovie.append(
            Func(self.sendUpdate, 'reportScoringMovieDone', []))
        self.scoreMovie.start()

    def exitScoring(self):
        self.scoreMovie.finish()
        self.scoreMovie = None
        self.scoreCircle.hide()

    def enterFinalResults(self):
        lerpTrack = Parallel()
        lerpDur = 0.5
        tY = 0.59999999999999998
        bY = -0.050000000000000003
        lX = -0.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]
            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(IceGameGlobals.ShowScoresDuration),
                     Func(self.gameOver)))
        self.showScoreTrack.start()

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

    def enterCleanup(self):
        self.notify.debug('enterCleanup')
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.start()

    def exitCleanup(self):
        pass

    def _DistributedIceGame__placeToon(self, avId):
        toon = self.getAvatar(avId)
        if toon:
            toon.setPos(0, 0, 0)
            toon.setHpr(0, 0, 0)

    def moveCameraToTop(self):
        camera.reparentTo(render)
        p = self.cameraThreeQuarterView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])

    def setupTire(self, avId, index):
        (tireNp, tireBody, tireOdeGeom) = self.createTire(index)
        self.tireDict[avId] = {
            'tireNodePath': tireNp,
            'tireBody': tireBody,
            'tireOdeGeom': tireOdeGeom
        }
        if avId <= 0:
            tireBlocker = tireNp.find('**/tireblockermesh')
            if not tireBlocker.isEmpty():
                tireBlocker.hide()

        if avId == self.localAvId:
            tireNp = self.tireDict[avId]['tireNodePath']
            self.treasureSphereName = 'treasureCollider'
            self.treasureCollSphere = CollisionSphere(
                0, 0, 0, IceGameGlobals.TireRadius)
            self.treasureCollSphere.setTangible(0)
            self.treasureCollNode = CollisionNode(self.treasureSphereName)
            self.treasureCollNode.setFromCollideMask(
                ToontownGlobals.PieBitmask)
            self.treasureCollNode.addSolid(self.treasureCollSphere)
            self.treasureCollNodePath = tireNp.attachNewNode(
                self.treasureCollNode)
            self.treasureHandler = CollisionHandlerEvent()
            self.treasureHandler.addInPattern('%fn-intoTreasure')
            base.cTrav.addCollider(self.treasureCollNodePath,
                                   self.treasureHandler)
            eventName = '%s-intoTreasure' % self.treasureCollNodePath.getName()
            self.notify.debug('eventName = %s' % eventName)
            self.accept(eventName, self.toonHitSomething)

    def setupForceArrow(self, avId):
        arrow = loader.loadModel('phase_4/models/minigames/ice_game_arrow')
        priority = 0
        if avId < 0:
            priority = -avId
        else:
            priority = self.avIdList.index(avId)
            if avId == self.localAvId:
                priority = 10

        self.forceArrowDict[avId] = arrow

    def hideForceArrows(self):
        for forceArrow in self.forceArrowDict.values():
            forceArrow.hide()

    def showForceArrows(self, realPlayersOnly=True):
        for avId in self.forceArrowDict:
            if realPlayersOnly:
                if avId > 0:
                    self.forceArrowDict[avId].show()
                else:
                    self.forceArrowDict[avId].hide()
            avId > 0
            self.forceArrowDict[avId].show()

    def localForceArrow(self):
        if self.localAvId in self.forceArrowDict:
            return self.forceArrowDict[self.localAvId]
        else:
            return None

    def setChoices(self, input0, input1, input2, input3):
        pass

    def startDebugTask(self):
        taskMgr.add(self.debugTask, self.debugTaskName)

    def stopDebugTask(self):
        taskMgr.remove(self.debugTaskName)

    def debugTask(self, task):
        if self.canDrive and self.tireDict.has_key(localAvatar.doId):
            dt = globalClock.getDt()
            forceMove = 25000
            forceMoveDt = forceMove
            tireBody = self.tireDict[localAvatar.doId]['tireBody']
            if self.arrowKeys.upPressed() and not tireBody.isEnabled():
                x = 0
                y = 1
                tireBody.enable()
                tireBody.addForce(Vec3(x * forceMoveDt, y * forceMoveDt, 0))

            if self.arrowKeys.downPressed() and not tireBody.isEnabled():
                x = 0
                y = -1
                tireBody.enable()
                tireBody.addForce(Vec3(x * forceMoveDt, y * forceMoveDt, 0))

            if self.arrowKeys.leftPressed() and not tireBody.isEnabled():
                x = -1
                y = 0
                tireBody.enable()
                tireBody.addForce(Vec3(x * forceMoveDt, y * forceMoveDt, 0))

            if self.arrowKeys.rightPressed() and not tireBody.isEnabled():
                x = 1
                y = 0
                tireBody.enable()
                tireBody.addForce(Vec3(x * forceMoveDt, y * forceMoveDt, 0))

        return task.cont

    def _DistributedIceGame__upArrowPressed(self):
        pass

    def _DistributedIceGame__downArrowPressed(self):
        pass

    def _DistributedIceGame__leftArrowPressed(self):
        pass

    def _DistributedIceGame__rightArrowPressed(self):
        pass

    def _DistributedIceGame__controlPressed(self):
        if self.gameFSM.getCurrentState().getName() == 'inputChoice':
            self.sendForceArrowUpdateAsap = True
            self.updateLocalForceArrow()
            self.controlKeyPressed = True
            self.sendUpdate('setAvatarChoice',
                            [self.curForce, self.curHeading])
            self.gameFSM.request('waitServerChoices')

    def startTimer(self):
        now = globalClock.getFrameTime()
        elapsed = now - self.timerStartTime
        self.timer.posInTopRightCorner()
        self.timer.setTime(IceGameGlobals.InputTimeout)
        self.timer.countdown(IceGameGlobals.InputTimeout - elapsed,
                             self.handleChoiceTimeout)
        self.timer.show()

    def setTimerStartTime(self, timestamp):
        if not self.hasLocalToon:
            return None

        self.timerStartTime = globalClockDelta.networkToLocalTime(timestamp)
        if self.timer != None:
            self.startTimer()

    def handleChoiceTimeout(self):
        self.sendUpdate('setAvatarChoice', [0, 0])
        self.gameFSM.request('waitServerChoices')

    def localTireNp(self):
        ret = None
        if self.localAvId in self.tireDict:
            ret = self.tireDict[self.localAvId]['tireNodePath']

        return ret

    def localTireBody(self):
        ret = None
        if self.localAvId in self.tireDict:
            ret = self.tireDict[self.localAvId]['tireBody']

        return ret

    def getTireBody(self, avId):
        ret = None
        if avId in self.tireDict:
            ret = self.tireDict[avId]['tireBody']

        return ret

    def getTireNp(self, avId):
        ret = None
        if avId in self.tireDict:
            ret = self.tireDict[avId]['tireNodePath']

        return ret

    def updateForceArrow(self, avId, curHeading, curForce):
        forceArrow = self.forceArrowDict[avId]
        tireNp = self.tireDict[avId]['tireNodePath']
        tireNp.setH(curHeading)
        tireBody = self.tireDict[avId]['tireBody']
        tireBody.setQuaternion(tireNp.getQuat())
        self.notify.debug('curHeading = %s' % curHeading)
        yScale = curForce / 100.0
        yScale *= 1
        headY = yScale * 15
        xScale = (yScale - 1) / 2.0 + 1.0
        shaft = forceArrow.find('**/arrow_shaft')
        head = forceArrow.find('**/arrow_head')
        shaft.setScale(xScale, yScale, 1)
        head.setPos(0, headY, 0)
        head.setScale(xScale, xScale, 1)

    def updateLocalForceArrow(self):
        avId = self.localAvId
        self.b_setForceArrowInfo(avId, self.curHeading, self.curForce)

    def _DistributedIceGame__aimTask(self, task):
        if not hasattr(self, 'arrowKeys'):
            return task.done

        dt = globalClock.getDt()
        headingMomentumChange = dt * 60.0
        forceMomentumChange = dt * 160.0
        arrowUpdate = False
        arrowRotating = False
        arrowUp = False
        arrowDown = False
        if self.arrowKeys.upPressed() and not self.arrowKeys.downPressed():
            self.forceMomentum += forceMomentumChange
            if self.forceMomentum < 0:
                self.forceMomentum = 0

            if self.forceMomentum > 50:
                self.forceMomentum = 50

            oldForce = self.curForce
            self.curForce += self.forceMomentum * dt
            arrowUpdate = True
            if oldForce < self.MaxLocalForce:
                arrowUp = True

        elif self.arrowKeys.downPressed() and not self.arrowKeys.upPressed():
            self.forceMomentum += forceMomentumChange
            if self.forceMomentum < 0:
                self.forceMomentum = 0

            if self.forceMomentum > 50:
                self.forceMomentum = 50

            oldForce = self.curForce
            self.curForce -= self.forceMomentum * dt
            arrowUpdate = True
            if oldForce > 0.01:
                arrowDown = True

        else:
            self.forceMomentum = 0
        if self.arrowKeys.leftPressed() and not self.arrowKeys.rightPressed():
            self.headingMomentum += headingMomentumChange
            if self.headingMomentum < 0:
                self.headingMomentum = 0

            if self.headingMomentum > 50:
                self.headingMomentum = 50

            self.curHeading += self.headingMomentum * dt
            arrowUpdate = True
            arrowRotating = True
        elif self.arrowKeys.rightPressed(
        ) and not self.arrowKeys.leftPressed():
            self.headingMomentum += headingMomentumChange
            if self.headingMomentum < 0:
                self.headingMomentum = 0

            if self.headingMomentum > 50:
                self.headingMomentum = 50

            self.curHeading -= self.headingMomentum * dt
            arrowUpdate = True
            arrowRotating = True
        else:
            self.headingMomentum = 0
        if arrowUpdate:
            self.normalizeHeadingAndForce()
            self.updateLocalForceArrow()

        if arrowRotating:
            if not self.arrowRotateSound.status(
            ) == self.arrowRotateSound.PLAYING:
                base.playSfx(self.arrowRotateSound, looping=True)

        else:
            self.arrowRotateSound.stop()
        if arrowUp:
            if not self.arrowUpSound.status() == self.arrowUpSound.PLAYING:
                base.playSfx(self.arrowUpSound, looping=False)

        else:
            self.arrowUpSound.stop()
        if arrowDown:
            if not self.arrowDownSound.status() == self.arrowDownSound.PLAYING:
                base.playSfx(self.arrowDownSound, looping=False)

        else:
            self.arrowDownSound.stop()
        return task.cont

    def normalizeHeadingAndForce(self):
        if self.curForce > self.MaxLocalForce:
            self.curForce = self.MaxLocalForce

        if self.curForce < 0.01:
            self.curForce = 0.01

    def setTireInputs(self, tireInputs):
        if not self.hasLocalToon:
            return None

        self.allTireInputs = tireInputs
        self.gameFSM.request('moveTires')

    def enableAllTireBodies(self):
        for avId in self.tireDict.keys():
            self.tireDict[avId]['tireBody'].enable()

    def disableAllTireBodies(self):
        for avId in self.tireDict.keys():
            self.tireDict[avId]['tireBody'].disable()

    def areAllTiresDisabled(self):
        for avId in self.tireDict.keys():
            if self.tireDict[avId]['tireBody'].isEnabled():
                return False
                continue

        return True

    def _DistributedIceGame__moveTiresTask(self, task):
        if self.areAllTiresDisabled():
            self.sendTirePositions()
            self.gameFSM.request('synch')
            return task.done

        return task.cont

    def sendTirePositions(self):
        tirePositions = []
        for index in xrange(len(self.avIdList)):
            avId = self.avIdList[index]
            tire = self.getTireBody(avId)
            pos = Point3(tire.getPosition())
            tirePositions.append([pos[0], pos[1], pos[2]])

        for index in xrange(len(self.avIdList), 4):
            avId = -index
            tire = self.getTireBody(avId)
            pos = Point3(tire.getPosition())
            tirePositions.append([pos[0], pos[1], pos[2]])

        self.sendUpdate('endingPositions', [tirePositions])

    def setFinalPositions(self, finalPos):
        if not self.hasLocalToon:
            return None

        for index in xrange(len(self.avIdList)):
            avId = self.avIdList[index]
            tire = self.getTireBody(avId)
            np = self.getTireNp(avId)
            pos = finalPos[index]
            tire.setPosition(pos[0], pos[1], pos[2])
            np.setPos(pos[0], pos[1], pos[2])

        for index in xrange(len(self.avIdList), 4):
            avId = -index
            tire = self.getTireBody(avId)
            np = self.getTireNp(avId)
            pos = finalPos[index]
            tire.setPosition(pos[0], pos[1], pos[2])
            np.setPos(pos[0], pos[1], pos[2])

    def updateInfoLabel(self):
        self.infoLabel['text'] = TTLocalizer.IceGameInfo % {
            'curMatch': self.curMatch + 1,
            'numMatch': IceGameGlobals.NumMatches,
            'curRound': self.curRound + 1,
            'numRound': IceGameGlobals.NumRounds
        }

    def setMatchAndRound(self, match, round):
        if not self.hasLocalToon:
            return None

        self.curMatch = match
        self.curRound = round
        self.updateInfoLabel()

    def setScores(self, match, round, scores):
        if not self.hasLocalToon:
            return None

        self.newMatch = match
        self.newRound = round
        self.newScores = scores

    def setNewState(self, state):
        if not self.hasLocalToon:
            return None

        self.notify.debug('setNewState gameFSM=%s newState=%s' %
                          (self.gameFSM, state))
        self.gameFSM.request(state)

    def putAllTiresInStartingPositions(self):
        for index in xrange(len(self.avIdList)):
            avId = self.avIdList[index]
            np = self.tireDict[avId]['tireNodePath']
            np.setPos(IceGameGlobals.StartingPositions[index])
            self.notify.debug('avId=%s newPos=%s' % (avId, np.getPos))
            np.setHpr(0, 0, 0)
            quat = np.getQuat()
            body = self.tireDict[avId]['tireBody']
            body.setPosition(IceGameGlobals.StartingPositions[index])
            body.setQuaternion(quat)

        for index in xrange(len(self.avIdList), 4):
            avId = -index
            np = self.tireDict[avId]['tireNodePath']
            np.setPos(IceGameGlobals.StartingPositions[index])
            self.notify.debug('avId=%s newPos=%s' % (avId, np.getPos))
            np.setHpr(0, 0, 0)
            quat = np.getQuat()
            body = self.tireDict[avId]['tireBody']
            body.setPosition(IceGameGlobals.StartingPositions[index])
            body.setQuaternion(quat)

    def b_setForceArrowInfo(self, avId, force, heading):
        self.setForceArrowInfo(avId, force, heading)
        self.d_setForceArrowInfo(avId, force, heading)

    def d_setForceArrowInfo(self, avId, force, heading):
        sendIt = False
        curTime = self.getCurrentGameTime()
        if self.sendForceArrowUpdateAsap:
            sendIt = True
        elif curTime - self.lastForceArrowUpdateTime > 0.20000000000000001:
            sendIt = True

        if sendIt:
            self.sendUpdate('setForceArrowInfo', [avId, force, heading])
            self.sendForceArrowUpdateAsap = False
            self.lastForceArrowUpdateTime = self.getCurrentGameTime()

    def setForceArrowInfo(self, avId, force, heading):
        if not self.hasLocalToon:
            return None

        self.updateForceArrow(avId, force, heading)

    def setupStartOfMatch(self):
        self.putAllTiresInStartingPositions()
        szId = self.getSafezoneId()
        self.numTreasures = IceGameGlobals.NumTreasures[szId]
        if self.treasures:
            for treasure in self.treasures:
                treasure.destroy()

            self.treasures = []

        index = 0
        treasureMargin = IceGameGlobals.TireRadius + 1.0
        while len(self.treasures) < self.numTreasures:
            xPos = self.randomNumGen.randrange(IceGameGlobals.MinWall[0] + 5,
                                               IceGameGlobals.MaxWall[0] - 5)
            yPos = self.randomNumGen.randrange(IceGameGlobals.MinWall[1] + 5,
                                               IceGameGlobals.MaxWall[1] - 5)
            self.notify.debug('yPos=%s' % yPos)
            pos = Point3(xPos, yPos, IceGameGlobals.TireRadius)
            newTreasure = IceTreasure.IceTreasure(self.treasureModel,
                                                  pos,
                                                  index,
                                                  self.doId,
                                                  penalty=False)
            goodSpot = True
            for obstacle in self.obstacles:
                if newTreasure.nodePath.getDistance(obstacle) < treasureMargin:
                    goodSpot = False
                    break
                    continue

            if goodSpot:
                for treasure in self.treasures:
                    if newTreasure.nodePath.getDistance(
                            treasure.nodePath) < treasureMargin:
                        goodSpot = False
                        break
                        continue

            if goodSpot:
                self.treasures.append(newTreasure)
                index += 1
                continue
            newTreasure.destroy()
        self.numPenalties = IceGameGlobals.NumPenalties[szId]
        if self.penalties:
            for penalty in self.penalties:
                penalty.destroy()

            self.penalties = []

        index = 0
        while len(self.penalties) < self.numPenalties:
            xPos = self.randomNumGen.randrange(IceGameGlobals.MinWall[0] + 5,
                                               IceGameGlobals.MaxWall[0] - 5)
            yPos = self.randomNumGen.randrange(IceGameGlobals.MinWall[1] + 5,
                                               IceGameGlobals.MaxWall[1] - 5)
            self.notify.debug('yPos=%s' % yPos)
            pos = Point3(xPos, yPos, IceGameGlobals.TireRadius)
            newPenalty = IceTreasure.IceTreasure(self.penaltyModel,
                                                 pos,
                                                 index,
                                                 self.doId,
                                                 penalty=True)
            goodSpot = True
            for obstacle in self.obstacles:
                if newPenalty.nodePath.getDistance(obstacle) < treasureMargin:
                    goodSpot = False
                    break
                    continue

            if goodSpot:
                for treasure in self.treasures:
                    if newPenalty.nodePath.getDistance(
                            treasure.nodePath) < treasureMargin:
                        goodSpot = False
                        break
                        continue

            if goodSpot:
                for penalty in self.penalties:
                    if newPenalty.nodePath.getDistance(
                            penalty.nodePath) < treasureMargin:
                        goodSpot = False
                        break
                        continue

            if goodSpot:
                self.penalties.append(newPenalty)
                index += 1
                continue
            newPenalty.destroy()

    def toonHitSomething(self, entry):
        self.notify.debug('---- treasure Enter ---- ')
        self.notify.debug('%s' % entry)
        name = entry.getIntoNodePath().getName()
        parts = name.split('-')
        if len(parts) < 3:
            self.notify.debug('collided with %s, but returning' % name)
            return None

        if not int(parts[1]) == self.doId:
            self.notify.debug("collided with %s, but doId doesn't match" %
                              name)
            return None

        treasureNum = int(parts[2])
        if 'penalty' in parts[0]:
            self._DistributedIceGame__penaltyGrabbed(treasureNum)
        else:
            self._DistributedIceGame__treasureGrabbed(treasureNum)

    def _DistributedIceGame__treasureGrabbed(self, treasureNum):
        self.treasures[treasureNum].showGrab()
        self.treasureGrabSound.play()
        self.sendUpdate('claimTreasure', [treasureNum])

    def setTreasureGrabbed(self, avId, treasureNum):
        if not self.hasLocalToon:
            return None

        self.notify.debug('treasure %s grabbed by %s' % (treasureNum, avId))
        if avId != self.localAvId:
            self.treasures[treasureNum].showGrab()

        i = self.avIdList.index(avId)
        self.scores[i] += 1
        self.scorePanels[i].setScore(self.scores[i])

    def _DistributedIceGame__penaltyGrabbed(self, penaltyNum):
        self.penalties[penaltyNum].showGrab()
        self.sendUpdate('claimPenalty', [penaltyNum])

    def setPenaltyGrabbed(self, avId, penaltyNum):
        if not self.hasLocalToon:
            return None

        self.notify.debug('penalty %s grabbed by %s' % (penaltyNum, avId))
        if avId != self.localAvId:
            self.penalties[penaltyNum].showGrab()

        i = self.avIdList.index(avId)
        self.scores[i] -= 1
        self.scorePanels[i].setScore(self.scores[i])

    def postStep(self):
        DistributedIceWorld.DistributedIceWorld.postStep(self)
        for count in range(self.colCount):
            (c0, c1) = self.getOrderedContacts(count)
            if c1 in self.tireCollideIds:
                tireIndex = self.tireCollideIds.index(c1)
                if c0 in self.tireCollideIds:
                    self.tireSounds[tireIndex]['tireHit'].play()
                elif c0 == self.wallCollideId:
                    self.tireSounds[tireIndex]['wallHit'].play()
                elif c0 == self.obstacleCollideId:
                    self.tireSounds[tireIndex]['obstacleHit'].play()

            c0 in self.tireCollideIds

    def forceLocalToonToTire(self):
        toon = localAvatar
        if toon and self.localAvId in self.tireDict:
            tireNp = self.tireDict[self.localAvId]['tireNodePath']
            toon.reparentTo(tireNp)
            toon.setPosHpr(0, 0, 0, 0, 0, 0)
            toon.setY(1.0)
            toon.setZ(-3)
Beispiel #58
0
class DistributedNPCToon(DistributedToon):
    notify = directNotify.newCategory('DistributedNPCToon')

    def __init__(self, cr):
        DistributedToon.__init__(self, cr)
        self.collisionNodePath = None
        self.cameraTrack = None
        self.originIndex = None
        self.npcId = None
        self.currentChatIndex = 0
        self.chatArray = None
        return

    def setLoadout(self, foo):
        pass

    def lookAtAvatar(self, avId):
        av = self.cr.doId2do.get(avId)
        if av:
            self.headsUp(av)

    def setNpcId(self, id):
        self.npcId = id

    def getNpcId(self):
        return self.npcId

    def setOriginIndex(self, index):
        self.originIndex = index

    def getOriginIndex(self):
        return self.originIndex

    def __setupCollisions(self):
        sphere = CollisionSphere(0, 0, 0, 4)
        sphere.setTangible(0)
        collisionNode = CollisionNode(self.uniqueName('NPCToonSphere'))
        collisionNode.addSolid(sphere)
        collisionNode.setCollideMask(CIGlobals.WallBitmask)
        self.collisionNodePath = self.attachNewNode(collisionNode)
        self.collisionNodePath.setY(1.5)

    def __removeCollisions(self):
        if self.collisionNodePath:
            self.collisionNodePath.removeNode()
            self.collisionNodePath = None
        return

    def handleEnterCollision(self, entry):
        self.cr.playGame.getPlace().fsm.request('stop')
        base.localAvatar.stopSmartCamera()
        self.sendUpdate('requestEnter', [])

    def doCameraNPCInteraction(self):
        currCamPos = camera.getPos()
        currCamHpr = camera.getHpr()
        camera.setX(camera.getX() + 5)
        camera.setY(camera.getY() + 5)
        camera.headsUp(self)
        newCamPos = camera.getPos()
        newCamHpr = camera.getHpr()
        camera.setPos(currCamPos)
        camera.setHpr(currCamHpr)
        self.cameraTrack = Parallel(LerpPosInterval(camera, duration=1.0, pos=newCamPos, startPos=currCamPos, blendType='easeOut'), LerpQuatInterval(camera, duration=1.0, quat=newCamHpr, startHpr=currCamHpr, blendType='easeOut'))
        self.cameraTrack.start()

    def stopCameraTrack(self):
        if self.cameraTrack:
            self.cameraTrack.finish()
            self.cameraTrack = None
        return

    def oneChatThenExit(self):
        self.acceptOnce('mouse1-up', self.d_requestExit)

    def enterAccepted(self):
        self.doCameraNPCInteraction()
        questData = base.localAvatar.questManager.getQuestAndIdWhereCurrentObjectiveIsToVisit(self.npcId)
        if questData:
            quest = questData[1]
            self.currentQuestObjective = quest.currentObjectiveIndex
            self.currentQuestId = questData[0]
            self.currentChatIndex = 0
            if CIGlobals.NPCToonDict[self.npcId][3] == CIGlobals.NPC_REGULAR:
                self.doNPCChat(array=Quests.QuestNPCDialogue)

    def doNPCChat(self, array = Quests.QuestNPCDialogue, chat = None):
        if array and not chat:
            self.chatArray = array
            self.b_setChat(array[self.currentQuestId][self.currentQuestObjective][self.currentChatIndex])
            self.currentChatIndex += 1
            Sequence(Wait(0.1), Func(self.acceptOnce, 'mouse1-up', self.doNextNPCChat)).start()
        elif chat and not array:
            self.b_setChat(chat)
            Sequence(Wait(0.1), Func(self.acceptOnce, 'mouse1-up', self.d_requestExit)).start()

    def d_requestExit(self):
        self.sendUpdate('requestExit', [])

    def doNextNPCChat(self):
        if self.currentChatIndex >= len(self.chatArray[self.currentQuestId][self.currentQuestObjective]):
            self.chatArray = None
            self.d_requestExit()
        else:
            self.doNPCChat(self.chatArray)
        return

    def rejectEnter(self):
        self.exitAccepted()

    def exitAccepted(self):
        self.stopCameraTrack()
        self.cr.playGame.getPlace().fsm.request('walk')
        self.acceptCollisions()

    def acceptCollisions(self):
        self.acceptOnce('enter' + self.uniqueName('NPCToonSphere'), self.handleEnterCollision)

    def ignoreCollisions(self):
        self.ignore('enter' + self.uniqueName('NPCToonSphere'))

    def __npcOriginPoll(self, task):
        if task.time > 4.0:
            self.notify.warning('Giving up waiting for npc origin after %d seconds. Will parent to render.' % task.time)
            self.reparentTo(render)
            return task.done
        npcOrigin = render.find('**/npc_origin_' + str(self.originIndex))
        if not npcOrigin.isEmpty():
            self.reparentTo(npcOrigin)
            return task.done
        return task.cont

    def startNPCOriginPoll(self):
        base.taskMgr.add(self.__npcOriginPoll, self.uniqueName('NPCOriginPoll'))

    def stopNPCOriginPoll(self):
        base.taskMgr.remove(self.uniqueName('NPCOriginPoll'))

    def announceGenerate(self):
        DistributedToon.announceGenerate(self)
        self.startLookAround()
        self.__setupCollisions()
        npcOrigin = render.find('**/npc_origin_' + str(self.originIndex))
        if not npcOrigin.isEmpty():
            self.reparentTo(npcOrigin)
        else:
            self.startNPCOriginPoll()
        self.acceptCollisions()
        self.nameTag.setClickable(0)

    def disable(self):
        self.ignore('mouse1-up')
        self.stopLookAround()
        self.stopNPCOriginPoll()
        self.chatArray = None
        self.originIndex = None
        self.npcId = None
        self.stopCameraTrack()
        self.ignoreCollisions()
        self.__removeCollisions()
        DistributedToon.disable(self)
        return
class SquirtingFlower(SquirtGag):

    def __init__(self):
        SquirtGag.__init__(self, CIGlobals.SquirtFlower, GagGlobals.getProp(3.5, 'button'), 3, GagGlobals.FLOWER_HIT_SFX, GagGlobals.FLOWER_HIT_SFX, GagGlobals.NULL_SFX, None, 0, 0, 0)
        self.setImage('phase_3.5/maps/squirting-flower.png')
        self.flower = None
        self.flowerScale = 1.5
        self.track = Parallel()
        self.timeout = 4.0
        self.sprayRotation = Vec3(0, 20, 0)
        return

    def start(self):
        SquirtGag.start(self)
        self.buildFlower()
        self.build()
        self.equip()
        if self.isLocal():
            self.startTimeout()
        self.origin = self.getSprayStartPos()

        def attachFlower():
            flowerJoint = self.avatar.find('**/def_joint_attachFlower')
            if flowerJoint.isEmpty():
                flowerJoint = self.avatar.find('**/joint_attachFlower')
            self.flower.reparentTo(flowerJoint)
            self.flower.setY(self.flower.getY())

        totalAnimationTime = 2.5
        flowerAppear = 1.0
        flowerScaleTime = 0.5
        animTrack = ActorInterval(self.avatar, 'push-button')
        self.track.append(animTrack)
        flowerTrack = Sequence(Func(attachFlower), Wait(flowerAppear), LerpScaleInterval(self.flower, flowerScaleTime, 1.5, startScale=GagGlobals.PNT3NEAR0), Wait(totalAnimationTime - flowerScaleTime - flowerAppear))
        flowerTrack.append(Func(self.release))
        flowerTrack.append(LerpScaleInterval(self.flower, flowerScaleTime, GagGlobals.PNT3NEAR0))
        flowerTrack.append(LerpScaleInterval(self.gag, flowerScaleTime, GagGlobals.PNT3NEAR0))
        flowerTrack.append(Func(self.unEquip))
        self.track.append(flowerTrack)
        self.track.start()

    def getSprayStartPos(self):
        if not self.avatar.isEmpty() and not self.flower.isEmpty():
            self.avatar.update(0)
            return self.flower.getPos(render)

    def release(self):
        SquirtGag.release(self)
        if not self.avatar.isEmpty() and self.gag:
            self.sprayJoint = self.flower.find('**/joint_attachSpray')
            self.sprayRange = self.avatar.getPos(render) + Point3(0, GagGlobals.SELTZER_RANGE, 0)
            self.doSpray(0.2, 0.2, 0.1, horizScale=0.3, vertScale=0.3)
            if self.isLocal():
                base.localAvatar.sendUpdate('usedGag', [self.id])

    def unEquip(self):
        SquirtGag.unEquip(self)
        self.cleanup()
        self.reset()

    def cleanup(self):
        if self.flower:
            self.flower.removeNode()
            self.flower = None
        if self.track:
            self.track.pause()
            self.track = Parallel()
        return

    def setHandJoint(self):
        if self.avatar:
            self.handJoint = self.avatar.find('**/def_joint_left_hold')
            if not self.handJoint:
                print self.avatar.findAllMatches('**/*joint*')

    def buildFlower(self):
        if self.flower:
            self.flower.removeNode()
            self.flower = None
        self.flower = loader.loadModel(GagGlobals.getProp(3.5, 'squirting-flower'))
        self.flower.setScale(GagGlobals.PNT3NEAR0)
        return
Beispiel #60
0
    def start(self):
        SoundGag.start(self)
        INSTRUMENT_SCALE_MODIFIER = 0.5
        delayTime = 2.45
        delayUntilAppearSound = 1.0
        tracks = Parallel()
        instrMin = Vec3(0.001, 0.001, 0.001)
        instrMax = Vec3(0.65, 0.65, 0.65)
        instrMax *= INSTRUMENT_SCALE_MODIFIER
        instrStretch = Vec3(0.6, 1.1, 0.6)
        instrStretch *= INSTRUMENT_SCALE_MODIFIER

        def setInstrumentStats():
            self.gag.setPos(-1.1, -1.4, 0.1)
            self.gag.setHpr(145, 0, 0)
            self.gag.setScale(instrMin)

        megaphoneShow = Sequence(
            Func(self.placeProp, self.handJoint, self.megaphone),
            Func(self.placeProp, self.handJoint, self.gag),
            Func(setInstrumentStats))
        grow = self.getScaleIntervals(self.gag,
                                      duration=0.2,
                                      startScale=instrMin,
                                      endScale=instrMax)
        instrumentAppear = grow
        stretchInstr = self.getScaleBlendIntervals(self.gag,
                                                   duration=0.2,
                                                   startScale=instrMax,
                                                   endScale=instrStretch,
                                                   blendType='easeOut')
        backInstr = self.getScaleBlendIntervals(self.gag,
                                                duration=0.2,
                                                startScale=instrStretch,
                                                endScale=instrMax,
                                                blendType='easeIn')
        stretchMega = self.getScaleBlendIntervals(
            self.megaphone,
            duration=0.2,
            startScale=self.megaphone.getScale(),
            endScale=0.9,
            blendType='easeOut')
        backMega = self.getScaleBlendIntervals(
            self.megaphone,
            duration=0.2,
            startScale=0.9,
            endScale=self.megaphone.getScale(),
            blendType='easeIn')
        attackTrack = Parallel(Sequence(stretchInstr, backInstr),
                               Sequence(stretchMega, backMega))
        megaphoneTrack = Sequence(
            megaphoneShow, Wait(delayUntilAppearSound),
            SoundInterval(self.appearSfx, node=self.avatar), instrumentAppear)
        tracks.append(megaphoneTrack)
        tracks.append(ActorInterval(self.avatar, 'sound'))
        instrumentshrink = self.getScaleIntervals(self.gag,
                                                  duration=0.1,
                                                  startScale=instrMax,
                                                  endScale=instrMin)
        soundTrack = Sequence(
            Wait(delayTime),
            Parallel(attackTrack, SoundInterval(self.soundSfx,
                                                node=self.avatar), Wait(0.2),
                     instrumentshrink, Func(self.damageCogsNearby), Wait(0.4),
                     Func(self.finish)))
        tracks.append(soundTrack)
        tracks.start()
        self.tracks = tracks