def getPlayerSprite(self, playerId, ignoreLocal=False):
        player = self.universe.getPlayer(playerId)
        if player is None:
            return None
        if playerId == self.localPlayerId and not ignoreLocal:
            p = self.localPlayerSprite
            if p.spriteTeam != p.player.team:
                self.localPlayerSprite = PlayerSprite(self.app, self, p.player)
                if p is self.gameViewer.viewManager.target:
                    self.gameViewer.viewManager.setTarget(
                        self.localPlayerSprite)
                p = self.localPlayerSprite
            return p

        try:
            p = self.playerSprites[playerId]
        except KeyError:
            self.playerSprites[player.id] = p = PlayerSprite(
                self.app, self, player)
            return p

        if p.spriteTeam != player.team:
            # Player has changed teams.
            self.playerSprites[player.id] = p = PlayerSprite(
                self.app, self, player)
        return p
Beispiel #2
0
    def drawLoading(self, screen):
        if self.loadingPlayer is None:
            player = Player(self.universe.universe, 'Loading...', None, '')
            player.motionState = 'ground'
            player.updateState('right', True)
            self.loadingPlayer = PlayerSprite(self.app,
                                              self.universe,
                                              player,
                                              timer=timeNow)

        if self.loadingScreen is None:
            self.loadingScreen = pygame.Surface(screen.get_size())
            self.loadingScreen.fill((255, 255, 255))

            font = self.app.screenManager.fonts.mainMenuFont
            colour = self.app.theme.colours.mainMenuColour
            text = font.render(self.app, 'Loading...', True, colour,
                               (255, 255, 255))
            r = text.get_rect()
            r.center = self.loadingScreen.get_rect().center
            self.loadingScreen.blit(text, r)

            self.loadingPlayer.rect.midbottom = r.midtop

        if random.random() < 0.03:
            self.loadingPlayer.unit.lookAt(random.random() * 2 * math.pi)

        screen.blit(self.loadingScreen, (0, 0))
        self.loadingPlayer.update()
        screen.fill((255, 255, 255), self.loadingPlayer.rect)
        screen.blit(self.loadingPlayer.image, self.loadingPlayer.rect)
 def detailLevelChanged(self):
     # Clear everything long-lived
     self.playerSprites = {}
     self.collectableCoinSprites = {}
     self.trosballSprite = None
     if self.localPlayerId is not None:
         p = self.localPlayerSprite
         self.localPlayerSprite = PlayerSprite(self.app, self, p.player)
 def overridePlayer(self, player):
     self.localPlayerId = player.id
     self.localPlayerSprite = PlayerSprite(self.app, self, player)
Beispiel #5
0
class ViewManager(framework.Element):
    '''A ViewManager object takes a given universe, and displays a screenfull
    of the current state of the universe on the specified screen object.  This
    class displays only a section of the universe and no other information
    (scores, menu etc.).

    Note: self._focus represents the position that the ViewManager is currently
    looking at.  self.target is what the ViewManager should be trying to look
    at.

    self.target = None - the ViewManager will use its algorithm to follow a
        point of action.
    self.target = (x, y) - the ViewManager will look at the specified point.
    self.target = player - the ViewManager will follow the specified player.
    '''

    # The fastest speed that the viewing position can shift in pixels per sec
    maxSpeed = 1800
    acceleration = 1080

    # How far ahead of the targeted player we should look.
    lengthFromPlayer = 125

    def __init__(self, app, parent, universe, target=None, replay=False):
        '''
        Called upon creation of a ViewManager object.  screen is a pygame
        screen object.  universe is the Universe object to draw.  target is
        either a point, a PlayerSprite object, or None.  If target is None, the
        view manager will follow the action, otherwise it will follow the
        specified point or player.
        '''
        super(ViewManager, self).__init__(app)
        self.universe = universe
        self.parent = parent
        self.replay = replay

        # self._focus represents the point where the viewManager is currently
        # looking.
        self._focus = (universe.map.layout.centreX,
                       universe.map.layout.centreY)
        self._oldTargetPt = self._focus
        self.lastUpdateTime = timeNow()
        self.autoFocusInfo = (0, set(), set())
        self.speed = 0  # Speed that the focus is moving.

        self.loadingScreen = None
        self.loadingPlayer = None
        self.backgroundDrawer = BackgroundDrawer(app, universe)
        self.sRect = None

        # Now fill the backdrop with what we're looking at now.
        self.appResized()
        self.setTarget(target)

    def reset(self):
        if self.target is None:
            if self.autoFocusInfo == [0, set(), set()]:
                self._focus = (self.universe.map.layout.centreX,
                               self.universe.map.layout.centreY)

    def stop(self):
        self.backgroundDrawer.stop()

    def tick(self, deltaT):
        if not self.active:
            return
        super(ViewManager, self).tick(deltaT)
        self.updateMumble()

    def updateMumble(self):
        mumbleUpdater.update(self.getTargetPlayer(),
                             self.getTargetPoint(),
                             serverId='')

    def appResized(self):
        self.loadingScreen = None
        self.sRect = sRect = pygame.Rect((0, 0), self.app.screenManager.size)
        centre = sRect.center
        if not self.replay:
            settings = self.app.displaySettings
            sRect.width = min(settings.maxViewportWidth, sRect.width)
            sRect.height = min(settings.maxViewportHeight, sRect.height)
        sRect.center = centre

    def setTarget(self, target):
        '''Makes the viewManager's target the specified value.'''
        self.target = target
        if isinstance(self.target, PlayerSprite):
            # Move directly to looking at player.
            self._focus = target.pos
        elif isinstance(self.target, (tuple, list)):
            self.target = self.trimTargetToMap(self.target)
        else:
            countdown, players, zones = self.autoFocusInfo
            if not players and not zones:
                self._oldTargetPt = self._focus

    def getTargetPlayer(self):
        if isinstance(self.target, PlayerSprite):
            return self.target
        return None

    def getTargetPoint(self):
        '''Returns the position of the current target.'''
        if self.target is None:
            return self._focus
        return getattr(self.target, 'pos', self.target)

    def drawLoading(self, screen):
        if self.loadingPlayer is None:
            player = Player(self.universe.universe, 'Loading...', None, '')
            player.motionState = 'ground'
            player.updateState('right', True)
            self.loadingPlayer = PlayerSprite(self.app,
                                              self.universe,
                                              player,
                                              timer=timeNow)

        if self.loadingScreen is None:
            self.loadingScreen = pygame.Surface(screen.get_size())
            self.loadingScreen.fill((255, 255, 255))

            font = self.app.screenManager.fonts.mainMenuFont
            colour = self.app.theme.colours.mainMenuColour
            text = font.render(self.app, 'Loading...', True, colour,
                               (255, 255, 255))
            r = text.get_rect()
            r.center = self.loadingScreen.get_rect().center
            self.loadingScreen.blit(text, r)

            self.loadingPlayer.rect.midbottom = r.midtop

        if random.random() < 0.03:
            self.loadingPlayer.unit.lookAt(random.random() * 2 * math.pi)

        screen.blit(self.loadingScreen, (0, 0))
        self.loadingPlayer.update()
        screen.fill((255, 255, 255), self.loadingPlayer.rect)
        screen.blit(self.loadingPlayer.image, self.loadingPlayer.rect)

    def draw(self, screen, drawCoins=True):
        '''Draws the current state of the universe at the current viewing
        location on the screen.  Does not call pygame.display.flip()'''

        if self.universe.universe.loading:
            self.drawLoading(screen)
            return

        # Update where we're looking at.
        self.updateFocus()

        if self.sRect.topleft != (0, 0):
            screen.fill((0, 0, 0))

        oldClip = screen.get_clip()

        screen.set_clip(self.sRect)
        self.backgroundDrawer.draw(screen, self.sRect, self._focus, drawCoins)
        self._drawSprites(screen)
        self.drawOverlay(screen)
        screen.set_clip(oldClip)

    def drawOverlay(self, screen):
        area = self.sRect

        target = self.getTargetPlayer()
        if target is not None:
            physics = target.world.physics
            gunRange = physics.shotLifetime * physics.shotSpeed
            radius = int(gunRange * MAP_TO_SCREEN_SCALE + 0.5)
            pygame.draw.circle(screen, (192, 64, 64), area.center, radius, 1)

    def _drawSprites(self, screen):
        focus = self._focus
        area = self.sRect

        # Go through and update the positions of the players on the screen.
        ntGroup = set()
        visPlayers = set()

        for player in self.universe.iterPlayers():
            self.addSpritesForPlayer(player, visPlayers, ntGroup)
            hook = player.items.get(GrapplingHook)
            if hook and hook.hookState != HOOK_NOT_ACTIVE:
                pygame.draw.line(
                    screen, (255, 0, 0),
                    mapPosToScreen(player.pos, focus, area),
                    mapPosToScreen(hook.hookPosition, focus, area), 5)

        # Draw the on-screen players and nametags.
        for s in visPlayers:
            s.update()
            screen.blit(s.image, s.rect)
        for s in ntGroup:
            screen.blit(s.image, s.rect)

        def drawSprite(sprite, pos=None):
            # Calculate the position of the sprite.
            if pos is None:
                pos = sprite.pos
            sprite.rect.center = mapPosToScreen(pos, focus, area)
            if sprite.rect.colliderect(area):
                sprite.update()
                screen.blit(sprite.image, sprite.rect)

        # Draw the shots.
        for shot in self.universe.iterShots():
            drawSprite(shot)

        for coin in self.universe.iterCollectableCoins():
            drawSprite(coin)

        try:
            # Draw the grenades.
            for grenade in self.universe.iterGrenades():
                drawSprite(grenade)
        except Exception as e:
            log.exception(str(e))

        for sprite in self.universe.iterExtras():
            drawSprite(sprite)

        if __debug__ and globaldebug.enabled:
            if globaldebug.showSpriteCircles:
                for pos, radius in globaldebug.getSpriteCircles():
                    screenPos = mapPosToScreen(pos, focus, area)
                    pygame.draw.circle(screen, (255, 255, 0), screenPos,
                                       radius, 2)

            for sprite in visPlayers:
                sprite.player.onOverlayDebugHook(self, screen, sprite)

            for region in self.universe.universe.regions:
                region.debug_draw(self, screen)

    def addSpritesForPlayer(self, player, visPlayers, ntGroup):
        focus = self._focus
        area = self.sRect

        targetPlayer = self.getTargetPlayer()
        showPlayer = (not player.invisible or targetPlayer is None
                      or player.isFriendsWith(targetPlayer.player))

        if showPlayer:
            # Calculate the position of the player.
            if player is targetPlayer:
                player.rect.center = area.center
            else:
                player.rect.center = mapPosToScreen(player.pos, focus, area)

            # Check if this player needs its nametag shown.
            if player.rect.colliderect(area):
                visPlayers.add(player)

                if ntGroup is None:
                    return

                if player.unit.items.has(Bomber):
                    player.countdown.update()
                    player.countdown.rect.midbottom = player.rect.midtop
                    ntGroup.add(player.countdown)

                lastPoint = player.rect.midbottom

                shield = player.unit.items.get(Shield)
                if shield:
                    shieldBar = player.shieldBar
                    shieldBar.setHealth(shield.protections,
                                        shield.maxProtections)
                    if shieldBar.visible:
                        ntGroup.add(shieldBar)
                        shieldBar.rect.midtop = lastPoint
                        lastPoint = shieldBar.rect.midbottom

                healthBar = player.healthBar
                healthBar.setHealth(
                    player.unit.health,
                    player.unit.world.physics.playerRespawnHealth)
                if healthBar.visible:
                    ntGroup.add(healthBar)
                    healthBar.rect.midtop = lastPoint
                    lastPoint = healthBar.rect.midbottom

                player.nametag.rect.midtop = lastPoint

                # Check that entire nametag's on screen.
                if player.nametag.rect.left < area.left:
                    player.nametag.rect.left = area.left
                elif player.nametag.rect.right > area.right:
                    player.nametag.rect.right = area.right
                if player.nametag.rect.top < area.top:
                    player.nametag.rect.top = area.top
                elif player.nametag.rect.bottom > area.bottom:
                    player.nametag.rect.bottom = area.bottom
                ntGroup.add(player.nametag)

                if not player.dead:
                    # Place the coin rectangle below the nametag.
                    mx, my = player.nametag.rect.midbottom
                    player.coinTally.setCoins(player.getCoinDisplayCount())
                    player.coinTally.rect.midtop = (mx, my - 5)
                    ntGroup.add(player.coinTally)

    def updateFocus(self):
        '''Updates the location that the ViewManager is focused on.  First
        calculates where it would ideally be focused, then moves the focus
        towards that point. The focus cannot move faster than self.maxSpeed
        pix/s, and will only accelerate or decelerate at self.acceleration
        pix/s/s. This method returns the negative of the amount scrolled by.
        This is useful for moving the backdrop by the right amount.
        '''

        # Calculate where we should be looking at.
        if isinstance(self.target, PlayerSprite):
            # Take into account where the player's looking.
            targetPt = self.target.pos

            # If the player no longer exists, look wherever we want.
            if not self.universe.hasPlayer(self.target.player):
                self.setTarget(None)
        elif isinstance(self.target, (tuple, list)):
            targetPt = self.target
        else:
            targetPt = self.followAction()

        # Calculate time that's passed.
        now = timeNow()
        deltaT = now - self.lastUpdateTime
        self.lastUpdateTime = now

        # Calculate distance to target.
        self._oldTargetPt = targetPt
        sTarget = sum((targetPt[i] - self._focus[i])**2 for i in (0, 1))**0.5

        if sTarget == 0:
            return (0, 0)

        if self.target is not None:
            s = sTarget
        else:
            # Calculate the maximum velocity that will result in deceleration
            # to reach target. This is based on v**2 = u**2 + 2as
            vDecel = (2. * self.acceleration * sTarget)**0.5

            # Actual velocity is limited by this and maximum velocity.
            self.speed = min(self.maxSpeed, vDecel,
                             self.speed + self.acceleration * deltaT)

            # Distance travelled should never overshoot the target.
            s = min(sTarget, self.speed * deltaT)

        # How far does the backdrop need to move by?
        #  (This will be negative what the focus moves by.)
        deltaBackdrop = tuple(-s * (targetPt[i] - self._focus[i]) / sTarget
                              for i in (0, 1))

        # Calculate the new focus.
        self._focus = tuple(
            round(self._focus[i] - deltaBackdrop[i], 0) for i in (0, 1))

    def getZoneAtPoint(self, pt):
        x, y = screenToMapPos(pt, self._focus, self.sRect)

        i, j = MapLayout.getMapBlockIndices(x, y)
        try:
            return self.universe.map.zoneBlocks[i][j].getZoneAtPoint(x, y)
        except IndexError:
            return None

    def followAction(self):
        # Follow the action.
        countdown, players, zones = self.autoFocusInfo
        targetPt = tuple(self._focus)

        if self.universe.getPlayerCount() == 0:
            # No players anywhere. No change.
            self.autoFocusInfo = (0, set(), set())
            return targetPt

        # First check for non-existent players.
        for p in list(players):
            if not self.universe.hasPlayer(p):
                players.remove(p)

        # Every 10 iterations recheck for players that have entered
        # view area.
        r = pygame.Rect(self.sRect)
        r.width //= MAP_TO_SCREEN_SCALE
        r.height //= MAP_TO_SCREEN_SCALE
        r.center = self._oldTargetPt
        if countdown <= 0:
            players.clear()
            for p in self.universe.iterPlayers():
                if r.collidepoint(p.pos):
                    players.add(p)

            zones.clear()
            for z in self.universe.map.zones:
                if r.collidepoint(z.defn.pos):
                    if any(t != z.owner for t in z.teamsAbleToTag()):
                        zones.add(z)
            countdown = 10
        else:
            # Keep track of which players are still visible.
            for p in list(players):
                if not r.collidepoint(p.pos):
                    players.remove(p)
            countdown -= 1

        if len(players) + len(zones) <= 1:
            # Nothing interesting in view. Look for action.
            maxP = 0
            curZone = None
            for z in self.universe.zones:
                count = len(self.universe.getPlayersInZone(z))
                if any(t != z.owner for t in z.teamsAbleToTag()):
                    count += 2
                if count > maxP:
                    maxP = count
                    curZone = z
            if curZone is None:
                players = set()
            else:
                players = set(self.universe.getPlayersInZone(curZone))

            countdown = 20

        # Look at centre-of-range of these players.
        if players or zones:
            interestingPoints = []
            for p in players:
                interestingPoints.append(p.pos)
            for z in zones:
                interestingPoints.append(z.defn.pos)
            minPos = [min(pt[i] for pt in interestingPoints) for i in (0, 1)]
            maxPos = [max(pt[i] for pt in interestingPoints) for i in (0, 1)]
            targetPt = [0.5 * (minPos[i] + maxPos[i]) for i in (0, 1)]

        # No need to ever look beyond the boundary of the map
        targetPt = self.trimTargetToMap(targetPt)
        r.center = targetPt

        self.autoFocusInfo = (countdown, players, zones)
        return targetPt

    def trimTargetToMap(self, targetPt):
        # No need to ever look beyond the boundary of the map
        mapRect = pygame.Rect((0, 0), self.universe.map.layout.worldSize)
        r = pygame.Rect(self.sRect)
        r.width //= MAP_TO_SCREEN_SCALE
        r.height //= MAP_TO_SCREEN_SCALE
        r.center = targetPt
        if r.width > mapRect.width:
            r.centerx = mapRect.centerx
        else:
            r.right = min(r.right, mapRect.right)
            r.left = max(r.left, mapRect.left)
        if r.height > mapRect.height:
            r.centery = mapRect.centery
        else:
            r.bottom = min(r.bottom, mapRect.bottom)
            r.top = max(r.top, mapRect.top)
        return r.center