source: trunk/game/scripts/gui/hud.py @ 466

Revision 466, 25.3 KB checked in by b0rland_parpg, 10 years ago (diff)

Ticket #232: patch by b0rland
Fixed a minor bug -- the value was returned wrongly

  • 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
19import fife
20import pychan
21import pychan.widgets as widgets
22from pychan.tools import callbackWithArguments as cbwa
23from scripts.gui.filebrowser import FileBrowser
24from scripts.gui.context_menu import ContextMenu
25from scripts.gui import inventorygui
26from scripts.gui.popups import ExaminePopup, ContainerGUI
27from scripts.gui.dialoguegui import DialogueGUI
28
29class Hud(object):
30    """Main Hud class"""
31    def __init__(self, engine, settings, data, callbacks):
32        """Initialise the instance.
33           @type engine: fife.Engine
34           @param engine: An instance of the fife engine
35           @type settings: settings.Setting
36           @param settings: The settings data
37           @type inv_model: dict
38           @type callbacks: dict
39           @param callbacks: a dict of callbacks
40               saveGame: called when the user clicks on Save
41               loadGame: called when the user clicks on Load
42               quitGame: called when the user clicks on Quit
43           @return: None"""
44        pychan.init(engine, debug = True)
45
46        # TODO: perhaps this should not be hard-coded here
47        self.hud = pychan.loadXML("gui/hud.xml")
48        self.engine = engine
49        self.settings = settings
50        self.data = data
51        self.inventory = None
52       
53
54
55        self.save_game_callback = callbacks['saveGame']
56        self.load_game_callback = callbacks['loadGame']
57        self.quit_callback      = callbacks['quitGame']
58
59        self.box_container = None
60        self.examine_box = None
61
62        self.actions_box = self.hud.findChild(name="actionsBox")
63        self.actions_text = []
64        self.menu_displayed = False
65        self.inventory_storage = None
66        self.initializeHud()
67        self.initializeMainMenu()
68        self.initializeContextMenu()
69        self.initializeOptionsMenu()
70        self.initializeHelpMenu()
71        self.initializeEvents()
72        self.initializeQuitDialog()
73
74    def initializeHud(self):
75        """Initialize and show the main HUD
76           @return: None"""
77        self.events_to_map = {"menuButton":self.displayMenu,}
78        self.hud.mapEvents(self.events_to_map) 
79        # set HUD size according to screen size
80        screen_width = int(self.settings.readSetting('ScreenWidth'))
81        self.hud.findChild(name="mainHudWindow").size = (screen_width, 65)
82        self.hud.findChild(name="inventoryButton").position = \
83                                                    (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
91        if (screen_width <=800) :
92            gap = 0
93        else :
94            gap = 40
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.actions_text
112           @return: None"""
113        self.actions_box.items = self.actions_text
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.actions_text.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 initializeInventory(self):
134        inv_callbacks = {
135            'refreshReadyImages': self.refreshReadyImages,
136            'toggleInventoryButton': self.toggleInventoryButton,
137        }
138        self.inventory_storage = self.data.game_state.PC.inventory
139        if self.inventory == None:
140            self.inventory = inventorygui.InventoryGUI(self.engine,
141                                                       self.inventory_storage,
142                                                       inv_callbacks)
143        self.refreshReadyImages()
144       
145
146    def initializeContextMenu(self):
147        """Initialize the Context Menu
148           @return: None"""
149        self.context_menu = ContextMenu (self.engine, [], (0,0))
150        self.context_menu.hide()
151
152    def showContextMenu(self, data, pos):
153        """Display the Context Menu with data at pos
154           @type data: list
155           @param data: data to pass to context menu
156           @type pos: tuple
157           @param pos: tuple of x and y coordinates
158           @return: None"""
159        self.context_menu = ContextMenu(self.engine, data, pos)
160
161    def hideContextMenu(self):
162        """Hides the context menu
163           @return: None"""
164        self.context_menu.hide()
165
166    def initializeMainMenu(self):
167        """Initalize the main menu.
168           @return: None"""
169        self.main_menu = pychan.loadXML("gui/hud_main_menu.xml")
170        self.menu_events = {"resumeButton":self.hideMenu, 
171                            "optionsButton":self.displayOptions,
172                            "helpButton":self.displayHelp}
173        self.main_menu.mapEvents(self.menu_events)
174
175    def displayMenu(self):
176        """Displays the main in-game menu.
177           @return: None"""
178        if (self.menu_displayed == False):
179            self.main_menu.show()
180            self.menu_displayed = True
181        elif (self.menu_displayed == True):
182            self.hideMenu()
183
184    def hideMenu(self):
185        """Hides the main in-game menu.
186           @return: None"""
187        self.main_menu.hide()
188        self.menu_displayed = False
189
190
191    def initializeHelpMenu(self):
192        """Initialize the help menu
193           @return: None"""
194        self.help_dialog = pychan.loadXML("gui/help.xml")
195        help_events = {"closeButton":self.help_dialog.hide}
196        self.help_dialog.mapEvents(help_events)
197        main_help_text = u"Welcome to Post-Apocalyptic RPG or PARPG![br][br]"\
198        "This game is still in development, so please expect for there to be "\
199        "bugs and[br]feel free to tell us about them at "\
200        "http://www.forums.parpg.net.[br]This game uses a "\
201        "\"Point 'N' Click\" interface, which means that to move around,[br]"\
202        "just click where you would like to go and your character will move "\
203        "there.[br]PARPG also utilizes a context menu. To access this, just "\
204        "right click anywhere[br]on the screen and a menu will come up. This "\
205        "menu will change depending on[br]what you have clicked on, hence "\
206        "it's name \"context menu\".[br][br]"
207       
208        k_text = u" Keybindings" 
209        k_text += "[br] A : Add a test action to the actions display"
210        k_text += "[br] I : Toggle the inventory screen"
211        k_text += "[br] F5 : Take a screenshot"
212        k_text += "[br]      (saves to <parpg>/screenshots/)"
213        k_text += "[br] F10 : Toggle console"
214        k_text += "[br] PAUSE : (Un)Pause the game"
215        k_text += "[br] Q : Quit the game"
216        self.help_dialog.distributeInitialData({
217                "MainHelpText":main_help_text,
218                "KeybindText":k_text
219                })
220
221    def displayHelp(self):
222        """Display the help screen.
223           @return: None"""
224        self.help_dialog.show()
225
226    def switchResolutions(self):
227        """ Sync the contents of the resolution box (windowed or fullscreen
228            resolutions to the selection made in the Fullscreen checkbox.
229            @return: None"""
230           
231        if self.options_menu.collectData('FullscreenBox'):
232            self.options_menu.distributeInitialData({
233                                'ResolutionBox' : self.resolutions_fullscreen
234                                                     })
235        else:
236            self.options_menu.distributeInitialData({
237                                'ResolutionBox' : self.resolutions_windowed
238                                                     })
239       
240    def initializeOptionsMenu(self):
241        """Initialize the options menu, this will generate a list of fullscreen
242           resolutions and a list of windowed resolutions. Next to this the
243           current active settings are read and also selected in the matching
244           widgets.
245           @return: None"""
246           
247        self.options_menu = pychan.loadXML("gui/hud_options.xml")
248        self.options_events = {"applyButton":self.applyOptions,
249                               "closeButton":self.options_menu.hide,
250                               "defaultsButton":self.setToDefaults,
251                               "FullscreenBox": self.switchResolutions,
252                               "InitialVolumeSlider":self.updateVolumeText}
253       
254        settings = self.engine.getSettings()
255        # The settings need to be set to fullscreen for the call to
256        # getPossibleResolutions() to function.
257        current_fullscreen = settings.isFullScreen()
258        settings.setFullScreen(True)
259       
260        available_fullscreen_resolutions = settings.getPossibleResolutions()
261        available_windowed_resolutions = ((1920, 1200), (1920, 1080), \
262                                          (1856, 1392), (1792, 1344), \
263                                          (1680, 1050), (1600, 1200), \
264                                          (1600, 1024), (1440,  900), \
265                                          (1400, 1050), (1360,  768), \
266                                          (1280, 1024), (1280,  960), \
267                                          (1152,  864), (1024,  768))
268        # Filter too small resolutions from the fullscreen resolutions
269        self.resolutions_fullscreen = []
270        for x in available_fullscreen_resolutions:
271            if x[0] >= 1024 and x[1] >= 768:
272                self.resolutions_fullscreen.append(str(x[0]) + 'x' + str(x[1]))
273
274        # Filter too large resolution from the windowed resolutions
275        self.resolutions_windowed = []
276        for x in available_windowed_resolutions:
277            if x[0] <= available_fullscreen_resolutions[0][0] and \
278            x[1] <= available_fullscreen_resolutions[0][1]:
279                self.resolutions_windowed.append(str(x[0]) + 'x' + str(x[1]))       
280       
281        settings.setFullScreen(current_fullscreen)
282        self.render_backends = ['OpenGL', 'SDL']
283        self.render_number = 0
284        if (str(self.settings.readSetting('RenderBackend')) == "SDL"):
285            self.render_number = 1
286        initial_volume = float(self.settings.readSetting('InitialVolume'))
287        initial_volume_text = str('Initial Volume: %.0f%s' %
288                                (int(initial_volume*10), "%"))
289        initial_data_to_distribute = {   
290                'RenderBox'          : self.render_backends,
291                'InitialVolumeLabel' : initial_volume_text
292                }
293
294        s_fullscreen = self.settings.readSetting(name="FullScreen")
295        s_sounds = self.settings.readSetting(name="PlaySounds")
296        s_render = self.render_number
297        s_volume = initial_volume
298
299        # Find the current active resolution so we can select it
300        screen_width = self.settings.readSetting(name="ScreenWidth")
301        screen_height = self.settings.readSetting(name="ScreenHeight")
302        index_res = str(screen_width + 'x' + screen_height)
303        try:
304            if int(s_fullscreen) == 0:
305                s_resolution = self.resolutions_windowed.index(index_res)
306            else:
307                s_resolution = self.resolutions_fullscreen.index(index_res)
308            resolution_in_list = True
309        except:
310            resolution_in_list = False
311           
312        data_to_distribute = {
313                'FullscreenBox'      : int(s_fullscreen), 
314                'SoundsBox'          : int(s_sounds),
315                'RenderBox'          : s_render,
316                'InitialVolumeSlider': s_volume
317                }
318
319        if int(s_fullscreen) == 0:
320            initial_data_to_distribute['ResolutionBox'] = self.resolutions_windowed
321        else:
322            initial_data_to_distribute['ResolutionBox'] = self.resolutions_fullscreen
323           
324        if (resolution_in_list == True):
325            data_to_distribute['ResolutionBox'] = s_resolution
326
327        self.options_menu.distributeInitialData(initial_data_to_distribute)
328        self.options_menu.distributeData(data_to_distribute)
329        self.options_menu.mapEvents(self.options_events)
330
331    def saveGame(self):
332        """ Called when the user wants to save the game.
333            @return: None"""
334        save_browser = FileBrowser(self.engine,
335                                   self.save_game_callback,
336                                   save_file=True,
337                                   gui_xml_path="gui/savebrowser.xml",
338                                   extensions = ('.dat'))
339        save_browser.showBrowser()
340           
341    def newGame(self):
342        """Called when user request to start a new game.
343           @return: None"""
344        print 'new game'
345
346    def loadGame(self):
347        """ Called when the user wants to load a game.
348            @return: None"""
349        load_browser = FileBrowser(self.engine,
350                                   self.load_game_callback,
351                                   save_file=False,
352                                   gui_xml_path='gui/loadbrowser.xml',
353                                   extensions=('.dat'))
354        load_browser.showBrowser()
355   
356    def initializeQuitDialog(self):
357        """Creates the quit confirmation dialog
358           @return: None"""
359        self.quit_window = pychan.widgets.Window(title=unicode("Quit?"), \
360                                                 min_size=(200,0))
361
362        hbox = pychan.widgets.HBox()
363        are_you_sure = "Are you sure you want to quit?"
364        label = pychan.widgets.Label(text=unicode(are_you_sure))
365        yes_button = pychan.widgets.Button(name="yes_button", 
366                                           text=unicode("Yes"),
367                                           min_size=(90,20),
368                                           max_size=(90,20))
369        no_button = pychan.widgets.Button(name="no_button",
370                                          text=unicode("No"),
371                                          min_size=(90,20),
372                                          max_size=(90,20))
373
374        self.quit_window.addChild(label)
375        hbox.addChild(yes_button)
376        hbox.addChild(no_button)
377        self.quit_window.addChild(hbox)
378
379        events_to_map = { "yes_button": self.quit_callback,
380                          "no_button":  self.quit_window.hide }
381       
382        self.quit_window.mapEvents(events_to_map)
383
384
385    def quitGame(self):
386        """Called when user requests to quit game.
387           @return: None"""
388
389        self.quit_window.show()
390
391    def toggleInventoryButton(self):
392        """Manually toggles the inventory button.
393           @return: None"""
394        button = self.hud.findChild(name="inventoryButton")
395        if button.toggled == 0:
396            button.toggled = 1
397        else:
398            button.toggled = 0
399
400    def toggleInventory(self, toggleImage=True):
401        """Displays the inventory screen
402           @return: None"""
403        if self.inventory == None:
404            self.initializeInventory()
405        self.inventory.toggleInventory(toggleImage)
406
407    def refreshReadyImages(self):
408        """Make the Ready slot images on the HUD be the same as those
409           on the inventory
410           @return: None"""
411        for ready in range(1,5):
412            button = self.hud.findChild(name=("hudReady%d" % ready))
413            if self.inventory_storage == None :
414                origin = None
415            else:
416               origin = self.inventory_storage.getItemsInSlot('ready', ready-1)
417            if origin == None:
418                self.setImages(button, self.inventory.slot_empty_images['ready'])
419            else:
420                self.setImages(button,origin.getInventoryThumbnail())
421
422    def setImages(self, widget, image):
423        """Set the up, down, and hover images of an Imagebutton.
424           @type widget: pychan.widget
425           @param widget: widget to set
426           @type image: string
427           @param image: image to use
428           @return: None"""
429        widget.up_image = image
430        widget.down_image = image
431        widget.hover_image = image
432
433    def initializeEvents(self):
434        """Intialize Hud events
435           @return: None"""
436        events_to_map = {}
437
438        # when we click the toggle button don't change the image
439        events_to_map["inventoryButton"] = cbwa(self.toggleInventory, False)
440        events_to_map["saveButton"] = self.saveGame
441        events_to_map["loadButton"] = self.loadGame
442
443        hud_ready_buttons = ["hudReady1", "hudReady2", \
444                             "hudReady3", "hudReady4"]
445
446        for item in hud_ready_buttons:
447            events_to_map[item] = cbwa(self.readyAction, item)
448
449        self.hud.mapEvents(events_to_map)
450
451        menu_events = {}
452        menu_events["newButton"] = self.newGame
453        menu_events["quitButton"] = self.quitGame
454        menu_events["saveButton"] = self.saveGame
455        menu_events["loadButton"] = self.loadGame
456        self.main_menu.mapEvents(menu_events)
457
458    def updateVolumeText(self):
459        """
460        Update the initial volume label to reflect the value of the slider
461        """
462        volume = float(self.options_menu.collectData("InitialVolumeSlider"))
463        volume_label = self.options_menu.findChild(name="InitialVolumeLabel")
464        volume_label.text = unicode("Initial Volume: %.0f%s" %
465                                    (int(volume*10), "%"))
466
467    def requireRestartDialog(self):
468        """Show a dialog asking the user to restart PARPG in order for their
469           changes to take effect.
470           @return: None"""
471        require_restart_dialog = pychan.loadXML('gui/hud_require_restart.xml')
472        require_restart_dialog.mapEvents(\
473                                {'okButton':require_restart_dialog.hide})
474        require_restart_dialog.show()
475
476    def applyOptions(self):
477        """Apply the current options.
478           @return: None"""
479        # At first no restart is required
480        self.require_restart = False
481
482        # get the current values of each setting from the options menu
483        enable_fullscreen = self.options_menu.collectData('FullscreenBox')
484        enable_sound = self.options_menu.collectData('SoundsBox')
485        screen_resolution = self.options_menu.collectData('ResolutionBox')
486        if enable_fullscreen:
487            partition = self.resolutions_fullscreen[screen_resolution].partition('x')
488        else:
489            partition = self.resolutions_windowed[screen_resolution].partition('x')
490        screen_width = partition[0]
491        screen_height = partition[2]
492        render_backend = self.options_menu.collectData('RenderBox')
493        initial_volume = self.options_menu.collectData('InitialVolumeSlider')
494        initial_volume = "%.1f" % initial_volume
495
496        # get the options that are being used right now from settings.xml
497        s_fullscreen = self.settings.readSetting('FullScreen')
498        s_sounds = self.settings.readSetting('PlaySounds')
499        s_render = self.settings.readSetting('RenderBackend')
500        s_volume = self.settings.readSetting('InitialVolume')
501
502        s_screen_height = self.settings.readSetting('ScreenHeight')
503        s_screen_width = self.settings.readSetting('ScreenWidth')
504        s_resolution = s_screen_width + 'x' + s_screen_height
505
506        # On each:
507        # - Check to see whether the option within the xml matches the
508        #   option within the options menu
509        # - If they do not match, set the option within the xml to
510        #   to be what is within the options menu
511        # - Require a restart
512
513        if (int(enable_fullscreen) != int(s_fullscreen)):
514            self.setOption('FullScreen', int(enable_fullscreen))
515            self.require_restart = True
516           
517        if (int(enable_sound) != int(s_sounds)):
518            self.setOption('PlaySounds', int(enable_sound))
519            self.require_restart = True
520
521        if (screen_resolution != s_resolution):
522            self.setOption('ScreenWidth', int(screen_width))
523            self.setOption('ScreenHeight', int(screen_height))
524            self.require_restart = True
525
526        # Convert the number from the list of render backends to
527        # the string that FIFE wants for its settings.xml
528        if (render_backend == 0):
529            render_backend = 'OpenGL'
530        else:
531            render_backend = 'SDL'
532
533        if (render_backend != str(s_render)):
534            self.setOption('RenderBackend', render_backend)
535            self.require_restart = True
536
537        if (initial_volume != float(s_volume)):
538            self.setOption('InitialVolume', initial_volume)
539            self.require_restart = True
540       
541        # Write all the settings to settings.xml
542        self.settings.tree.write('settings.xml')
543       
544        # If the changes require a restart, popup the dialog telling
545        # the user to do so
546        if (self.require_restart):
547            self.requireRestartDialog()
548        # Once we are done, we close the options menu
549        self.options_menu.hide()
550
551    def setOption(self, name, value):
552        """Set an option within the xml.
553           @type name: string
554           @param name: The name of the option within the xml
555           @type value: any
556           @param value: The value that the option 'name' should be set to
557           @return: None"""
558        element = self.settings.root_element.find(name)
559        if(element != None):
560            if(value != element.text):
561                element.text = str(value)
562        else:
563            print 'Setting,', name, 'does not exist!'
564
565    def setToDefaults(self):
566        """Reset all the options to the options in settings-dist.xml.
567           @return: None"""
568        shutil.copyfile('settings-dist.xml', 'settings.xml')
569        self.requireRestartDialog()
570        self.options_menu.hide()
571
572    def displayOptions(self):
573        """Display the options menu.
574           @return: None"""
575        self.options_menu.show()
576   
577    def readyAction(self, ready_button):
578        """ Called when the user selects a ready button from the HUD """
579        text = "Used the item from %s" % ready_button
580        self.addAction(text)
581       
582    def createBoxGUI(self, title, container):
583        """Creates a window to display the contents of a box
584           @type title: string
585           @param title: The title for the window
586           @param items: The box to display
587           @return: A new ContainerGui"""
588        events = {'takeAllButton':self.hideContainer,
589                  'closeButton':self.hideContainer}
590        #hide previous container if any, to avoid orphaned dialogs
591        self.hideContainer()
592
593        self.box_container = ContainerGUI(self.engine, \
594                                              unicode(title), container)
595        self.box_container.container_gui.mapEvents(events)
596        self.box_container.showContainer()
597        return self.box_container
598
599    def hideContainer(self):
600        """Hide the container box
601           @return: None"""
602        if self.box_container:
603            self.box_container.hideContainer()
604            #TODO: Move the close() call into OpenBoxAction(). This can be done
605            # after createBoxGUI becomes a blocking call or it's otherwise possible
606            # to wait till the box GUI is closed.
607            self.box_container.container.close()
608
609    def createExamineBox(self, title, desc):
610        """Create an examine box. It displays some textual description of an
611           object
612           @type title: string
613           @param title: The title of the examine box
614           @type desc: string
615           @param desc: The main body of the examine box
616           @return: None"""
617
618        if self.examine_box is not None:
619            self.examine_box.closePopUp()
620        self.examine_box = ExaminePopup(self.engine, title, desc)
621        self.examine_box.showPopUp()
622
623    def showDialogue(self, npc):
624        """Show the NPC dialogue window
625           @type npc: actors.NonPlayerCharacter
626           @param npc: the npc that we are having a dialogue with
627           @return: None"""
628        dialogue = DialogueGUI(
629                    npc,
630                    self.data.game_state.quest_engine,
631                    self.data.game_state.PC)
632        dialogue.initiateDialogue()
Note: See TracBrowser for help on using the repository browser.