Exemple #1
0
class Menu(object):
    '''
    @param name     the caption of this menu
    @param items    a sequence of MenuItem objects to be displayed in this menu
    '''
    def __init__(self, name, items=(), listener=None):
        self.name = name
        self.items = list(items)

        self.onAction = Event()  # (action)
        if listener is not None:
            self.onAction.addListener(listener)
        for item in items:
            item.onAction.addListener(self.onAction.execute)

    def popItem(self, index):
        item = self.items.pop(index)
        item.onAction.removeListener(self.onAction.execute)
        return item

    def removeItem(self, item):
        self.popItem(self.items.index(item))

    def insertItem(self, pos, item):
        self.items.insert(pos, item)
        item.onAction.addListener(self.onAction.execute)

    def appendItem(self, item):
        self.insertItem(len(self.items), item)
class GameVoteMenu(CompoundElement):
    def __init__(self, app, world, onChange=None):
        CompoundElement.__init__(self, app)
        self.world = world
        self.playerId = NO_PLAYER

        self.readyMenu = ReadyMenu(self, 0)
        self.levelMenu = LevelMenu(self, MENU_WIDTH)
        self.teamMenu = TeamMenu(self, 2 * MENU_WIDTH)
        self.sizeMenu = SizeMenu(self, 3 * MENU_WIDTH)
        self.durationMenu = DurationMenu(self, 4 * MENU_WIDTH)

        self.onChange = Event()
        if onChange is not None:
            self.onChange.addListener(onChange)

        self.subMenu = GameVoteSubMenu(app)
        font = app.screenManager.fonts.ingameMenuFont
        self.elements = [
            SolidRect(
                app, (0, 64, 192), None,
                Region(topleft=Abs(0, 0),
                       width=Abs(4 * MENU_WIDTH),
                       height=Abs(font.getHeight(app) + 2))),
            self.subMenu,
            self.readyMenu.button,
            self.levelMenu.button,
            self.teamMenu.button,
            self.sizeMenu.button,
            self.durationMenu.button,
        ]

    def update(self, player):
        self.playerId = player.id
        self.readyMenu.updateTitle(player)
        self.levelMenu.updateTitle(player)
        self.teamMenu.updateTitle(player)
        self.sizeMenu.updateTitle(player)
        self.durationMenu.updateTitle(player)
        if self.subMenu.manager.menu is not None:
            self.subMenu.manager.menu.update()

    def showSubMenu(self, menu):
        menu.update()
        if self.subMenu.manager.menu is menu or self.subMenu.hidden:
            self.subMenu.hide()
        self.subMenu.manager.setDefaultMenu(menu)
        self.subMenu.location = menu.button.pos

    def hideSubMenu(self):
        if not self.subMenu.hidden:
            self.subMenu.hide()
class SettingsMenu(framework.CompoundElement):
    def __init__(self,
                 app,
                 onClose=None,
                 onRestart=None,
                 showThemes=False,
                 showSound=True,
                 showDisplay=True,
                 showKeymap=True):
        super(SettingsMenu, self).__init__(app)

        self.onClose = Event()
        if onClose is not None:
            self.onClose.addListener(onClose)
        self.onRestart = Event()
        if onRestart is not None:
            self.onRestart.addListener(onRestart)

        area = ScaledArea(50, 140, 924, 570)
        bg = pygame.Surface((924, 500))
        bg.fill(app.theme.colours.settingsMenu)
        if app.displaySettings.alphaOverlays:
            bg.set_alpha(192)
        font = app.screenManager.fonts.bigMenuFont
        self.tabContainer = TabContainer(self.app, area, font,
                                         app.theme.colours.settingsTabBorder)
        bp = elements.SizedPicture(
            app, bg,
            Location(AttachedPoint((0, 0), self.tabContainer._getTabRect)),
            TabSize(self.tabContainer))

        if showDisplay:
            displayTab = DisplaySettingsTab(app, onClose=self.onClose.execute)
            self.tabContainer.addTab(displayTab)

        if showKeymap:
            keymapTab = KeymapTab(app, onClose=self.onClose.execute)
            self.tabContainer.addTab(keymapTab)

        if showSound:
            soundTab = SoundSettingsTab(app, onClose=self.onClose.execute)
            self.tabContainer.addTab(soundTab)

        if showThemes:
            themeTab = ThemeTab(app,
                                onClose=self.onClose.execute,
                                onRestart=self.onRestart.execute)
            self.tabContainer.addTab(themeTab)

        self.elements = [bp, self.tabContainer]
Exemple #4
0
class ConnectionFailedDialog(DialogBox):
    def __init__(self, app, reason):
        width = app.screenManager.scaleFactor * absoluteWidth

        font = app.fonts.connectionFailedFont
        boundary = app.screenManager.scaleFactor * 20
        lines = wrapWords(app, reason, font, width - 2 * boundary)

        x = boundary
        elements = []
        colours = app.theme.colours
        for text in lines:
            elements.append(
                TextElement(app,
                            text,
                            font,
                            Location(
                                DialogBoxAttachedPoint(self, (0, x), 'midtop'),
                                'midtop'),
                            colour=colours.errorColour))
            x += font.getHeight(app)

        # Boundary * 3 for top, bottom, and middle
        height = boundary * 3 + font.getHeight(app) * (len(lines) + 1)
        size = ScaledSize(absoluteWidth, height)
        super(ConnectionFailedDialog, self).__init__(app, size,
                                                     'Connection Failed')

        self.elements = elements
        self.elements.append(
            TextButton(app,
                       Location(
                           DialogBoxAttachedPoint(self, (0, -boundary),
                                                  'midbottom'), 'midbottom'),
                       'OK',
                       font,
                       colours.dialogButtonColour,
                       colours.radioMouseover,
                       onClick=lambda sender: self.close()))
        self.onEnter = Event()
        self.onEnter.addListener(self.close)

    def processEvent(self, event):
        if event.type == pygame.KEYDOWN and event.key in (pygame.K_KP_ENTER,
                                                          pygame.K_RETURN):
            self.onEnter.execute()
            return None
        else:
            return super(ConnectionFailedDialog, self).processEvent(event)
Exemple #5
0
class MenuItem(object):
    '''
    @param name     the caption of the menu item
    @param action   a callable for when the item is selected
    '''
    def __init__(self, name, action=None, listener=None, enabled=True):
        self.name = name
        self.onClick = Event()  # (MenuItem)
        self.onAction = Event()  # (action)
        self.action = action
        self.enabled = enabled
        if listener is not None:
            self.onClick.addListener(listener)

    def execute(self):
        self.onClick.execute(self)
        if self.action is not None:
            self.onAction.execute(self.action)
Exemple #6
0
class PractiseScreen(framework.CompoundElement):
    def __init__(self, app, onClose=None, onStart=None):
        super(PractiseScreen, self).__init__(app)

        self.onClose = Event()
        if onClose is not None:
            self.onClose.addListener(onClose)
        self.onStart = Event()
        if onStart is not None:
            self.onStart.addListener(onStart)

        if app.displaySettings.alphaOverlays:
            alpha = 192
        else:
            alpha = None
        bg = SolidRect(app, app.theme.colours.playMenu, alpha,
                       Region(centre=Canvas(512, 384), size=Canvas(924, 500)))

        #colour = app.theme.colours.mainMenuColour
        #font = app.screenManager.fonts.consoleFont

        font = app.screenManager.fonts.bigMenuFont
        cancel = TextButton(app,
                            Location(Canvas(512, 624), 'midbottom'),
                            'Cancel',
                            font,
                            app.theme.colours.secondMenuColour,
                            app.theme.colours.white,
                            onClick=self.cancel)
        self.elements = [bg, cancel]

    def cancel(self, element):
        self.onClose.execute()

    def startGame(self):
        db = self.app.layoutDatabase
        game = LocalGame(db, SIZE[0], SIZE[1], onceOnly=True)

        for i in range(AICOUNT):
            game.addBot('ranger')

        self.onStart.execute(game)
Exemple #7
0
class Hotkeys(Element):
    '''
    Traps any keys which occur in the mapping and carries out some action on
    them. Note that this observes only, but does not stop the keypress events
    from passing through.
    '''
    def __init__(self, app, mapping, onActivated=None):
        super(Hotkeys, self).__init__(app)
        self.mapping = mapping
        self.onActivated = Event()
        if onActivated is not None:
            self.onActivated.addListener(onActivated)

    def processEvent(self, event):
        if event.type == pygame.KEYDOWN:
            try:
                action = self.mapping[event.key]
            except KeyError:
                return event

            # Process this hotkey.
            self.onActivated.execute(action)
        return event

    def __repr__(self):
        if isinstance(self.key, int):
            key = self.key
        else:
            # Calculate the physical key.
            for key, value in self.mapping.iteritems():
                if value == self.key:
                    break
            else:
                return '??'

        return keyboard.shortcutName(key, 0)
Exemple #8
0
class GameInterface(framework.CompoundElement, ConcreteAgent):
    '''Interface for when we are connected to a game.'''

    achievementDefs = availableAchievements

    def __init__(self,
                 app,
                 game,
                 onDisconnectRequest=None,
                 onConnectionLost=None,
                 replay=False,
                 authTag=0):
        super(GameInterface, self).__init__(app, game=game)
        self.localState.onShoxwave.addListener(self.localShoxwaveFired)
        self.localState.onGameInfoChanged.addListener(self.gameInfoChanged)
        self.world.onOpenChatReceived.addListener(self.openChat)
        self.world.onTeamChatReceived.addListener(self.teamChat)
        self.world.onReset.addListener(self.worldReset)
        self.world.onGrenadeExplosion.addListener(self.grenadeExploded)
        self.world.onTrosballExplosion.addListener(self.trosballExploded)
        self.world.onBomberExplosion.addListener(self.trosballExploded)
        self.world.uiOptions.onChange.addListener(self.uiOptionsChanged)

        self.authTag = authTag

        self.subscribedPlayers = set()

        self.onDisconnectRequest = Event()
        if onDisconnectRequest is not None:
            self.onDisconnectRequest.addListener(onDisconnectRequest)

        self.onConnectionLost = Event()
        if onConnectionLost is not None:
            self.onConnectionLost.addListener(onConnectionLost)
        self.game = game

        self.joinDialogReason = None

        self.keyMapping = keyboard.KeyboardMapping(keymap.default_game_keys)
        self.runningPlayerInterface = None
        self.updateKeyMapping()
        self.gameViewer = viewManager.GameViewer(self.app, self, game, replay)
        if replay:
            self.joinController = None
        else:
            self.joinController = JoinGameController(self.app, self, self.game)
        self.detailsInterface = DetailsInterface(self.app, self)
        self.winnerMsg = WinnerMsg(app)
        self.timingInfo = TimingInfo(
            app,
            self,
            Location(Canvas(307, 768), 'midbottom'),
            app.screenManager.fonts.consoleFont,
        )
        self.gameInfoDisplay = GameInfoDisplay(
            app, self, Region(topleft=Screen(0.01, 0.05),
                              size=Canvas(330, 200)))
        self.hotkeys = hotkey.Hotkeys(self.app, self.keyMapping,
                                      self.detailsInterface.doAction)
        self.terminal = None

        self.joinInProgress = False

        self.vcInterface = None
        if replay:
            self.vcInterface = ViewControlInterface(self.app, self)

        self.ready = False
        defer.maybeDeferred(game.addAgent, self).addCallback(self.addedAgent)

        self.setElements()

    def gameInfoChanged(self):
        self.gameInfoDisplay.refreshInfo()

    def addedAgent(self, result):
        self.ready = True
        if self.joinController:
            self.joinDialogReason = 'automatic'
            self.joinController.start()

    def spectatorWantsToJoin(self):
        if self.runningPlayerInterface or not self.joinController:
            return
        self.joinDialogReason = 'from menu'
        self.joinController.maybeShowJoinDialog(autoJoin=True)

    def sendRequest(self, msg):
        if not self.ready:
            # Not yet completely connected to game
            return
        super(GameInterface, self).sendRequest(msg)

    def worldReset(self, *args, **kwarsg):
        self.winnerMsg.hide()
        if self.ready and self.joinController:
            self.joinController.gotWorldReset()
        self.gameViewer.reset()

    def updateKeyMapping(self):
        # Set up the keyboard mapping.
        try:
            # Try to load keyboard mappings from the user's personal settings.
            config = open(getPath(user, 'keymap'), 'rU').read()
            self.keyMapping.load(config)
            if self.runningPlayerInterface:
                self.runningPlayerInterface.keyMappingUpdated()
        except IOError:
            pass

    @ConnectionLostMsg.handler
    def connectionLost(self, msg):
        self.cleanUp()
        if self.joinController:
            self.joinController.hide()
        self.onConnectionLost.execute()

    def joined(self, player):
        '''Called when joining of game is successful.'''
        pygame.key.set_repeat()
        self.gameViewer.worldgui.overridePlayer(self.localState.player)
        self.runningPlayerInterface = pi = PlayerInterface(self.app, self)
        self.detailsInterface.setPlayer(pi.player)
        self.setElements()

        self.joinController.hide()
        self.gameViewer.leaderboard.update()

    def spectate(self):
        '''
        Called by join controller if user selects to only spectate.
        '''
        self.vcInterface = ViewControlInterface(self.app, self)
        self.setElements()
        self.joinController.hide()

    def joinDialogCancelled(self):
        if self.joinDialogReason == 'automatic':
            self.disconnect()
        else:
            self.spectate()

    def stop(self):
        super(GameInterface, self).stop()
        self.localState.onShoxwave.removeListener(self.localShoxwaveFired)
        self.localState.onGameInfoChanged.removeListener(self.gameInfoChanged)
        self.world.onOpenChatReceived.removeListener(self.openChat)
        self.world.onTeamChatReceived.removeListener(self.teamChat)
        self.world.onReset.removeListener(self.worldReset)
        self.world.onGrenadeExplosion.removeListener(self.grenadeExploded)
        self.world.onTrosballExplosion.removeListener(self.trosballExploded)
        self.world.onBomberExplosion.removeListener(self.trosballExploded)
        self.world.uiOptions.onChange.removeListener(self.uiOptionsChanged)
        self.gameViewer.stop()
        if self.runningPlayerInterface is not None:
            self.runningPlayerInterface.stop()

    def setElements(self):
        spectate = replay = False
        if self.runningPlayerInterface:
            self.elements = [
                self.gameViewer, self.runningPlayerInterface,
                self.gameInfoDisplay, self.hotkeys, self.detailsInterface,
                self.winnerMsg, self.timingInfo
            ]
        else:
            self.elements = [
                self.gameViewer, self.gameInfoDisplay, self.hotkeys,
                self.detailsInterface, self.winnerMsg, self.timingInfo
            ]
            if self.vcInterface is not None:
                self.elements.insert(2, self.vcInterface)

            if self.joinController:
                spectate = True
            else:
                replay = True
        self.detailsInterface.menuManager.setMode(spectate=spectate,
                                                  replay=replay)

    def toggleTerminal(self):
        if self.terminal is None:
            locs = {'app': self.app}
            if hasattr(self.app, 'getConsoleLocals'):
                locs.update(self.app.getConsoleLocals())
            self.terminal = console.TrosnothInteractiveConsole(
                self.app,
                self.app.screenManager.fonts.consoleFont,
                Region(size=Screen(1, 0.4), bottomright=Screen(1, 1)),
                locals=locs)
            self.terminal.interact().addCallback(self._terminalQuit)

        from trosnoth.utils.utils import timeNow
        if self.terminal in self.elements:
            if timeNow() > self._termWaitTime:
                self.elements.remove(self.terminal)
        else:
            self._termWaitTime = timeNow() + 0.1
            self.elements.append(self.terminal)
            self.setFocus(self.terminal)

    def _terminalQuit(self, result):
        if self.terminal in self.elements:
            self.elements.remove(self.terminal)
        self.terminal = None

    def disconnect(self):
        self.cleanUp()
        self.onDisconnectRequest.execute()

    def joinGame(self, nick, team, timeout=10):
        if self.joinInProgress:
            return

        if team is None:
            teamId = NEUTRAL_TEAM_ID
        else:
            teamId = team.id

        self.joinInProgress = True
        self.sendJoinRequest(teamId, nick, authTag=self.authTag)
        WeakCallLater(timeout, self, '_joinTimedOut')

    def setPlayer(self, player):
        if not player:
            self.gameViewer.worldgui.removeOverride()
            self.lostPlayer()

        super(GameInterface, self).setPlayer(player)

        if player:
            if __debug__ and globaldebug.enabled:
                globaldebug.localPlayerId = player.id

            self.joinInProgress = False
            self.joined(player)

    @CannotJoinMsg.handler
    def joinFailed(self, msg):
        self.joinInProgress = False
        self.joinController.joinFailed(msg.reasonId)

    def _joinTimedOut(self):
        if self.player or not self.joinInProgress:
            return
        self.joinInProgress = False
        self.joinController.joinFailed('timeout')

    def cleanUp(self):
        if self.gameViewer.timerBar is not None:
            self.gameViewer.timerBar = None
        pygame.key.set_repeat(300, 30)

    def uiOptionsChanged(self):
        if self.world.uiOptions.showGameOver:
            winner = self.world.uiOptions.winningTeam
            if winner:
                self.winnerMsg.show(
                    '{} win'.format(winner),
                    self.app.theme.colours.chatColour(winner),
                )
            else:
                self.winnerMsg.show('Game drawn', (128, 128, 128))
        else:
            self.winnerMsg.hide()

    @PlayerCoinsSpentMsg.handler
    def discard(self, msg):
        pass

    @AwardPlayerCoinMsg.handler
    def playerAwardedCoin(self, msg):
        if not self.localState.player:
            return
        if msg.sound and msg.playerId == self.localState.player.id:
            self.playSound('gotCoin')

    @PlayerHasElephantMsg.handler
    def gotElephant(self, msg, _lastElephantPlayer=[None]):
        player = self.world.getPlayer(msg.playerId)
        if player and player != _lastElephantPlayer[0]:
            message = '%s now has Jerakeen!' % (player.nick, )
            self.detailsInterface.newMessage(message)
            _lastElephantPlayer[0] = player

    @PlayerHasTrosballMsg.handler
    def gotTrosball(self, msg, _lastTrosballPlayer=[None]):
        player = self.world.playerWithId.get(msg.playerId)

        if player != _lastTrosballPlayer[0]:
            _lastTrosballPlayer[0] = player
            if player is None:
                message = 'The ball has been dropped!'
            else:
                message = '%s has the ball!' % (player.nick, )
            self.detailsInterface.newMessage(message)

    @AddPlayerMsg.handler
    def addPlayer(self, msg):
        player = self.world.getPlayer(msg.playerId)
        if player and player not in self.subscribedPlayers:
            self.subscribedPlayers.add(player)
            team = player.team if player.team else self.world.rogueTeamName
            message = '%s has joined %s' % (player.nick, team)
            self.detailsInterface.newMessage(message)
            player.onDied.addListener(partial(self.playerDied, player))

    @SetPlayerTeamMsg.handler
    def changeTeam(self, msg):
        self.defaultHandler(msg)  # Make sure the local player changes team
        player = self.world.getPlayer(msg.playerId)
        if player:
            message = '%s has joined %s' % (player.nick,
                                            self.world.getTeamName(msg.teamId))
            self.detailsInterface.newMessage(message)

    @RemovePlayerMsg.handler
    def handle_RemovePlayerMsg(self, msg):
        player = self.world.getPlayer(msg.playerId)
        if player:
            message = '%s has left the game' % (player.nick, )
            self.detailsInterface.newMessage(message)
            self.subscribedPlayers.discard(player)

    def lostPlayer(self):
        self.runningPlayerInterface.stop()
        self.runningPlayerInterface = None
        self.setElements()

    @CannotBuyUpgradeMsg.handler
    def notEnoughCoins(self, msg):
        if msg.reasonId == NOT_ENOUGH_COINS_REASON:
            text = 'Your team does not have enough coins.'
        elif msg.reasonId == CANNOT_REACTIVATE_REASON:
            text = 'You already have that item.'
        elif msg.reasonId == PLAYER_DEAD_REASON:
            text = 'You cannot buy an upgrade while dead.'
        elif msg.reasonId == GAME_NOT_STARTED_REASON:
            text = 'Upgrades can' 't be bought at this time.'
        elif msg.reasonId == ALREADY_TURRET_REASON:
            text = 'There is already a turret in this zone.'
        elif msg.reasonId == TOO_CLOSE_TO_EDGE_REASON:
            text = 'You are too close to the zone edge.'
        elif msg.reasonId == TOO_CLOSE_TO_ORB_REASON:
            text = 'You are too close to the orb.'
        elif msg.reasonId == NOT_IN_DARK_ZONE_REASON:
            text = 'You are not in a dark friendly zone.'
        elif msg.reasonId == INVALID_UPGRADE_REASON:
            text = 'Upgrade not recognised by server.'
        elif msg.reasonId == DISABLED_UPGRADE_REASON:
            text = 'That upgrade is currently disabled.'
        else:
            text = 'You cannot buy that item at this time.'
        self.detailsInterface.newMessage(text)
        self.defaultHandler(msg)

    @PlayerHasUpgradeMsg.handler
    def gotUpgrade(self, msg):
        player = self.world.getPlayer(msg.playerId)
        if player:
            self.detailsInterface.upgradeUsed(player, msg.upgradeType)
            upgradeClass = self.world.getUpgradeType(msg.upgradeType)
            existing = player.items.get(upgradeClass)
            if not existing:
                if (self.detailsInterface.player is None
                        or self.detailsInterface.player.isFriendsWith(player)):
                    if upgradeClass == Turret:
                        self.playSound('turret')
                    else:
                        self.playSound('buyUpgrade')

        self.defaultHandler(msg)

    @ChatFromServerMsg.handler
    def gotChatFromServer(self, msg):
        self.detailsInterface.newMessage(msg.text.decode(), error=msg.error)

    @TaggingZoneMsg.handler
    def zoneTagged(self, msg):
        try:
            zone = self.world.zoneWithId[msg.zoneId]
            zoneLabel = zone.defn.label
        except KeyError:
            zoneLabel = '<?>'

        if msg.playerId != NO_PLAYER:
            try:
                player = self.world.playerWithId[msg.playerId]
            except KeyError:
                nick = '<?>'
            else:
                nick = player.nick
            message = '%s tagged zone %s' % (nick, zoneLabel)

            self.detailsInterface.newMessage(message)

    def playerDied(self, target, killer, deathType):
        if deathType == OFF_MAP_DEATH:
            messages = [
                'fell into the void', 'looked into the abyss',
                'dug too greedily and too deep'
            ]
            message = '%s %s' % (target.nick, random.choice(messages))
        elif deathType == TROSBALL_DEATH:
            message = '%s was killed by the Trosball' % (target.nick, )
        elif deathType == BOMBER_DEATH:
            message = '%s head asplode' % (target.nick, )
            thisPlayer = self.detailsInterface.player
            if thisPlayer and target.id == thisPlayer.id:
                self.detailsInterface.doAction('no upgrade')
        else:
            if killer is None:
                message = '%s was killed' % (target.nick, )
                self.detailsInterface.newMessage(message)
            else:
                message = '%s killed %s' % (killer.nick, target.nick)

        self.detailsInterface.newMessage(message)

    @RespawnMsg.handler
    def playerRespawn(self, msg):
        player = self.world.getPlayer(msg.playerId)
        if player:
            message = '%s is back in the game' % (player.nick, )
            self.detailsInterface.newMessage(message)

    @CannotRespawnMsg.handler
    def respawnFailed(self, msg):
        if msg.reasonId == GAME_NOT_STARTED_REASON:
            message = 'The game has not started yet.'
        elif msg.reasonId == ALREADY_ALIVE_REASON:
            message = 'You are already alive.'
        elif msg.reasonId == BE_PATIENT_REASON:
            message = 'You cannot respawn yet.'
        elif msg.reasonId == ENEMY_ZONE_REASON:
            message = 'Cannot respawn outside friendly zone.'
        elif msg.reasonId == FROZEN_ZONE_REASON:
            message = 'That zone has been frozen!'
        else:
            message = 'You cannot respawn here.'
        self.detailsInterface.newMessage(
            message, self.app.theme.colours.errorMessageColour)

    def sendPrivateChat(self, player, targetId, text):
        self.sendRequest(ChatMsg(PRIVATE_CHAT, targetId, text=text.encode()))

    def sendTeamChat(self, player, text):
        self.sendRequest(ChatMsg(TEAM_CHAT, player.teamId, text=text.encode()))

    def sendPublicChat(self, player, text):
        self.sendRequest(ChatMsg(OPEN_CHAT, text=text.encode()))

    def openChat(self, text, sender):
        text = ': ' + text
        self.detailsInterface.newChat(text, sender)

    def teamChat(self, team, text, sender):
        player = self.detailsInterface.player
        if player and player.isFriendsWithTeam(team):
            text = " (team): " + text
            self.detailsInterface.newChat(text, sender)

    @AchievementUnlockedMsg.handler
    def achievementUnlocked(self, msg):
        player = self.world.getPlayer(msg.playerId)
        if not player:
            return

        achievementName = self.achievementDefs.getAchievementDetails(
            msg.achievementId)[0]
        self.detailsInterface.newMessage(
            '%s has unlocked "%s"!' % (player.nick, achievementName),
            self.app.theme.colours.achievementMessageColour)

        focusPlayer = self.detailsInterface.player
        if (focusPlayer is not None and focusPlayer.id == msg.playerId):
            self.detailsInterface.localAchievement(msg.achievementId)

    @ShotFiredMsg.handler
    def shotFired(self, msg):
        self.defaultHandler(msg)
        try:
            shot = self.world.getShot(msg.shotId)
        except KeyError:
            return

        pos = shot.pos
        dist = self.distance(pos)
        self.playSound('shoot', self.getSoundVolume(dist))

    def grenadeExploded(self, pos, radius):
        self.gameViewer.worldgui.addExplosion(pos)
        dist = self.distance(pos)
        self.playSound('explodeGrenade', self.getSoundVolume(dist))

    def trosballExploded(self, player):
        self.gameViewer.worldgui.addTrosballExplosion(player.pos)
        dist = self.distance(player.pos)
        self.playSound('explodeGrenade', self.getSoundVolume(dist))

    @FireShoxwaveMsg.handler
    def shoxwaveExplosion(self, msg):
        localPlayer = self.localState.player
        if localPlayer and msg.playerId == localPlayer.id:
            return
        self.gameViewer.worldgui.addShoxwaveExplosion((msg.xpos, msg.ypos))

    def localShoxwaveFired(self):
        localPlayer = self.localState.player
        self.gameViewer.worldgui.addShoxwaveExplosion(localPlayer.pos)

    @UpgradeChangedMsg.handler
    def upgradeChanged(self, msg):
        self.detailsInterface.upgradeDisplay.refresh()

    def distance(self, pos):
        return distance(self.gameViewer.viewManager.getTargetPoint(), pos)

    def getSoundVolume(self, distance):
        'The volume for something that far away from the player'
        # Up to 500px away is within the "full sound zone" - full sound
        distFromScreen = max(0, distance - 500)
        # 1000px away from "full sound zone" is 0 volume:
        return 1 - min(1, (distFromScreen / 1000.))

    def playSound(self, action, volume=1):
        self.app.soundPlayer.play(action, volume)

    @PlaySoundMsg.handler
    def playSoundFromServerCommand(self, msg):
        self.app.soundPlayer.playFromServerCommand(
            msg.filename.decode('utf-8'))

    @TickMsg.handler
    def handle_TickMsg(self, msg):
        super(GameInterface, self).handle_TickMsg(msg)
        self.timingInfo.ticksSeen += 1
Exemple #9
0
class HoverButton(Element):
    '''A button which changes image when the mouse hovers over it.
    @param  pos     should be a trosnoth.ui.common.Location instance
    '''
    def __init__(self,
                 app,
                 pos,
                 stdImage,
                 hvrImage,
                 hotkey=None,
                 onClick=None):
        super(HoverButton, self).__init__(app)

        self.stdImage = stdImage
        self.hvrImage = hvrImage
        self.pos = pos
        self.onClick = Event()
        self.hotkey = hotkey

        self.mouseOver = False

        if onClick is not None:
            self.onClick.addListener(onClick)

    def _getSurfaceToBlit(self):
        img = self._getImageToUse()
        if hasattr(img, 'getImage'):
            return img.getImage(self.app)
        else:
            return img

    def _getImageToUse(self):
        if self.mouseOver:
            return self.hvrImage
        else:
            return self.stdImage

    def _getSize(self):
        img = self._getImageToUse()
        if hasattr(img, 'getSize'):
            return img.getSize(self.app)
        else:
            # pygame.surface.Surface
            return img.get_size()

    def _getPt(self):
        if hasattr(self.pos, 'apply'):
            rect = pygame.Rect((0, 0), self._getSize())
            self.pos.apply(self.app, rect)
            return rect.topleft
        else:
            return self.pos

    def _getRect(self):
        if hasattr(self.pos, 'apply'):
            rect = pygame.Rect((0, 0), self._getSize())
            self.pos.apply(self.app, rect)
            return rect
        return pygame.Rect(self.pos, self._getSize())

    def processEvent(self, event):
        rect = self._getRect()
        # Passive events.
        if event.type == pygame.MOUSEMOTION:
            self.mouseOver = rect.collidepoint(event.pos)

        # Active events.
        if (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1
                and rect.collidepoint(event.pos)):
            self.onClick.execute(self)
        elif event.type == pygame.KEYDOWN and event.key == self.hotkey:
            self.onClick.execute(self)
        else:
            return event
        return None

    def draw(self, screen):
        # Draw the button.
        screen.blit(self._getSurfaceToBlit(), self._getPt())
class ThemeTab(Tab, framework.TabFriendlyCompoundElement):
    def __init__(self, app, onClose=None, onRestart=None):
        super(ThemeTab, self).__init__(app, 'Themes')

        self.onClose = Event()
        if onClose is not None:
            self.onClose.addListener(onClose)
        self.onRestart = Event()
        if onRestart is not None:
            self.onRestart.addListener(onRestart)

        font = self.app.screenManager.fonts.menuFont
        colours = self.app.theme.colours

        self.inactiveTheme = False
        self.originalTheme = app.theme.name

        # Static text
        self.staticText = [
            TextElement(self.app, 'theme information:', font,
                        ScaledLocation(960, 250, 'topright'),
                        colours.headingColour),
            TextElement(self.app, 'theme contents:', font,
                        ScaledLocation(960, 390, 'topright'),
                        colours.headingColour)
        ]

        # Dynamic text
        self.feedbackText = TextElement(
            self.app, 'Your current theme: %s' % (app.theme.name, ), font,
            ScaledLocation(512, 200, 'midtop'), colours.startButton)
        self.listHeaderText = TextElement(self.app, 'available themes:', font,
                                          ScaledLocation(70, 250),
                                          colours.headingColour)
        self.themeNameText = TextElement(self.app, 'Default Theme', font,
                                         ScaledLocation(960, 290, 'topright'),
                                         colours.startButton)
        self.themeAuthorText = TextElement(
            self.app, 'created by: Trosnoth Team', font,
            ScaledLocation(960, 330, 'topright'), colours.startButton)

        self.contents = []

        numContents = 4
        for yPos in range(430, 430 + numContents * 40 + 1, 40):
            self.contents.append(
                TextElement(self.app, '', font,
                            ScaledLocation(960, yPos, 'topright'),
                            colours.startButton))

        self.dynamicText = [
            self.feedbackText, self.listHeaderText, self.themeNameText,
            self.themeAuthorText
        ] + self.contents

        # Theme list
        self.themeList = ListBox(self.app, ScaledArea(70, 290, 400, 290), [],
                                 font, colours.listboxButtons)
        self.themeList.onValueChanged.addListener(self.updateSidebar)

        # Text buttons
        self.useThemeButton = button(app, 'use selected theme',
                                     self.applyTheme, (0, -125), 'midbottom')
        self.refreshButton = button(app, 'refresh', self.populateList,
                                    (-100, -75), 'midbottom')
        self.cancelButton = button(app, 'cancel', self.backToMain, (100, -75),
                                   'midbottom')
        self.restartButton = button(app, 'restart Trosnoth', self.restart,
                                    (0, -125), 'midbottom')

        self.buttons = [
            self.useThemeButton, self.refreshButton, self.cancelButton,
            self.restartButton
        ]

        # Combine the elements
        self.elements = self.staticText + self.dynamicText + self.buttons + [
            self.themeList
        ]

        self.contentTypes = {
            "sprites": "sprite",
            "blocks": "map block",
            "fonts": "font",
            "startupMenu": "backdrop"
        }

        # Populate the list of replays
        self.populateList()

    def populateList(self):

        defaultTheme = {
            "name": "Default Theme",
            "filename": "default",
            "author": "Trosnoth Team",
            "content": None,
            "source": "internal"
        }

        # Clear out the sidebar
        self.themeNameText.setText('')
        self.themeAuthorText.setText('')
        for element in self.contents:
            element.setText('')
        self.useThemeButton.setText('')
        self.restartButton.setText('')
        self.listHeaderText.setText('available themes:')
        self.themeList.index = -1

        userThemeDir = getPath(user, 'themes')
        internalThemeDir = getPath(themes)
        makeDirs(userThemeDir)

        themeList = []

        # Get a list of internal themes
        for dirName in os.listdir(internalThemeDir):
            if os.path.isdir(os.path.join(internalThemeDir, dirName)):
                themeList.append("i/%s" % dirName)

        # Get a list of user-defined themes
        for dirName in os.listdir(userThemeDir):
            if os.path.isdir(os.path.join(userThemeDir, dirName)):
                # Internal themes overrule user-defined themes
                if "i/" + dirName not in themeList and dirName != "default":
                    themeList.append("u/%s" % dirName)

        # Assume all themes are valid for now
        validThemes = themeList[:]

        self.themeInfo = {}

        for themeName in themeList:
            themeInfo = {"content": {}}

            if themeName.startswith("i/"):
                themeInfo['source'] = 'internal'
                directory = internalThemeDir
            else:
                themeInfo['source'] = 'user-defined'
                directory = userThemeDir

            themeNameList = themeName
            themeName = themeName[2:]

            themeInfo['filename'] = themeName[2:]

            anyContent = False

            for contentType in list(self.contentTypes.keys()):
                if themeInfo['source'] == 'internal':
                    contentDir = os.path.join(directory, themeName,
                                              contentType)
                else:
                    contentDir = os.path.join(directory, themeName,
                                              contentType)

                if not os.path.isdir(contentDir):
                    continue
                else:
                    fileCount = len([
                        f for f in os.listdir(contentDir)
                        if os.path.isfile(os.path.join(contentDir, f))
                    ])
                    if fileCount > 0:
                        anyContent = True
                        themeInfo["content"][contentType] = fileCount

            if not anyContent:
                validThemes.remove(themeNameList)
                continue

            infoFile = os.path.join(directory, themeName, "info.txt")
            if os.path.isfile(infoFile):
                infoFile = open(infoFile)
                infoContents = infoFile.readlines()
            else:
                infoContents = []

            if len(infoContents) >= 2:
                themeInfo["author"] = infoContents[1].strip()
            else:
                themeInfo["author"] = None

            if len(infoContents) >= 1:
                themeInfo["name"] = infoContents[0].strip()
            else:
                themeInfo["name"] = themeName

            self.themeInfo[themeName] = themeInfo

        self.themeInfo["default"] = defaultTheme

        # Sort the themes alphabetically
        items = [(v['filename'], n) for n, v in self.themeInfo.items()]
        items.sort()
        items = [n for v, n in items]
        self.themeList.setItems(items)

        if len(self.themeInfo) == 1:
            self.listHeaderText.setText("1 available theme:")
            self.themeList.index = 0
            self.updateSidebar(0)
        else:
            self.listHeaderText.setText("%d available themes:" %
                                        len(self.themeInfo))

    def updateSidebar(self, listID):
        # Update the details on the sidebar
        displayName = self.themeList.getItem(listID)

        themeInfo = self.themeInfo[displayName]

        if themeInfo['source'] == "internal":
            self.themeNameText.setText(themeInfo['name'] + " (built-in)")
        else:
            self.themeNameText.setText(themeInfo['name'])
        if themeInfo['author'] is not None:
            self.themeAuthorText.setText("by " + themeInfo['author'])
        else:
            self.themeAuthorText.setText("(creator unknown)")

        if themeInfo['content'] is None:
            for element in self.contents:
                element.setText('')
            self.contents[0].setText('N/A')
        else:
            count = 0
            for k, v in themeInfo['content'].items():
                suffix = ""
                if v != 1:
                    suffix = "s"

                text = "%d %s%s" % (v, self.contentTypes[k], suffix)
                self.contents[count].setText(text)
                count += 1

        self.restartButton.setText("")
        if self.app.displaySettings.theme != displayName:
            if displayName == self.originalTheme:
                self.useThemeButton.setText("revert back to this theme")
            else:
                self.useThemeButton.setText("use this theme")
        else:
            self.useThemeButton.setText("")

            if self.inactiveTheme:
                self.restartButton.setText('restart Trosnoth')

    def applyTheme(self):
        themeName = self.themeList.getItem(self.themeList.index)

        self.app.displaySettings.theme = themeName
        self.app.displaySettings.save()

        self.useThemeButton.setText("")

        if themeName != self.originalTheme:
            self.feedbackText.setText("You must restart Trosnoth for the "
                                      "'%s' theme to take effect." % themeName)
            self.restartButton.setText('restart Trosnoth')
            self.inactiveTheme = True
        else:
            self.feedbackText.setText('Your current theme: %s' %
                                      (self.app.theme.name, ))
            self.restartButton.setText("")
            self.inactiveTheme = False

    def restart(self):
        self.onRestart.execute()

    def backToMain(self):
        if self.inactiveTheme:
            self.feedbackText.setText("Your current theme: %s "
                                      "(restart required)" %
                                      self.app.displaySettings.theme)

        self.onClose.execute()

    def draw(self, surface):
        super(ThemeTab, self).draw(surface)
        width = max(int(3 * self.app.screenManager.scaleFactor), 1)
        scalePoint = self.app.screenManager.placePoint
        pygame.draw.line(surface, self.app.theme.colours.mainMenuColour,
                         scalePoint((512, 260)), scalePoint((512, 580)), width)
Exemple #11
0
class SoundSettingsTab(Tab, framework.CompoundElement):
    def __init__(self, app, onClose=None):
        super(SoundSettingsTab, self).__init__(app, 'Sounds')

        self.onClose = Event()
        if onClose is not None:
            self.onClose.addListener(onClose)

        font = self.app.screenManager.fonts.bigMenuFont
        colours = app.theme.colours

        text = [
            TextElement(self.app, 'Music Volume', font,
                        ScaledLocation(400, 285, 'topright'),
                        colours.headingColour),
            TextElement(self.app, 'Enable Music', font,
                        ScaledLocation(400, 355, 'topright'),
                        colours.headingColour),
            TextElement(self.app, 'Sound Volume', font,
                        ScaledLocation(400, 425, 'topright'),
                        colours.headingColour),
            TextElement(self.app, 'Enable Sound', font,
                        ScaledLocation(400, 495, 'topright'),
                        colours.headingColour),
        ]

        initVolume = app.soundSettings.musicVolume
        musicVolumeLabel = TextElement(self.app, '%d' % (initVolume, ), font,
                                       ScaledLocation(870, 280, 'topleft'),
                                       colours.headingColour)

        self.musicVolumeSlider = Slider(self.app,
                                        ScaledArea(450, 280, 400, 40))
        onSlide = lambda volume: musicVolumeLabel.setText('%d' % volume)
        self.musicVolumeSlider.onSlide.addListener(onSlide)
        self.musicVolumeSlider.onValueChanged.addListener(onSlide)
        self.musicVolumeSlider.setVal(initVolume)

        self.musicBox = CheckBox(self.app,
                                 ScaledLocation(450, 360),
                                 text='',
                                 font=font,
                                 colour=(192, 192, 192),
                                 initValue=app.soundSettings.musicEnabled)

        initSndVolume = app.soundSettings.soundVolume
        soundVolumeLabel = TextElement(self.app,
                                       '%d' % (initSndVolume, ), font,
                                       ScaledLocation(870, 420, 'topleft'),
                                       colours.headingColour)

        self.soundVolumeSlider = Slider(self.app,
                                        ScaledArea(450, 420, 400, 40))
        onSlide = lambda volume: soundVolumeLabel.setText('%d' % volume)
        self.soundVolumeSlider.onSlide.addListener(onSlide)
        self.soundVolumeSlider.onValueChanged.addListener(onSlide)
        self.soundVolumeSlider.setVal(initSndVolume)

        self.soundBox = CheckBox(self.app,
                                 ScaledLocation(450, 500),
                                 text='',
                                 font=font,
                                 colour=(192, 192, 192),
                                 initValue=app.soundSettings.soundEnabled)

        self.buttons = [
            button(app,
                   'save',
                   self.saveSettings, (-100, -75),
                   'midbottom',
                   secondColour=app.theme.colours.white),
            button(app,
                   'cancel',
                   self.onClose.execute, (100, -75),
                   'midbottom',
                   secondColour=app.theme.colours.white)
        ]

        self.elements = text + [
            musicVolumeLabel, self.musicVolumeSlider, self.musicBox,
            soundVolumeLabel, self.soundVolumeSlider, self.soundBox
        ] + self.buttons

    def saveSettings(self, sender=None):
        playMusic, volume, playSound, sndVolume = self.getValues()

        ss = self.app.soundSettings
        ss.musicEnabled = playMusic
        ss.musicVolume = volume
        ss.soundEnabled = playSound
        ss.soundVolume = sndVolume

        ss.save()
        ss.apply()

        self.onClose.execute()

    def getValues(self):

        playMusic = self.musicBox.value
        volume = self.musicVolumeSlider.getVal()

        playSound = self.soundBox.value
        sndVolume = self.soundVolumeSlider.getVal()

        return [playMusic, volume, playSound, sndVolume]
Exemple #12
0
class KeymapTab(Tab):
    def __init__(self, app, onClose=None):
        super(KeymapTab, self).__init__(app, 'Controls')
        self.font = app.screenManager.fonts.bigMenuFont

        self.onClose = Event()
        if onClose is not None:
            self.onClose.addListener(onClose)

        # Break things up into categories
        movement = ['jump', 'down', 'left', 'right']
        menus = ['menu', 'more actions']
        actions = [
            'respawn', 'select upgrade', 'activate upgrade', 'change nickname',
            'ready'
        ]
        misc = ['chat', 'follow']
        upgrades = [
            upgradeClass.action for upgradeClass in sorted(
                allUpgrades, key=lambda upgradeClass: upgradeClass.order)
        ]
        upgrades.append('no upgrade')

        display = ['leaderboard', 'toggle interface', 'toggle terminal']

        actionNames = {
            'select upgrade': 'Select upgrade',
            'activate upgrade': 'Activate upgrade',
            'change nickname': 'Change nickname',
            'chat': 'Chat',
            'down': 'Drop down',
            'follow': 'Auto pan (replay)',
            'jump': 'Jump',
            'leaderboard': 'Show leaderboard',
            'left': 'Move left',
            'menu': 'Main menu',
            'more actions': 'Advanced',
            'no upgrade': 'Deselect upgrade',
            'ready': 'Toggle ready',
            'respawn': 'Respawn',
            'right': 'Move right',
            'status bar': 'Status bar',
            'timer': 'Show timer',
            'toggle interface': 'Toggle HUD',
            'toggle terminal': 'Toggle terminal',
            'zone progress': 'Show zone bar',
        }
        actionNames.update((upgradeClass.action, upgradeClass.name)
                           for upgradeClass in allUpgrades)

        # Organise the categories by column
        self.layout = [
            [movement, menus],
            [actions, display],
            [upgrades, misc],
        ]

        self.errorInfo = TextElement(self.app, '', self.font,
                                     ScaledLocation(512, 580, 'center'))
        self.text = [self.errorInfo]
        self.inputLookup = {}
        xPos = 190

        # Lay everything out automatically.
        keymapFont = self.app.screenManager.fonts.keymapFont
        keymapInputFont = self.app.screenManager.fonts.keymapInputFont
        for column in self.layout:  # Each column
            yPos = 200
            for category in column:  # Each category
                for action in category:  # Each action
                    # Draw action name (eg. Respawn)
                    self.text.append(
                        TextElement(self.app, actionNames[action], keymapFont,
                                    ScaledLocation(xPos, yPos + 6, 'topright'),
                                    self.app.theme.colours.headingColour))

                    # Create input box
                    box = prompt.KeycodeBox(self.app,
                                            ScaledArea(xPos + 10, yPos, 100,
                                                       30),
                                            font=keymapInputFont)
                    box.onClick.addListener(self.setFocus)
                    box.onChange.addListener(self.inputChanged)
                    box.__action = action
                    self.inputLookup[action] = box

                    yPos += 35  # Between items
                yPos += 35  # Between categories
            xPos += 320  # Between columns

        self.elements = self.text + self.inputLookup.values() + [
            button(app,
                   'restore default controls',
                   self.restoreDefaults, (0, -125),
                   'midbottom',
                   secondColour=app.theme.colours.white),
            button(app,
                   'save',
                   self.saveSettings, (-100, -75),
                   'midbottom',
                   secondColour=app.theme.colours.white),
            button(app,
                   'cancel',
                   self.cancel, (100, -75),
                   'midbottom',
                   secondColour=app.theme.colours.white),
        ]

        self.populateInputs()

    def inputChanged(self, box):
        # Remove the old key.
        try:
            oldKey = self.keyMapping.getkey(box.__action)
        except KeyError:
            pass
        else:
            del self.keyMapping[oldKey]

        # Set the new key.
        self.keyMapping[box.value] = box.__action

        # Refresh the display.
        self.refreshInputs()

    def populateInputs(self):
        # Set up the keyboard mapping.
        self.keyMapping = keyboard.KeyboardMapping(keymap.default_game_keys)

        try:
            # Try to load keyboard mappings from the user's personal settings.
            config = open(getPath(user, 'keymap'), 'rU').read()
            self.keyMapping.load(config)
        except IOError:
            pass

        # Refresh the display.
        self.refreshInputs()

    def refreshInputs(self):
        for column in self.layout:
            for category in column:
                for action in category:
                    # Get the current key and put it in the box.
                    try:
                        key = self.keyMapping.getkey(action)
                    except KeyError:
                        key = None
                    self.inputLookup[action].value = key

                    # Make the box white
                    self.inputLookup[action].backColour = (255, 255, 255)

    def restoreDefaults(self):
        self.keyMapping = keyboard.KeyboardMapping(keymap.default_game_keys)
        self.refreshInputs()

        self.incorrectInput(
            "Default controls restored: press 'save' to "
            "confirm", (0, 128, 0))

    def clearBackgrounds(self):
        for action in self.inputLookup:
            self.inputLookup[action].backColour = (255, 255, 255)
        self.setFocus(None)

    def saveSettings(self):
        # Perform the save.
        open(getPath(user, 'keymap'), 'w').write(self.keyMapping.save())

        emptyBoxes = []

        for box in self.inputLookup.itervalues():
            if box.value is None:
                emptyBoxes.append(box)

        if len(emptyBoxes) > 0:
            self.populateInputs()
            for box in emptyBoxes:
                box.backColour = self.app.theme.colours.invalidDataColour

            self.incorrectInput('Warning: some actions have no key',
                                (192, 0, 0))
        else:
            self.mainMenu()

    def incorrectInput(self, string, colour):
        self.errorInfo.setColour(colour)
        self.errorInfo.setText(string)
        self.errorInfo.setFont(self.font)

    def cancel(self):
        self.populateInputs()
        self.mainMenu()

    def mainMenu(self):
        self.incorrectInput('', (0, 0, 0))
        self.clearBackgrounds()
        self.onClose.execute()
Exemple #13
0
class AIAgent(ConcreteAgent):
    '''
    Base class for an AI agent.
    '''
    def __init__(self, game, aiClass, fromLevel, nick=None, *args, **kwargs):
        super(AIAgent, self).__init__(game=game, *args, **kwargs)
        self.aiClass = aiClass
        self.fromLevel = fromLevel
        self._initialisationNick = nick
        self.ai = None
        self.team = None
        self.requestedNick = None
        self._onBotSet = Event()
        self._loop = WeakLoopingCall(self, '_tick')

    def start(self, team=None):
        self.team = team
        self._loop.start(2)

    def stop(self):
        super(AIAgent, self).stop()
        self._loop.stop()
        if self.ai:
            self.ai.disable()
            self.ai = None

    def _tick(self):
        if self.ai is not None:
            return

        if self.fromLevel and self._initialisationNick:
            nick = self._initialisationNick
        else:
            nick = self.aiClass.nick

        if self.team is None:
            teamId = NEUTRAL_TEAM_ID
        else:
            teamId = self.team.id

        self._joinGame(nick, teamId)

    def _joinGame(self, nick, teamId):
        self.requestedNick = nick
        self.sendJoinRequest(teamId, nick, bot=True, fromLevel=self.fromLevel)

    @CannotJoinMsg.handler
    def _joinFailed(self, msg):
        r = msg.reasonId
        nick = self.requestedNick

        if r == GAME_FULL_REASON:
            message = 'full'
        elif r == UNAUTHORISED_REASON:
            message = 'not authenticated'
        elif r == NICK_USED_REASON:
            message = 'nick in use'
        elif r == USER_IN_GAME_REASON:
            message = 'user already in game'  # Should never happen
        elif r == ALREADY_JOINED_REASON:
            message = 'tried to join twice'  # Should never happen
        else:
            message = repr(r)

        log.error('Join failed for AI %r (%s)', nick, message)
        self.stop()

    def setPlayer(self, player):
        if player is None and self.ai:
            self.ai.disable()
            self.ai = None

        super(AIAgent, self).setPlayer(player)

        if player:
            self.requestedNick = None
            self.ai = self.aiClass(self.world, self.localState.player, self)

            self._onBotSet(self.ai)
            self._onBotSet.clear()

    def getBot(self):
        '''
        @return: a Deferred which fires with this agent's Bot object,
            as soon as it has one.
        '''
        d = defer.Deferred()
        if self.ai is not None:
            d.callback(self.ai)
        else:
            self._onBotSet.addListener(d.callback, weak=False)
        return d

    @TickMsg.handler
    def handle_TickMsg(self, msg):
        super(AIAgent, self).handle_TickMsg(msg)
        if self.ai:
            self.ai.consumeMsg(msg)

    @ResyncPlayerMsg.handler
    def handle_ResyncPlayerMsg(self, msg):
        super(AIAgent, self).handle_ResyncPlayerMsg(msg)
        if self.ai:
            self.ai.playerResynced()

    def defaultHandler(self, msg):
        super(AIAgent, self).defaultHandler(msg)
        if self.ai:
            self.ai.consumeMsg(msg)
Exemple #14
0
class DisplaySettingsTab(Tab, framework.TabFriendlyCompoundElement):
    def __init__(self, app, onClose=None):
        super(DisplaySettingsTab, self).__init__(app, 'Display')

        self.onClose = Event()
        if onClose is not None:
            self.onClose.addListener(onClose)

        font = self.app.screenManager.fonts.bigMenuFont
        smallNoteFont = self.app.screenManager.fonts.smallNoteFont

        colour = self.app.theme.colours.headingColour

        def mkText(text, x, y, textFont=font, anchor='topright'):
            return TextElement(self.app, text, textFont,
                               ScaledLocation(x, y, anchor), colour)

        self.text = [
            mkText('X', 640, 280),
            mkText('Screen resolution', 430, 280),
            mkText('Fullscreen mode', 430, 360),
            mkText('Graphics detail', 430, 440),
            mkText('low', 460, 475, textFont=smallNoteFont, anchor='midtop'),
            mkText('high', 845, 475, textFont=smallNoteFont, anchor='midtop'),
            mkText('Show timings', 430, 525),
        ]

        self.invalidInputText = TextElement(self.app, '', font,
                                            ScaledLocation(512, 230, 'midtop'),
                                            (192, 0, 0))

        self.widthInput = prompt.InputBox(self.app,
                                          ScaledArea(460, 265, 150, 60),
                                          initValue=str(
                                              self.app.screenManager.size[0]),
                                          font=font,
                                          maxLength=4,
                                          validator=prompt.intValidator)

        self.widthInput.onEnter.addListener(lambda sender: self.saveSettings())
        self.widthInput.onClick.addListener(self.setFocus)
        self.widthInput.onTab.addListener(self.tabNext)

        self.heightInput = prompt.InputBox(self.app,
                                           ScaledArea(652, 265, 150, 60),
                                           initValue=str(
                                               self.app.screenManager.size[1]),
                                           font=font,
                                           maxLength=4,
                                           validator=prompt.intValidator)

        self.heightInput.onEnter.addListener(
            lambda sender: self.saveSettings())
        self.heightInput.onClick.addListener(self.setFocus)
        self.heightInput.onTab.addListener(self.tabNext)

        self.tabOrder = [self.widthInput, self.heightInput]

        self.fullscreenBox = CheckBox(
            self.app,
            ScaledLocation(460, 365),
            text='',
            font=font,
            colour=(192, 192, 192),
            initValue=self.app.screenManager.isFullScreen(),
        )
        self.fullscreenBox.onValueChanged.addListener(self.fullscreenChanged)

        displaySettings = app.displaySettings

        self.detailSlider = Slider(
            self.app,
            ScaledArea(460, 430, 390, 40),
            bounds=(0, len(displaySettings.DETAIL_LEVELS) - 1),
            snap=True)
        self.detailSlider.setVal(
            displaySettings.DETAIL_LEVELS.index(displaySettings.detailLevel))
        self.detailSlider.onValueChanged.addListener(self.detailChanged)

        self.showTimingsBox = CheckBox(
            self.app,
            ScaledLocation(460, 530),
            text='',
            font=font,
            colour=(192, 192, 192),
            initValue=displaySettings.showTimings,
        )

        self.input = [
            self.widthInput, self.heightInput, self.widthInput,
            self.fullscreenBox, self.detailSlider, self.showTimingsBox
        ]

        self.elements = self.text + self.input + [
            self.invalidInputText,
            button(app,
                   'save',
                   self.saveSettings, (-100, -75),
                   'midbottom',
                   secondColour=app.theme.colours.white),
            button(app,
                   'cancel',
                   self.cancelMenu, (100, -75),
                   'midbottom',
                   secondColour=app.theme.colours.white),
        ]
        self.setFocus(self.widthInput)

    def detailChanged(self, newLevel):
        pass

    def cancelMenu(self):
        self.fullscreenBox.setValue(self.app.screenManager.isFullScreen())
        self.showTimingsBox.setValue(self.app.displaySettings.showTimings)
        self.heightInput.setValue(str(self.app.screenManager.size[1]))
        self.widthInput.setValue(str(self.app.screenManager.size[0]))

        self.onClose.execute()

    def saveSettings(self):
        displaySettings = self.app.displaySettings

        height = self.getInt(self.heightInput.value)
        width = self.getInt(self.widthInput.value)
        fullScreen = self.fullscreenBox.value
        detailLevel = displaySettings.DETAIL_LEVELS[self.detailSlider.getVal()]
        showTimings = self.showTimingsBox.value

        # The resolutionList is used when fullScreen is true.
        resolutionList = pygame.display.list_modes()
        resolutionList.sort()
        minResolution = resolutionList[0]
        maxResolution = resolutionList[-1]

        if not fullScreen:
            minResolution = (320, 240)

        # These values are used when fullScreen is false.
        widthRange = (minResolution[0], maxResolution[0])
        heightRange = (minResolution[1], maxResolution[1])

        if not widthRange[0] <= width <= widthRange[1]:
            self.incorrectInput('Screen width must be between %d and %d' %
                                (widthRange[0], widthRange[1]))
            width = None
            return
        if not heightRange[0] <= height <= heightRange[1]:
            self.incorrectInput('Screen height must be between %d and %d' %
                                (heightRange[0], heightRange[1]))
            height = None
            return
        if fullScreen:
            selectedResolution = (width, height)
            if selectedResolution not in resolutionList:
                self.incorrectInput('Selected resolution is not valid for '
                                    'this display')
                height = width = None
                return

        self.incorrectInput('')

        # Save these values.
        displaySettings.fullScreen = fullScreen
        displaySettings.detailLevel = detailLevel
        displaySettings.showTimings = showTimings
        if fullScreen:
            displaySettings.fsSize = (width, height)
        else:
            displaySettings.size = (width, height)

        # Write to file and apply.
        displaySettings.save()
        displaySettings.apply()

        self.onClose.execute()

    def getInt(self, value):
        if value == '':
            return 0
        return int(value)

    def incorrectInput(self, string):
        self.invalidInputText.setText(string)
        self.invalidInputText.setFont(self.app.screenManager.fonts.bigMenuFont)

    def fullscreenChanged(self, element):
        # If the resolution boxes haven't been touched, swap their values to
        # the appropriate resolution for the new mode.

        height = self.getInt(self.heightInput.value)
        width = self.getInt(self.widthInput.value)
        fullScreen = self.fullscreenBox.value

        if fullScreen:
            # Going to full screen mode.
            if (width, height) != self.app.displaySettings.size:
                return
            width, height = self.app.displaySettings.fsSize
        else:
            # Going from full screen mode.
            if (width, height) != self.app.displaySettings.fsSize:
                return
            width, height = self.app.displaySettings.size

        self.heightInput.setValue(str(height))
        self.widthInput.setValue(str(width))
Exemple #15
0
class CreditsScreen(framework.CompoundElement):
    def __init__(self,
                 app,
                 colour,
                 onCancel=None,
                 speed=None,
                 loop=True,
                 startOff=False,
                 backText='back to main menu',
                 highlight=(255, 255, 255)):
        super(CreditsScreen, self).__init__(app)
        self.colour = colour
        self.highlight = highlight

        self.onCancel = Event()
        if onCancel is not None:
            self.onCancel.addListener(onCancel)

        f = open(getPath(startupMenu, 'credits.txt'), 'rU', encoding='utf-8')
        area = ScaledArea(50, 130, 900, 540)
        fonts = {
            'body': self.app.fonts.creditsFont,
            'h1': self.app.fonts.creditsH1,
            'h2': self.app.fonts.creditsH2
        }
        text = f.read()
        if not startOff:
            # Make at least some room on top and bottom
            text = '\n' * 3 + text + '\n' * 2
        self.credits = scrollingText.ScrollingText(self.app,
                                                   area,
                                                   text,
                                                   self.colour,
                                                   fonts,
                                                   textAlign='middle',
                                                   loop=loop,
                                                   startOff=startOff)
        self.credits.setShadowColour((192, 192, 192))

        self.credits.setAutoscroll(True)
        if speed is None:
            speed = 80
        self.credits.setSpeed(ScalingSpeed(speed))
        self.credits.setBorder(False)

        self.backText = backText
        cancelButton = self.button(backText, self.onCancel.execute, (-50, -30),
                                   'bottomright')

        self.elements = [self.credits, cancelButton]

    @property
    def finished(self):
        return self.credits.reachedEnd

    def setColour(self, colour):
        self.colour = colour
        self.elements[-1] = self.button(self.backText, self.onCancel.execute,
                                        (-50, -30), 'bottomright')
        self.credits.setColour(colour)

    def button(self, text, onClick, pos, anchor='topleft', hugeFont=False):
        pos = Location(
            ScaledScreenAttachedPoint(ScaledSize(pos[0], pos[1]), anchor),
            anchor)
        if hugeFont:
            font = self.app.screenManager.fonts.hugeMenuFont
        else:
            font = self.app.screenManager.fonts.bigMenuFont
        result = elements.TextButton(self.app, pos, text, font, self.colour,
                                     self.highlight)
        result.onClick.addListener(lambda sender: onClick())
        return result

    def restart(self):
        self.credits.returnToTop()
class InputBox(framework.Element):
    pxFromLeft = 2
    pxFromRight = 5
    pxToSkipWhenCursorGoesToTheRight = 90
    cursorFlashTime = 0.6

    def __init__(self,
                 app,
                 area,
                 initValue='',
                 validator=None,
                 font=None,
                 colour=(255, 255, 255),
                 maxLength=None,
                 onClick=None,
                 onEnter=None,
                 onTab=None,
                 onEsc=None,
                 onEdit=None):
        super(InputBox, self).__init__(app)

        self.area = area
        self.onClick = Event()
        if onClick is not None:
            self.onClick.addListener(onClick)
        self.onEnter = Event()
        if onEnter is not None:
            self.onEnter.addListener(onEnter)
        self.onTab = Event()
        if onTab is not None:
            self.onTab.addListener(onTab)
        self.onEsc = Event()
        if onEsc is not None:
            self.onEsc.addListener(onEsc)
        self.onEdit = Event()
        if onEdit is not None:
            self.onEdit.addListener(onEdit)

        if validator:
            self.validator = validator
        else:
            self.validator = lambda x: True

        if font:
            self.font = font
        else:
            self.font = self.app.screenManager.fonts.defaultTextBoxFont

        self.value = initValue
        self.maxLength = maxLength
        self.fontColour = (0, 0, 0)
        self.backColour = colour
        self.cursorColour = (255, 0, 0)
        self._cursorVisible = True
        self._timeToFlash = self.cursorFlashTime

        # Calculate the white part of the input area
        self.cursorIndex = len(self.value)
        self.offset = 0

    def clear(self):
        self.setValue('')

    def setValue(self, value):
        self.value = value
        self.offset = 0
        self.cursorIndex = len(self.value)

    def getValue(self):
        return self.value

    def setFont(self, font):
        self.font = font

    def setValidator(self, validator):
        self.validator = validator

    def setMaxLength(self, length):
        self.maxLength = length

    def _getSize(self):
        return self._getRect().size

    def _getCursorImage(self):
        s = pygame.Surface((2,
                            min(int(self.font.getHeight(self.app)),
                                self._getSize()[1] * 0.8)))
        s.fill(self.cursorColour)
        return s

    def _getRect(self):
        return self.area.getRect(self.app)

    def _getPt(self):
        return self._getRect().topleft

    def setFontColour(self, fontColour):
        self.fontColour = fontColour

    def setBackColour(self, backColour):
        self.backColour = backColour

    def setCursorColour(self, cursorColour):
        self.cursorColour = cursorColour

    def gotFocus(self):
        self._cursorVisible = True
        self._timeToFlash = self.cursorFlashTime

    def _renderText(self):
        '''
        Provided so that PasswordBox can override it.
        '''
        return self.font.render(self.app, self.value, True, self.fontColour,
                                self.backColour)

    def draw(self, surface):
        rect = self._getRect()
        size = rect.size
        pos = rect.topleft
        # Fill the input area with the specified colour
        surface.fill(self.backColour, rect)

        # Put what's currently inputted into the input area
        inputText = self._renderText()

        # Adjust offset
        cursorPos = self._getCursorPos()
        if cursorPos < self.offset:
            self.offset = max(
                0, self.offset - self.pxToSkipWhenCursorGoesToTheRight)
        else:
            minOffset = cursorPos - size[0] + self.pxFromRight + self.pxFromLeft
            if self.offset < minOffset:
                self.offset = minOffset

        text_rect = inputText.get_rect()
        text_rect.centery = rect.centery
        diff = (text_rect.height - rect.height) / 2
        # Find the currently-displaying text (based on where the cursor is):
        area = pygame.Rect(self.offset, diff, size[0] - self.pxFromRight,
                           rect.height)

        # Put the text on the screen
        surface.blit(inputText, (pos[0] + self.pxFromLeft, rect.top), area)

        # Add the cursor where it is.
        if self.hasFocus and self._cursorVisible:
            cs = self._getCursorImage()
            cursor_rect = cs.get_rect()
            cursor_rect.centery = rect.centery
            surface.blit(
                cs,
                (pos[0] + self._getCursorPos() - self.offset, cursor_rect.top))

    def _getCursorPos(self):
        return self._getFontSize(self.value[:self.cursorIndex])[0] + 1

    def _getFontSize(self, text):
        '''
        Provided so that PasswordBox can override it.
        '''
        return self.font.size(self.app, text)

    # Get the index of the position clicked
    def __getCursorIndex(self, clickOffset):
        totalOffset = clickOffset + self.offset + self.pxFromLeft
        i = 1
        fontOffset = 0
        last = 0
        while fontOffset < totalOffset and i <= len(self.value):
            last = fontOffset
            fontOffset = self._getFontSize(self.value[:i])[0]
            i += 1
        if (fontOffset - totalOffset) <= (totalOffset - last):
            return i - 1
        else:
            return i - 2
        return i

    def processEvent(self, event):
        '''Processes the key press. If we use the event, return nothing.
        If we do not use it,
        return the event so something else can use it.'''

        if self.hasFocus and event.type == pygame.KEYDOWN:
            self._cursorVisible = True
            self._timeToFlash = self.cursorFlashTime
            if event.key in (pygame.K_RETURN, pygame.K_KP_ENTER):
                self.onEnter.execute(self)
                return None
            if event.key == pygame.K_ESCAPE:
                self.onEsc.execute(self)
                return None
            if event.key == pygame.K_TAB:
                self.onTab.execute(self)
                return None

            # Delete the letter behind the cursor
            if event.key == pygame.K_BACKSPACE:
                # Make sure there's something in the string behind the cursor
                # first, don't want to kill what's not there
                if self.cursorIndex > 0:
                    self.value = (self.value[:self.cursorIndex - 1] +
                                  self.value[self.cursorIndex:])
                    self.cursorIndex -= 1
                    self.onEdit.execute(self)

            elif event.key == pygame.K_DELETE:
                # Make sure there's something in the string in front of the
                # cursor first, don't want to kill what's not there
                if self.cursorIndex < len(self.value):
                    self.value = (self.value[:self.cursorIndex] +
                                  self.value[self.cursorIndex + 1:])
                    self.onEdit.execute(self)

            elif event.key == pygame.K_LEFT:
                if self.cursorIndex > 0:
                    self.cursorIndex -= 1

            elif event.key == pygame.K_RIGHT:
                if self.cursorIndex < len(self.value):
                    self.cursorIndex += 1

            elif event.key == pygame.K_END:
                self.cursorIndex = len(self.value)

            elif event.key == pygame.K_HOME:
                self.cursorIndex = 0
                self.offset = 0

            # Add the character to our string
            elif event.unicode == '':
                return event
            else:
                # Validate the input.
                if not self.validator(event.unicode):
                    return event

                # Check the maxLength
                if self.maxLength is not None:
                    if len(self.value) >= self.maxLength:
                        return event

                # Add the typed letter to the string
                self.value = (self.value[:self.cursorIndex] + event.unicode +
                              self.value[self.cursorIndex:])
                self.cursorIndex += len(event.unicode)
                self.onEdit.execute(self)

        else:
            rect = self._getRect()

            if (event.type == pygame.MOUSEBUTTONDOWN
                    and rect.collidepoint(event.pos) and event.button == 1):

                self.onClick.execute(self)
                xOffset = event.pos[0] - rect.left
                self.cursorIndex = self.__getCursorIndex(xOffset)
                self._timeToFlash = self.cursorFlashTime
                self._cursorVisible = True
            else:
                # It's not a keydown event. Pass it on.
                return event

    def tick(self, deltaT):
        self._timeToFlash -= deltaT
        while self._timeToFlash < 0:
            self._timeToFlash += self.cursorFlashTime
            self._cursorVisible = not self._cursorVisible
class KeymapTab(Tab):
    def __init__(self, app, onClose=None):
        super(KeymapTab, self).__init__(app, 'Controls')
        self.font = app.screenManager.fonts.bigMenuFont

        self.onClose = Event()
        if onClose is not None:
            self.onClose.addListener(onClose)

        # Break things up into categories
        movement = [
            ACTION_JUMP, ACTION_DOWN, ACTION_LEFT, ACTION_RIGHT, ACTION_HOOK
        ]
        menus = [ACTION_MAIN_MENU, ACTION_MORE_MENU]
        actions = [
            ACTION_UPGRADE_MENU, ACTION_USE_UPGRADE, ACTION_EDIT_PLAYER_INFO,
            ACTION_READY, ACTION_SHOW_TRAJECTORY, ACTION_EMOTE
        ]
        misc = [ACTION_CHAT, ACTION_FOLLOW]
        upgrades = [
            upgradeClass.action for upgradeClass in sorted(
                allUpgrades, key=lambda upgradeClass: upgradeClass.order)
        ]
        upgrades.append(ACTION_CLEAR_UPGRADE)

        display = [
            ACTION_LEADERBOARD_TOGGLE, ACTION_HUD_TOGGLE,
            ACTION_TERMINAL_TOGGLE
        ]

        actionNames = {
            ACTION_EDIT_PLAYER_INFO: 'Change nick / hat',
            ACTION_CHAT: 'Chat',
            ACTION_CLEAR_UPGRADE: 'Deselect upgrade',
            ACTION_DOWN: 'Drop down',
            ACTION_FOLLOW: 'Auto pan (replay)',
            ACTION_HOOK: 'Grappling hook',
            ACTION_HUD_TOGGLE: 'Toggle HUD',
            ACTION_JUMP: 'Jump',
            ACTION_LEADERBOARD_TOGGLE: 'Show leaderboard',
            ACTION_LEFT: 'Move left',
            ACTION_MAIN_MENU: 'Main menu',
            ACTION_MORE_MENU: 'Advanced',
            ACTION_READY: 'Toggle ready',
            ACTION_RIGHT: 'Move right',
            ACTION_TERMINAL_TOGGLE: 'Toggle terminal',
            ACTION_UPGRADE_MENU: 'Select upgrade',
            ACTION_USE_UPGRADE: 'Activate upgrade',
            ACTION_SHOW_TRAJECTORY: 'Show trajectory',
            ACTION_EMOTE: 'Emote',
        }
        actionNames.update((upgradeClass.action, upgradeClass.name)
                           for upgradeClass in allUpgrades)

        # Organise the categories by column
        self.layout = [
            [movement, menus],
            [actions, display],
            [upgrades, misc],
        ]

        self.errorInfo = TextElement(self.app, '', self.font,
                                     ScaledLocation(512, 580, 'center'))
        self.text = [self.errorInfo]
        self.inputLookup = {}
        xPos = 210

        # Lay everything out automatically.
        keymapFont = self.app.screenManager.fonts.keymapFont
        keymapInputFont = self.app.screenManager.fonts.keymapInputFont
        for column in self.layout:  # Each column
            yPos = 200
            for category in column:  # Each category
                for action in category:  # Each action
                    # Draw action name (eg. Respawn)
                    self.text.append(
                        TextElement(self.app, actionNames[action], keymapFont,
                                    ScaledLocation(xPos, yPos + 6, 'topright'),
                                    self.app.theme.colours.headingColour))

                    # Create input box
                    box = prompt.KeycodeBox(self.app,
                                            ScaledArea(xPos + 10, yPos, 100,
                                                       30),
                                            font=keymapInputFont,
                                            acceptMouse=True)
                    box.onClick.addListener(self.setFocus)
                    box.onChange.addListener(self.inputChanged)
                    box.__action = action
                    self.inputLookup[action] = box

                    yPos += 35  # Between items
                yPos += 35  # Between categories
            xPos += 310  # Between columns

        self.elements = self.text + list(self.inputLookup.values()) + [
            button(app,
                   'restore default controls',
                   self.restoreDefaults, (0, -125),
                   'midbottom',
                   secondColour=app.theme.colours.white),
            button(app,
                   'save',
                   self.saveSettings, (-100, -75),
                   'midbottom',
                   secondColour=app.theme.colours.white),
            button(app,
                   'cancel',
                   self.cancel, (100, -75),
                   'midbottom',
                   secondColour=app.theme.colours.white),
        ]

        self.populateInputs()

    def inputChanged(self, box):
        # Remove the old key.
        try:
            oldKey = self.keyMapping.getkey(box.__action)
        except KeyError:
            pass
        else:
            del self.keyMapping[oldKey]

        # Set the new key.
        self.keyMapping[box.value] = box.__action

        # Refresh the display.
        self.refreshInputs()

    def populateInputs(self):
        # Set up the keyboard mapping.
        self.keyMapping = keyboard.KeyboardMapping(keymap.default_game_keys)

        try:
            # Try to load keyboard mappings from the user's personal settings.
            with open(getPath(user, 'keymap'), 'r') as f:
                config = f.read()
            self.keyMapping.load(config)
        except IOError:
            pass

        # Refresh the display.
        self.refreshInputs()

    def refreshInputs(self):
        for column in self.layout:
            for category in column:
                for action in category:
                    # Get the current key and put it in the box.
                    try:
                        key = self.keyMapping.getkey(action)
                    except KeyError:
                        key = None
                    self.inputLookup[action].value = key

                    # Make the box white
                    self.inputLookup[action].backColour = (255, 255, 255)

    def restoreDefaults(self):
        self.keyMapping = keyboard.KeyboardMapping(keymap.default_game_keys)
        self.refreshInputs()

        self.showMessage(
            "Default controls restored: press 'save' to "
            "confirm", (0, 128, 0))

    def clearBackgrounds(self):
        for action in self.inputLookup:
            self.inputLookup[action].backColour = (255, 255, 255)
        self.setFocus(None)

    def saveSettings(self):
        # Perform the save.
        open(getPath(user, 'keymap'), 'w').write(self.keyMapping.save())
        self.mainMenu()

    def showMessage(self, string, colour):
        self.errorInfo.setColour(colour)
        self.errorInfo.setText(string)
        self.errorInfo.setFont(self.font)

    def cancel(self):
        self.populateInputs()
        self.mainMenu()

    def mainMenu(self):
        self.showMessage('', (0, 0, 0))
        self.clearBackgrounds()
        self.onClose.execute()