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

Revision 361, 24.9 KB checked in by orlandov, 10 years ago (diff)

Ticket #104 - Patch by Vaporice and or1andov. Integrate quests into game dialogue and make it possible to persist quest state. fixes[s:trac, t:134]

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