示例#1
0
 def setupFloorEventSphere(self, avatarNodePath, bitmask, avatarRadius):
     cSphere = CollisionSphere(0.0, 0.0, 0.0, 0.75)
     cSphereNode = CollisionNode('Flyer.cFloorEventSphere')
     cSphereNode.addSolid(cSphere)
     cSphereNodePath = avatarNodePath.attachNewNode(cSphereNode)
     cSphereNode.setFromCollideMask(bitmask)
     cSphereNode.setIntoCollideMask(BitMask32.allOff())
     self.floorCollisionEvent = CollisionHandlerEvent()
     self.floorCollisionEvent.addInPattern('%fn-enter-%in')
     self.floorCollisionEvent.addAgainPattern('%fn-again-%in')
     self.floorCollisionEvent.addOutPattern('%fn-exit-%in')
     base.cTrav.addCollider(cSphereNodePath, self.floorCollisionEvent)
     self.cFloorEventSphereNodePath = cSphereNodePath
示例#2
0
 def __initCollisions(self):
     collSphere = CollisionSphere(0, 0, 0, Globals.PlayerCollisionRadius)
     collSphere.setTangible(0)
     self.mazeCollisionName = Globals.LocalPlayerCollisionName
     collNode = CollisionNode(self.mazeCollisionName)
     collNode.addSolid(collSphere)
     collNodePath = self.toon.attachNewNode(collNode)
     collNodePath.hide()
     handler = CollisionHandlerEvent()
     handler.addInPattern('%fn-into-%in')
     base.cTrav.addCollider(collNodePath, handler)
     self.handler = handler
     self._collNodePath = collNodePath
示例#3
0
 def setupHeadSphere(self, avatarNodePath):
     collSphere = CollisionSphere(0, 0, 0, 1)
     collSphere.setTangible(1)
     collNode = CollisionNode('Flyer.cHeadCollSphere')
     collNode.setFromCollideMask(ToontownGlobals.CeilingBitmask)
     collNode.setIntoCollideMask(BitMask32.allOff())
     collNode.addSolid(collSphere)
     self.cHeadSphereNodePath = avatarNodePath.attachNewNode(collNode)
     self.cHeadSphereNodePath.setZ(base.localAvatar.getHeight() + 1.0)
     self.headCollisionEvent = CollisionHandlerEvent()
     self.headCollisionEvent.addInPattern('%fn-enter-%in')
     self.headCollisionEvent.addOutPattern('%fn-exit-%in')
     base.cTrav.addCollider(self.cHeadSphereNodePath,
                            self.headCollisionEvent)
示例#4
0
 def setupEventSphere(self, bitmask, avatarRadius):
     self.avatarRadius = avatarRadius
     cSphere = CollisionSphere(0.0, 0.0, self.avatarRadius + 0.75,
                               self.avatarRadius * 1.04)
     cSphere.setTangible(0)
     cSphereNode = CollisionNode('Flyer.cEventSphereNode')
     cSphereNode.addSolid(cSphere)
     cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode)
     cSphereNode.setFromCollideMask(bitmask)
     cSphereNode.setIntoCollideMask(BitMask32.allOff())
     self.event = CollisionHandlerEvent()
     self.event.addInPattern('enter%in')
     self.event.addOutPattern('exit%in')
     self.cEventSphereNodePath = cSphereNodePath
示例#5
0
 def getFlyBallBubble(self):
     if self.__flyBallBubble == None:
         bubble = CollisionSphere(0, 0, 0, GolfGlobals.GOLF_BALL_RADIUS)
         node = CollisionNode('flyBallBubble')
         node.addSolid(bubble)
         node.setFromCollideMask(ToontownGlobals.PieBitmask
                                 | ToontownGlobals.CameraBitmask
                                 | ToontownGlobals.FloorBitmask)
         node.setIntoCollideMask(BitMask32.allOff())
         self.__flyBallBubble = NodePath(node)
         self.flyBallHandler = CollisionHandlerEvent()
         self.flyBallHandler.addInPattern('flyBallHit-%d' % self.index)
         #self.flyBallHandler.addInPattern('flyBallHit-%d--%in')
     return self.__flyBallBubble
示例#6
0
 def __init__(self, id, toon, game, guiMgr):
     CogdoMazePlayer.__init__(self, id, toon)
     self.disableGagCollision()
     self.game = game
     self.maze = self.game.maze
     self._guiMgr = guiMgr
     self.cameraMgr = CogdoMazeCameraManager(self.toon, self.maze, camera,
                                             render)
     self._proximityRadius = self.maze.cellWidth * Globals.CameraRemoteToonRadius
     orthoDrive = OrthoDrive(
         Globals.ToonRunSpeed,
         maxFrameMove=self.maze.cellWidth / 2,
         customCollisionCallback=self.maze.doOrthoCollisions,
         wantSound=True)
     self.orthoWalk = OrthoWalk(orthoDrive)
     self._audioMgr = base.cogdoGameAudioMgr
     self._getMemoSfx = self._audioMgr.createSfx('getMemo',
                                                 source=self.toon)
     self._waterCoolerFillSfx = self._audioMgr.createSfx('waterCoolerFill',
                                                         source=self.toon)
     self._hitByDropSfx = self._audioMgr.createSfx('toonHitByDrop',
                                                   source=self.toon)
     self._winSfx = self._audioMgr.createSfx('win')
     self._loseSfx = self._audioMgr.createSfx('lose')
     self.enabled = False
     self.pickupCount = 0
     self.numEntered = 0
     self.throwPending = False
     self.coolDownAfterHitInterval = Sequence(
         Wait(Globals.HitCooldownTime),
         Func(self.setInvulnerable, False),
         name='coolDownAfterHitInterval-%i' % self.toon.doId)
     self.invulnerable = False
     self.gagHandler = CollisionHandlerEvent()
     self.gagHandler.addInPattern('%fn-into-%in')
     self.exited = False
     self.hints = {
         'find': False,
         'throw': False,
         'squashed': False,
         'boss': False,
         'minion': False
     }
     self.accept('control', self.controlKeyPressed)
示例#7
0
    def startActivity(self, timestamp):
        self.pieHandler = CollisionHandlerEvent()
        self.pieHandler.setInPattern('pieHit-%fn')

        if self.player is not None:
            self.player.resetScore()
            self.hideTeamFlags(self.player.team)

        for player in list(self.players.values()):
            self.finishToonIval(player.toon.doId)
            player.enable()

        for cog in self.cogManager.cogs:
            cog.request("Active", timestamp)

        for ival in self.pieIvals:
            if ival.isPlaying():
                ival.finish()
        self.pieIvals = []
示例#8
0
class PartyCogActivity(DirectObject):
    notify = directNotify.newCategory("PartyCogActivity")

    cog = None
    arena = None
    player = None
    players = {}

    def __init__(self, activity):
        self.activity = activity
        self.root = self.activity.root

        self.toonPieTracks = {}
        self.toonPieEventNames = {}
        self.toonIdsToAnimIntervals = {}
        self.pieIvals = []
        self.resultsIval = None

    def load(self):
        self.arena = loader.loadModel(
            "phase_13/models/parties/cogPieArena_model")
        self.arena.reparentTo(self.root)

        ground = self.arena.find("**/ground")
        # Make the ground plane draw before the shadow!
        ground.setBin("ground", 1)

        entranceArrows = self.arena.findAllMatches("**/arrowFlat*")
        for arrow in entranceArrows:
            arrow.setBin("ground", 5)

        # Get Entrance/Exit Locations
        self.leftEntranceLocator = self.arena.find("**/leftEntrance_locator")
        self.rightEntranceLocator = self.arena.find("**/rightEntrance_locator")
        self.leftExitLocator = self.arena.find("**/leftExit_locator")
        self.rightExitLocator = self.arena.find("**/rightExit_locator")

        self.teamCamPosLocators = (self.arena.find("**/team0CamPos_locator"),
                                   self.arena.find("**/team1CamPos_locator"))

        self.teamCamAimLocators = (
            self.arena.find("**/team0CamAim_locator"),
            self.arena.find("**/team1CamAim_locator"),
        )

        # Setup team locators
        # Toons are parented to these guys in order to do
        # Orthowalk properly
        leftTeamLocator = NodePath("TeamLocator-%d" %
                                   PartyGlobals.TeamActivityTeams.LeftTeam)
        leftTeamLocator.reparentTo(self.root)
        leftTeamLocator.setH(90)

        rightTeamLocator = NodePath("TeamLocator-%d" %
                                    PartyGlobals.TeamActivityTeams.RightTeam)
        rightTeamLocator.reparentTo(self.root)
        rightTeamLocator.setH(-90)

        self.teamLocators = (leftTeamLocator, rightTeamLocator)

        # Used to place the toons in even spaces
        self._lengthBetweenEntrances = self.leftEntranceLocator.getY(
        ) - self.rightExitLocator.getY()

        # Setup Sky Collisions. Important for cannons.
        self._skyCollisionsCollection = self.arena.findAllMatches(
            "**/cogPieArena_sky*_collision")

        if len(self._skyCollisionsCollection) > 0:
            self._skyCollisionParent = self._skyCollisionsCollection[
                0].getParent()
        else:
            self._skyCollisionParent = self.arena

        # Get all the wall collisions:
        self._wallCollisionsCollection = self.arena.findAllMatches(
            "**/cogPieArena_wall*_collision")

        # Get a hold of the flags:
        self._arenaFlagGroups = (self.arena.find("**/flagsL_grp"),
                                 self.arena.find("**/flagsR_grp"))

        self._initArenaDoors()

        # Setup Cogs
        self.cogManager = PartyCogManager()
        self.arrows = []

        self.distanceLabels = []
        self.teamColors = list(PartyGlobals.CogActivityColors) + [
            PartyGlobals.TeamActivityStatusColor
        ]

        for i in range(3):
            start = self.arena.find("**/cog%d_start_locator" % (i + 1))
            end = self.arena.find("**/cog%d_end_locator" % (i + 1))

            cog = self.cogManager.generateCog(self.arena)
            cog.setEndPoints(start.getPos(), end.getPos())

            arrow1 = StretchingArrow(self.arena, useColor="orange")
            arrow2 = StretchingArrow(self.arena, useColor="blue")
            arrow1.setZ(0.1)
            arrow2.setZ(0.1)
            self.arrows.append([arrow1, arrow2])

            distanceLabel = self.createDistanceLabel(0, self.teamColors[1])
            distanceLabel[0].stash()
            distanceLabel2 = self.createDistanceLabel(0, self.teamColors[0])
            distanceLabel2[0].stash()
            self.distanceLabels.append([distanceLabel, distanceLabel2])

        self.winText = []
        text1 = self.createText(0, Point3(-0.5, 0.0, -0.5), self.teamColors[1])
        text2 = self.createText(1, Point3(0.5, 0.0, -0.5), self.teamColors[0])
        self.winText.append(text1)
        self.winText.append(text2)

        self.winStatus = self.createText(2, Point3(0.0, 0.0, -0.8),
                                         self.teamColors[0])

        signLocator = self.arena.find("**/eventSign_locator")
        self.activity.sign.setPos(signLocator.getPos(self.root))

        self.enable()

    def _initArenaDoors(self):
        """Initializes arena door locators, timers, and animations"""

        # Setup doors
        self._arenaDoors = (
            self.arena.find("**/doorL"),
            self.arena.find("**/doorR"),
        )

        arenaDoorLocators = (self.arena.find("**/doorL_locator"),
                             self.arena.find("**/doorR_locator"))

        # Reparent those locators to the doors.
        for i in range(len(arenaDoorLocators)):
            arenaDoorLocators[i].wrtReparentTo(self._arenaDoors[i])

        self._arenaDoorTimers = (self.createDoorTimer(
            PartyGlobals.TeamActivityTeams.LeftTeam),
                                 self.createDoorTimer(
                                     PartyGlobals.TeamActivityTeams.RightTeam))

        self._arenaDoorIvals = [None, None]
        self._doorStartPos = []

        for i in range(len(self._arenaDoors)):
            door = self._arenaDoors[i]

            timer = self._arenaDoorTimers[i]
            timer.reparentTo(arenaDoorLocators[i])
            timer.hide()

            self._doorStartPos.append(door.getPos())

            door.setPos(door, 0, 0, -7.0)

    def _destroyArenaDoors(self):
        for ival in self._arenaDoorIvals:
            ival.finish()

        self._arenaDoorIvals = None

        self._arenaDoors = None

        for timer in self._arenaDoorTimers:
            timer.stop()
            timer.removeNode()
        self._arenaDoorTimers = None

    def createDoorTimer(self, team):
        timer = ToontownTimer(useImage=False, highlightNearEnd=False)
        timer["text_font"] = ToontownGlobals.getMinnieFont()
        timer.setFontColor(PartyGlobals.CogActivityColors[team])
        timer.setScale(7.0)
        timer.setPos(0.2, -0.03, 0.0)

        return timer

    def createText(self, number, position, color):
        text = TextNode("winText%d" % number)
        text.setAlign(TextNode.ACenter)
        text.setTextColor(color)
        text.setFont(ToontownGlobals.getSignFont())
        text.setText("")

        noteText = aspect2d.attachNewNode(text)
        noteText.setScale(0.2)
        noteText.setPos(position)
        noteText.stash()

        return text, noteText

    def createDistanceLabel(self, number, color):
        text = TextNode("distanceText-%d" % number)
        text.setAlign(TextNode.ACenter)
        text.setTextColor(color)
        text.setFont(ToontownGlobals.getSignFont())
        text.setText("10 ft")

        node = self.root.attachNewNode(text)
        node.setBillboardPointEye()
        node.setScale(2.5)
        node.setZ(5.0)

        return (node, text)

    def unload(self):
        self.disable()

        self._cleanupResultsIval()

        if self.winText is not None:
            for pair in self.winText:
                pair[1].reparentTo(hidden)
                pair[1].removeNode()
            self.winText = None

        if self.winStatus is not None:
            self.winStatus[1].reparentTo(hidden)
            self.winStatus[1].removeNode()
            self.winStatus = None

        if self.cogManager is not None:
            self.cogManager.unload()
            self.cogManager = None

        if self.arrows is not None:
            for pair in self.arrows:
                for arrow in pair:
                    arrow.destroy()
                    arrow = None
                pair = None
            self.arrows = None

        if self.distanceLabels is not None:
            for pair in self.distanceLabels:
                for (node, text) in pair:
                    node.removeNode()
                pair = None
        self.distanceLabels = None

        if len(self.players):
            for player in list(self.players.values()):
                player.disable()
                player.destroy()

        self.players.clear()
        self.player = None

        if self.arena is not None:
            self.leftEntranceLocator = None
            self.rightEntranceLocator = None
            self.leftExitLocator = None
            self.rightExitLocator = None

            self._skyCollisions = None
            self._skyCollisionParent = None

            self._arenaFlagGroups = None

            self._destroyArenaDoors()

            self.arena.removeNode()
            self.arena = None

        for ival in list(self.toonPieTracks.values()):
            if ival is not None and ival.isPlaying():
                ival.finish()
        self.toonPieTracks = {}

        for ival in self.pieIvals:
            if ival is not None and ival.isPlaying():
                ival.finish()
        self.pieIvals = []
        self.toonIdsToAnimIntervals = {}

        for eventName in list(self.toonPieEventNames.values()):
            self.ignore(eventName)

        self.toonPieEventNames = {}

    def enable(self):
        self.enableEnterGateCollision()

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

    def hideTeamFlags(self, team):
        self._arenaFlagGroups[team].stash()

    def showTeamFlags(self, team):
        self._arenaFlagGroups[team].unstash()

    def _playArenaDoorIval(self, team, opening=True):
        ival = self._arenaDoorIvals[team]

        if ival is not None and ival.isPlaying():
            ival.pause()

        if not opening:
            pos = LVecBase3f(tuple(self._doorStartPos[team]))
        else:
            pos = LVecBase3f(tuple(self._doorStartPos[team])) + LVecBase3f(
                Point3(0, 0, -7.0))
        ival = self._arenaDoors[team].posInterval(0.75,
                                                  pos,
                                                  blendType="easeIn")

        self._arenaDoorIvals[team] = ival
        ival.start()

    def openArenaDoorForTeam(self, team):
        self._playArenaDoorIval(team, opening=True)

    def closeArenaDoorForTeam(self, team):
        self._playArenaDoorIval(team, opening=False)

    def openArenaDoors(self):
        self.enableEnterGateCollision()
        for i in range(len(self._arenaDoors)):
            self.openArenaDoorForTeam(i)

    def closeArenaDoors(self):
        self.disableEnterGateCollision()
        for i in range(len(self._arenaDoors)):
            self.closeArenaDoorForTeam(i)

    def showArenaDoorTimers(self, duration):
        for timer in self._arenaDoorTimers:
            timer.setTime(duration)
            timer.countdown(duration)
            timer.show()

    def hideArenaDoorTimers(self):
        for timer in self._arenaDoorTimers:
            timer.hide()

    def enableEnterGateCollision(self):
        self.acceptOnce("entercogPieArena_entranceLeft_collision",
                        self.handleEnterLeftEntranceTrigger)
        self.acceptOnce("entercogPieArena_entranceRight_collision",
                        self.handleEnterRightEntranceTrigger)

    def disableEnterGateCollision(self):
        self.ignore("entercogPieArena_entranceLeft_collision")
        self.ignore("entercogPieArena_entranceRight_collision")

    def enableWallCollisions(self):
        self._wallCollisionsCollection.unstash()

    def disableWallCollisions(self):
        self._wallCollisionsCollection.stash()

    def enableSkyCollisions(self):
        self._skyCollisionsCollection.unstash()

    def disableSkyCollisions(self):
        self._skyCollisionsCollection.stash()

    def handleEnterLeftEntranceTrigger(self, collEntry):
        assert (self.notify.debug("handleEnterGateCollision"))

        self.activity.d_toonJoinRequest(
            PartyGlobals.TeamActivityTeams.LeftTeam)

    def handleEnterRightEntranceTrigger(self, collEntry):
        assert (self.notify.debug("handleEnterGateCollision"))

        self.activity.d_toonJoinRequest(
            PartyGlobals.TeamActivityTeams.RightTeam)

    def checkOrthoDriveCollision(self, oldPos, newPos):
        """Used by OrthoDrive to guarantee that the toon's pos stays inside the play area"""
        x = bound(newPos[0], -16.8, 16.8)
        y = bound(newPos[1], -17.25, -24.1)
        newPos.setX(x)
        newPos.setY(y)

        return newPos

    def getPlayerStartPos(self, team, spot):
        if team == PartyGlobals.TeamActivityTeams.LeftTeam:
            node = self.leftExitLocator
        else:
            node = self.rightExitLocator

        d = self._lengthBetweenEntrances / (
            self.activity.getMaxPlayersPerTeam() + 1)
        yOffset = node.getY(self.root) + d * (spot + 1)

        pos = node.getPos(self.root)
        pos.setY(yOffset)

        return pos

    def handleToonJoined(self, toon, team, lateEntry=False):
        pos = self.getPlayerStartPos(team,
                                     self.activity.getIndex(toon.doId, team))

        if toon == base.localAvatar:
            player = PartyCogActivityLocalPlayer(self.activity, pos, team,
                                                 self.handleToonExited)
            player.entersActivity()

            self.player = player

            self.disableSkyCollisions()
            self.playPlayerEnterIval()

        else:
            player = PartyCogActivityPlayer(self.activity, toon, pos, team)
            player.entersActivity()

            # This only happens if the toon is joining after the activity has started
            if lateEntry:
                player.updateToonPosition()

        self.players[toon.doId] = player

    def handleToonSwitchedTeams(self, toon):
        toonId = toon.doId
        player = self.players.get(toonId)

        if player is None:
            self.notify.warning(
                "handleToonSwitchedTeams: toonId %s not found" % toonId)
            return

        team = self.activity.getTeam(toonId)
        spot = self.activity.getIndex(toonId, team)
        pos = self.getPlayerStartPos(team, spot)

        self.finishToonIval(toonId)
        player.setTeam(team)
        player.setToonStartPosition(pos)
        player.updateToonPosition()

    def handleToonShifted(self, toon):
        toonId = toon.doId

        if toonId in self.players:
            player = self.players[toonId]

            spot = self.activity.getIndex(toonId, player.team)
            pos = self.getPlayerStartPos(player.team, spot)

            player.setToonStartPosition(pos)

            if self.player is not None and toon == self.player.toon:
                self.playToonIval(base.localAvatar.doId,
                                  self.player.getRunToStartPositionIval())

    def handleToonDisabled(self, toonId):
        self.finishToonIval(toonId)

        player = self.players.get(toonId)

        if player is not None:
            player.disable()

            if player == self.player:
                self.player = None

            del self.players[toonId]

    def playPlayerEnterIval(self):
        # Note: Disable "Switch Team" button while running b/c an unknown, bad interaction between
        # LerpPosInterval and startPosHprBroadcast (both in the run ival) causes the toon to be
        # immobile for approx. 200 ms or more if we call ival.finish().
        def conditionallyShowSwitchButton(self=self, enable=True):
            if enable and self.activity.activityFSM._state in [
                    "WaitForEnough", "WaitToStart"
            ]:
                self.activity.teamActivityGui.enableSwitchButton()
            else:
                self.activity.teamActivityGui.disableSwitchButton()

        ival = Sequence(Func(self.disableWallCollisions),
                        Func(conditionallyShowSwitchButton, self, False),
                        self.player.getRunToStartPositionIval(),
                        Func(conditionallyShowSwitchButton, self, True),
                        Func(self.enableWallCollisions))

        self.playToonIval(base.localAvatar.doId, ival)

    def finishToonIval(self, toonId):
        if self.toonIdsToAnimIntervals.get(toonId) is not None and \
            self.toonIdsToAnimIntervals[toonId].isPlaying():

            self.toonIdsToAnimIntervals[toonId].finish()

    def playToonIval(self, toonId, ival):
        self.finishToonIval(toonId)

        self.toonIdsToAnimIntervals[toonId] = ival
        ival.start()

    def startActivity(self, timestamp):
        self.pieHandler = CollisionHandlerEvent()
        self.pieHandler.setInPattern('pieHit-%fn')

        if self.player is not None:
            self.player.resetScore()
            self.hideTeamFlags(self.player.team)

        for player in list(self.players.values()):
            self.finishToonIval(player.toon.doId)
            player.enable()

        for cog in self.cogManager.cogs:
            cog.request("Active", timestamp)

        for ival in self.pieIvals:
            if ival.isPlaying():
                ival.finish()
        self.pieIvals = []

    def stopActivity(self):
        for player in list(self.players.values()):
            player.disable()

        for eventName in list(self.toonPieEventNames.values()):
            self.ignore(eventName)

        self.toonPieEventNames.clear()

        for cog in self.cogManager.cogs:
            cog.request("Static")

    def handleToonExited(self, toon):
        self.finishToonIval(toon.doId)

        player = self.players[toon.doId]
        player.disable()
        player.exitsActivity()
        player.destroy()

        if player == self.player:
            self.showTeamFlags(self.activity.getTeam(toon.doId))
            self.player = None
            self.enableEnterGateCollision()
            self.enableSkyCollisions()

        del self.players[toon.doId]

    def pieThrow(self, avId, timestamp, heading, pos, power):
        """Show local or remote toon throwing a pie."""

        toon = self.activity.getAvatar(avId)

        if toon is None:
            return

        tossTrack, pieTrack, flyPie = self.getTossPieInterval(
            toon, pos[0], pos[1], pos[2], heading, 0, 0, power)

        if avId == base.localAvatar.doId:
            flyPie.setTag('throwerId', str(avId))

            collSphere = CollisionSphere(0, 0, 0, 0.5)
            # Make the sphere intangible
            collSphere.setTangible(0)
            name = "PieSphere-%d" % avId
            collSphereName = self.activity.uniqueName(name)
            collNode = CollisionNode(collSphereName)
            collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
            collNode.addSolid(collSphere)
            collNP = flyPie.attachNewNode(collNode)

            base.cTrav.addCollider(collNP, self.pieHandler)

            self.toonPieEventNames[collNP] = 'pieHit-' + collSphereName
            self.accept(self.toonPieEventNames[collNP],
                        self.handlePieCollision)
        else:
            player = self.players.get(avId)
            if player is not None:
                player.faceForward()

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

        newTossTrack = Sequence(tossTrack, Func(matchRunningAnim))

        pieTrack = Parallel(newTossTrack,
                            pieTrack,
                            name="PartyCogActivity.pieTrack-%d-%s" %
                            (avId, timestamp))

        elapsedTime = globalClockDelta.localElapsedTime(timestamp)

        if elapsedTime < 16. / 24.:
            elapsedTime = 16. / 24.  # make the pie fly immediately

        pieTrack.start(elapsedTime)

        self.pieIvals.append(pieTrack)
        self.toonPieTracks[avId] = pieTrack

    def getTossPieInterval(self,
                           toon,
                           x,
                           y,
                           z,
                           h,
                           p,
                           r,
                           power,
                           beginFlyIval=Sequence()):
        """Adapted from toon.py to suit our needs.
        Returns (toss, pie, flyPie), where toss is an interval to
        animate the toon tossing a pie, pie is the interval to
        animate the pie flying through the air, and pieModel is the
        model that flies.  This is used in the final BossBattle
        sequence of CogHQ when we all throw pies directly at the
        boss cog.
        """

        from toontown.toonbase import ToontownBattleGlobals
        from toontown.battle import BattleProps

        pie = toon.getPieModel()
        pie.setScale(0.5)
        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.mp3')

        # First, create a ProjectileInterval to compute the relative
        # velocity.

        assert 0 <= power <= 100, "invalid pie throw power %s" % power

        t = power / 100.0

        # Distance ranges from CogActivityPieMinDist to CogActivityPieMaxDist ft, time ranges from 1 to 1.5 s.
        dist = lerp(PartyGlobals.CogActivityPieMinDist,
                    PartyGlobals.CogActivityPieMaxDist, t)
        time = lerp(1.0, 1.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),
                    animPie,
                    Parallel(
                        ActorInterval(
                            toon,
                            'throw',
                            startFrame=48,
                            #duration=0.25, #self.throwPieLimitTime,
                            playRate=1.5,
                            partName='torso'),
                        animPie),
                    Func(toon.setAnimState, 'Happy'),
                )),
            (16. / 24., Func(pie.detachNode)))

        fly = Track(
            (14. / 24.,
             SoundInterval(
                 sound, node=toon, cutOff=PartyGlobals.PARTY_COG_CUTOFF)),
            (
                16. / 24.,
                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),
                    #LerpPosInterval(flyPie, duration = 3, Point3(0.52,50,2.24)),
                    Func(flyPie.detachNode),
                )),
        )
        return (toss, fly, flyPie)

    def handlePieCollision(self, colEntry):
        """Handle the pie thrown by the local toon hitting something."""
        if not self.activity.isState("Active") or self.player is None:
            return

        handled = False
        into = colEntry.getIntoNodePath()
        intoName = into.getName()
        timestamp = globalClockDelta.localToNetworkTime(
            globalClock.getFrameTime(), bits=32)

        if "PartyCog" in intoName:
            if self.toonPieTracks.get(base.localAvatar.doId) is not None:
                self.toonPieTracks[base.localAvatar.doId].finish()
                self.toonPieTracks[base.localAvatar.doId] = None

            parts = intoName.split('-')
            cogID = int(parts[1])
            point = colEntry.getSurfacePoint(self.cogManager.cogs[cogID].root)
            cog = self.cogManager.cogs[cogID]
            hitHead = ((point.getZ() > cog.getHeadLocation())
                       and not parts[2].startswith("Arm"))

            if self.activity.getTeam(
                    base.localAvatar.doId
            ) == PartyGlobals.TeamActivityTeams.LeftTeam:
                direction = -1.0
            else:
                direction = 1.0

            self.activity.b_pieHitsCog(timestamp, cogID, point, direction,
                                       hitHead)

            if hitHead:
                hitPoints = self.player.hitHead()
            else:
                hitPoints = self.player.hitBody()

            self.player.updateScore()

            if hitPoints > 0:
                cog.showHitScore(hitPoints)

            handled = True

        elif "distAvatarCollNode" in intoName:
            parts = intoName.split('-')
            hitToonId = int(parts[1])
            toon = base.cr.doId2do.get(hitToonId)

            if toon is not None and self.activity.getTeam(
                    hitToonId) != self.player.team:
                point = colEntry.getSurfacePoint(toon)
                self.activity.b_pieHitsToon(hitToonId, timestamp, point)
                handled = True

        # Ignore other collision events if this collision was handled.
        if handled:
            eventName = self.toonPieEventNames.get(colEntry.getFromNodePath())
            if eventName is not None:
                self.ignore(eventName)
                del self.toonPieEventNames[colEntry.getFromNodePath()]

    def pieHitsCog(self, timestamp, cogNum, pos, direction, part):
        """A toon hit the suit, make the suit do something."""
        cog = self.cogManager.cogs[cogNum]
        cog.respondToPieHit(timestamp, pos, part, direction)

    def pieHitsToon(self, toonId, timestamp, pos):
        player = self.players.get(toonId)

        if player is not None:
            player.respondToPieHit(timestamp, pos)

    def setCogDistances(self, distances):
        self.cogManager.updateDistances(distances)

    def showCogs(self):
        for cog in self.cogManager.cogs:
            cog.request("Static")

    def hideCogs(self):
        for cog in self.cogManager.cogs:
            cog.request("Down")

    def showResults(self, resultsText, winner, totals):
        if self.player is None:
            return

        base.localAvatar.showName()

        self.resultsIval = Sequence(
            Wait(0.1),
            Func(self.activity.setStatus, TTLocalizer.PartyCogTimeUp),
            Func(self.activity.showStatus),
            Wait(2.0),
            Func(self.activity.hideStatus),
            Wait(0.5),
            Func(self.player.lookAtArena),
            Func(self.showTeamFlags,
                 self.activity.getTeam(base.localAvatar.doId)),
            Wait(1.0),
            Func(self.showArrow, 0),
            Wait(1.3),
            Func(self.showArrow, 1),
            Wait(1.3),
            Func(self.showArrow, 2),
            Wait(1.3),
            Func(self.showTotals, totals),
            Wait(1.0),
            Func(self.showWinner, resultsText, winner),
            Func(self._cleanupResultsIval),
            name="PartyCog-conclusionSequence")

        # Cancel the rewards ival if the jellybean screen pops up. If this happens it means the client
        # is lagging; the rewards screen tears down the GUI, which this ival uses.
        self.accept('DistributedPartyActivity-showJellybeanReward',
                    self._cleanupResultsIval)

        self.resultsIval.start()

    def _cleanupResultsIval(self):
        if self.resultsIval:
            if self.resultsIval.isPlaying():
                self.resultsIval.pause()
            self.resultsIval = None
        self.ignore('DistributedPartyActivity-showJellybeanReward')

    def showTotals(self, totals):
        newtotals = (totals[1] - totals[0] +
                     (PartyGlobals.CogActivityArenaLength / 2.0) * 3,
                     totals[0] - totals[1] +
                     (PartyGlobals.CogActivityArenaLength / 2.0) * 3)

        self.winText[0][0].setText(TTLocalizer.PartyCogDistance % newtotals[0])
        self.winText[1][0].setText(TTLocalizer.PartyCogDistance % newtotals[1])

        for textPair in self.winText:
            textPair[1].unstash()

    def hideTotals(self):
        for textPair in self.winText:
            textPair[0].setText("")
            textPair[1].stash()

    def showWinner(self, text, winner):
        self.winStatus[0].setText(text)
        self.winStatus[0].setTextColor(self.teamColors[winner])
        self.winStatus[1].unstash()

    def hideWinner(self):
        self.winStatus[0].setText("")
        self.winStatus[1].stash()

    def showArrow(self, arrowNum):
        arrows = self.arrows[arrowNum]
        cog = self.cogManager.cogs[arrowNum]
        points = [
            self.arena.find("**/cog%d_start_locator" % (arrowNum + 1)),
            self.arena.find("**/cog%d_end_locator" % (arrowNum + 1))
        ]
        Y = cog.root.getY()
        for point in points:
            point.setY(Y)

        for i in range(len(arrows)):
            arrow = arrows[i]
            arrow.draw(points[i].getPos(), cog.root.getPos(), animate=False)
            arrow.unstash()

        i = -1
        length = PartyGlobals.CogActivityArenaLength

        for (node, text) in self.distanceLabels[arrowNum]:
            current = bound(i, 0, 1)
            node.setPos(cog.root.getPos(self.root) + Point3(i * 4, 2, 4))

            dist = PartyCogUtils.getCogDistanceUnitsFromCenter(cog.currentT)

            dist = abs(dist - (i * length / 2))

            #if i == -1:
            #    dist = length/2 + -i*dist

            if dist > (length - dist):
                node.setScale(2.8)
            else:
                node.setScale(2.2)

            text.setText(TTLocalizer.PartyCogDistance % dist)

            if dist > 0:
                node.unstash()
            else:
                arrows[current].stash()
            i += 2

    def hideArrows(self):
        for pair in self.arrows:
            for arrow in pair:
                arrow.stash()

        for pair in self.distanceLabels:
            for (node, text) in pair:
                node.stash()

    def hideResults(self):
        self.hideArrows()
        self.hideTotals()
        self.hideWinner()
示例#9
0
class CogdoMazeLocalPlayer(CogdoMazePlayer):
    notify = directNotify.newCategory('CogdoMazeLocalPlayer')

    def __init__(self, id, toon, game, guiMgr):
        CogdoMazePlayer.__init__(self, id, toon)
        self.disableGagCollision()
        self.game = game
        self.maze = self.game.maze
        self._guiMgr = guiMgr
        self.cameraMgr = CogdoMazeCameraManager(self.toon, self.maze, camera,
                                                render)
        self._proximityRadius = self.maze.cellWidth * Globals.CameraRemoteToonRadius
        orthoDrive = OrthoDrive(
            Globals.ToonRunSpeed,
            maxFrameMove=self.maze.cellWidth / 2,
            customCollisionCallback=self.maze.doOrthoCollisions,
            wantSound=True)
        self.orthoWalk = OrthoWalk(orthoDrive)
        self._audioMgr = base.cogdoGameAudioMgr
        self._getMemoSfx = self._audioMgr.createSfx('getMemo',
                                                    source=self.toon)
        self._waterCoolerFillSfx = self._audioMgr.createSfx('waterCoolerFill',
                                                            source=self.toon)
        self._hitByDropSfx = self._audioMgr.createSfx('toonHitByDrop',
                                                      source=self.toon)
        self._winSfx = self._audioMgr.createSfx('win')
        self._loseSfx = self._audioMgr.createSfx('lose')
        self.enabled = False
        self.pickupCount = 0
        self.numEntered = 0
        self.throwPending = False
        self.coolDownAfterHitInterval = Sequence(
            Wait(Globals.HitCooldownTime),
            Func(self.setInvulnerable, False),
            name='coolDownAfterHitInterval-%i' % self.toon.doId)
        self.invulnerable = False
        self.gagHandler = CollisionHandlerEvent()
        self.gagHandler.addInPattern('%fn-into-%in')
        self.exited = False
        self.hints = {
            'find': False,
            'throw': False,
            'squashed': False,
            'boss': False,
            'minion': False
        }
        self.accept('control', self.controlKeyPressed)

    def destroy(self):
        self.toon.showName()
        self.ignoreAll()
        self.coolDownAfterHitInterval.clearToInitial()
        del self.coolDownAfterHitInterval
        del self._getMemoSfx
        del self._waterCoolerFillSfx
        del self._hitByDropSfx
        del self._winSfx
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        CogdoMazePlayer.destroy(self)

    def __initCollisions(self):
        collSphere = CollisionSphere(0, 0, 0, Globals.PlayerCollisionRadius)
        collSphere.setTangible(0)
        self.mazeCollisionName = Globals.LocalPlayerCollisionName
        collNode = CollisionNode(self.mazeCollisionName)
        collNode.addSolid(collSphere)
        collNodePath = self.toon.attachNewNode(collNode)
        collNodePath.hide()
        handler = CollisionHandlerEvent()
        handler.addInPattern('%fn-into-%in')
        base.cTrav.addCollider(collNodePath, handler)
        self.handler = handler
        self._collNodePath = collNodePath

    def clearCollisions(self):
        self.handler.clear()

    def __disableCollisions(self):
        self._collNodePath.removeNode()
        del self._collNodePath

    def _isNearPlayer(self, player):
        return self.toon.getDistance(player.toon) <= self._proximityRadius

    def update(self, dt):
        if self.getCurrentOrNextState() != 'Off':
            self._updateCamera(dt)

    def _updateCamera(self, dt):
        numPlayers = 0.0
        for player in self.game.players:
            if player != self and player.toon and self._isNearPlayer(player):
                numPlayers += 1

        d = clamp(
            Globals.CameraMinDistance + numPlayers /
            (CogdoGameConsts.MaxPlayers - 1) *
            (Globals.CameraMaxDistance - Globals.CameraMinDistance),
            Globals.CameraMinDistance, Globals.CameraMaxDistance)
        self.cameraMgr.setCameraTargetDistance(d)
        self.cameraMgr.update(dt)

    def enterOff(self):
        CogdoMazePlayer.enterOff(self)

    def exitOff(self):
        CogdoMazePlayer.exitOff(self)
        self.toon.hideName()

    def enterReady(self):
        CogdoMazePlayer.enterReady(self)
        self.cameraMgr.enable()

    def exitReady(self):
        CogdoMazePlayer.enterReady(self)

    def enterNormal(self):
        CogdoMazePlayer.enterNormal(self)
        self.orthoWalk.start()

    def exitNormal(self):
        CogdoMazePlayer.exitNormal(self)
        self.orthoWalk.stop()

    def enterHit(self, elapsedTime=0.0):
        CogdoMazePlayer.enterHit(self, elapsedTime)
        self.setInvulnerable(True)

    def exitHit(self):
        CogdoMazePlayer.exitHit(self)
        self.coolDownAfterHitInterval.clearToInitial()
        self.coolDownAfterHitInterval.start()

    def enterDone(self):
        CogdoMazePlayer.enterDone(self)
        self._guiMgr.hideQuestArrow()
        self.ignore('control')
        self._guiMgr.setMessage('')
        if self.exited == False:
            self.lostMemos()

    def exitDone(self):
        CogdoMazePlayer.exitDone(self)

    def hitByDrop(self):
        if self.equippedGag is not None and not self.hints['squashed']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeSquashHint,
                                             Globals.HintTimeout)
            self.hints['squashed'] = True
        self._hitByDropSfx.play()
        CogdoMazePlayer.hitByDrop(self)
        return

    def equipGag(self):
        CogdoMazePlayer.equipGag(self)
        self._waterCoolerFillSfx.play()
        messenger.send(Globals.WaterCoolerHideEventName, [])
        if not self.hints['throw']:
            self._guiMgr.setMessage(TTLocalizer.CogdoMazeThrowHint)
            self.hints['throw'] = True

    def hitSuit(self, suitType):
        if suitType == Globals.SuitTypes.Boss and not self.hints['boss']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeBossHint,
                                             Globals.HintTimeout)
            self.hints['boss'] = True
        if suitType != Globals.SuitTypes.Boss and not self.hints['minion']:
            self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeMinionHint,
                                             Globals.HintTimeout)
            self.hints['minion'] = True

    def createThrowGag(self, gag):
        throwGag = CogdoMazePlayer.createThrowGag(self, gag)
        collSphere = CollisionSphere(0, 0, 0, 0.5)
        collSphere.setTangible(0)
        name = Globals.GagCollisionName
        collNode = CollisionNode(name)
        collNode.setFromCollideMask(ToontownGlobals.PieBitmask)
        collNode.addSolid(collSphere)
        colNp = throwGag.attachNewNode(collNode)
        base.cTrav.addCollider(colNp, self.gagHandler)
        return throwGag

    def showToonThrowingGag(self, heading, pos):
        self._guiMgr.clearMessage()
        return CogdoMazePlayer.showToonThrowingGag(self, heading, pos)

    def removeGag(self):
        if self.equippedGag is None:
            return
        CogdoMazePlayer.removeGag(self)
        self.throwPending = False
        messenger.send(Globals.WaterCoolerShowEventName, [])
        return

    def controlKeyPressed(self):
        if self.game.finished or self.throwPending or self.getCurrentOrNextState(
        ) == 'Hit' or self.equippedGag == None:
            return
        self.throwPending = True
        heading = self.toon.getH()
        pos = self.toon.getPos()
        self.game.requestUseGag(pos.getX(), pos.getY(), heading)
        return

    def completeThrow(self):
        self.clearCollisions()
        CogdoMazePlayer.completeThrow(self)

    def shakeCamera(self, strength):
        self.cameraMgr.shake(strength)

    def getCameraShake(self):
        return self.cameraMgr.shakeStrength

    def setInvulnerable(self, bool):
        self.invulnerable = bool

    def handleGameStart(self):
        self.numEntered = len(self.game.players)
        self.__initCollisions()
        self._guiMgr.startGame(TTLocalizer.CogdoMazeFindHint)
        self.hints['find'] = True
        self.notify.info(
            'toonId:%d laff:%d/%d  %d player(s) started maze game' %
            (self.toon.doId, self.toon.hp, self.toon.maxHp,
             len(self.game.players)))

    def handleGameExit(self):
        self.cameraMgr.disable()
        self.__disableCollisions()

    def handlePickUp(self, toonId):
        if toonId == self.toon.doId:
            self.pickupCount += 1
            self._guiMgr.setPickupCount(self.pickupCount)
            if self.pickupCount == 1:
                self._guiMgr.showPickupCounter()
            self._getMemoSfx.play()

    def handleOpenDoor(self, door):
        self._guiMgr.setMessage(TTLocalizer.CogdoMazeGameDoorOpens)
        self._guiMgr.showQuestArrow(self.toon, door,
                                    Point3(0, 0,
                                           self.toon.getHeight() + 2))

    def handleTimeAlert(self):
        self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeGameTimeAlert)

    def handleToonRevealsDoor(self, toonId, door):
        if toonId == self.toon.doId:
            self._guiMgr.setMessageTemporary(
                TTLocalizer.CogdoMazeGameLocalToonFoundExit)

    def handleToonEntersDoor(self, toonId, door):
        self.exited = True
        message = ''
        if door.getPlayerCount() < len(self.game.players):
            message = TTLocalizer.CogdoMazeGameWaitingForToons
        if toonId == self.toon.doId:
            self._guiMgr.setMessage(message)
            self._winSfx.play()
            self._audioMgr.stopMusic()
        self.notify.info(
            'toonId:%d laff:%d/%d  %d player(s) succeeded in maze game. Going to the executive suit building.'
            % (toonId, self.toon.hp, self.toon.maxHp, len(self.game.players)))
        if self.numEntered > len(self.game.players):
            self.notify.info('%d player(s) failed in maze game' %
                             (self.numEntered - len(self.game.players)))

    def lostMemos(self):
        self.pickupCount = 0
        self._guiMgr.setMessageTemporary(TTLocalizer.CogdoMazeGameTimeOut)
        self._guiMgr.setPickupCount(self.pickupCount)
        self.notify.info(
            'toonId:%d laff:%d/%d  %d player(s) failed in maze game' %
            (self.toon.doId, self.toon.hp, self.toon.maxHp,
             len(self.game.players)))
示例#10
0
class CogdoFlyingCollisions(GravityWalker):
    wantFloorSphere = 0

    def __init__(self):
        GravityWalker.__init__(self, gravity=0.0)

    def initializeCollisions(self,
                             collisionTraverser,
                             avatarNodePath,
                             avatarRadius=1.4,
                             floorOffset=1.0,
                             reach=1.0):
        self.cHeadSphereNodePath = None
        self.cFloorEventSphereNodePath = None
        self.setupHeadSphere(avatarNodePath)
        self.setupFloorEventSphere(avatarNodePath,
                                   ToontownGlobals.FloorEventBitmask,
                                   avatarRadius)
        GravityWalker.initializeCollisions(self, collisionTraverser,
                                           avatarNodePath, avatarRadius,
                                           floorOffset, reach)
        return

    def setupWallSphere(self, bitmask, avatarRadius):
        self.avatarRadius = avatarRadius
        cSphere = CollisionSphere(0.0, 0.0, self.avatarRadius + 0.75,
                                  self.avatarRadius)
        cSphereNode = CollisionNode('Flyer.cWallSphereNode')
        cSphereNode.addSolid(cSphere)
        cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode)
        cSphereNode.setFromCollideMask(bitmask)
        cSphereNode.setIntoCollideMask(BitMask32.allOff())
        if ConfigVariableBool('want-fluid-pusher', 0).getValue():
            self.pusher = CollisionHandlerFluidPusher()
        else:
            self.pusher = CollisionHandlerPusher()
        self.pusher.addCollider(cSphereNodePath, self.avatarNodePath)
        self.cWallSphereNodePath = cSphereNodePath

    def setupEventSphere(self, bitmask, avatarRadius):
        self.avatarRadius = avatarRadius
        cSphere = CollisionSphere(0.0, 0.0, self.avatarRadius + 0.75,
                                  self.avatarRadius * 1.04)
        cSphere.setTangible(0)
        cSphereNode = CollisionNode('Flyer.cEventSphereNode')
        cSphereNode.addSolid(cSphere)
        cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode)
        cSphereNode.setFromCollideMask(bitmask)
        cSphereNode.setIntoCollideMask(BitMask32.allOff())
        self.event = CollisionHandlerEvent()
        self.event.addInPattern('enter%in')
        self.event.addOutPattern('exit%in')
        self.cEventSphereNodePath = cSphereNodePath

    def setupRay(self, bitmask, floorOffset, reach):
        cRay = CollisionRay(0.0, 0.0, 3.0, 0.0, 0.0, -1.0)
        cRayNode = CollisionNode('Flyer.cRayNode')
        cRayNode.addSolid(cRay)
        self.cRayNodePath = self.avatarNodePath.attachNewNode(cRayNode)
        cRayNode.setFromCollideMask(bitmask)
        cRayNode.setIntoCollideMask(BitMask32.allOff())
        self.lifter = CollisionHandlerGravity()
        self.lifter.setLegacyMode(self._legacyLifter)
        self.lifter.setGravity(self.getGravity(0))
        self.lifter.addInPattern('%fn-enter-%in')
        self.lifter.addAgainPattern('%fn-again-%in')
        self.lifter.addOutPattern('%fn-exit-%in')
        self.lifter.setOffset(floorOffset)
        self.lifter.setReach(reach)
        self.lifter.addCollider(self.cRayNodePath, self.avatarNodePath)

    def setupHeadSphere(self, avatarNodePath):
        collSphere = CollisionSphere(0, 0, 0, 1)
        collSphere.setTangible(1)
        collNode = CollisionNode('Flyer.cHeadCollSphere')
        collNode.setFromCollideMask(ToontownGlobals.CeilingBitmask)
        collNode.setIntoCollideMask(BitMask32.allOff())
        collNode.addSolid(collSphere)
        self.cHeadSphereNodePath = avatarNodePath.attachNewNode(collNode)
        self.cHeadSphereNodePath.setZ(base.localAvatar.getHeight() + 1.0)
        self.headCollisionEvent = CollisionHandlerEvent()
        self.headCollisionEvent.addInPattern('%fn-enter-%in')
        self.headCollisionEvent.addOutPattern('%fn-exit-%in')
        base.cTrav.addCollider(self.cHeadSphereNodePath,
                               self.headCollisionEvent)

    def setupFloorEventSphere(self, avatarNodePath, bitmask, avatarRadius):
        cSphere = CollisionSphere(0.0, 0.0, 0.0, 0.75)
        cSphereNode = CollisionNode('Flyer.cFloorEventSphere')
        cSphereNode.addSolid(cSphere)
        cSphereNodePath = avatarNodePath.attachNewNode(cSphereNode)
        cSphereNode.setFromCollideMask(bitmask)
        cSphereNode.setIntoCollideMask(BitMask32.allOff())
        self.floorCollisionEvent = CollisionHandlerEvent()
        self.floorCollisionEvent.addInPattern('%fn-enter-%in')
        self.floorCollisionEvent.addAgainPattern('%fn-again-%in')
        self.floorCollisionEvent.addOutPattern('%fn-exit-%in')
        base.cTrav.addCollider(cSphereNodePath, self.floorCollisionEvent)
        self.cFloorEventSphereNodePath = cSphereNodePath

    def deleteCollisions(self):
        GravityWalker.deleteCollisions(self)
        if self.cHeadSphereNodePath != None:
            base.cTrav.removeCollider(self.cHeadSphereNodePath)
            self.cHeadSphereNodePath.detachNode()
            self.cHeadSphereNodePath = None
            self.headCollisionsEvent = None
        if self.cFloorEventSphereNodePath != None:
            base.cTrav.removeCollider(self.cFloorEventSphereNodePath)
            self.cFloorEventSphereNodePath.detachNode()
            self.cFloorEventSphereNodePath = None
            self.floorCollisionEvent = None
        self.cRayNodePath.detachNode()
        del self.cRayNodePath
        self.cEventSphereNodePath.detachNode()
        del self.cEventSphereNodePath
        return

    def setCollisionsActive(self, active=1):
        if self.collisionsActive != active:
            if self.cHeadSphereNodePath != None:
                base.cTrav.removeCollider(self.cHeadSphereNodePath)
                if active:
                    base.cTrav.addCollider(self.cHeadSphereNodePath,
                                           self.headCollisionEvent)
            if self.cFloorEventSphereNodePath != None:
                base.cTrav.removeCollider(self.cFloorEventSphereNodePath)
                if active:
                    base.cTrav.addCollider(self.cFloorEventSphereNodePath,
                                           self.floorCollisionEvent)
        GravityWalker.setCollisionsActive(self, active)
        return

    def enableAvatarControls(self):
        pass

    def disableAvatarControls(self):
        pass

    def handleAvatarControls(self, task):
        pass
示例#11
0
class DistributedGolfSpot(DistributedObject.DistributedObject, FSM.FSM):
    """ This is one of four golf spots to appear in the corner of the CEO banquet
    room.  """

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

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

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

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

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

        self.golfSpotAdviceLabel = None

        # This number increments each time we change direction on the
        # crane controls.  It's used to update the animation
        # appropriately.
        self.changeSeq = 0
        self.lastChangeSeq = 0

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

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

    def setBossCogId(self, bossCogId):
        """Handle receiving the CEO doId from the server."""
        self.bossCogId = bossCogId

        # This would be risky if we had toons entering the zone during
        # a battle--but since all the toons are always there from the
        # beginning, we can be confident that the BossCog has already
        # been generated by the time we receive the generate for its
        # associated battles.
        self.boss = base.cr.doId2do[bossCogId]
        self.boss.setGolfSpot(self, self.index)

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

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

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

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

        self.boss = None

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

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

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

        # Make a trigger sphere so we can detect when the local avatar
        # runs up to the controls.  We bury the sphere mostly under
        # the floor to minimize accidental collisions.
        cs = CollisionSphere(0, 0, 0, 1)
        cs.setTangible(0)
        cn = CollisionNode(self.triggerName)
        cn.addSolid(cs)
        cn.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.trigger = self.root.attachNewNode(cn)
        self.trigger.stash()

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

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

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

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

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

        taskMgr.remove(self.triggerName)

        assert self.notify.debugStateCall(self)

        self.boss = None

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

    ### FSM States ###

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

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

    def enterFree(self):
        """Handle entering the free state."""
        assert self.notify.debugStateCall(self)
        if self.fadeTrack:
            self.fadeTrack.finish()
            self.fadeTrack = None

        # Wait a few seconds before neutralizing the scale; maybe the
        # same avatar wants to come right back (after his 5-second
        # timeout).
        self.restoreScaleTrack = Sequence(Wait(6),
                                          self.getRestoreScaleInterval(),
                                          name="restoreScaleTrack")
        self.restoreScaleTrack.start()

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

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

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

        self.avId = 0

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

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

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

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

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

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

            self.boss.toCraneMode()

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

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

            localAvatar.sendCurrentPosition()

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

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

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

        self.grabTrack.start()

        pass

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

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

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

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

        self.releaseTrack = self.makeToonReleaseInterval(self.toon)
        self.stopPosHprBroadcast()
        #self.stopShadow()
        self.stopSmooth()
        if self.avId == localAvatar.doId:
            # The local toon is no longer in control of the crane.

            self.__disableControlInterface()
            #self.__deactivatePhysics()
            #self.tube.unstash()
            if not self.goingToReward:
                camera.reparentTo(base.localAvatar)
                camera.setPos(base.localAvatar.cameraPositions[0][0])
                camera.setHpr(0, 0, 0)

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

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

        self.fadeTrack.start()

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

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

    def getRestoreScaleInterval(self):
        # This undoes the effect of accomodateToon(), to restore the
        # controls' scale to neutral position.  Unlike
        # accomodateToon(), it has no side effects; you must play (or
        # immediately finish) the interval to restore the scale.
        return Sequence()
        #lerpTime = 1
        #return Parallel(
        #    self.controlModel.scaleInterval(lerpTime, 1, blendType = 'easeInOut'),
        #    self.cc.posInterval(lerpTime, Point3(0, 0, 0), blendType = 'easeInOut'),
        #    self.bottom.posInterval(lerpTime, self.bottomPos, blendType = 'easeInOut'),
        #    self.stickHinge.quatInterval(lerpTime, self.neutralStickHinge, blendType = 'easeInOut'),
        #    )

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

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

    def makeToonGrabInterval(self, toon):
        # Generates an interval showing the crane controls scaling to
        # match the toon and the toon simultaneously reaching to grab
        # the controls.  Thenceforth, the toon will animate with the
        # controls.
        origPos = toon.getPos(self.root)
        origHpr = toon.getHpr(self.root)
        a = self.accomodateToon(toon)
        newPos = toon.getPos()
        newHpr = toon.getHpr()
        origHpr.setX(PythonUtil.fitSrcAngle2Dest(origHpr[0], newHpr[0]))
        self.notify.debug('toon.setPosHpr %s %s' % (origPos, origHpr))
        toon.setPosHpr(origPos, origHpr)

        walkTime = 0.2
        reach = Sequence()  #ActorInterval(toon, 'GolfPuttLoop')
        if reach.getDuration() < walkTime:
            reach = Sequence(
                ActorInterval(toon,
                              'walk',
                              loop=1,
                              duration=walkTime - reach.getDuration()), reach)

        i = Sequence(
            Parallel(toon.posInterval(walkTime, newPos, origPos),
                     toon.hprInterval(walkTime, newHpr, origHpr), reach),
            #Func(self.startWatchJoystick, toon)
            Func(toon.stopLookAround),
        )
        if toon == base.localAvatar:
            i.append(Func(self.switchToAnimState, 'GolfPuttLoop'))
        i.append(Func(self.startAdjustClubTask))
        i = Parallel(i, a)

        return i

    def accomodateToon(self, toon):
        # This method has two effects:

        # (1) It computes and returns an interval to scale and slide
        # the crane controls to suit the indicated toon.

        # (2) As a side effect, when it returns, the crane controls are
        # *already* scaled and slid to accomodate the toon, and the toon
        # has been positioned in place to operate the controls.

        # Thus, you can use it either by calling it and playing the
        # interval that it returns to get a smooth lerp, or simply by
        # calling it and ignoring the return value, to jump to
        # position.
        toon.wrtReparentTo(self.root)
        toon.setPos(self.toonGolfOffsetPos)
        toon.setHpr(self.toonGolfOffsetHpr)
        return Sequence()

    def switchToAnimState(self, animStateName, forced=False):
        """Switch the toon to another anim state if not in it already."""
        # temp since we only have anims for male medium torso medium legs
        #dna = base.localAvatar.getStyle()
        #if not( dna.gender == 'm' and (dna.torso =='ms' or dna.torso =='ls')\
        #        and (dna.legs=='m' or dna.legs=='l')):
        #    return
        curAnimState = base.localAvatar.animFSM.getCurrentState()
        #self.notify.debug('curAnimState=%s' % curAnimState)
        curAnimStateName = ''
        if curAnimState:
            curAnimStateName = curAnimState.getName()
        if curAnimStateName != animStateName or forced:
            base.localAvatar.b_setAnimState(animStateName)

    def __enableControlInterface(self):
        """Enable the control interface."""
        gui = loader.loadModel("phase_3.5/models/gui/avatar_panel_gui")

        self.closeButton = DirectButton(
            image=(
                gui.find("**/CloseBtn_UP"),
                gui.find("**/CloseBtn_DN"),
                gui.find("**/CloseBtn_Rllvr"),
                gui.find("**/CloseBtn_UP"),
            ),
            relief=None,
            scale=2,
            text=TTLocalizer.BossbotGolfSpotLeave,
            text_scale=0.04,
            text_pos=(0, -0.07),
            text_fg=VBase4(1, 1, 1, 1),
            pos=(1.05, 0, -0.82),
            command=self.__exitGolfSpot,
        )

        self.accept('escape', self.__exitGolfSpot)

        self.accept('control', self.__controlPressed)
        self.accept('control-up', self.__controlReleased)
        self.accept('InputState-forward', self.__upArrow)
        self.accept('InputState-reverse', self.__downArrow)
        self.accept('InputState-turnLeft', self.__leftArrow)
        self.accept('InputState-turnRight', self.__rightArrow)

        taskMgr.add(self.__watchControls, 'watchGolfSpotControls')

        # In case they don't figure it out, hit them over the head
        # with it after a few seconds.
        taskMgr.doMethodLater(5, self.__displayGolfSpotAdvice,
                              self.golfSpotAdviceName)
        #taskMgr.doMethodLater(10, self.__displayMagnetAdvice,
        #                      self.magnetAdviceName)

        # Up in the sky, it's hard to read what people are saying.
        #NametagGlobals.setOnscreenChatForced(1)

        self.arrowVert = 0
        self.arrowHorz = 0
        if self.powerBar:
            self.powerBar.show()

    def __disableControlInterface(self):
        """Disable the control interface."""
        #self.__turnOffMagnet()

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

        self.__cleanupGolfSpotAdvice()
        #self.__cleanupMagnetAdvice()

        self.ignore('escape')
        self.ignore('control')
        self.ignore('control-up')
        self.ignore('InputState-forward')
        self.ignore('InputState-reverse')
        self.ignore('InputState-turnLeft')
        self.ignore('InputState-turnRight')

        self.arrowVert = 0
        self.arrowHorz = 0

        #NametagGlobals.setOnscreenChatForced(0)

        taskMgr.remove('watchGolfSpotControls')
        if self.powerBar:
            self.powerBar.hide()
        else:
            self.notify.debug('self.powerBar is none')
        #self.__setMoveSound(None)

    def setupPowerBar(self):
        """Create the power bar for the water golfSpot."""
        self.powerBar = DirectWaitBar(
            pos=(0.0, 0, -0.94),
            relief=DGG.SUNKEN,
            frameSize=(-2.0, 2.0, -0.2, 0.2),
            borderWidth=(0.02, 0.02),
            scale=0.25,
            range=100,
            sortOrder=50,
            frameColor=(0.5, 0.5, 0.5, 0.5),
            barColor=(1.0, 0.0, 0.0, 1.0),
            text="",
            text_scale=0.26,
            text_fg=(1, 1, 1, 1),
            text_align=TextNode.ACenter,
            text_pos=(0, -0.05),
        )

        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar.hide()

    def resetPowerBar(self):
        """Bring the power and power bar to zero."""
        self.power = 0
        self.powerBar['value'] = self.power
        self.powerBar['text'] = ''

    def __displayGolfSpotAdvice(self, task):
        """Display golfSpot advice on the screen."""
        if self.golfSpotAdviceLabel == None:
            self.golfSpotAdviceLabel = DirectLabel(
                text=TTLocalizer.BossbotGolfSpotAdvice,
                text_fg=VBase4(1, 1, 1, 1),
                text_align=TextNode.ACenter,
                relief=None,
                pos=(0, 0, 0.69),
                scale=0.1)

    def __cleanupGolfSpotAdvice(self):
        """Remove golfSpot advice from the screen."""
        if self.golfSpotAdviceLabel:
            self.golfSpotAdviceLabel.destroy()
            self.golfSpotAdviceLabel = None
        taskMgr.remove(self.golfSpotAdviceName)

    def showExiting(self):
        """Indicate that we've sent an exiting message to AI."""
        if self.closeButton:
            self.closeButton.destroy()
            self.closeButton = DirectLabel(
                relief=None,
                text=TTLocalizer.BossbotGolfSpotLeaving,
                pos=(1.05, 0, -0.88),
                text_pos=(0, 0),
                text_scale=0.06,
                text_fg=VBase4(1, 1, 1, 1),
            )

        self.__cleanupGolfSpotAdvice()

    def __exitGolfSpot(self):
        """Handle the toon clicking on exit button."""

        self.d_requestFree(False)

    def __controlPressed(self):
        """Handle control key being pressed."""
        if self.controlKeyAllowed:
            self.__beginFireBall()
        pass
        #self.__cleanupMagnetAdvice()
        #self.__turnOnMagnet()

    def __controlReleased(self):
        """Handle control key being released."""
        if self.controlKeyAllowed:
            self.__endFireBall()

    def __upArrow(self, pressed):
        """Handle up arrow key being pressed."""
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowVert = 1
        elif self.arrowVert > 0:
            self.arrowVert = 0

    def __downArrow(self, pressed):
        """Handle down arrow key being pressed."""
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowVert = -1
        elif self.arrowVert < 0:
            self.arrowVert = 0

    def __rightArrow(self, pressed):
        """Handle right arrow key being pressed."""
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowHorz = 1
            self.switchToAnimState('GolfRotateLeft')
        elif self.arrowHorz > 0:
            self.arrowHorz = 0
            self.switchToAnimState('GolfPuttLoop')

    def __leftArrow(self, pressed):
        """Handle left arrow key being pressed."""
        self.__incrementChangeSeq()
        self.__cleanupGolfSpotAdvice()
        if pressed:
            self.arrowHorz = -1
            self.switchToAnimState('GolfRotateRight')
        elif self.arrowHorz < 0:
            self.arrowHorz = 0
            self.switchToAnimState('GolfPuttLoop')

    def __watchControls(self, task):
        """Check the arrow key press and call move golfSpot if needed."""
        if self.arrowHorz:
            self.__moveGolfSpot(self.arrowHorz)
        else:
            pass
            #self.__setMoveSound(None)
        return Task.cont

    def __moveGolfSpot(self, xd):
        """Rotate the golfSpot by the given xdelta."""
        dt = globalClock.getDt()

        h = self.root.getH() - xd * self.rotateSpeed * dt
        h %= 360
        limitH = h
        self.root.setH(limitH)
        #self.__setMoveSound(self.craneMoveSfx)

    def __incrementChangeSeq(self):
        """Increment our change counter."""
        self.changeSeq = (self.changeSeq + 1) & 0xff

    def __beginFireBall(self):
        """Handle player pressing control and starting the power meter."""
        # The control key was pressed.
        if self.aimStart != None:
            # This is probably just key-repeat.
            return
        if not self._state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        time = globalClock.getFrameTime()
        self.aimStart = time
        messenger.send('wakeup')
        taskMgr.add(self.__updateBallPower, self.ballPowerTaskName)

    def __endFireBall(self):
        """Handle player releasing control and shooting the ball."""
        # The control key was released.  Fire the ball.
        if self.aimStart == None:
            return
        if not self._state == 'Controlled':
            return
        if not self.avId == localAvatar.doId:
            return
        #if not self.power:
        #    return
        taskMgr.remove(self.ballPowerTaskName)
        self.disableControlKey()
        messenger.send('wakeup')
        self.aimStart = None
        power = self.power
        angle = self.root.getH()
        self.notify.debug('incrementing self.__flyBallSequenceNum')
        self.__flyBallSequenceNum = (self.__flyBallSequenceNum + 1) % 0xff

        self.sendSwingInfo(power, angle, self.__flyBallSequenceNum)
        self.setSwingInfo(power, angle, self.__flyBallSequenceNum)

        self.resetPowerBar()
        pass
        #self.__turnOffMagnet()

    def __updateBallPower(self, task):
        """Change the value of the power meter."""
        if not self.powerBar:
            print("### no power bar!!!")
            return task.done

        newPower = self.__getBallPower(globalClock.getFrameTime())
        self.power = newPower
        self.powerBar['value'] = newPower
        return task.cont

    def __getBallPower(self, time):
        """Return a value between 0 and 100 to indicate golf power."""
        elapsed = max(time - self.aimStart, 0.0)
        t = elapsed / self.golfPowerSpeed
        t = math.pow(t, self.golfPowerExponent)
        power = int(t * 100) % 200
        if power > 100:
            power = 200 - power
        return power

    def stopPosHprBroadcast(self):
        """Stop the pitcher rotation broadcast task."""
        taskName = self.posHprBroadcastName
        taskMgr.remove(taskName)

    def startPosHprBroadcast(self):
        """Start the golfSpot rotation broadcast task."""
        taskName = self.posHprBroadcastName

        # Broadcast our initial position
        self.b_clearSmoothing()
        self.d_sendGolfSpotPos()

        # remove any old tasks
        taskMgr.remove(taskName)
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast,
                              taskName)

    def __posHprBroadcast(self, task):
        """Periodically broadcast the golfSpot rotation."""
        self.d_sendGolfSpotPos()
        taskName = self.posHprBroadcastName
        taskMgr.doMethodLater(self.__broadcastPeriod, self.__posHprBroadcast,
                              taskName)
        return Task.done

    def d_sendGolfSpotPos(self):
        """Send the golfSpot rotation to the other clients."""
        timestamp = globalClockDelta.getFrameNetworkTime()

        self.sendUpdate(
            'setGolfSpotPos',
            [self.changeSeq, self.root.getH(), timestamp])

    def setGolfSpotPos(self, changeSeq, h, timestamp):
        """Handle another client sending an update on the golfSpot rotation."""
        #assert self.notify.debugStateCall(self)
        self.changeSeq = changeSeq
        if self.smoothStarted:
            now = globalClock.getFrameTime()
            local = globalClockDelta.networkToLocalTime(timestamp, now)

            #self.golfSpotSmoother.setY(y)
            self.golfSpotSmoother.setH(h)
            self.golfSpotSmoother.setTimestamp(local)
            self.golfSpotSmoother.markPosition()
        else:
            #self.crane.setY(y)
            self.root.setH(h)

    ### Handle smoothing of distributed updates.  This is similar to
    ### code in DistributedSmoothNode, but streamlined for our
    ### purposes.

    def b_clearSmoothing(self):
        """Tell us and other clients to clear smoothing."""
        self.d_clearSmoothing()
        self.clearSmoothing()

    def d_clearSmoothing(self):
        """Tell other clients to clear smoothing."""
        self.sendUpdate("clearSmoothing", [0])

    def clearSmoothing(self, bogus=None):
        """Invalidate old position reports."""
        # Call this to invalidate all the old position reports
        # (e.g. just before popping to a new position).
        self.golfSpotSmoother.clearPositions(1)

    def doSmoothTask(self, task):
        """
        This function updates the position of the node to its computed
        smoothed position.  This may be overridden by a derived class
        to specialize the behavior.
        """
        self.golfSpotSmoother.computeAndApplySmoothHpr(self.root)

        return Task.cont

    def startSmooth(self):
        """
        This function starts the task that ensures the node is
        positioned correctly every frame.  However, while the task is
        running, you won't be able to lerp the node or directly
        position it.
        """
        if not self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.reloadPosition()
            taskMgr.add(self.doSmoothTask, taskName)
            self.smoothStarted = 1

    def stopSmooth(self):
        """
        This function stops the task spawned by startSmooth(), and
        allows show code to move the node around directly.
        """
        if self.smoothStarted:
            taskName = self.smoothName
            taskMgr.remove(taskName)
            self.forceToTruePosition()
            self.smoothStarted = 0

    def makeToonReleaseInterval(self, toon):
        """Return an interval of the toon jumping to pitcher position."""
        def getSlideToPos(toon=toon):
            return render.getRelativePoint(toon, Point3(0, -5, 0))

        if self.gotHitByBoss:
            grabIval = Sequence(Func(self.detachClub),
                                name='makeToonReleaseInterval-gotHitByBoss')
            if not toon.isEmpty():
                toonIval = Sequence(Func(toon.wrtReparentTo, render),
                                    Parallel(
                                        ActorInterval(toon, 'slip-backward'),
                                        toon.posInterval(0.5,
                                                         getSlideToPos,
                                                         fluid=1)),
                                    name='makeToonReleaseInterval-toonIval')
                #Func(toon.loop, 'neutral'),
                grabIval.append(toonIval)

        else:
            grabIval = Sequence(Func(self.detachClub), )
            if not toon.isEmpty():
                toonIval = Sequence(
                    Parallel(
                        ActorInterval(toon,
                                      'walk',
                                      duration=1.0,
                                      playRate=-1.0),
                        LerpPosInterval(
                            toon,
                            duration=1.0,
                            pos=Point3(-10, 0, 0),
                        )),
                    Func(toon.wrtReparentTo, render),
                    #Func(toon.loop, 'neutral'),
                )
                grabIval.append(toonIval)

        if localAvatar.doId == toon.doId:
            if not self.goingToReward and toon.hp > 0:
                grabIval.append(Func(self.goToFinalBattle))
                grabIval.append(
                    Func(self.notify.debug, 'goingToFinalBattlemode'))
                grabIval.append(Func(self.safeBossToFinalBattleMode))

        return grabIval

    def safeBossToFinalBattleMode(self):
        """Call boss.toFinalBattleMode if self.boss is valid."""
        if self.boss:
            self.boss.toFinalBattleMode()

    def goToFinalBattle(self):
        """Go to final battle if we're in crane mode."""
        # This is a bit hacky.  Go back to finalBattle mode, but
        # only if we're still in crane mode.  (We might have been
        # zapped to 'ouch' mode by a hit.)
        if self.cr:
            place = self.cr.playGame.getPlace()
            if place and hasattr(place, 'fsm'):
                curState = place.fsm.getCurrentState().getName()
                if place.fsm.getCurrentState().getName() == 'crane':
                    place.setState('finalBattle')
                else:
                    self.notify.debug('NOT going to final battle, state=%s' %
                                      curState)

    def attachClub(self, avId, pointToBall=False):
        """Attach the club to the right hand."""
        club = self.club
        if club:
            av = base.cr.doId2do.get(avId)
            if av:
                av.useLOD(1000)
                lHand = av.getLeftHands()[0]
                club.setPos(0, 0, 0)
                club.reparentTo(lHand)
                # we have to account for small toons like the mouse
                netScale = club.getNetTransform().getScale()[1]
                counterActToonScale = lHand.find('**/counteractToonScale')
                if counterActToonScale.isEmpty():
                    counterActToonScale = lHand.attachNewNode(
                        'counteractToonScale')
                    counterActToonScale.setScale(1 / netScale)
                    self.notify.debug('creating counterActToonScale for %s' %
                                      av.getName())
                club.reparentTo(counterActToonScale)
                club.setX(-0.25 * netScale)
                if pointToBall:
                    club.lookAt(self.clubLookatSpot)
                # self.notify.debug('after lookat, hpr = %s' % club.getHpr())

    def detachClub(self):
        """Detach the club and store it someplace safe."""
        if not self.club.isEmpty():
            self.club.reparentTo(self.root)
            self.club.setZ(-20)
            self.club.setScale(1)

    def adjustClub(self):
        """Change the club so that the head is more or less behind the ball."""
        club = self.club
        if club:
            distance = club.getDistance(self.clubLookatSpot)
            # from maya the club has a length of 2.058,
            scaleFactor = distance / 2.058
            # self.notify.debug('scaleFactor  = %s' % scaleFactor)
            club.setScale(1, scaleFactor, 1)

    def startAdjustClubTask(self):
        """Start the task to automatically adjust the club so it looks right."""
        taskMgr.add(self.adjustClubTask, self.adjustClubTaskName)

    def stopAdjustClubTask(self):
        """Stop the task to automatically adjust the club so it looks right."""
        taskMgr.remove(self.adjustClubTaskName)

    def adjustClubTask(self, task):
        """Continuously adjust the club so the head is behind the ball."""
        self.attachClub(self.avId, True)
        self.adjustClub()
        return task.cont

    def enableControlKey(self):
        """Allow control key events to come in."""
        self.controlKeyAllowed = True

    def disableControlKey(self):
        """Disable control key events from coming in."""
        self.controlKeyAllowed = False

    def sendSwingInfo(self, power, angle, sequenceNum):
        """Tell the other clients we're firing."""
        self.sendUpdate('setSwingInfo', [power, angle, sequenceNum])

    def startBallPlayback(self, power, angle, sequenceNum):
        """Start the ball flying in the air."""
        assert self.notify.debugStateCall(self)
        # duplicate our current ball model
        flyBall = self.ballModel.copyTo(NodePath())
        flyBall.setScale(1.0)

        flyBallBubble = self.getFlyBallBubble().instanceTo(NodePath())
        flyBallBubble.reparentTo(flyBall)

        flyBall.setTag('pieSequence', str(sequenceNum))
        flyBall.setTag('throwerId', str(self.avId))

        # First, create a ProjectileInterval to compute the relative
        # velocity.

        t = power / 100.0

        # make the ball travel farther, the longer you press the bar
        t = 1.0 - t

        # Distance ranges from 300 - 100 ft, time ranges from 1.5 - 2 s.
        dist = 300 - 200 * t
        time = 1.5 + 0.5 * t
        proj = ProjectileInterval(
            None,
            startPos=Point3(0, 0, 0),
            endPos=Point3(0, dist, 0),
            duration=time,
        )
        relVel = proj.startVel

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

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

        pass

    def setSwingInfo(self, power, angle, sequenceNum):
        """Handle a toon swinging at the golf ball."""
        assert self.notify.debugStateCall(self)
        av = base.cr.doId2do.get(self.avId)
        self.swingInterval = Sequence()
        if av:
            self.stopAdjustClubTask()
            self.swingInterval = Sequence(
                ActorInterval(av,
                              'swing-putt',
                              startFrame=0,
                              endFrame=GolfGlobals.BALL_CONTACT_FRAME),
                Func(self.startBallPlayback, power, angle, sequenceNum),
                Func(self.ballModel.hide),
                ActorInterval(av,
                              'swing-putt',
                              startFrame=GolfGlobals.BALL_CONTACT_FRAME,
                              endFrame=24),
                Func(self.ballModel.setScale, 0.1),
                Func(self.ballModel.show),
                LerpScaleInterval(self.ballModel, 1.0, Point3(1, 1, 1)),
                Func(self.enableControlKey),
            )
            if av == localAvatar:
                self.swingInterval.append(
                    Func(self.switchToAnimState, 'GolfPuttLoop', True))

        self.swingInterval.start()

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

    def __flyBallHit(self, entry):
        """Handle the flying golf ball hitting something in the world."""
        #import pdb; pdb.set_trace()
        print(entry)
        pass

    def flyBallFinishedFlying(self, sequence):
        """Handle the flyball sequence finishing."""
        if sequence in self.flyBallTracks:
            del self.flyBallTracks[sequence]

    def __finishFlyBallTrack(self, sequence):
        """Force the flyball sequence to finish prematurely."""
        if sequence in self.flyBallTracks:
            flyBallTrack = self.flyBallTracks[sequence]
            del self.flyBallTracks[sequence]
            flyBallTrack.finish()

    def flyBallFinishedSplatting(self, sequence):
        """Handle the flyball splatt finishing."""
        if sequence in self.splatTracks:
            del self.splatTracks[sequence]

    def __flyBallHit(self, entry):
        """Handle the flyball colliding against something in the world."""
        if not entry.hasSurfacePoint() or not entry.hasInto():
            # Not a collision solid we understand.  Weird.
            return
        if not entry.getInto().isTangible():
            # Just a trigger polygon.  Ignore it.
            return

        sequence = int(entry.getFromNodePath().getNetTag('pieSequence'))
        self.__finishFlyBallTrack(sequence)

        if sequence in self.splatTracks:
            splatTrack = self.splatTracks[sequence]
            del self.splatTracks[sequence]
            splatTrack.finish()

        # The pie hit something solid.  Generate a distributed splat.

        # Check the thing we hit for a pieCode.  If it has one, it
        # gets passed along with the message.  This may mean something
        # different according to context (it may indicate, for
        # instance, the kind of target we hit).
        flyBallCode = 0
        flyBallCodeStr = entry.getIntoNodePath().getNetTag('pieCode')
        if flyBallCodeStr:
            flyBallCode = int(flyBallCodeStr)

        pos = entry.getSurfacePoint(render)
        timestamp32 = globalClockDelta.getFrameNetworkTime(bits=32)
        throwerId = int(entry.getFromNodePath().getNetTag('throwerId'))
        splat = self.getFlyBallSplatInterval(pos[0], pos[1], pos[2],
                                             flyBallCode, throwerId)

        splat = Sequence(splat, Func(self.flyBallFinishedSplatting, sequence))
        assert sequence not in self.splatTracks
        self.splatTracks[sequence] = splat
        splat.start()

        self.notify.debug(
            'doId=%d into=%s flyBallCode=%d, throwerId=%d' %
            (self.doId, entry.getIntoNodePath(), flyBallCode, throwerId))

        if flyBallCode ==  ToontownGlobals.PieCodeBossCog and \
           self.avId == localAvatar.doId and \
           self.lastHitSequenceNum != self.__flyBallSequenceNum:
            self.lastHitSequenceNum = self.__flyBallSequenceNum
            self.boss.d_ballHitBoss(1)
        elif flyBallCode == ToontownGlobals.PieCodeToon and \
             self.avId == localAvatar.doId and \
             self.lastHitSequenceNum != self.__flyBallSequenceNum:
            self.lastHitSequenceNum = self.__flyBallSequenceNum
            avatarDoId = entry.getIntoNodePath().getNetTag('avatarDoId')
            if avatarDoId == '':
                self.notify.warning("Toon %s has no avatarDoId tag." %
                                    (repr(entry.getIntoNodePath())))
                return
            doId = int(avatarDoId)
            if doId != localAvatar.doId:
                # turn this off since food comes out of the belt
                # self.boss.d_hitToon(doId)
                pass

    def getFlyBallSplatInterval(self, x, y, z, flyBallCode, throwerId):
        """Return an interval showing a flyBall hitting a target in the world."""
        from toontown.toonbase import ToontownBattleGlobals
        from toontown.battle import BattleProps

        splatName = 'dust'
        splat = BattleProps.globalPropPool.getProp(splatName)
        splat.setBillboardPointWorld(2)

        color = ToontownGlobals.PieCodeColors.get(flyBallCode)
        if color:
            splat.setColor(*color)
        if flyBallCode == ToontownGlobals.PieCodeBossCog:
            self.notify.debug('changing color to %s' % self.ballColor)
            splat.setColor(self.ballColor)

        sound = loader.loadSfx('phase_11/audio/sfx/LB_evidence_miss.mp3')
        vol = 1.0
        if flyBallCode == ToontownGlobals.PieCodeBossCog:
            sound = loader.loadSfx('phase_4/audio/sfx/Golf_Hit_Barrier_1.mp3')
        soundIval = SoundInterval(sound, node=splat, volume=vol)

        if flyBallCode == ToontownGlobals.PieCodeBossCog and \
           localAvatar.doId == throwerId:
            vol = 1.0
            soundIval = SoundInterval(sound, node=localAvatar, volume=vol)

        ival = Parallel(
            Func(splat.reparentTo, render),
            Func(splat.setPos, x, y, z),
            soundIval,
            Sequence(ActorInterval(splat, splatName), Func(splat.detachNode)),
        )
        return ival

    def setGoingToReward(self):
        """Flag that the boss battle is going to the reward state."""
        assert self.notify.debugStateCall(self)
        self.goingToReward = True

    def gotBossZapped(self):
        """Handle the local toon getting hit by a ranged attack."""
        self.showExiting()
        self.d_requestFree(True)