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

Revision 181, 18.4 KB checked in by bretzel_parpg, 10 years ago (diff)

When you press the open button on a crate, it now will move you to the crate and display a dialog with
9 slots to show the contents of the crate. The dialog will interface with the inventory.

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