1
####################################################################
2
# -*- coding: iso-8859-1 -*- #
5
# Copyright (C) 2006 Sami Kyļæ½stilļæ½ #
8
# 2008 evilynux <evilynux@gmail.com> #
10
# This program is free software; you can redistribute it and/or #
11
# modify it under the terms of the GNU General Public License #
12
# as published by the Free Software Foundation; either version 2 #
13
# of the License, or (at your option) any later version. #
15
# This program is distributed in the hope that it will be useful, #
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
18
# GNU General Public License for more details. #
20
# You should have received a copy of the GNU General Public License #
21
# along with this program; if not, write to the Free Software #
22
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, #
23
# MA 02110-1301, USA. #
24
#####################################################################
26
from OpenGL.GL import *
28
from FakeNetworking import socket
30
from View import BackgroundLayer
32
from Lobby import Lobby
33
from Svg import ImgDrawing
34
from Language import _
44
from Shader import shaders
48
#myfingershurt: needed for random menu music:
54
class MainMenu(BackgroundLayer):
55
def __init__(self, engine):
58
self.logClassInits = Config.get("game", "log_class_inits")
59
if self.logClassInits == 1:
60
Log.debug("MainMenu class init (MainMenu.py)...")
66
Player.practiceMode = False
68
#myfingershurt: removing neck menu requirement:
69
#self.neckMenuEnabled = False
71
#self.neckMenuEnabled = Config.get("game", "neck_select_enabled")
73
self.gfxVersionTag = Config.get("game", "gfx_version_tag")
75
#self.tut = Config.get("game", "tut")
76
self.chosenNeck = Config.get("game", "default_neck")
78
#neck fallback to random if doesn't exist.
80
# evilynux - first assume the chosenNeck contains the full filename
81
engine.loadImgDrawing(self, "ok", os.path.join("necks",self.chosenNeck+".png"))
84
engine.loadImgDrawing(self, "ok", os.path.join("necks","Neck_"+self.chosenNeck+".png"))
91
#MFH - fallback logic now supports a couple valid default neck filenames
92
#MFH - check for Neck_1
95
engine.loadImgDrawing(self, "ok", os.path.join("necks","Neck_1.png"))
99
Config.set("game", "default_neck", "1")
100
Log.warn("Default chosen neck not valid; fallback Neck_1.png forced.")
102
#MFH - check for defaultneck
105
engine.loadImgDrawing(self, "ok", os.path.join("necks","defaultneck.png"))
106
except IOError: #we don't really need to be accepting this except... ...yea, sorry.
107
raise IOError, "Default chosen neck not valid; fallbacks Neck_1.png and defaultneck.png also not valid!"
109
Log.warn("Default chosen neck not valid; fallback defaultneck.png forced.")
110
Config.set("game", "default_neck", "defaultneck")
114
self.theme = self.engine.data.theme
115
self.themeCoOp = self.engine.data.themeCoOp
116
self.themename = self.engine.data.themeLabel
117
self.useSoloMenu = Theme.use_solo_submenu
126
self.menux = Theme.menuX
127
self.menuy = Theme.menuY
129
Log.warn("Unable to load Theme menuX / Y positions: %s" % e)
133
self.rbmenu = Theme.menuRB
136
self.main_menu_scale = Theme.main_menu_scaleVar
137
self.main_menu_vspacing = Theme.main_menu_vspacingVar
139
if self.main_menu_scale == None:
140
self.main_menu_scale = .5
141
if self.main_menu_vspacing == None:
142
self.main_menu_vspacing = 0.09
150
self.engine.loadImgDrawing(self, "background", os.path.join("themes",self.themename,"menu","mainbg.png"))
152
self.background = None
153
self.engine.loadImgDrawing(self, "BGText", os.path.join("themes",self.themename,"menu","maintext.png"))
155
self.engine.loadImgDrawing(self, "optionsBG", os.path.join("themes",self.themename,"menu","optionsbg.png"))
157
self.optionsBG = None
158
self.engine.loadImgDrawing(self, "optionsPanel", os.path.join("themes",self.themename,"menu","optionspanel.png"))
160
#racer: added version tag
161
if self.gfxVersionTag or Theme.versiontag == True:
163
self.engine.loadImgDrawing(self, "version", os.path.join("themes",self.themename,"menu","versiontag.png"))
166
self.engine.loadImgDrawing(self, "version", "versiontag.png") #falls back on default versiontag.png in data\ folder
174
#myfingershurt: random main menu music function, menu.ogg and menuXX.ogg (any filename with "menu" as the first 4 letters)
175
filepath = self.engine.getPath(os.path.join("themes",self.themename,"sounds"))
177
allfiles = os.listdir(filepath)
178
for name in allfiles:
179
if os.path.splitext(name)[1] == ".ogg":
180
if string.find(name,"menu") > -1:
181
self.files.append(name)
185
i = random.randint(0,len(self.files)-1)
186
filename = self.files[i]
187
sound = os.path.join("themes",self.themename,"sounds",filename)
188
self.menumusic = True
189
engine.menuMusic = True
191
self.song = Audio.Music(self.engine.resource.fileName(sound))
192
self.song.setVolume(self.engine.config.get("audio", "menu_volume"))
193
self.song.play(0) #no loop
195
self.menumusic = False
198
#####======= Racer: New Main Menu ======####
200
self.opt_text_color = Theme.hexToColor(Theme.opt_text_colorVar)
201
self.opt_selected_color = Theme.hexToColor(Theme.opt_selected_colorVar)
203
if self.opt_text_color == None:
204
self.opt_text_color = (1,1,1)
205
if self.opt_selected_color == None:
206
self.opt_selected_color = (1,0.75,0)
209
newMultiplayerMenu = [
210
(_("Host Multiplayer Game"), self.hostMultiplayerGame),
211
(_("Join Multiplayer Game"), self.joinMultiplayerGame),
214
editorMenu = Menu(self.engine, [
215
(_("Edit Existing Song"), self.startEditor),
216
(_("Import New Song"), self.startImporter),
217
(_("Import GH(tm) Songs"), self.startGHImporter),
221
(_("Tutorials"), self.showTutorial),
222
(_("Practice"), lambda: self.newLocalGame(mode1p = 1)),
225
self.opt_bkg_size = [float(i) for i in Theme.opt_bkg_size]
235
if self.theme == 1 and self.themeCoOp: #Worldrave - Put GH Co-op ahead of FoFix co-op for GH based theme's. Made more sense.
237
(_("Face-Off"), lambda: self.newLocalGame(players = 2, maxplayers = -1)),
238
(_("Pro Face-Off"), lambda: self.newLocalGame(players = 2, mode2p = 1, maxplayers = -1)),
239
(_("GH Battle"), lambda: self.newLocalGame(players = 2, mode2p = 6, maxplayers = -1, allowDrum = False)), #akedrou- so you can block drums
240
(_("Party Mode"), lambda: self.newLocalGame(mode2p = 2)),
241
(_("Co-Op"), lambda: self.newLocalGame(players = 2, mode2p = 5)),
242
(_("FoFiX Co-Op"), lambda: self.newLocalGame(players = 2, mode2p = 3, allowMic = allowMic)), #Worldrave - Re-added this option for now.
244
elif self.theme == 1 and not self.themeCoOp:
246
(_("Face-Off"), lambda: self.newLocalGame(players = 2, maxplayers = -1)),
247
(_("Pro Face-Off"), lambda: self.newLocalGame(players = 2, mode2p = 1, maxplayers = -1)),
248
(_("Party Mode"), lambda: self.newLocalGame(mode2p = 2)),
250
elif self.theme == 2:
252
(_("FoFiX Co-Op"), lambda: self.newLocalGame(players = 2, mode2p = 3, maxplayers = 4, allowMic = allowMic)),
253
(_("RB Co-Op"), lambda: self.newLocalGame(players = 2, mode2p = 4, maxplayers = 4, allowMic = allowMic)),
254
(_("GH Co-Op"), lambda: self.newLocalGame(players = 2, mode2p = 5, maxplayers = 4)),
255
(_("Face-Off"), lambda: self.newLocalGame(players = 2, maxplayers = -1)),
256
(_("Pro Face-Off"), lambda: self.newLocalGame(players = 2, mode2p = 1, maxplayers = -1)),
257
(_("Party Mode"), lambda: self.newLocalGame(mode2p = 2)),
261
(_("FoFiX Co-Op"), lambda: self.newLocalGame(players = 2, mode2p = 3, allowMic = allowMic)),
262
(_("Face-Off"), lambda: self.newLocalGame(players = 2, maxplayers = -1)),
263
(_("Pro Face-Off"), lambda: self.newLocalGame(players = 2, mode2p = 1, maxplayers = -1)),
264
(_("Party Mode"), lambda: self.newLocalGame(mode2p = 2)),
267
if self.useSoloMenu is None:
268
if self.theme == 0 or self.theme == 1: #GH themes = 6 main menu selections
269
self.useSoloMenu = False
270
else: #RB themes = 5 main menu selections
271
self.useSoloMenu = True
273
if not self.useSoloMenu:
276
(strCareer, lambda: self.newLocalGame(mode1p = 2, allowMic = allowMic)),
277
(strQuickplay, lambda: self.newLocalGame(allowMic = allowMic)),
278
((strMultiplayer,"multiplayer"), multPlayerMenu),
279
((strTraining,"training"), trainingMenu),
280
((strSettings,"settings"), self.settingsMenu),
281
(strQuit, self.quit),
287
(_("Solo Tour"), lambda: self.newLocalGame(mode1p = 2, allowMic = allowMic)),
288
(_("Quickplay"), lambda: self.newLocalGame(allowMic = allowMic)),
292
#( ( _(strSolo), 1, (0,0) ), soloMenu),
293
((strSolo,"solo"), soloMenu),
294
((strMultiplayer,"multiplayer"), multPlayerMenu),
295
((strTraining,"training"), trainingMenu),
296
((strSettings,"settings"), self.settingsMenu),
297
(strQuit, self.quit),
302
self.menu = Menu(self.engine, mainMenu, onClose = lambda: self.engine.view.popLayer(self), pos = (12,12), textColor = self.opt_text_color, selectedColor = self.opt_selected_color)
304
engine.mainMenu = self #Points engine.mainMenu to the one and only MainMenu object instance
306
## whether the main menu has come into view at least once
307
self.shownOnce = False
309
def settingsMenu(self):
310
if self.engine.advSettings:
311
self.settingsMenuObject = Settings.SettingsMenu(self.engine)
313
self.settingsMenuObject = Settings.BasicSettingsMenu(self.engine)
314
return self.settingsMenuObject
317
self.engine.view.pushLayer(self.menu)
318
self.engine.stopServer()
319
shaders.checkIfEnabled()
320
if not self.shownOnce:
321
self.shownOnce = True
322
if hasattr(sys, 'frozen'):
323
#stump: Check whether this is a non-svn binary being run from an svn working copy.
324
if os.path.isdir(os.path.join('src', '.svn')) and 'development' not in Version.version():
325
Dialogs.showMessage(self.engine, _('This binary release is being run from a Subversion working copy. This is not the correct way to run FoFiX from Subversion. Please see one of the following web pages to set your Subversion working copy up correctly:') +
326
'\n\nhttp://code.google.com/p/fofix/wiki/RunningUnderPython26' +
327
'\nhttp://code.google.com/p/fofix/wiki/RequiredSourceModules')
328
#stump: Check whether this is an svn binary not being run from an svn working copy
329
elif not os.path.isdir(os.path.join('src', '.svn')) and 'development' in Version.version():
330
Dialogs.showMessage(self.engine, _('This binary was built from a Subversion working copy but is not running from one. The FoFiX Team will not provide any support whatsoever for this binary. Please see the following site for official binary releases:') +
331
'\n\nhttp://code.google.com/p/fofix/')
334
if not self.song.isPlaying(): #re-randomize
336
i = random.randint(0,len(self.files)-1)
337
filename = self.files[i]
338
sound = os.path.join("themes",self.themename,"sounds",filename)
339
self.menumusic = True
340
self.engine.menuMusic = True
342
#self.song = Audio.Sound(self.engine.resource.fileName(sound))
343
self.song = Audio.Music(self.engine.resource.fileName(sound))
344
self.song.setVolume(self.engine.config.get("audio", "menu_volume"))
346
self.song.play(0) #no loop
348
self.menumusic = False
349
self.engine.menuMusic = False
351
def setMenuVolume(self):
352
if self.menumusic and self.song.isPlaying():
353
self.song.setVolume(self.engine.config.get("audio", "menu_volume"))
357
if self.song and not self.engine.menuMusic:
358
self.song.fadeout(1400)
361
self.engine.view.popLayer(self.menu)
364
self.engine.view.pushLayer(self.nextLayer())
365
self.nextLayer = None
370
self.engine.view.popLayer(self.menu)
372
def catchErrors(function):
373
def harness(self, *args, **kwargs):
376
function(self, *args, **kwargs)
379
Log.error("Traceback:" + traceback.format_exc() )
380
traceback.print_exc()
382
except socket.error, e:
383
Dialogs.showMessage(self.engine, unicode(e[1]))
384
except KeyboardInterrupt:
387
#MFH - enhancing error trapping and locating logic
389
Dialogs.showMessage(self.engine, unicode(e))
392
def launchLayer(self, layerFunc):
393
if not self.nextLayer:
394
self.nextLayer = layerFunc
395
self.engine.view.popAllLayers()
396
#launchLayer = catchErrors(launchLayer) #MFH - trying to catch errors
398
def showTutorial(self):
399
# evilynux - Make sure tutorial exists before launching
400
#tutorialpath = self.engine.getPath(os.path.join("songs","tutorial"))
401
tutorialpath = self.engine.tutorialFolder
402
if not os.path.isdir(self.engine.resource.fileName(tutorialpath)):
403
Log.debug("No folder found: %s" % tutorialpath)
404
Dialogs.showMessage(self.engine, _("No tutorials found!"))
407
if self.engine.isServerRunning():
410
players = Dialogs.activateControllers(self.engine, 1) #akedrou
414
Config.set("game","game_mode", 0) #MFH - ensure tutorial can work with new logic that depends on this mode variable
415
Config.set("game","multiplayer_mode", 0) #MFH - ensure tutorial can work with new logic that depends on this mode variable
416
Config.set("game", "players", 1)
417
Config.set("game", "tut", True)
419
#Config.set("game","game_mode", 1) #MFH - don't force practice mode.... this is problematic.
421
self.engine.startServer()
422
self.engine.resource.load(self, "session", lambda: self.engine.connect("127.0.0.1"), synch = True)
424
if Dialogs.showLoadingScreen(self.engine, lambda: self.session and self.session.isConnected):
425
self.launchLayer(lambda: Lobby(self.engine, self.session, singlePlayer = True))
426
showTutorial = catchErrors(showTutorial)
428
#MFH: adding deprecated support for EOF's method of quickstarting a song to test it
429
def newSinglePlayerGame(self):
430
self.newLocalGame() #just call start function with default settings = 1p quickplay
432
def newLocalGame(self, players=1, mode1p=0, mode2p=0, maxplayers = None, allowGuitar = True, allowDrum = True, allowMic = False): #mode1p=0(quickplay),1(practice),2(career) / mode2p=0(faceoff),1(profaceoff)
433
self.engine.data.acceptSound.play()
434
players = Dialogs.activateControllers(self.engine, players, maxplayers, allowGuitar, allowDrum, allowMic) #akedrou
436
if self.engine.cmdPlay == 2:
437
self.engine.cmdPlay = 0
439
Config.set("game", "players", players)
440
Config.set("game","game_mode", mode1p)
441
Config.set("game","multiplayer_mode", mode2p)
442
if Config.get("game", "tut") == True:
443
Config.set("game", "tut", False)
444
#Config.set("game", "selected_library", "")
445
#Config.set("game", "selected_song", "")
447
#MFH - testing new traceback logging:
450
if self.engine.isServerRunning():
452
self.engine.startServer()
453
self.engine.resource.load(self, "session", lambda: self.engine.connect("127.0.0.1"), synch = True)
455
if Dialogs.showLoadingScreen(self.engine, lambda: self.session and self.session.isConnected):
456
self.launchLayer(lambda: Lobby(self.engine, self.session, singlePlayer = True))
457
newLocalGame = catchErrors(newLocalGame)
459
def hostMultiplayerGame(self):
460
self.engine.startServer()
461
self.engine.resource.load(self, "session", lambda: self.engine.connect("127.0.0.1"))
463
if Dialogs.showLoadingScreen(self.engine, lambda: self.session and self.session.isConnected):
464
self.launchLayer(lambda: Lobby(self.engine, self.session))
465
hostMultiplayerGame = catchErrors(hostMultiplayerGame)
467
def joinMultiplayerGame(self, address = None):
469
address = Dialogs.getText(self.engine, _("Enter the server address:"), "127.0.0.1")
474
self.engine.resource.load(self, "session", lambda: self.engine.connect(address))
476
if Dialogs.showLoadingScreen(self.engine, lambda: self.session and self.session.isConnected, text = _("Connecting...")):
477
self.launchLayer(lambda: Lobby(self.engine, self.session))
478
joinMultiplayerGame = catchErrors(joinMultiplayerGame)
480
def startEditor(self):
481
self.launchLayer(lambda: Editor(self.engine))
482
startEditor = catchErrors(startEditor)
484
def startImporter(self):
485
self.launchLayer(lambda: Importer(self.engine))
486
startImporter = catchErrors(startImporter)
488
def startGHImporter(self):
489
self.launchLayer(lambda: GHImporter(self.engine))
490
startGHImporter = catchErrors(startGHImporter)
492
def run(self, ticks):
493
self.time += ticks / 50.0
494
if self.engine.cmdPlay == 1:
495
self.engine.cmdPlay = 4
496
elif self.engine.cmdPlay == 4: #this frame runs the engine an extra loop to allow the font to load...
497
#evilynux - improve cmdline support
498
self.engine.cmdPlay = 2
499
self.newLocalGame(players = Config.get("game", "players"), mode1p = Config.get("game","game_mode"), mode2p = Config.get("game","multiplayer_mode"))
500
elif self.engine.cmdPlay == 3:
504
if self.menumusic: #MFH
508
def render(self, visibility, topMost):
509
self.engine.view.setViewport(1,0)
510
self.visibility = visibility
512
v = 1.0 - ((1 - visibility) ** 2)
516
self.engine.view.transitionTime = 1
518
if self.menu.active and not self.active:
523
w, h, = self.engine.view.geometry[2:4]
526
if not self.useSoloMenu:
529
if self.engine.view.topLayer() is not None:
530
if self.optionsBG != None:
531
self.engine.drawImage(self.optionsBG, (self.opt_bkg_size[2],-self.opt_bkg_size[3]), (w*self.opt_bkg_size[0],h*self.opt_bkg_size[1]), stretched = 3)
533
self.engine.drawImage(self.optionsPanel, (0.5,-0.5), (w/1.7, h/2))
535
self.engine.drawImage(self.engine.data.loadingImage, (1.0,-1.0), (w/2, h/2), stretched = 3)
537
if self.menu.active and self.engine.cmdPlay == 0:
538
if self.background != None:
540
self.engine.drawImage(self.background, (1.0,-1.0), (w/2, h/2), stretched = 3)
544
if self.menu.currentIndex == i:
553
#============blazingamer============
554
#if menux and/or menuy are not set it will use the default positions for the main text
555
if self.menux == None or self.menuy == None:
557
textcoord = (w*0.5,h*0.45-(h*self.main_menu_vspacing)*i)
558
elif self.theme == 1:
559
textcoord = (w*0.7,h*0.8-(h*self.main_menu_vspacing)*i)
560
#if menux and menuy are set it will use those
563
textcoord = (w*self.menux,h*self.menuy-(h*self.main_menu_vspacing)*i)
565
Log.warn("Unable to translate BGText: %s" % e)
567
#===================================
569
self.engine.drawImage(self.BGText, (.5*self.main_menu_scale,-1/6.0*self.main_menu_scale), textcoord,
570
rect = (xpos[0],xpos[1],ypos,ypos+1/6.0))
575
if self.engine.view.topLayer() is not None:
576
if self.optionsBG != None:
577
self.engine.drawImage(self.optionsBG, (self.opt_bkg_size[2],-self.opt_bkg_size[3]), (w*self.opt_bkg_size[0],h*self.opt_bkg_size[1]), stretched = 3)
579
self.engine.drawImage(self.optionsPanel, (0.5,-0.5), (w*0.4, h/2))
581
self.engine.drawImage(self.engine.data.loadingImage, scale = (1.0,-1.0), coord = (w/2,h/2), stretched = 3)
583
if self.menu.active and self.engine.cmdPlay == 0:
584
if self.background != None:
585
self.engine.drawImage(self.background, (1.0,-1.0), (w/2, h/2), stretched = 3)
589
if self.menu.currentIndex == i:
598
#============blazingamer============
599
#if menux and/or menuy are not set it will use the default positions for the main text
600
if self.menux == None or self.menuy == None:
601
textcoord = (w*0.2,(h*0.8-(h*self.main_menu_vspacing)*i)*v)
602
#if menux and menuy are set it will use those
605
textcoord = (w*self.menux,(h*self.menuy-(h*self.main_menu_vspacing)*i)*v)
607
Log.warn("Unable to translate BGText: %s" % e)
609
#===================================
611
self.engine.drawImage(self.BGText, (.5*self.main_menu_scale,(-1/5.0*self.main_menu_scale)),
612
textcoord, rect = (xpos[0],xpos[1],ypos,ypos+1/5.0))
614
#racer: added version tag to main menu:
615
if self.version != None:
616
wfactor = self.version.widthf(pixelw = 640.000)
617
self.engine.drawImage(self.version, (0.5,-0.5),(w*Theme.versiontagposX, h*Theme.versiontagposY)) #worldrave - Added theme settings to control X+Y positions of versiontag.