Module GameEngine
[hide private]
[frames] | no frames]

Source Code for Module GameEngine

  1  ##################################################################### 
  2  # -*- coding: iso-8859-1 -*-                                        # 
  3  #                                                                   # 
  4  # Frets on Fire                                                     # 
  5  # Copyright (C) 2006 Sami Kyöstilä                                  # 
  6  #                                                                   # 
  7  # This program is free software; you can redistribute it and/or     # 
  8  # modify it under the terms of the GNU General Public License       # 
  9  # as published by the Free Software Foundation; either version 2    # 
 10  # of the License, or (at your option) any later version.            # 
 11  #                                                                   # 
 12  # This program is distributed in the hope that it will be useful,   # 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of    # 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     # 
 15  # GNU General Public License for more details.                      # 
 16  #                                                                   # 
 17  # You should have received a copy of the GNU General Public License # 
 18  # along with this program; if not, write to the Free Software       # 
 19  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,        # 
 20  # MA  02110-1301, USA.                                              # 
 21  ##################################################################### 
 22   
 23  from OpenGL.GL import * 
 24  import pygame 
 25  import os 
 26  import sys 
 27   
 28  from Engine import Engine, Task 
 29  from Video import Video 
 30  from Audio import Audio 
 31  from View import View 
 32  from Input import Input, KeyListener, SystemEventListener 
 33  from Resource import Resource 
 34  from Data import Data 
 35  from Server import Server 
 36  from Session import ClientSession 
 37  from Svg import SvgContext, SvgDrawing, LOW_QUALITY, NORMAL_QUALITY, HIGH_QUALITY 
 38  from Debug import DebugLayer 
 39  from Language import _ 
 40  import Network 
 41  import Log 
 42  import Config 
 43  import Dialogs 
 44  import Theme 
 45  import Version 
 46  import Mod 
 47   
 48  # define configuration keys 
 49  Config.define("engine", "tickrate",     float, 1.0) 
 50  Config.define("engine", "highpriority", bool,  True) 
 51  Config.define("game",   "uploadscores", bool,  False, text = _("Upload Highscores"),    options = {False: _("No"), True: _("Yes")}) 
 52  Config.define("game",   "uploadurl",    str,   "http://fretsonfire.sourceforge.net/play") 
 53  Config.define("game",   "leftymode",    bool,  False, text = _("Lefty mode"),           options = {False: _("No"), True: _("Yes")}) 
 54  Config.define("game",   "tapping",      bool,  True,  text = _("Tappable notes"),       options = {False: _("No"), True: _("Yes")}) 
 55  Config.define("video",  "fullscreen",   bool,  True,  text = _("Fullscreen Mode"),      options = {False: _("No"), True: _("Yes")}) 
 56  Config.define("video",  "multisamples", int,   4,     text = _("Antialiasing Quality"), options = {0: _("None"), 2: _("2x"), 4: _("4x"), 6: _("6x"), 8: _("8x")}) 
 57  Config.define("video",  "resolution",   str,   "640x480") 
 58  Config.define("video",  "fps",          int,   80,    text = _("Frames per Second"), options = dict([(n, n) for n in range(1, 120)])) 
 59  Config.define("opengl", "svgquality",   int,   NORMAL_QUALITY,  text = _("SVG Quality"), options = {LOW_QUALITY: _("Low"), NORMAL_QUALITY: _("Normal"), HIGH_QUALITY: _("High")}) 
 60  Config.define("audio",  "frequency",    int,   44100, text = _("Sample Frequency"), options = [8000, 11025, 22050, 32000, 44100, 48000]) 
 61  Config.define("audio",  "bits",         int,   16,    text = _("Sample Bits"), options = [16, 8]) 
 62  Config.define("audio",  "stereo",       bool,  True) 
 63  Config.define("audio",  "buffersize",   int,   2048,  text = _("Buffer Size"), options = [256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536]) 
 64  Config.define("audio",  "delay",        int,   100,   text = _("A/V delay"), options = dict([(n, n) for n in range(0, 301)])) 
 65  Config.define("audio",  "screwupvol", float,   0.25,  text = _("Screw Up Sounds"), options = {0.0: _("Off"), .25: _("Quiet"), .5: _("Loud"), 1.0: _("Painful")}) 
 66  Config.define("audio",  "guitarvol",  float,    1.0,  text = _("Guitar Volume"),   options = dict([(n / 100.0, "%02d/10" % (n / 9)) for n in range(0, 110, 10)])) 
 67  Config.define("audio",  "songvol",    float,    1.0,  text = _("Song Volume"),     options = dict([(n / 100.0, "%02d/10" % (n / 9)) for n in range(0, 110, 10)])) 
 68  Config.define("audio",  "rhythmvol",  float,    1.0,  text = _("Rhythm Volume"),   options = dict([(n / 100.0, "%02d/10" % (n / 9)) for n in range(0, 110, 10)])) 
 69  Config.define("video",  "fontscale",  float,    1.0,  text = _("Text scale"),      options = dict([(n / 100.0, "%3d%%" % n) for n in range(50, 260, 10)])) 
 70   
71 -class FullScreenSwitcher(KeyListener):
72 """ 73 A keyboard listener that looks for special built-in key combinations, 74 such as the fullscreen toggle (Alt-Enter). 75 """
76 - def __init__(self, engine):
77 self.engine = engine 78 self.altStatus = False
79
80 - def keyPressed(self, key, unicode):
81 if key == pygame.K_LALT: 82 self.altStatus = True 83 elif key == pygame.K_RETURN and self.altStatus: 84 if not self.engine.toggleFullscreen(): 85 Log.error("Unable to toggle fullscreen mode.") 86 return True 87 elif key == pygame.K_d and self.altStatus: 88 self.engine.setDebugModeEnabled(not self.engine.isDebugModeEnabled()) 89 return True 90 elif key == pygame.K_g and self.altStatus and self.engine.isDebugModeEnabled(): 91 self.engine.debugLayer.gcDump() 92 return True
93
94 - def keyReleased(self, key):
95 if key == pygame.K_LALT: 96 self.altStatus = False
97
98 -class SystemEventHandler(SystemEventListener):
99 """ 100 A system event listener that takes care of restarting the game when needed 101 and reacting to screen resize events. 102 """
103 - def __init__(self, engine):
104 self.engine = engine
105
106 - def screenResized(self, size):
107 self.engine.resizeScreen(size[0], size[1])
108
109 - def restartRequested(self):
110 self.engine.restart()
111
112 - def quit(self):
113 self.engine.quit()
114
115 -class GameEngine(Engine):
116 """The main game engine."""
117 - def __init__(self, config = None):
118 """ 119 Constructor. 120 121 @param config: L{Config} instance for settings 122 """ 123 124 if not config: 125 config = Config.load() 126 127 self.config = config 128 129 fps = self.config.get("video", "fps") 130 tickrate = self.config.get("engine", "tickrate") 131 Engine.__init__(self, fps = fps, tickrate = tickrate) 132 133 pygame.init() 134 135 self.title = _("Frets on Fire") 136 self.restartRequested = False 137 self.handlingException = False 138 self.video = Video(self.title) 139 self.audio = Audio() 140 141 Log.debug("Initializing audio.") 142 frequency = self.config.get("audio", "frequency") 143 bits = self.config.get("audio", "bits") 144 stereo = self.config.get("audio", "stereo") 145 bufferSize = self.config.get("audio", "buffersize") 146 147 self.audio.pre_open(frequency = frequency, bits = bits, stereo = stereo, bufferSize = bufferSize) 148 pygame.init() 149 self.audio.open(frequency = frequency, bits = bits, stereo = stereo, bufferSize = bufferSize) 150 151 Log.debug("Initializing video.") 152 width, height = [int(s) for s in self.config.get("video", "resolution").split("x")] 153 fullscreen = self.config.get("video", "fullscreen") 154 multisamples = self.config.get("video", "multisamples") 155 self.video.setMode((width, height), fullscreen = fullscreen, multisamples = multisamples) 156 157 # Enable the high priority timer if configured 158 if self.config.get("engine", "highpriority"): 159 Log.debug("Enabling high priority timer.") 160 self.timer.highPriority = True 161 162 viewport = glGetIntegerv(GL_VIEWPORT) 163 h = viewport[3] - viewport[1] 164 w = viewport[2] - viewport[0] 165 geometry = (0, 0, w, h) 166 self.svg = SvgContext(geometry) 167 self.svg.setRenderingQuality(self.config.get("opengl", "svgquality")) 168 glViewport(int(viewport[0]), int(viewport[1]), int(viewport[2]), int(viewport[3])) 169 170 self.input = Input() 171 self.view = View(self, geometry) 172 self.resizeScreen(w, h) 173 174 self.resource = Resource(Version.dataPath()) 175 self.server = None 176 self.sessions = [] 177 self.mainloop = self.loading 178 179 # Load game modifications 180 Mod.init(self) 181 theme = Config.load(self.resource.fileName("theme.ini")) 182 Theme.open(theme) 183 184 # Make sure we are using the new upload URL 185 if self.config.get("game", "uploadurl").startswith("http://kempele.fi"): 186 self.config.set("game", "uploadurl", "http://fretsonfire.sourceforge.net/play") 187 188 self.addTask(self.input, synchronized = False) 189 self.addTask(self.view) 190 self.addTask(self.resource, synchronized = False) 191 self.data = Data(self.resource, self.svg) 192 193 self.input.addKeyListener(FullScreenSwitcher(self), priority = True) 194 self.input.addSystemEventListener(SystemEventHandler(self)) 195 196 self.debugLayer = None 197 self.startupLayer = None 198 self.loadingScreenShown = False 199 200 Log.debug("Ready.")
201
202 - def setStartupLayer(self, startupLayer):
203 """ 204 Set the L{Layer} that will be shown when the all 205 the resources have been loaded. See L{Data} 206 207 @param startupLayer: Startup L{Layer} 208 """ 209 self.startupLayer = startupLayer
210
211 - def isDebugModeEnabled(self):
212 return bool(self.debugLayer)
213
214 - def setDebugModeEnabled(self, enabled):
215 """ 216 Show or hide the debug layer. 217 218 @type enabled: bool 219 """ 220 if enabled: 221 self.debugLayer = DebugLayer(self) 222 else: 223 self.debugLayer = None
224
225 - def toggleFullscreen(self):
226 """ 227 Toggle between fullscreen and windowed mode. 228 229 @return: True on success 230 """ 231 if not self.video.toggleFullscreen(): 232 # on windows, the fullscreen toggle kills our textures, se we must restart the whole game 233 self.input.broadcastSystemEvent("restartRequested") 234 self.config.set("video", "fullscreen", not self.video.fullscreen) 235 return True 236 self.config.set("video", "fullscreen", self.video.fullscreen) 237 return True
238
239 - def restart(self):
240 """Restart the game.""" 241 if not self.restartRequested: 242 self.restartRequested = True 243 self.input.broadcastSystemEvent("restartRequested") 244 else: 245 self.quit()
246
247 - def resizeScreen(self, width, height):
248 """ 249 Resize the game screen. 250 251 @param width: New width in pixels 252 @param height: New height in pixels 253 """ 254 self.view.setGeometry((0, 0, width, height)) 255 self.svg.setGeometry((0, 0, width, height))
256
257 - def isServerRunning(self):
258 return bool(self.server)
259
260 - def startServer(self):
261 """Start the game server.""" 262 if not self.server: 263 Log.debug("Starting server.") 264 self.server = Server(self) 265 self.addTask(self.server, synchronized = False)
266
267 - def connect(self, host):
268 """ 269 Connect to a game server. 270 271 @param host: Name of host to connect to 272 @return: L{Session} connected to remote server 273 """ 274 Log.debug("Connecting to host %s." % host) 275 session = ClientSession(self) 276 session.connect(host) 277 self.addTask(session, synchronized = False) 278 self.sessions.append(session) 279 return session
280
281 - def stopServer(self):
282 """Stop the game server.""" 283 if self.server: 284 Log.debug("Stopping server.") 285 self.removeTask(self.server) 286 self.server = None
287
288 - def disconnect(self, session):
289 """ 290 Disconnect a L{Session} 291 292 param session: L{Session} to disconnect 293 """ 294 if session in self.sessions: 295 Log.debug("Disconnecting.") 296 self.removeTask(session) 297 self.sessions.remove(session)
298
299 - def loadSvgDrawing(self, target, name, fileName, textureSize = None):
300 """ 301 Load an SVG drawing synchronously. 302 303 @param target: An object that will own the drawing 304 @param name: The name of the attribute the drawing will be assigned to 305 @param fileName: The name of the file in the data directory 306 @param textureSize Either None or (x, y), in which case the file will 307 be rendered to an x by y texture 308 @return: L{SvgDrawing} instance 309 """ 310 return self.data.loadSvgDrawing(target, name, fileName, textureSize)
311
312 - def loading(self):
313 """Loading state loop.""" 314 done = Engine.run(self) 315 self.clearScreen() 316 317 if self.data.essentialResourcesLoaded(): 318 if not self.loadingScreenShown: 319 self.loadingScreenShown = True 320 Dialogs.showLoadingScreen(self, self.data.resourcesLoaded) 321 if self.startupLayer: 322 self.view.pushLayer(self.startupLayer) 323 self.mainloop = self.main 324 self.view.render() 325 self.video.flip() 326 return done
327
328 - def clearScreen(self):
329 self.svg.clear(*Theme.backgroundColor)
330
331 - def main(self):
332 """Main state loop.""" 333 334 # Tune the scheduler priority so that transitions are as smooth as possible 335 if self.view.isTransitionInProgress(): 336 self.boostBackgroundThreads(False) 337 else: 338 self.boostBackgroundThreads(True) 339 340 done = Engine.run(self) 341 self.clearScreen() 342 self.view.render() 343 if self.debugLayer: 344 self.debugLayer.render(1.0, True) 345 self.video.flip() 346 return done
347
348 - def run(self):
349 try: 350 return self.mainloop() 351 except KeyboardInterrupt: 352 sys.exit(0) 353 except SystemExit: 354 sys.exit(0) 355 except Exception, e: 356 def clearMatrixStack(stack): 357 try: 358 glMatrixMode(stack) 359 for i in range(16): 360 glPopMatrix() 361 except: 362 pass
363 364 if self.handlingException: 365 # A recursive exception is fatal as we can't reliably reset the GL state 366 sys.exit(1) 367 368 self.handlingException = True 369 Log.error("%s: %s" % (e.__class__, e)) 370 import traceback 371 traceback.print_exc() 372 373 clearMatrixStack(GL_PROJECTION) 374 clearMatrixStack(GL_MODELVIEW) 375 376 Dialogs.showMessage(self, unicode(e)) 377 self.handlingException = False 378 return True
379