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

Revision 225, 19.0 KB checked in by bretzel_parpg, 10 years ago (diff)

Objects now use name and text without desc. Fixes #69.

  • 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 sounds import SoundEngine
25from settings import Setting
26from scripts import inventory, hud
27from scripts.popups import *
28from scripts.context_menu import ContextMenu
29from pychan.tools import callbackWithArguments as cbwa
30
31TDS = Setting()
32
33# this file should be the meta-file for all FIFE-related code
34# engine.py handles is our data model, whilst this is our view
35# in order to not replicate data, some of our data model will naturally
36# reside on this side of the fence (PC xpos and ypos, for example).
37# we should aim to never replicate any data as this leads to maintainance
38# issues (and just looks plain bad).
39# however, any logic needed to resolve this should sit in engine.py
40
41class Map(fife.MapChangeListener):
42    """Map class used to flag changes in the map"""
43    def __init__(self, fife_map):
44        fife.MapChangeListener.__init__(self)
45        self.map = fife_map
46
47class World(EventListenerBase):
48    """World holds the data needed by fife to render the engine
49       The engine keeps a copy of this class"""
50    def __init__(self, engine):
51        """Constructor for engine
52           @type engine: fife.Engine
53           @param engine: A fife.Engine instance
54           @return: None"""
55        super(World, self).__init__(engine, regMouse = True, regKeys = True)
56        # self.engine is a fife.Engine object, not an Engine object
57        self.engine = engine
58        self.eventmanager = engine.getEventManager()
59        self.model = engine.getModel()
60        self.view = self.engine.getView()
61        self.quitFunction = None
62        self.inventoryShown = False
63        self.agent_layer = None
64        self.cameras = {}
65        # self.data is an engine.Engine object, but is set in run.py
66        self.data = None
67        self.mouseCallback = None
68        self.obj_hash={}
69        # self.map is a Map object, set to none here
70        self.map = None
71        self.hud = hud.Hud(self.engine, TDS)
72        self.hud.events_to_map["inventoryButton"] = cbwa(self.displayInventory, True)
73        self.hud.events_to_map["saveButton"] = self.saveGame
74        self.hud.events_to_map["loadButton"] = self.loadGame
75        hud_ready_buttons = ["hudReady1", "hudReady2", "hudReady3", "hudReady4"]
76        for item in hud_ready_buttons:
77            self.hud.events_to_map[item] = cbwa(self.hud.readyAction, item)
78        self.hud.hud.mapEvents(self.hud.events_to_map)
79        self.hud.menu_events["newButton"] = self.newGame
80        self.hud.menu_events["quitButton"] = self.quitGame
81        self.hud.menu_events["saveButton"] = self.saveGame
82        self.hud.menu_events["loadButton"] = self.loadGame
83        self.hud.main_menu.mapEvents(self.hud.menu_events)
84        self.action_number = 1
85        # setup the inventory
86        # make slot 'A1' and 'A3' container daggers
87        inv_data = {'A1':'dagger01', 'A3':'dagger01'}
88        self.inventory = inventory.Inventory(self.engine, inv_data, 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.F10):
274            # F10 shows/hides the console
275            self.engine.getGuiManager().getConsole().toggleShowHide()
276        if(keyval == key.I):
277            # I opens and closes the inventory
278            self.displayInventory(callFromHud=False)
279        if(keyval == key.A):
280            # A adds a test action to the action box
281            # The test actions will follow this format: Action 1, Action 2, etc.
282            self.hud.addAction("Action " + str(self.action_number))
283            self.action_number += 1
284        if(keyval == key.ESCAPE):
285            # Escape brings up the main menu
286            self.hud.displayMenu()
287        if(keyval == key.M):
288            self.sounds.toggleMusic()
289
290    def mouseReleased(self, evt):
291        """If a mouse button is released, fife calls this routine.
292           We want to wait until the button is released, because otherwise
293           pychan captures the release if a menu is opened.
294           @type evt: fife.event
295           @param evt: The event that fife caught
296           @return: None"""
297        self.context_menu.hide() # hide the context menu in case it is displayed
298        scr_point = fife.ScreenPoint(evt.getX(), evt.getY())
299        if(evt.getButton() == fife.MouseEvent.LEFT):
300            self.data.handleMouseClick(self.getCoords(scr_point))     
301        elif(evt.getButton() == fife.MouseEvent.RIGHT):
302            # is there an object here?
303            instances = self.cameras['main'].getMatchingInstances(scr_point, self.agent_layer)
304            info = None
305            for inst in instances:
306                # check to see if this is an active item
307                if(self.data.objectActive(inst.getId())):           
308                    # yes, get the data
309                    info = self.data.getItemActions(inst.getId())
310                    break
311               
312            # take the menu items returned by the engine or show a default menu if no items   
313            data = info or [["Walk", "Walk here", self.onWalk, self.getCoords(scr_point)]]
314            # show the menu
315            self.context_menu = ContextMenu(self.engine, data, (scr_point.x,scr_point.y))
316
317    def onWalk(self, click):
318        """Callback sample for the context menu.
319        """
320        self.context_menu.hide()
321        self.data.gameState.PC.run(click)
322
323    def mouseMoved(self, evt):
324        """Called when the mouse is moved
325           @type evt: fife.event
326           @param evt: The event that fife caught
327           @return: None"""
328        click = fife.ScreenPoint(evt.getX(), evt.getY())
329        i=self.cameras['main'].getMatchingInstances(click, self.agent_layer)
330        # no object returns an empty tuple
331        if(i != ()):
332            for obj in i:
333                # check to see if this in our list at all
334                if(self.data.objectActive(obj.getId())):
335                    # yes, so outline   
336                    self.outline_render.addOutlined(obj, 0, 137, 255, 2)
337                    # get the text
338                    item = self.data.objectActive(obj.getId())
339                    if(item):
340                        self.displayObjectText(obj, item.name)
341        else:
342            # erase the outline
343            self.outline_render.removeAllOutlines()
344
345    def getCoords(self, click):
346        """Get the map location x, y cords from the screen co-ords
347           @type click: fife.ScreenPoint
348           @param click: Screen co-ords
349           @rtype: fife.Location
350           @return: The map co-ords"""
351        coord = self.cameras["main"].toMapCoordinates(click, False)
352        coord.z = 0
353        location = fife.Location(self.agent_layer)
354        location.setMapCoordinates(coord)
355        return location
356
357    def toggle_renderer(self, r_name):
358        """Enable or disable the grid renderer.
359           @return: None"""
360        renderer = self.cameras['main'].getRenderer('GridRenderer')
361        renderer.setEnabled(not renderer.isEnabled())
362
363    def clearMenu(self):
364        """ Hides the context menu. Just nice to have it as a function.
365            @return: None"""
366        if hasattr(self, "context_menu"):
367            self.context_menu.vbox.hide()
368            delattr(self, "context_menu")
369
370    def newGame(self):
371        """Called when user request to start a new game.
372           @return: None"""
373        print 'new game'
374
375    def quitGame(self):
376        """Called when user requests to quit game.
377           @return: None"""
378        if(self.quitFunction != None):
379            window = pychan.widgets.Window(title=unicode("Quit?"))
380
381            hbox = pychan.widgets.HBox()
382            are_you_sure = "Are you sure you want to quit?"
383            label = pychan.widgets.Label(text=unicode(are_you_sure))
384            yes_button = pychan.widgets.Button(name="yes_button", 
385                                               text=unicode("Yes"))
386            no_button = pychan.widgets.Button(name="no_button",
387                                              text=unicode("No"))
388
389            window.addChild(label)
390            hbox.addChild(yes_button)
391            hbox.addChild(no_button)
392            window.addChild(hbox)
393
394            events_to_map = {"yes_button":self.quitFunction,
395                             "no_button":window.hide}
396           
397            window.mapEvents(events_to_map)
398            window.show()
399
400    def saveGame(self):
401        """ Called when the user wants to save the game.
402            @return: None"""
403        self.save_browser = PARPGFileBrowser(self.engine,
404                                        self.data.save,
405                                        savefile=True,
406                                        guixmlpath="gui/savebrowser.xml",
407                                        extensions = ('.dat'))
408        self.save_browser.showBrowser()
409           
410
411    def loadGame(self):
412        """ Called when the user wants to load a game.
413            @return: None"""
414        self.load_browser = PARPGFileBrowser(self.engine,
415                                        self.data.load,
416                                        savefile=False,
417                                        guixmlpath='gui/loadbrowser.xml',
418                                        extensions=('.dat'))
419        self.load_browser.showBrowser()
420
421
422    def createBoxGUI(self, title):
423        """
424        Creates a window to display the contents of a box
425
426        @type title: string
427        @param title: The title for the window
428        @return: None
429        """
430        if ((self.boxCreated == True) and (self.boxOpen == False)):
431            # if it has already been created, just show it
432            self.box_container.showContainer()
433            self.boxOpen = True
434        else:
435            # otherwise create it then show it
436            data = ["dagger01", "empty", "empty", "empty", "empty",
437                    "empty", "empty", "empty", "empty"]
438            self.box_container = ContainerGUI(self.engine, unicode(title), data)
439            def close_and_delete():
440                self.box_container.hideContainer()
441                self.boxOpen = False
442            events = {'takeAllButton':close_and_delete,
443                      'closeButton':close_and_delete}
444            self.box_container.container_gui.mapEvents(events)
445            self.box_container.showContainer()
446            self.boxOpen = True
447            self.boxCreated = True
448
449    def createExamineBox(self, title, desc):
450        self.examineBox = ExaminePopup(self.engine, title, desc)
451        self.examineBox.showPopUp()
452
453    def pump(self):
454        """Routine called during each frame. Our main loop is in ./run.py
455           We ignore this main loop (but FIFE complains if it is missing)."""
456        pass
Note: See TracBrowser for help on using the repository browser.