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

Revision 162, 17.2 KB checked in by bretzel_parpg, 10 years ago (diff)

You can now select the file you want to save to or load from. filebrowser.py is actually
built into FIFE, but I copied it to the scripts folder because I had to make some
modifications to it.

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