source: trunk/game/scripts/hud.py @ 310

Revision 304, 21.6 KB checked in by eliedebrauwer, 10 years ago (diff)

Ticket #98: Patch by Elie De Brauwer. Implemented minimum resolution filter of 1024x768 in options dialog, also adapted settings-dist.xml accordingly. comment[s:trac, t:98]

  • Property svn:eol-style set to native
Line 
1#!/usr/bin/python
2
3#   This file is part of PARPG.
4
5#   PARPG is free software: you can redistribute it and/or modify
6#   it under the terms of the GNU General Public License as published by
7#   the Free Software Foundation, either version 3 of the License, or
8#   (at your option) any later version.
9
10#   PARPG is distributed in the hope that it will be useful,
11#   but WITHOUT ANY WARRANTY; without even the implied warranty of
12#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#   GNU General Public License for more details.
14
15#   You should have received a copy of the GNU General Public License
16#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
17
18import shutil, fife, pychan
19import pychan.widgets as widgets
20from pychan.tools import callbackWithArguments as cbwa
21from scripts.filebrowser import FileBrowser
22from scripts.context_menu import ContextMenu
23from scripts import inventory
24from scripts.popups import ExaminePopup, ContainerGUI
25from scripts.dialoguegui import DialogueGUI
26
27class Hud(object):
28    """Main Hud class"""
29    def __init__(self, engine, settings, inv_model, callbacks):
30        """Initialise the instance.
31           @type engine: fife.Engine
32           @param engine: An instance of the fife engine
33           @type settings: settings.Setting
34           @param settings: The settings data
35           @type inv_model: dict
36           @type callbacks: dict
37           @param callbacks: a dict of callbacks
38               saveGame: called when the user clicks on Save
39               loadGame: called when the user clicks on Load
40               quitGame: called when the user clicks on Quit
41           @return: None"""
42        pychan.init(engine, debug = True)
43
44        # TODO: perhaps this should not be hard-coded here
45        self.hud = pychan.loadXML("gui/hud.xml")
46        self.engine = engine
47        self.settings = settings
48
49        inv_callbacks = {
50            'refreshReadyImages': self.refreshReadyImages,
51            'toggleInventoryButton': self.toggleInventoryButton,
52        }
53
54        self.inventory = inventory.Inventory(self.engine, inv_model, inv_callbacks)
55        self.refreshReadyImages()
56
57        self.saveGameCallback = callbacks['saveGame']
58        self.loadGameCallback = callbacks['loadGame']
59        self.quitCallback     = callbacks['quitGame']
60
61        self.box_container = None
62        self.examine_box = None
63
64        self.actionsBox = self.hud.findChild(name="actionsBox")
65        self.actionsText = []
66        self.menu_displayed = False
67        self.initializeHud()
68        self.initializeMainMenu()
69        self.initializeContextMenu()
70        self.initializeOptionsMenu()
71        self.initializeHelpMenu()
72        self.initializeEvents()
73        self.initializeQuitDialog()
74
75    def initializeHud(self):
76        """Initialize and show the main HUD
77           @return: None"""
78        self.events_to_map = {"menuButton":self.displayMenu,}
79        self.hud.mapEvents(self.events_to_map) 
80        # set HUD size accoriding to screen size
81        screen_width = int(self.settings.readSetting('ScreenWidth'))
82        self.hud.findChild(name="mainHudWindow").size = (screen_width, 65)
83        self.hud.findChild(name="inventoryButton").position = (screen_width-59, 7)
84        # add ready slots
85        ready1 = self.hud.findChild(name='hudReady1')
86        ready2 = self.hud.findChild(name='hudReady2')
87        ready3 = self.hud.findChild(name='hudReady3')
88        ready4 = self.hud.findChild(name='hudReady4')
89        actions_scroll_area = self.hud.findChild(name='actionsScrollArea')
90        if (screen_width <=800) :
91            gap = 0
92        else :
93            gap = 40
94        # annoying code that is both essential and boring to enter
95        ready1.position = (160+gap, 7)
96        ready2.position = (220+gap, 7)
97        ready3.position = (screen_width-180-gap, 7)
98        ready4.position = (screen_width-120-gap, 7)
99        actions_scroll_area.position = (280+gap, 5)
100        actions_width = screen_width - 470 - 2*gap
101
102        # and finally add an actions box
103        self.hud.findChild(name="actionsBox").min_size = (actions_width, 0)
104        actions_scroll_area.min_size = (actions_width, 55)
105        actions_scroll_area.max_size = (actions_width, 55)
106        # now it should be OK to display it all
107        self.hud.show()
108
109    def refreshActionsBox(self):
110        """Refresh the actions box so that it displays the contents of
111           self.actionsText
112           @return: None"""
113        self.actionsBox.items = self.actionsText
114
115    def addAction(self, action):
116        """Add an action to the actions box.
117           @type action: string
118           @param action: The text that you want to display in the actions box
119           @return: None"""
120        self.actionsText.insert(0, action)
121        self.refreshActionsBox()
122
123    def showHUD(self):
124        """Show the HUD.
125           @return: None"""
126        self.hud.show()
127
128    def hideHUD(self):
129        """Hide the HUD.
130           @return: None"""
131        self.hud.hide()
132
133    def initializeContextMenu(self):
134        """Initialize the Context Menu
135           @return: None"""
136        self.context_menu = ContextMenu (self.engine, [], (0,0))
137        self.context_menu.hide()
138
139    def showContextMenu(self, data, pos):
140        """Display the Context Menu with data at pos
141           @type data: list
142           @param data: data to pass to context menu
143           @type pos: tuple
144           @param pos: tuple of x and y coordinates
145           @return: None"""
146        self.context_menu = ContextMenu(self.engine, data, pos)
147
148    def hideContextMenu(self):
149        """Hides the context menu
150           @return: None"""
151        self.context_menu.hide()
152
153    def initializeMainMenu(self):
154        """Initalize the main menu.
155           @return: None"""
156        self.main_menu = pychan.loadXML("gui/hud_main_menu.xml")
157        self.menu_events = {"resumeButton":self.hideMenu, 
158                            "optionsButton":self.displayOptions,
159                            "helpButton":self.displayHelp}
160        self.main_menu.mapEvents(self.menu_events)
161
162    def displayMenu(self):
163        """Displays the main in-game menu.
164           @return: None"""
165        if (self.menu_displayed == False):
166            self.main_menu.show()
167            self.menu_displayed = True
168        elif (self.menu_displayed == True):
169            self.hideMenu()
170
171    def hideMenu(self):
172        """Hides the main in-game menu.
173           @return: None"""
174        self.main_menu.hide()
175        self.menu_displayed = False
176
177
178    def initializeHelpMenu(self):
179        """Initialize the help menu
180           @return: None"""
181        self.help_dialog = pychan.loadXML("gui/help.xml")
182        help_events = {"closeButton":self.help_dialog.hide}
183        self.help_dialog.mapEvents(help_events)
184        main_help_text = u"Welcome to Post-Apocalyptic RPG or PARPG![br][br]"\
185        "This game is still in development, so please expect for there to be bugs"\
186        " and[br]feel free to tell us about them at http://www.forums.parpg.net.[br]"\
187        "This game uses a \"Point 'N' Click\" interface, which means that to move around,[br]"\
188        "just click where you would like to go and your character will move there.[br]"\
189        "PARPG also utilizes a context menu. To access this, just right click "\
190        "anywhere[br]on the screen and a menu will come up. This menu will change"\
191        " depending on[br]what you have clicked on, hence it's name \"context menu\".[br][br]"
192       
193        k_text = u" Keybindings" 
194        k_text+="[br] A : Add a test action to the actions display"
195        k_text+="[br] I : Toggle the inventory screen"
196        k_text+="[br] F5 : Take a screenshot"
197        k_text+="[br]      (saves to <parpg>/screenshots/)"
198        k_text+="[br] Q : Quit the game"
199        self.help_dialog.distributeInitialData({
200                "MainHelpText":main_help_text,
201                "KeybindText":k_text
202                })
203
204    def displayHelp(self):
205        """Display the help screen.
206           @return: None"""
207        self.help_dialog.show()
208
209    def initializeOptionsMenu(self):
210        """Initalize the options menu.
211           @return: None"""
212        self.options_menu = pychan.loadXML("gui/hud_options.xml")
213        self.options_events = {"applyButton":self.applyOptions,
214                               "closeButton":self.options_menu.hide,
215                               "defaultsButton":self.setToDefaults,
216                               "InitialVolumeSlider":self.updateVolumeText}
217       
218        settings = self.engine.getSettings()
219        current_fullscreen = settings.isFullScreen()
220        settings.setFullScreen(True)
221        availableResolutions = settings.getPossibleResolutions()
222
223        # Filter too small resolutions
224        self.Resolutions=[]
225        for x in availableResolutions:
226            if x[0]>=1024 and x[1]>=768:
227                self.Resolutions.append(str(x[0])+'x'+str(x[1]))
228
229        settings.setFullScreen(current_fullscreen)
230        self.RenderBackends = ['OpenGL', 'SDL']
231        self.renderNumber = 0
232        if (str(self.settings.readSetting('RenderBackend')) == "SDL"):
233            self.renderNumber = 1
234        initialVolume = float(self.settings.readSetting('InitialVolume'))
235        initialVolumeText = str('Initial Volume: %.0f%s' %
236                                (int(initialVolume*10), "%"))
237        self.options_menu.distributeInitialData({
238                'ResolutionBox': self.Resolutions,
239                'RenderBox': self.RenderBackends,
240                'InitialVolumeLabel' : initialVolumeText
241                })
242
243        sFullscreen = self.settings.readSetting(name="FullScreen")
244        sSounds = self.settings.readSetting(name="PlaySounds")
245        sRender = self.renderNumber
246        sVolume = initialVolume
247
248        screen_width = self.settings.readSetting(name="ScreenWidth")
249        screen_height = self.settings.readSetting(name="ScreenHeight")
250        indexRes = str(screen_width + 'x' + screen_height)
251        try:
252            sResolution = self.Resolutions.index(indexRes)
253            resolutionInList = True
254        except:
255            resolutionInList = False
256
257        dataToDistribute = {
258                'FullscreenBox':int(sFullscreen), 
259                'SoundsBox':int(sSounds),
260                'RenderBox': sRender,
261                'InitialVolumeSlider':sVolume
262                }
263
264        if (resolutionInList == True):
265            dataToDistribute['ResolutionBox'] = sResolution
266
267        self.options_menu.distributeData(dataToDistribute)
268
269        self.options_menu.mapEvents(self.options_events)
270
271    def saveGame(self):
272        """ Called when the user wants to save the game.
273            @return: None"""
274        save_browser = FileBrowser(self.engine,
275                                   self.saveGameCallback,
276                                   savefile=True,
277                                   guixmlpath="gui/savebrowser.xml",
278                                   extensions = ('.dat'))
279        save_browser.showBrowser()
280           
281    def newGame(self):
282        """Called when user request to start a new game.
283           @return: None"""
284        print 'new game'
285
286    def loadGame(self):
287        """ Called when the user wants to load a game.
288            @return: None"""
289        load_browser = FileBrowser(self.engine,
290                                   self.loadGameCallback,
291                                   savefile=False,
292                                   guixmlpath='gui/loadbrowser.xml',
293                                   extensions=('.dat'))
294        load_browser.showBrowser()
295   
296    def initializeQuitDialog(self):
297        """Creates the quit confirmation dialog
298           @return: None"""
299        self.quitWindow = pychan.widgets.Window(title=unicode("Quit?"),min_size=(200,0))
300
301        hbox = pychan.widgets.HBox()
302        are_you_sure = "Are you sure you want to quit?"
303        label = pychan.widgets.Label(text=unicode(are_you_sure))
304        yes_button = pychan.widgets.Button(name="yes_button", 
305                                           text=unicode("Yes"),
306                                           min_size=(90,20),
307                                           max_size=(90,20))
308        no_button = pychan.widgets.Button(name="no_button",
309                                          text=unicode("No"),
310                                          min_size=(90,20),
311                                          max_size=(90,20))
312
313        self.quitWindow.addChild(label)
314        hbox.addChild(yes_button)
315        hbox.addChild(no_button)
316        self.quitWindow.addChild(hbox)
317
318        events_to_map = { "yes_button": self.quitCallback,
319                          "no_button":  self.quitWindow.hide }
320       
321        self.quitWindow.mapEvents(events_to_map)
322
323
324    def quitGame(self):
325        """Called when user requests to quit game.
326           @return: None"""
327
328        self.quitWindow.show()
329
330    def toggleInventoryButton(self):
331        """Manually toggles the inventory button.
332           @return: None"""
333        button = self.hud.findChild(name="inventoryButton")
334        if button.toggled == 0:
335            button.toggled = 1
336        else:
337            button.toggled = 0
338
339    def toggleInventory(self, toggleImage=True):
340        """Display's the inventory screen
341           @return: None"""
342
343        self.inventory.toggleInventory(toggleImage)
344
345    def refreshReadyImages(self):
346        """Make the Ready slot images on the HUD be the same as those
347           on the inventory
348           @return: None"""
349        self.setImages(self.hud.findChild(name="hudReady1"),
350                       self.inventory.getImage("Ready1").up_image)
351
352        self.setImages(self.hud.findChild(name="hudReady2"),
353                       self.inventory.getImage("Ready2").up_image)
354
355        self.setImages(self.hud.findChild(name="hudReady3"),
356                       self.inventory.getImage("Ready3").up_image)
357
358        self.setImages(self.hud.findChild(name="hudReady4"),
359                       self.inventory.getImage("Ready4").up_image)
360
361    def setImages(self, widget, image):
362        """Set the up, down, and hover images of an Imagebutton.
363           @type widget: pychan.widget
364           @param widget: widget to set
365           @type image: string
366           @param image: image to use
367           @return: None"""
368        widget.up_image = image
369        widget.down_image = image
370        widget.hover_image = image
371
372    def initializeEvents(self):
373        """Intialize Hud events
374           @return: None"""
375        events_to_map = {}
376
377        # when we click the toggle button don't change the image
378        events_to_map["inventoryButton"] = cbwa(self.toggleInventory, False)
379        events_to_map["saveButton"] = self.saveGame
380        events_to_map["loadButton"] = self.loadGame
381
382        hud_ready_buttons = ["hudReady1", "hudReady2", "hudReady3", "hudReady4"]
383
384        for item in hud_ready_buttons:
385            events_to_map[item] = cbwa(self.readyAction, item)
386
387        self.hud.mapEvents(events_to_map)
388
389        menu_events = {}
390        menu_events["newButton"] = self.newGame
391        menu_events["quitButton"] = self.quitGame
392        menu_events["saveButton"] = self.saveGame
393        menu_events["loadButton"] = self.loadGame
394        self.main_menu.mapEvents(menu_events)
395
396    def updateVolumeText(self):
397        """
398        Update the initial volume label to reflect the value of the slider
399        """
400        volume = float(self.options_menu.collectData("InitialVolumeSlider"))
401        volume_label = self.options_menu.findChild(name="InitialVolumeLabel")
402        volume_label.text = unicode("Initial Volume: %.0f%s" %
403                                    (int(volume*10), "%"))
404
405    def requireRestartDialog(self):
406        """Show a dialog asking the user to restart PARPG in order for their
407           changes to take effect.
408           @return: None"""
409        require_restart_dialog = pychan.loadXML('gui/hud_require_restart.xml')
410        require_restart_dialog.mapEvents({'okButton':require_restart_dialog.hide})
411        require_restart_dialog.show()
412
413    def applyOptions(self):
414        """Apply the current options.
415           @return: None"""
416        # At first no restart is required
417        self.requireRestart = False
418
419        # get the current values of each setting from the options menu
420        enable_fullscreen = self.options_menu.collectData('FullscreenBox')
421        enable_sound = self.options_menu.collectData('SoundsBox')
422        screen_resolution = self.options_menu.collectData('ResolutionBox')
423        partition = self.Resolutions[screen_resolution].partition('x')
424        screen_width = partition[0]
425        screen_height = partition[2]
426        render_backend = self.options_menu.collectData('RenderBox')
427        initial_volume = self.options_menu.collectData('InitialVolumeSlider')
428        initial_volume = "%.1f" % initial_volume
429
430        # get the options that are being used right now from settings.xml
431        sFullscreen = self.settings.readSetting('FullScreen')
432        sSound = self.settings.readSetting('PlaySounds')
433        sRender = self.settings.readSetting('RenderBackend')
434        sVolume = self.settings.readSetting('InitialVolume')
435
436        sScreenHeight = self.settings.readSetting('ScreenHeight')
437        sScreenWidth = self.settings.readSetting('ScreenWidth')
438        sResolution = sScreenWidth + 'x' + sScreenHeight
439
440        # On each:
441        # - Check to see whether the option within the xml matches the
442        #   option within the options menu
443        # - If they do not match, set the option within the xml to
444        #   to be what is within the options menu
445        # - Require a restart
446
447        if (int(enable_fullscreen) != int(sFullscreen)):
448            self.setOption('FullScreen', int(enable_fullscreen))
449            self.requireRestart = True
450           
451        if (int(enable_sound) != int(sSound)):
452            self.setOption('PlaySounds', int(enable_sound))
453            self.requireRestart = True
454
455        if (screen_resolution != sResolution):
456            self.setOption('ScreenWidth', int(screen_width))
457            self.setOption('ScreenHeight', int(screen_height))
458            self.requireRestart = True
459
460        # Convert the number from the list of render backends to
461        # the string that FIFE wants for its settings.xml
462        if (render_backend == 0):
463            render_backend = 'OpenGL'
464        else:
465            render_backend = 'SDL'
466
467        if (render_backend != str(sRender)):
468            self.setOption('RenderBackend', render_backend)
469            self.requireRestart = True
470
471        if (initial_volume != float(sVolume)):
472            self.setOption('InitialVolume', initial_volume)
473            self.requireRestart = True
474       
475        # Write all the settings to settings.xml
476        self.settings.tree.write('settings.xml')
477       
478        # If the changes require a restart, popup the dialog telling
479        # the user to do so
480        if (self.requireRestart):
481            self.requireRestartDialog()
482        # Once we are done, we close the options menu
483        self.options_menu.hide()
484
485    def setOption(self, name, value):
486        """Set an option within the xml.
487           @type name: string
488           @param name: The name of the option within the xml
489           @type value: any
490           @param value: The value that the option 'name' should be set to
491           @return: None"""
492        element = self.settings.root_element.find(name)
493        if(element != None):
494            if(value != element.text):
495                element.text = str(value)
496        else:
497            print 'Setting,', name, 'does not exist!'
498
499    def setToDefaults(self):
500        """Reset all the options to the options in settings-dist.xml.
501           @return: None"""
502        shutil.copyfile('settings-dist.xml', 'settings.xml')
503        self.requireRestartDialog()
504        self.options_menu.hide()
505
506    def displayOptions(self):
507        """Display the options menu.
508           @return: None"""
509        self.options_menu.show()
510   
511    def readyAction(self, ready_button):
512        """ Called when the user selects a ready button from the HUD """
513        text = "Used the item from %s" % ready_button
514        self.addAction(text)
515       
516    def createBoxGUI(self, title):
517        """Creates a window to display the contents of a box
518           @type title: string
519           @param title: The title for the window
520           @return: None"""
521        if self.box_container:
522            # if it has already been created, just show it
523            self.box_container.showContainer()
524        else:
525            # otherwise create it then show it
526            data = ["dagger01", "empty", "empty", "empty", "empty",
527                    "empty", "empty", "empty", "empty"]
528            self.box_container = ContainerGUI(self.engine, unicode(title), data)
529            def close_and_delete():
530                self.hideContainer()
531            events = {'takeAllButton':close_and_delete,
532                      'closeButton':close_and_delete}
533            self.box_container.container_gui.mapEvents(events)
534            self.box_container.showContainer()
535
536    def hideContainer(self):
537        """Hide the container box
538           @return: None"""
539        if self.box_container:
540            self.box_container.hideContainer()
541
542    def createExamineBox(self, title, desc):
543        """Create an examine box. It displays some textual description of an
544           object
545           @type title: string
546           @param title: The title of the examine box
547           @type desc: string
548           @param desc: The main body of the examine box
549           @return: None"""
550
551        if self.examine_box:
552            self.examine_box.closePopUp()
553        self.examine_box = ExaminePopup(self.engine, title, desc)
554        self.examine_box.showPopUp()
555
556    def showDialogue(self, npc):
557        """Show the NPC dialogue window
558           @type npc: ???
559           @param npc: the npc that we are having a dialogue with"""
560        dialogue = DialogueGUI(npc)
561        dialogue.initiateDialogue()
Note: See TracBrowser for help on using the repository browser.