class TextCell(Cell): def __init__(self, app, row, column): super(TextCell, self).__init__(app, row, column) textAlign = self.styleGet('textAlign') self.textElement = TextElement( self.app, '', self.styleGet('font'), Location(CellAttachedPoint((0, 0), self, textAlign), textAlign), colour=self.styleGet('foreColour')) self.elements = [self.textElement] self._oldText = '' def setText(self, text): if text != self._oldText: self.textElement.setText(text) self._styleChanged = True self._oldText = text def _update(self): self.textElement.setFont(self.styleGet('font')) self.textElement.setColour(self.styleGet('foreColour')) textAlign = self.styleGet('textAlign') self.textElement.pos.anchor = textAlign self.textElement.pos.point.attachedAt = textAlign self.textElement.setShadow(self.styleGet('hasShadow')) self.textElement.setShadowColour(self.styleGet('shadowColour'))
class ConnectingScreen(framework.CompoundElement): def __init__(self, app, serverName='server', onCancel=None): super(ConnectingScreen, self).__init__(app) colours = app.theme.colours self.text = TextElement(self.app, 'Connecting to %s...' % serverName, self.app.screenManager.fonts.bigMenuFont, ScaledLocation(512, 384, 'center'), colour=colours.connectingColour) button = TextButton( self.app, Location(ScaledScreenAttachedPoint(ScaledSize(0, 300), 'center'), 'center'), 'cancel', self.app.screenManager.fonts.bigMenuFont, colours.mainMenuColour, colours.white, onClick=onCancel) self.onCancel = button.onClick self.elements = [button, self.text] def setServer(self, serverName): self.text.setText('Connecting to %s...' % serverName) self.text.setFont(self.app.screenManager.fonts.bigMenuFont)
class UpgradeDisplay(framework.CompoundElement): def __init__(self, app): super(UpgradeDisplay, self).__init__(app) self.player = None self.upgrade = None self.coinsText = TextElement( self.app, '', self.app.screenManager.fonts.coinsDisplayFont, Location(Screen(0.65, 0.01), 'midtop'), self.app.theme.colours.coinsDisplayColour, shadow=True) self.elements = [self.coinsText] def refresh(self): self.setUpgrade(self.upgrade, self.player) def tick(self, deltaT): super(UpgradeDisplay, self).tick(deltaT) if self.player: self.coinsText.setText('${}'.format(self.player.coins)) else: self.coinsText.setText('') def setUpgrade(self, upgradeType, player): self.player = player self.upgrade = upgradeType if player is None or upgradeType is None: self.elements = [self.coinsText] else: pos = Location(Screen(0.6, 0), 'midtop') image = self.app.theme.sprites.upgradeImage(upgradeType) area = Area( RelativePoint(Screen(0.6, 0), (0, 52)), ScaledSize(50, 10), 'midtop') self.elements = [ PictureElement(self.app, image, pos), self.coinsText, ] if upgradeType.enabled: self.elements.append( CoinGauge(self.app, area, player, upgradeType)) else: self.elements.append( TextElement(self.app, 'DISABLED', self.app.screenManager.fonts.ingameMenuFont, Location(CanvasX(620, 68), 'midbottom'), self.app.theme.colours.errorMessageColour))
class WinnerMsg(framework.CompoundElement): def __init__(self, app): super(WinnerMsg, self).__init__(app) self.winnerMsg = TextElement(app, '', app.screenManager.fonts.winMessageFont, Location(Screen(0.5, 0.05), 'midtop'), (64, 64, 64)) self.background = SolidRect( app, (128, 128, 128), 150, PaddedRegion(self.winnerMsg, ScaledScalar(15))) self.elements = [] def show(self, text, colour): self.winnerMsg.setText(text) self.background.colour = colour self.background.border = colour self.background.refresh() self.elements = [self.background, self.winnerMsg] def hide(self): self.elements = []
class PasswordGUI(DialogBox): def __init__(self, app): size = Canvas(512, 384) DialogBox.__init__(self, app, size, 'Please authenticate') self._deferred = None self._host = None font = app.screenManager.fonts.defaultTextBoxFont btnColour = app.theme.colours.dialogButtonColour highlightColour = app.theme.colours.black errorTextColour = app.theme.colours.errorColour self.tabContainer = TabContainer( app, Region(topleft=self.Relative(0, 0), size=self.Relative(1, 0.75)), font, app.theme.colours.tabContainerColour) self.tabContainer.addTab(LoginTab(app)) self.tabContainer.addTab(CreateAccountTab(app)) self.errorText = TextElement( app, '', font, Location(self.Relative(0.5, 0.8), 'center'), errorTextColour) font = app.screenManager.fonts.bigMenuFont self.elements = [ self.tabContainer, self.errorText, TextButton(app, Location(self.Relative(0.3, 0.9), 'center'), 'Ok', font, btnColour, highlightColour, onClick=self.okClicked), TextButton(app, Location(self.Relative(0.7, 0.9), 'center'), 'Cancel', font, btnColour, highlightColour, onClick=self.cancelClicked), ] self.cancelled = False def setCancelled(self, cancelled): self.cancelled = cancelled def processEvent(self, event): if event.type == pygame.KEYDOWN and event.key in (pygame.K_KP_ENTER, pygame.K_RETURN): self.okClicked() return None else: return DialogBox.processEvent(self, event) def cancelClicked(self, element=None): self.close() self._deferred.callback(None) def okClicked(self, element=None): if self.tabContainer.selectedIndex == 1: create = True # Check that passwords match. tab = self.tabContainer.tabs[1] if tab.passwordField.value != tab.passwordField2.value: self.setErrorText('Passwords do not match!') return else: create = False tab = self.tabContainer.tabs[0] username = tab.usernameField.value password = tab.passwordField.value if len(username) == 0: self.setErrorText('You must give a username!') return if len(password) == 0: self.setErrorText('Password cannot be blank!') return self.close() self.app.identitySettings.usernames[self._host] = username self._deferred.callback((create, username, password)) def getPassword(self, host, errorMsg=''): if self.showing: raise PasswordGUIError('PasswordGUI already showing') if self.cancelled: self.cancelled = False result = self._deferred = defer.Deferred() WeakCallLater(0.1, result, 'callback', None) return result self.setCaption(host) self.tabContainer.tabs[0].reset(host) self.tabContainer.tabs[1].reset() self.setErrorText(errorMsg) self.show() self._host = host result = self._deferred = defer.Deferred() return result def setErrorText(self, text): self.errorText.setText(text)
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)
class GameProgressBar(framework.CompoundElement): def __init__(self, app, world, gameViewer): super(GameProgressBar, self).__init__(app) self.world = world self.gameViewer = gameViewer self.app = app self.disrupt = False self.disruptTick = 0 colours = app.theme.colours self.black = colours.black self.blue = colours.team1Mn_zone self.red = colours.team2Mn_zone self.grey = colours.zoneBarNeutral self.barFont = app.screenManager.fonts.zoneBarFont # Define a few constants to make things easier self.triangleLength = 25 self.sideDistance = 0 self.barHeight = self.gameViewer.zoneBarHeight self.textSpace = 130 self.neutralTextSpace = 15 self._oldRect = None self._oldScale = None self.width = None self.mapLeft = None self.mapRight = None self.mapBottom = None self.xFarLeft = None self.xFarRight = None self.xLeft = None self.xRight = None self.yTop = None self.yBottom = None self.disruptAnimation = None self.calculateSize() def calculateSize(self): minimapRect = self.gameViewer.miniMap.getRect() scaleFactor = self.app.screenManager.scaleFactor if minimapRect == self._oldRect and scaleFactor == self._oldScale: return self._oldRect = minimapRect self._oldScale = scaleFactor # Width will always be between 1 and 2 inclusive self.width = min(max(int(3 * scaleFactor), 1), 2) self.mapLeft = minimapRect.left self.mapRight = minimapRect.right self.mapBottom = minimapRect.bottom self.xFarLeft = self.mapLeft + self.sideDistance self.xFarRight = self.mapRight - self.sideDistance self.xLeft = self.xFarLeft + self.triangleLength self.xRight = self.xFarRight - self.triangleLength self.yTop = self.mapBottom - 1 self.yBottom = self.yTop + self.barHeight # Create the disrupted animation self.disruptAnimation = self.createDisrupted() def draw(self, surface): if not self.world.map: return self.calculateSize() super(GameProgressBar, self).draw(surface) if self.disrupt and random.random() < 0.2: disrupt = True else: disrupt = False # Automatically generated constants yTop = self.yTop yBottom = self.yBottom yText = yTop xFarLeft = self.xFarLeft xFarRight = self.xFarRight xLeft = self.xLeft xRight = self.xRight # Define the coordinates for the static shapes border = [ (xFarLeft, yTop), (xFarRight, yTop), (xRight, yBottom), (xLeft, yBottom), ] blueTriangle = [ (xFarLeft, yTop), (xLeft, yTop), (xLeft, yBottom), ] redTriangle = [ (xFarRight, yTop), (xRight, yTop), (xRight, yBottom), ] # Get the information we need blueScore = self.getBlueScore() blueProportion = self.getBlueProportion() redScore = self.getRedScore() redProportion = self.getRedProportion() neutralScore = self.getNeutralScore() neutralProportion = self.getNeutralProportion() gameOver = self.isGameOver() # Define the coordinates for the dynamic shapes mutableBarLength = xRight - xLeft if redProportion > 0: blueBarLength = int(round(mutableBarLength * blueProportion)) else: blueBarLength = mutableBarLength if blueProportion > 0: redBarLength = int(round(mutableBarLength * redProportion)) else: redBarLength = mutableBarLength xNeutral = ((xLeft + blueBarLength) + (xRight - redBarLength)) / 2 xFirstBar = xLeft + blueBarLength xSecondBar = xRight - redBarLength blueBar = [ (xLeft, yTop), (xLeft + blueBarLength, yTop), (xLeft + blueBarLength, yBottom), (xLeft, yBottom), ] redBar = [ (xRight - redBarLength, yTop), (xRight, yTop), (xRight, yBottom), (xRight - redBarLength, yBottom), ] greyBar = [ (xLeft + blueBarLength + 1, yTop), (xRight - redBarLength - 1, yTop), (xRight - redBarLength - 1, yBottom), (xLeft + blueBarLength + 1, yBottom), ] # Draw the two triangles on the sides if blueProportion > 0: pygame.draw.polygon(surface, self.blue, blueTriangle, 0) elif redProportion > 0: pygame.draw.polygon(surface, self.red, blueTriangle, 0) else: pygame.draw.polygon(surface, self.grey, blueTriangle, 0) if redProportion > 0: pygame.draw.polygon(surface, self.red, redTriangle, 0) elif blueProportion > 0: pygame.draw.polygon(surface, self.blue, redTriangle, 0) else: pygame.draw.polygon(surface, self.grey, redTriangle, 0) # Draw the team colours if blueProportion > 0: pygame.draw.polygon(surface, self.blue, blueBar, 0) if redProportion > 0: pygame.draw.polygon(surface, self.red, redBar, 0) if neutralProportion > 0 and (blueScore > 0) == (redScore > 0): allNeutral = True pygame.draw.polygon(surface, self.grey, greyBar, 0) else: allNeutral = False # Draw the black seperator line(s) if not gameOver: pygame.draw.line(surface, self.black, (xFirstBar, yTop), (xFirstBar, yBottom), self.width) if neutralScore != 0: pygame.draw.line(surface, self.black, (xSecondBar, yTop), (xSecondBar, yBottom), self.width) # Draw the disruption if disrupt: surface.blit(self.disruptAnimation.getImage(), (self.xFarLeft, self.yTop)) # Draw the border last so that it goes on top colours = self.app.theme.colours pygame.draw.polygon(surface, self.black, border, self.width) pygame.draw.line(surface, colours.minimapBorder, (self.mapLeft, self.mapBottom - 1), (self.mapRight, self.mapBottom - 1), 2) # Define the necessary text self.blueText = TextElement(self.app, '', self.barFont, Location((xFirstBar - 5, yText), 'topright'), colour=colours.black) self.neutralText = TextElement(self.app, '', self.barFont, Location((xNeutral, yText), 'midtop'), colour=colours.black) self.redText = TextElement(self.app, '', self.barFont, Location((xSecondBar + 7, yText), 'topleft'), colour=colours.black) if not disrupt: blueString = self.getScoreText(blueScore) redString = self.getScoreText(redScore) if xSecondBar - xFirstBar > self.neutralTextSpace or allNeutral: neutralString = self.getScoreText(neutralScore) else: neutralString = '' self.blueText.setText(blueString) self.redText.setText(redString) self.neutralText.setText(neutralString) else: self.blueText.setText('') self.redText.setText('') self.neutralText.setText('') # Draw the text self.blueText.draw(surface) self.redText.draw(surface) self.neutralText.draw(surface) def createDisrupted(self): screens = [] for a in range(0, 4): screen = (pygame.surface.Surface((self.xFarRight - self.xFarLeft, self.gameViewer.zoneBarHeight))) x = y = 0 xL = 1 xR = self.xFarRight - self.xFarLeft - 1 rect = pygame.rect.Rect(0, 0, 2, 2) disruptColours = [self.red, self.blue] while y < self.gameViewer.zoneBarHeight: while x < xR: rect.left = x rect.top = y pygame.draw.rect(screen, random.choice(disruptColours), rect, 0) x += 2 xL += 2 xR -= 2 x = xL y += 2 screen.set_colorkey((0, 0, 0)) screens.append(screen) return Animation(0.1, timeNow, *screens) def getScoreText(self, score): raise NotImplementedError def getBlueScore(self): raise NotImplementedError def getRedScore(self): raise NotImplementedError def getNeutralScore(self): raise NotImplementedError def getBlueProportion(self): raise NotImplementedError def getNeutralProportion(self): raise NotImplementedError def getRedProportion(self): raise NotImplementedError def isGameOver(self): raise NotImplementedError
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
class AchievementBox(framework.CompoundElement): achievementDefs = availableAchievements def __init__(self, app, player, achievementId): super(AchievementBox, self).__init__(app) self.app = app self.player = player self.achievements = [achievementId] self.width = 453 self.height = 75 self._setColours() self.area = Area( FullScreenAttachedPoint(ScaledSize(0, -100), 'midbottom'), ScaledSize(self.width, self.height), 'midbottom') self.smlBox = Area( FullScreenAttachedPoint(ScaledSize(-self.width / 2 + 6, -104), 'midbottom'), ScaledSize(66, 66), 'bottomleft') self.titleText = TextElement( self.app, "ACHIEVEMENT UNLOCKED!", self.fonts.achievementTitleFont, Location( FullScreenAttachedPoint( ScaledSize(73 / 2, -100 - self.height + 10), 'midbottom'), 'midtop'), self.borderColour) self.nameText = TextElement( self.app, self.achievementDefs.getAchievementDetails(achievementId)[0], self.fonts.achievementNameFont, Location( FullScreenAttachedPoint(ScaledSize(73 / 2, -100 - 13), 'midbottom'), 'midbottom'), self.colours.black) self.elements = [self.titleText, self.nameText] self._updateImage() self.cycler = WeakLoopingCall(self, 'cycleAchievements') self.cycler.start(5.0, now=False) def addAchievement(self, achievementId): self.achievements.append(achievementId) self.titleText.setText("%d ACHIEVEMENTS UNLOCKED!" % len(self.achievements)) def _updateImage(self): try: filepath = self.app.theme.getPath('achievements', '%s.png' % self.achievements[0]) except IOError: filepath = self.app.theme.getPath('achievements', 'default.png') image = pygame.image.load(filepath).convert() image = pygame.transform.smoothscale( image, ScaledSize(64, 64).getSize(self.app)) if type(self.elements[-1]) == PictureElement: self.elements.pop() self.image = PictureElement( self.app, image, Location( FullScreenAttachedPoint(ScaledSize(-self.width / 2 + 7, -105), 'midbottom'), 'bottomleft')) self.elements.append(self.image) def cycleAchievements(self): del self.achievements[0] if len(self.achievements) == 0: self.cycler.stop() return elif len(self.achievements) == 1: self.titleText.setText("ACHIEVEMENT UNLOCKED!") else: self.titleText.setText("%d ACHIEVEMENTS UNLOCKED!" % len(self.achievements)) self.nameText.setText( self.achievementDefs.getAchievementDetails( self.achievements[0])[0]) self._updateImage() def _setColours(self): self.colours = self.app.theme.colours self.fonts = self.app.screenManager.fonts if self.player.teamId == 'A': self.borderColour = self.colours.achvBlueBorder self.bgColour = self.colours.achvBlueBackground else: self.borderColour = self.colours.achvRedBorder self.bgColour = self.colours.achvRedBackground def _getRect(self, area): return area.getRect(self.app) def draw(self, surface): mainRect = self._getRect(self.area) boxRect = self._getRect(self.smlBox) surface.fill(self.bgColour, mainRect) pygame.draw.rect(surface, self.borderColour, mainRect, 2) surface.fill(self.colours.white, boxRect) pygame.draw.rect(surface, self.borderColour, boxRect, 1) super(AchievementBox, self).draw(surface)
class ChatBox(framework.CompoundElement): def __init__(self, app, world, interface): super(ChatBox, self).__init__(app) self.world = world self.app = app self.interface = interface self.font = self.app.screenManager.fonts.newChatFont self.frameColour = self.app.theme.colours.chatFrameColour self.insideColour = self.app.theme.colours.chatInsideColour self.textColour = self.app.theme.colours.chatNormalColour self.sayToTeam = TextElement( self.app, text="Say to team:", font=self.font, pos=Location(FullScreenAttachedPoint((20, 501), 'topleft'), 'topleft'), colour=self.textColour, shadow=True, ) self.inputPosition = Area( FullScreenAttachedPoint((145, 500), 'topleft'), (370, 20), 'topleft') self.input = InputBox(self.app, self.inputPosition, font=self.font) self.input.onEnter.addListener( lambda sender: self.hitEnter(sender.value)) self.input.onEsc.addListener(lambda sender: self.close()) self.input.onClick.addListener(self.setFocus) self.messages = MessageBank( self.app, 10, 100, Location(FullScreenAttachedPoint((20, 470), 'topleft'), 'topleft'), 'left', 'bottom', self.font) self._chatOpen = False self.teamChat = True self.player = None self.messageBuffer = [] self.MESSAGE_GAP = self.font.getHeight(self.app) self.elements = [self.messages] def setPlayer(self, player): self.player = player if self.player.team is None: self.refreshMode() def canTeamChat(self): return self.player and self.player.team is not None def switchModes(self): self.teamChat = not self.teamChat self.refreshMode() def refreshMode(self): self.teamChat = self.teamChat and self.canTeamChat() if self.teamChat: self.sayToTeam.setText('Say to team:') else: self.sayToTeam.setText('Say to all:') def hitEnter(self, senderValue): if senderValue.strip() != '': self.sendChat(senderValue) self.input.clear() def sendChat(self, senderValue): # Interpret lines with initial hash. if senderValue.startswith('#'): i = 1 while senderValue[i:i + 1].isdigit(): i += 1 try: playerId = bytes([int(senderValue[1:i])]) except ValueError: pass else: self.interface.sendPrivateChat(self.player, playerId, senderValue[i:].lstrip()) return if self.teamChat: self.interface.sendTeamChat(self.player, senderValue) else: self.interface.sendPublicChat(self.player, senderValue) def refresh(self): if not self.isOpen(): return self.elements = [self.sayToTeam, self.input] initialY = 470 count = 0 for text, nick, colour, firstLine in reversed(self.messageBuffer): currentY = initialY - count * self.MESSAGE_GAP if currentY < 200 or count >= 10: break if firstLine and nick is not None: person = TextElement( self.app, text=nick, font=self.font, pos=Location( FullScreenAttachedPoint((20, currentY), 'topleft'), 'topleft'), colour=colour, shadow=True, ) xOffset = person._getRect().width self.elements.append(person) text = text[len(nick):] else: xOffset = 0 if nick is None: colour = self.app.theme.colours.serverChat else: colour = self.textColour message = TextElement( self.app, text=text, font=self.font, pos=Location( FullScreenAttachedPoint((20 + xOffset, currentY), 'topleft'), 'topleft'), colour=colour, shadow=True, ) self.elements.append(message) count += 1 def open(self): self._chatOpen = True self.refresh() self.setFocus(self.input) self.input.clear() def close(self): self._chatOpen = False self.elements = [self.messages] pygame.key.set_repeat() def isOpen(self): return self._chatOpen def newMessage(self, message, nick, colour): message = nick + message wrappedMessage = wrapline(message, self.font._getFont(self.app), 480) # Update the "box open" message elements firstLine = True for line in wrappedMessage: self.messageBuffer.append((line, nick, colour, firstLine)) firstLine = False while len(self.messageBuffer) > 100: self.messageBuffer.pop(0) # Update the "box closed" message elements firstLine = wrappedMessage.pop(0)[len(nick):] parts = [(nick, colour), (firstLine, self.textColour)] self.messages.newColourMessage(parts) for line in wrappedMessage: self.messages.newMessage(line, self.textColour) self.refresh() def newServerMessage(self, message): '''Server messages don't follow the format of normal messages and require special attention to display.''' colour = self.app.theme.colours.serverChat wrappedMessage = wrapline("SERVER" + message, self.font._getFont(self.app), 480) # Update the "box open" message elements firstLine = True for line in wrappedMessage: self.messageBuffer.append((line, None, colour, firstLine)) firstLine = False while len(self.messageBuffer) > 100: self.messageBuffer.remove(0) # Update the "box closed" message elements for line in wrappedMessage: self.messages.newMessage(line, colour) self.refresh() def _getRect(self): return self.area.getRect(self.app) def draw(self, surface): if self._chatOpen: pointX = 5 pointY = 300 # Draw the frame first frame = pygame.Surface((520, 230)) frame.fill(self.frameColour) mainBox = pygame.Surface((500, 180)) mainBox.fill(self.insideColour) sayBox = pygame.Surface((120, 20)) sayBox.fill(self.insideColour) if self.app.displaySettings.alphaOverlays: mainBox.set_alpha(128) sayBox.set_alpha(128) frame.set_alpha(128) surface.blit(frame, (pointX, pointY)) surface.blit(mainBox, (pointX + 10, pointY + 10)) surface.blit(sayBox, (pointX + 10, pointY + 200)) super(ChatBox, self).draw(surface) def processEvent(self, event): if not self.isOpen(): return event if event.type == pygame.KEYDOWN and event.key in (pygame.K_LCTRL, pygame.K_RCTRL): self.switchModes() else: return self.input.processEvent(event)
class SavedGameTab(Tab): def __init__(self, app, tabContainer, onCancel=None, onReplay=None): super(SavedGameTab, self).__init__(app, 'Saved Games') self.app = app self.tabContainer = tabContainer self.onCancel = Event(listener=onCancel) self.onReplay = Event(listener=onReplay) font = self.app.screenManager.fonts.ampleMenuFont smallFont = self.app.screenManager.fonts.menuFont colours = app.theme.colours # Static text self.staticText = [ TextElement(self.app, 'server details:', font, ScaledLocation(960, 200, 'topright'), colours.headingColour), TextElement(self.app, 'date and time:', font, ScaledLocation(960, 370, 'topright'), colours.headingColour), TextElement(self.app, 'replay:', font, ScaledLocation(620, 550, 'topleft'), colours.headingColour), TextElement(self.app, 'stats:', font, ScaledLocation(620, 605, 'topleft'), colours.headingColour) ] # Dynamic text self.listHeaderText = TextElement(self.app, 'available game files:', font, ScaledLocation(65, 200), colours.headingColour) self.noFiles1Text = TextElement(self.app, '', font, ScaledLocation(65, 260), colours.noGamesColour) self.noFiles2Text = TextElement(self.app, '', font, ScaledLocation(65, 310), colours.noGamesColour) self.serverNameText = TextElement(self.app, '', smallFont, ScaledLocation(960, 255, 'topright'), colours.startButton) self.serverDetailsText = TextElement( self.app, '', smallFont, ScaledLocation(960, 295, 'topright'), colours.startButton) self.dateText = TextElement(self.app, '', smallFont, ScaledLocation(960, 425, 'topright'), colours.startButton) self.lengthText = TextElement(self.app, '', smallFont, ScaledLocation(960, 465, 'topright'), colours.startButton) self.noReplayText = TextElement(self.app, '', smallFont, ScaledLocation(960, 550, 'topright'), colours.noGamesColour) self.noStatsText = TextElement(self.app, '', smallFont, ScaledLocation(960, 605, 'topright'), colours.noGamesColour) self.dynamicText = [ self.listHeaderText, self.noFiles1Text, self.noFiles2Text, self.serverNameText, self.serverDetailsText, self.dateText, self.lengthText, self.noReplayText, self.noStatsText ] # Text buttons self.watchButton = TextButton(self.app, ScaledLocation(960, 550, 'topright'), '', font, colours.secondMenuColour, colours.white) self.watchButton.onClick.addListener(self.watchReplay) self.statsButton = TextButton(self.app, ScaledLocation(960, 605, 'topright'), '', font, colours.secondMenuColour, colours.white) self.statsButton.onClick.addListener(self.viewStats) self.refreshButton = TextButton(self.app, ScaledLocation(620, 665, 'topleft'), 'refresh', font, colours.secondMenuColour, colours.white) self.refreshButton.onClick.addListener(self.populateList) self.cancelButton = TextButton(self.app, ScaledLocation(960, 665, 'topright'), 'cancel', font, colours.secondMenuColour, colours.white) self.cancelButton.onClick.addListener(self._cancel) self.loadFileButton = TextButton( self.app, ScaledLocation(960, 190, 'bottomright'), 'load file...', font, colours.mainMenuColour, colours.mainMenuHighlight) self.loadFileButton.onClick.addListener(self.showOpenDialog) self.buttons = [ self.watchButton, self.statsButton, self.refreshButton, self.cancelButton, self.loadFileButton ] # Replay list self.gameList = ListBox(self.app, ScaledArea(65, 255, 500, 450), [], smallFont, colours.listboxButtons) self.gameList.onValueChanged.addListener(self.updateSidebar) # Combine the elements self.elementsFiles = (self.staticText + self.dynamicText + self.buttons + [self.gameList]) self.elementsNoFiles = self.dynamicText + self.buttons # Populate the list of replays self.populateList() def _cancel(self, sender): self.onCancel.execute() def showOpenDialog(self, sender): root = Tk() root.withdraw() tksupport.install(root) filename = askopenfilename( defaultextension='.trosrepl', filetypes=[ ('Trosnoth replay', '*.trosrepl'), ], initialdir=getPath(user, replayDir), title='Select replay', ) if filename: self.onReplay.execute(filename) def populateList(self, sender=None): # Clear out the sidebar for item in self.dynamicText: item.setText('') self.listHeaderText.setText('available game files:') self.gameList.index = -1 self.elements = self.elementsFiles[:] # Get a list of files with the name '*.tros' logDir = getPath(user, gameDir) makeDirs(logDir) fileList = [] for fname in os.listdir(logDir): if os.path.splitext(fname)[1] == gameExt: fileList.append(fname) # Assume all files are valid for now validFiles = fileList[:] self.gameInfo = {} oldFound = False for fname in fileList: try: game = RecordedGame(os.path.join(logDir, fname)) except RecordedGameException: validFiles.remove(fname) continue except: log.warning('invalid file: %s', fname) continue else: if game.recordedGameVersion != recordedGameVersion: validFiles.remove(fname) oldFound = True self.gameInfo[os.path.splitext(fname)[0]] = game # Sort the games with most recent first. items = [(v.unixTimestamp, n) for n, v in self.gameInfo.iteritems()] items.sort(reverse=True) items = [n for v, n in items] self.gameList.setItems(items) if len(self.gameInfo) == 0: self.elements = self.elementsNoFiles[:] self.listHeaderText.setText('0 available game files:') if oldFound: self.noFiles1Text.setText('Some games were found from') self.noFiles2Text.setText('previous Trosnoth versions') else: self.noFiles1Text.setText('You have not yet run any') self.noFiles2Text.setText('games on this computer') else: self.gameList.setIndex(0) self.updateSidebar(0) if len(self.gameInfo) == 1: self.listHeaderText.setText('1 available game file:') else: self.listHeaderText.setText('{} available game files:'.format( len(self.gameInfo))) def updateSidebar(self, listID): # Update the details on the sidebar displayName = self.gameList.getItem(listID) # Server title self.serverNameText.setText(self.gameInfo[displayName].alias) # Date and time of match datePython = map(int, self.gameInfo[displayName].dateTime.split(',')) dateString = time.strftime('%a %d/%m/%y, %H:%M', datePython) self.dateText.setText(dateString) # Length of match dateUnix = time.mktime(datePython) if self.gameInfo[displayName].wasFinished(): lastUnix = self.gameInfo[displayName].gameFinishedTimestamp lengthSeconds = int(lastUnix - dateUnix) lengthMinutes, lengthSeconds = divmod(lengthSeconds, 60) secPlural = ('s', '')[lengthSeconds == 1] minPlural = ('s', '')[lengthMinutes == 1] if lengthMinutes == 0: lengthString = '{} second{}'.format(lengthSeconds, secPlural) else: lengthString = '{} min{}, {} sec{}'.format( lengthMinutes, minPlural, lengthSeconds, secPlural) self.lengthText.setText(lengthString) else: self.lengthText.setText('') # Enable the replay button if (self.gameInfo[displayName].replayFilename is not None and os.path.exists(self.gameInfo[displayName].replayFilename)): self.watchButton.setText('watch') self.noReplayText.setText('') else: self.watchButton.setText('') self.noReplayText.setText('unavailable') # Enabled the stats button if (self.gameInfo[displayName].statsFilename is not None and os.path.exists(self.gameInfo[displayName].statsFilename)): self.statsButton.setText('view') self.noStatsText.setText('') else: self.statsButton.setText('') self.noStatsText.setText('unavailable') def watchReplay(self, sender): '''Watch replay button was clicked.''' # Try to create a replay server. self.onReplay.execute( self.gameInfo[self.gameList.getItem()].replayFilename) def viewStats(self, sender=None): '''View stats button was clicked.''' game = self.gameInfo[self.gameList.getItem()] self.htmlPath = game.generateHtmlFile() browser.openPage(self.app, self.htmlPath) def draw(self, surface): super(SavedGameTab, self).draw(surface) rect = self.tabContainer._getTabRect() verLineX = rect.left + (rect.width * 0.6) horLineY = rect.top + (rect.height * 0.68) colour = self.app.theme.colours.replayTabBorder pygame.draw.line(surface, colour, (verLineX, rect.top), (verLineX, rect.bottom), self.tabContainer._getBorderWidth()) pygame.draw.line(surface, colour, (verLineX, horLineY), (rect.right, horLineY), self.tabContainer._getBorderWidth())
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))
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()
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()