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

Revision 167, 17.4 KB checked in by bretzel_parpg, 10 years ago (diff)

The ready slots on the HUD now do something when you click on them. All it is at the moment is saying
something in the "actions" box.

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