source: trunk/PARPG/scripts/world.py @ 189

Revision 189, 18.5 KB checked in by bretzel_parpg, 10 years ago (diff)

The examine function now works. Added a 'desc' attribute to all GameObject?'s that contains the detailed
description

  • 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 fife, time
19import pychan
20from scripts.parpgfilebrowser import PARPGFileBrowser
21from datetime import date
22from scripts.common.eventlistenerbase import EventListenerBase
23from loaders import loadMapFile
24from agents.hero import Hero
25from agents.npc import NPC
26from sounds import SoundEngine
27from settings import Setting
28from scripts import inventory, hud
29from scripts.popups import *
30from scripts.context_menu import ContextMenu
31from pychan.tools import callbackWithArguments as cbwa
32
33TDS = Setting()
34
35# this file should be the meta-file for all FIFE-related code
36# engine.py handles is our data model, whilst this is our view
37# in order to not replicate data, some of our data model will naturally
38# reside on this side of the fence (PC xpos and ypos, for example).
39# we should aim to never replicate any data as this leads to maintainance
40# issues (and just looks plain bad).
41# however, any logic needed to resolve this should sit in engine.py
42
43class Map(fife.MapChangeListener):
44    """Map class used to flag changes in the map"""
45    def __init__(self, fife_map):
46        fife.MapChangeListener.__init__(self)
47        self.map = fife_map
48
49class World(EventListenerBase):
50    """World holds the data needed by fife to render the engine
51       The engine keeps a copy of this class"""
52    def __init__(self, engine):
53        """Constructor for engine
54           @type engine: fife.Engine
55           @param engine: A fife.Engine instance
56           @return: None"""
57        super(World, self).__init__(engine, regMouse = True, regKeys = True)
58        # self.engine is a fife.Engine object, not an Engine object
59        self.engine = engine
60        self.eventmanager = engine.getEventManager()
61        self.model = engine.getModel()
62        self.view = self.engine.getView()
63        self.quitFunction = None
64        self.inventoryShown = False
65        self.agent_layer = None
66        self.cameras = {}
67        # self.data is an engine.Engine object, but is set in run.py
68        self.data = None
69        self.mouseCallback = None
70        self.obj_hash={}
71        # self.map is a Map object, set to none here
72        self.map = None
73        self.hud = hud.Hud(self.engine, TDS)
74        self.hud.events_to_map["inventoryButton"] = cbwa(self.displayInventory, True)
75        self.hud.events_to_map["saveButton"] = self.saveGame
76        self.hud.events_to_map["loadButton"] = self.loadGame
77        hud_ready_buttons = ["hudReady1", "hudReady2", "hudReady3", "hudReady4"]
78        for item in hud_ready_buttons:
79            self.hud.events_to_map[item] = cbwa(self.hud.readyAction, item)
80        self.hud.hud.mapEvents(self.hud.events_to_map)
81        self.hud.menu_events["quitButton"] = self.quitGame
82        self.hud.menu_events["saveButton"] = self.saveGame
83        self.hud.menu_events["loadButton"] = self.loadGame
84        self.hud.main_menu.mapEvents(self.hud.menu_events)
85        self.action_number = 1
86        # setup the inventory
87        self.inventory = inventory.Inventory(self.engine, self.refreshReadyImages)
88        self.inventory.events_to_map['close_button'] = self.closeInventoryAndToggle
89        self.inventory.inventory.mapEvents(self.inventory.events_to_map)
90        self.refreshReadyImages()
91        # init the sound (don't start playing yet)
92        self.sounds = SoundEngine(self.engine)
93       
94        self.context_menu = ContextMenu (self.engine, [], (0,0))
95        self.context_menu.hide()
96        self.boxOpen = False
97        self.boxCreated = False
98
99    def reset(self):
100        """Reset the data to default settings.
101           @return: None"""
102        # We have to delete the map in Fife.
103        # TODO: We're killing the PC now, but later we will have to save the PC
104        if self.map:
105            self.model.deleteObjects()
106            self.model.deleteMap(self.map)
107        self.transitions = []
108        self.map,self.agent_layer = None,None
109        # We have to clear the cameras in the view as well, or we can't reuse
110        # camera names like 'main'
111        self.view.clearCameras()
112        self.cameras = {}
113        self.cur_cam2_x,self.initial_cam2_x,self.cam2_scrolling_right = 0,0,True
114        self.target_rotation = 0
115
116    def load(self, filename):
117        """Load a map given the filename.
118           @type filename: string
119           @param filename: Name of map to load
120           @return: None"""
121        self.reset()
122        # some messy code to handle music changes when we enter a new map
123        if(self.sounds.music_on == True):
124            self.sounds.pauseMusic()
125            unpause = True
126        else:
127            unpause = False
128        self.map = loadMapFile(filename, self.engine)
129        self.maplistener = Map(self.map)
130        # there must be a PC object on the objects layer!
131        self.agent_layer = self.map.getLayer('ObjectLayer')
132        # it's possible there's no transition layer
133        size = len('TransitionLayer')
134        for layer in self.map.getLayers():
135            # could be many layers, but hopefully no more than 3
136            if(layer.getId()[:size] == 'TransitionLayer'):
137                self.transitions.append(self.map.getLayer(layer.getId()))
138        # init the camera
139        for cam in self.view.getCameras():
140            self.cameras[cam.getId()] = cam
141        self.view.resetRenderers()
142        self.target_rotation = self.cameras['main'].getRotation()
143        self.cord_render = self.cameras['main'].getRenderer('CoordinateRenderer')
144        self.outline_render = fife.InstanceRenderer.getInstance(self.cameras['main'])
145        # set the render text
146        rend = fife.FloatingTextRenderer.getInstance(self.cameras['main'])
147        text = self.engine.getGuiManager().createFont('fonts/rpgfont.png',
148                                                       0, str(TDS.readSetting("FontGlyphs", strip=False)))
149        rend.changeDefaultFont(text)
150        # start playing the music
151        # TODO: remove hard coding by putting this in the level data
152        # don't force restart if skipping to new section
153        if (TDS.readSetting("PlaySounds") == "1"):
154            if(self.sounds.music_init == False):
155                self.sounds.playMusic("/music/preciouswasteland.ogg")
156            elif(unpause == True):
157                self.sounds.playMusic()
158
159    def addPC(self, agent):
160        """Add the player character to the map
161           @type agent: Fife.instance
162           @param : The object to use as the PC sprite
163           @return: None"""
164        # actually this is real easy, we just have to
165        # attach the main camera to the PC
166        self.cameras['main'].attach(agent)
167
168    def addObject(self, xpos, ypos, gfx, name):
169        """Add an object or an NPC to the map.
170           It makes no difference to fife which is which.
171           @type xpos: integer
172           @param xpos: x position of object
173           @type ypos: integer
174           @param ypos: y position of object
175           @type gfx: string
176           @param gfx: name of gfx image
177           @type name: string
178           @param name: name of object
179           @return: None"""
180        obj = self.agent_layer.createInstance(
181                self.model.getObject(str(gfx), "PARPG"),
182                fife.ExactModelCoordinate(float(xpos), float(ypos), 0.0), str(name))
183        obj.setRotation(0)
184        fife.InstanceVisual.create(obj)
185        # save it for later use
186        self.obj_hash[name]=obj
187
188    def displayObjectText(self, obj, text):
189        """Display on screen the text of the object over the object.
190           @type obj: fife.instance
191           @param obj: object to draw over
192           @type text: text
193           @param text: text to display over object
194           @return: None"""
195        obj.say(str(text), 1000)
196
197    def refreshReadyImages(self):
198        """Make the Ready slot images on the HUD be the same as those
199           on the inventory
200           @return: None"""
201        self.setImages(self.hud.hud.findChild(name="hudReady1"),
202                       self.inventory.inventory.findChild(name="Ready1").up_image)
203        self.setImages(self.hud.hud.findChild(name="hudReady2"),
204                       self.inventory.inventory.findChild(name="Ready2").up_image)
205        self.setImages(self.hud.hud.findChild(name="hudReady3"),
206                       self.inventory.inventory.findChild(name="Ready3").up_image)
207        self.setImages(self.hud.hud.findChild(name="hudReady4"),
208                       self.inventory.inventory.findChild(name="Ready4").up_image)
209
210    def setImages(self, widget, image):
211        """Set the up, down, and hover images of an Imagebutton.
212           @type widget: pychan.widget
213           @param widget: widget to set
214           @type image: string
215           @param image: image to use
216           @return: None"""
217        widget.up_image = image
218        widget.down_image = image
219        widget.hover_image = image
220
221    def closeInventoryAndToggle(self):
222        """Close the inventory screen.
223           @return: None"""
224        self.inventory.closeInventory()
225        self.hud.toggleInventory()
226        self.inventoryShown = False
227
228    def displayInventory(self, callFromHud):
229        """Pause the game and enter the inventory screen, or close the
230           inventory screen and resume the game. callFromHud should be true
231           (must be True?) if you call this function from the HUD script
232           @type callFromHud: boolean
233           @param callFromHud: Whether this function is being called
234                               from the HUD script
235           @return: None"""
236        if (self.inventoryShown == False):
237            self.inventory.showInventory()
238            self.inventoryShown = True
239        else:
240            self.inventory.closeInventory()
241            self.inventoryShown = False
242        if (callFromHud == False):
243            self.hud.toggleInventory()
244
245    # all key / mouse event handling routines go here
246    def keyPressed(self, evt):
247        """Whenever a key is pressed, fife calls this routine.
248           @type evt: fife.event
249           @param evt: The event that fife caught
250           @return: None"""
251        key = evt.getKey()
252        keyval = key.getValue()
253
254        if(keyval == key.Q):
255            # we need to quit the game
256            self.quitGame()
257        if(keyval == key.T):
258            self.toggle_renderer('GridRenderer')
259        if(keyval == key.F1):
260            # display the help screen and pause the game
261            self.displayHelp()
262        if(keyval == key.F5):
263            # logic would say we use similar code to above and toggle
264            # logic here does not work, my friend :-(
265            self.cord_render.setEnabled(not self.cord_render.isEnabled())
266        if(keyval == key.F7):
267            # F7 saves a screenshot to fife/clients/parpg/screenshots
268            t = "screenshots/screen-%s-%s.png" % (date.today().strftime('%Y-%m-%d'),
269                                                  time.strftime('%H-%M-%S'))
270            print "PARPG: Saved:",t
271            self.engine.getRenderBackend().captureScreen(t)
272        if(keyval == key.I):
273            # I opens and closes the inventory
274            self.displayInventory(callFromHud=False)
275        if(keyval == key.A):
276            # A adds a test action to the action box
277            # The test actions will follow this format: Action 1, Action 2, etc.
278            self.hud.addAction("Action " + str(self.action_number))
279            self.action_number += 1
280        if(keyval == key.ESCAPE):
281            # Escape brings up the main menu
282            self.hud.displayMenu()
283        if(keyval == key.M):
284            self.sounds.toggleMusic()
285
286    def mouseReleased(self, evt):
287        """If a mouse button is released, fife calls this routine.
288           We want to wait until the button is released, because otherwise
289           pychan captures the release if a menu is opened.
290           @type evt: fife.event
291           @param evt: The event that fife caught
292           @return: None"""
293        self.context_menu.hide() # hide the context menu in case it is displayed
294        scr_point = fife.ScreenPoint(evt.getX(), evt.getY())
295        if(evt.getButton() == fife.MouseEvent.LEFT):
296            self.data.handleMouseClick(self.getCoords(scr_point))     
297        elif(evt.getButton() == fife.MouseEvent.RIGHT):
298            # is there an object here?
299            instances = self.cameras['main'].getMatchingInstances(scr_point, self.agent_layer)
300            info = None
301            for inst in instances:
302                # check to see if this is an active item
303                if(self.data.objectActive(inst.getId())):           
304                    # yes, get the data
305                    info = self.data.getItemActions(inst.getId())
306                    break
307               
308            # take the menu items returned by the engine or show a default menu if no items   
309            data = info or [["Walk", "Walk here", self.onWalk, self.getCoords(scr_point)]]
310            # show the menu
311            self.context_menu = ContextMenu(self.engine, data, (scr_point.x,scr_point.y))
312
313    def onWalk(self, click):
314        """Callback sample for the context menu.
315        """
316        self.context_menu.hide()
317        self.data.gameState.PC.run(click)
318
319    def mouseMoved(self, evt):
320        """Called when the mouse is moved
321           @type evt: fife.event
322           @param evt: The event that fife caught
323           @return: None"""
324        click = fife.ScreenPoint(evt.getX(), evt.getY())
325        i=self.cameras['main'].getMatchingInstances(click, self.agent_layer)
326        # no object returns an empty tuple
327        if(i != ()):
328            for obj in i:
329                # check to see if this in our list at all
330                if(self.data.objectActive(obj.getId())):
331                    # yes, so outline   
332                    self.outline_render.addOutlined(obj, 0, 137, 255, 2)
333                    # get the text
334                    item = self.data.objectActive(obj.getId())
335                    if(item):
336                        self.displayObjectText(obj, item.text)
337        else:
338            # erase the outline
339            self.outline_render.removeAllOutlines()
340
341    def getCoords(self, click):
342        """Get the map location x, y cords from the screen co-ords
343           @type click: fife.ScreenPoint
344           @param click: Screen co-ords
345           @rtype: fife.Location
346           @return: The map co-ords"""
347        coord = self.cameras["main"].toMapCoordinates(click, False)
348        coord.z = 0
349        location = fife.Location(self.agent_layer)
350        location.setMapCoordinates(coord)
351        return location
352
353    def toggle_renderer(self, r_name):
354        """Enable or disable the grid renderer.
355           @return: None"""
356        renderer = self.cameras['main'].getRenderer('GridRenderer')
357        renderer.setEnabled(not renderer.isEnabled())
358
359    def clearMenu(self):
360        """ Hides the context menu. Just nice to have it as a function.
361            @return: None"""
362        if hasattr(self, "context_menu"):
363            self.context_menu.vbox.hide()
364            delattr(self, "context_menu")
365
366    def quitGame(self):
367        """Called when user requests to quit game.
368           @return: None"""
369        if(self.quitFunction != None):
370            window = pychan.widgets.Window(title=unicode("Quit?"))
371
372            hbox = pychan.widgets.HBox()
373            are_you_sure = "Are you sure you want to quit?"
374            label = pychan.widgets.Label(text=unicode(are_you_sure))
375            yes_button = pychan.widgets.Button(name="yes_button", 
376                                               text=unicode("Yes"))
377            no_button = pychan.widgets.Button(name="no_button",
378                                              text=unicode("No"))
379
380            window.addChild(label)
381            hbox.addChild(yes_button)
382            hbox.addChild(no_button)
383            window.addChild(hbox)
384
385            events_to_map = {"yes_button":self.quitFunction,
386                             "no_button":window.hide}
387           
388            window.mapEvents(events_to_map)
389            window.show()
390
391    def saveGame(self):
392        """ Called when the user wants to save the game.
393            @return: None"""
394        self.save_browser = PARPGFileBrowser(self.engine,
395                                        self.data.save,
396                                        savefile=True,
397                                        guixmlpath="gui/savebrowser.xml",
398                                        extensions = ('.dat'))
399        self.save_browser.showBrowser()
400           
401
402    def loadGame(self):
403        """ Called when the user wants to load a game.
404            @return: None"""
405        self.load_browser = PARPGFileBrowser(self.engine,
406                                        self.data.load,
407                                        savefile=False,
408                                        guixmlpath='gui/loadbrowser.xml',
409                                        extensions=('.dat'))
410        self.load_browser.showBrowser()
411
412    def createBoxGUI(self):
413        """
414        Creates a window to display the contents of a box
415        """
416        if ((self.boxCreated == True) and (self.boxOpen == False)):
417            # if it has already been created, just show it
418            self.box_container.showContainer()
419            self.boxOpen = True
420        else:
421            # otherwise create it then show it
422            self.box_container = ContainerGUI(self.engine, unicode("Box"), "gui/inv_images/inv_backpack.png")
423            def close_and_delete():
424                self.box_container.hideContainer()
425                self.boxOpen = False
426            events = {'takeAllButton':close_and_delete,
427                      'closeButton':close_and_delete}
428            self.box_container.container_gui.mapEvents(events)
429            self.box_container.showContainer()
430            self.boxOpen = True
431            self.boxCreated = True
432
433    def createExamineBox(self, title, desc):
434        self.examineBox = ExaminePopup(self.engine, title, desc)
435        self.examineBox.showPopUp()
436
437    def pump(self):
438        """Routine called during each frame. Our main loop is in ./run.py
439           We ignore this main loop (but FIFE complains if it is missing)."""
440        pass
Note: See TracBrowser for help on using the repository browser.