diff --git a/.gitignore b/.gitignore
index 45c96aa66..e6eee0860 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,14 +14,20 @@ src/*.idb
src/build
src/pypitch/_pypitch.cpp
src/cmgl.c
+src/VideoPlayer.c
gstreamer/*
data/library.zip
-data/users/players/FoFiX-players.cache
-data/users/players/*.png
+data/users/players/*
+!data/users/players/default.png
+!data/users/players/default.ini
+data/users/controllers/*
+!data/users/controllers/default[dgm].ini
data/songs/
data/themes/*
!data/themes/MegaLight
!data/themes/MegaLight GH3
!data/themes/MegaLight V4
!data/themes/Uberlight
-!data/users/players/default.png
+win32/*
+!win32/*.sh
+!win32/*.py
diff --git a/README b/README
index 5af9b6e68..891855340 100644
--- a/README
+++ b/README
@@ -1,4 +1,11 @@
-For GNU/Linux instructions, please see our wiki page at:
-http://code.google.com/p/fofix/wiki/RunningUnderGNULinux
+This is Frets on Fire X, a highly customizable rhythm game supporting
+many modes of guitar, bass, drum, and vocal gameplay for up to four
+players. It is the continuation of a long succession of modifications
+to the original Frets on Fire by Unreal Voodoo.
-Also see documentation in the doc/ folder.
+Website: http://code.google.com/p/fofix/
+Repository (GitHub): http://github.com/stump/fofix
+ $ git clone git://github.com/stump/fofix.git
+
+For instructions on running from source, see RunningFromSource.mkd in
+the doc/ directory.
diff --git a/data/themes/MegaLight GH3/menu/maintext.png b/data/themes/MegaLight GH3/menu/maintext.png
index 8c7ee16b0..c76a06177 100644
Binary files a/data/themes/MegaLight GH3/menu/maintext.png and b/data/themes/MegaLight GH3/menu/maintext.png differ
diff --git a/data/themes/MegaLight GH3/menu/maintext_gh3.png b/data/themes/MegaLight GH3/menu/maintext_gh3.png
new file mode 100644
index 000000000..8c7ee16b0
Binary files /dev/null and b/data/themes/MegaLight GH3/menu/maintext_gh3.png differ
diff --git a/data/themes/MegaLight GH3/rockmeter.ini b/data/themes/MegaLight GH3/rockmeter.ini
index ae66429cb..e492653d6 100644
--- a/data/themes/MegaLight GH3/rockmeter.ini
+++ b/data/themes/MegaLight GH3/rockmeter.ini
@@ -1,103 +1,134 @@
+[layer0:Image]
+texture = rock_low.png
+xpos = .86
+ypos = .2
+xscale = .5
+yscale = .5
+;condition = rock < rockMax/3.0
+
[layer1:Image]
-texture = dotbase.png
-xpos = .0425
-ypos = .12
+texture = rock_hi.png
+xpos = .86
+ypos = .2
xscale = .5
-yscale = -.5
+yscale = .5
+;condition = rock > rockMax/3.0*2
[layer2:Image]
-texture = dots.png
-
-[layer2:fx0]
-type = streak
-xpos = .0425
-ypos = .12
+texture = rock_med.png
+xpos = .86
+ypos = .2
xscale = .5
-yscale = -.5
-angle = 0
-multdiv = True
+yscale = .5
+;condition = rock > rockMax/3.0
[layer3:Image]
-texture = mult.png
-
-[layer3:fx0]
-type = mult
-xpos = 0.134
-ypos = 0.19
-xscale = .5
-yscale = -.5
+texture = rock_arr.png
+xpos = .86
+ypos = .136
+xscale = 0.5
+yscale = 0.5
+;angle = -(.460 / 2) * math.pi + .460 * math.pi * 0.5
[layer4:Image]
-texture = rockmeter.png
-xpos = .134
+texture = rock_top.png
+xpos = .86
ypos = .22
xscale = .5
-yscale = -.5
+yscale = .5
[layer5:Image]
texture = splight.png
-issplights = True
-xstatic = 0.772, 0.795, 0.822
-ystatic = 0.284, 0.312, 0.329
-xstart = 0.841680344, 0.841680344, 0.851971546
-xend = 0.867126494, 0.902359625, 0.943112688
-ystart = 0.202291056, 0.205626286, 0.20994647
-yend = 0.34490625, 0.333114583, 0.302322917
-;angle = 0.87, 0.58, 0.37, 0.00, -0.34, -0.70
-scale = 23.0000, 32.0000
-
-[layer6:Image]
-texture = rock_med.png
-
-[layer6:fx0]
-type = rock
-texture = rock_low.png;rock_med.png;rock_hi.png
-xpos = .86
-ypos = .2
+xpos = 0.86
+ypos = 0.35
xscale = .5
-yscale = -.5
-
-[layer7:Image]
-texture = rock_arr.png
+yscale = .5
-[layer7:fx0]
-type = rock
-xpos = .86
-ypos = .136
+[layer6:Image]
+texture = dotbase.png
+xpos = .0425
+ypos = .12
xscale = .5
-yscale = -.5
-angle = -(.460 / 2) * math.pi + .460 * math.pi * self.arrowRotation
+yscale = .5
[layer8:Image]
texture = counter.png
xpos = .15
ypos = .1125
xscale = .5
-yscale = -.5
+yscale = .5
[layer9:Text]
text = streak%10
font = streakFont
-xpos = 0.193-wid/2
-ypos = 0.667-hgt/2
-color = #000000
+xpos = 0.193
+ypos = 0.667
[layer10:Text]
text = (streak%100-streak%10)/10
font = streakFont
-xpos = .161-wid/2
-ypos = 0.667-hgt/2
+xpos = .161
+ypos = 0.667
[layer11:Text]
text = (streak%1000-streak%100)/100
font = streakFont
-xpos = .132-wid/2
-ypos = 0.667-hgt/2
+xpos = 0.132
+ypos = 0.667
[layer12:Text]
text = score
font = scoreFont
-xpos = 0.19-wid
+xpos = 0.19
ypos = 0.518
+[layer13:Image]
+condition = not player.isBassGuitar and player.starPowerActive and streak >= streakMax
+texture = mult.png
+xpos = 0.134
+ypos = 0.19
+xscale = .5
+yscale = .5
+rect = (0.0, 1.0, float(multiplier-1)*0.125, float(multiplier)*0.125)
+
+[layer14:Image]
+condition = not player.isBassGuitar and player.starPowerActive and not streak >= streakMax
+texture = mult.png
+xpos = 0.134
+ypos = 0.19
+xscale = .5
+yscale = .5
+rect = (0.0, 1.0, float(multiplier-1)*0.125, float(multiplier)*0.125)
+
+[layer15:Image]
+condition = not player.isBassGuitar and not player.starPowerActive and streak >= streakMax
+texture = mult.png
+xpos = 0.134
+ypos = 0.19
+xscale = .5
+yscale = .5
+rect = (0.0, 1.0, float(multiplier-1)*0.125 + 1.000, float(multiplier)*0.125 + 1.000)
+
+[layer16:Image]
+condition = not player.isBassGuitar and not player.starPowerActive and not streak >= streakMax
+texture = mult.png
+xpos = 0.134
+ypos = 0.19
+xscale = .5
+yscale = .5
+rect = (0.0, 1.0, float(multiplier-1)*0.125, float(multiplier)*0.125)
+[layer14:Image]
+texture = rockmeter.png
+xpos = .134
+ypos = .22
+xscale = .5
+yscale = .5
+
+[layer15:Image]
+texture = dots.png
+xpos = .0425
+ypos = .12
+xscale = .5
+yscale = .5
+rect = ((multiplier-1)*.25, multiplier*.25, float(streak-1)*.1, float(streak)*.1)
\ No newline at end of file
diff --git a/data/themes/MegaLight V4/stages/default.png b/data/themes/MegaLight V4/backgrounds/default.png
similarity index 100%
rename from data/themes/MegaLight V4/stages/default.png
rename to data/themes/MegaLight V4/backgrounds/default.png
diff --git a/data/themes/MegaLight V4/hitflamesanimation.png.bak b/data/themes/MegaLight V4/hitflamesanimation.png.bak
new file mode 100644
index 000000000..78a1e5211
Binary files /dev/null and b/data/themes/MegaLight V4/hitflamesanimation.png.bak differ
diff --git a/data/themes/MegaLight V4/rockmeter.ini b/data/themes/MegaLight V4/rockmeter.ini
index 67b4b439a..402f51a6a 100644
--- a/data/themes/MegaLight V4/rockmeter.ini
+++ b/data/themes/MegaLight V4/rockmeter.ini
@@ -48,19 +48,12 @@ xpos = 620
ypos = .75
rect = (1.0 - (streak%10/10.0), 1, .2 + .2*(streak/10), .4 + .2*(streak/10))
alignment = right
-condition = streak <= streakMax
+condition = streak > 0
inPixels = xpos
-[layer7:Image]
-texture = dots.png
-xscale = .15
-yscale = .15
-xpos = 620
-ypos = .75
+[layer6:fx0:Replace]
rect = (0, 1, .8, 1)
-alignment = right
condition = streak >= streakMax
-inPixels = xpos
[layer9:Image]
texture = bars.png
diff --git a/data/themes/MegaLight V4/stage.ini b/data/themes/MegaLight V4/stage.ini
index 8b1378917..35f810d7c 100644
--- a/data/themes/MegaLight V4/stage.ini
+++ b/data/themes/MegaLight V4/stage.ini
@@ -1 +1,258 @@
+[layer1]
+texture = stage_background.png
+xres = 512
+yres = 512
+xscale = 1.3
+yscale = 1.3
+xpos = 0.0
+ypos = 0.0
+angle = 0.0
+[layer2]
+texture = stage_lights1.png
+xres = 256
+yres = 128
+xpos = -0.65
+ypos = -0.8
+
+[layer3]
+texture = stage_lights2.png
+xres = 256
+yres = 128
+xpos = 0.65
+ypos = -0.8
+
+[layer4]
+texture = stage_drums.png
+xres = 256
+yres = 256
+xpos = -0.7
+ypos = 0.0
+
+[layer4:fx1]
+type = scale
+trigger = beat
+xmagnitude = 0.01
+ymagnitude = 0.01
+
+[layer5]
+texture = stage_bassdrum.png
+xres = 128
+yres = 128
+xpos = -0.65
+ypos = 0.13
+
+[layer5:fx1]
+type = scale
+trigger = beat
+xmagnitude = 0.06
+ymagnitude = 0.06
+
+[layer6]
+texture = stage_speakers.png
+xres = 256
+yres = 256
+xpos = 0.7
+ypos = 0.0
+
+[layer6:fx1]
+type = scale
+trigger = beat
+xmagnitude = 0.03
+ymagnitude = 0.03
+delay = 0.5
+
+[layer7]
+texture = stage_speaker_cones.png
+xres = 256
+yres = 256
+xpos = 0.67
+ypos = -0.03
+
+[layer7:fx1]
+type = scale
+trigger = beat
+xmagnitude = 0.07
+ymagnitude = 0.07
+
+[layer8]
+texture = stage_audience1.png
+xres = 256
+yres = 256
+xpos = -0.8
+ypos = 0.4
+
+[layer8:fx1]
+type = wiggle
+trigger = beat
+xmagnitude = 0.02
+ymagnitude = -0.04
+frequency = 0.5
+
+[layer9]
+texture = stage_audience2.png
+xres = 256
+yres = 256
+xpos = 0.8
+ypos = 0.4
+
+[layer9:fx1]
+type = wiggle
+trigger = beat
+xmagnitude = 0.01
+ymagnitude = -0.03
+frequency = 1.0
+delay = .25
+
+[layer10]
+texture = stage_light.png
+xres = 256
+yres = 256
+xpos = -0.87
+ypos = -0.75
+xscale = 3.0
+yscale = 3.0
+src_blending = src_alpha
+dst_blending = one
+foreground = 1
+
+[layer10:fx1]
+type = light
+trigger = pick
+light_number = 0
+intensity = 0.7
+
+[layer10:fx2]
+type = rotate
+trigger = miss
+profile = sinstep
+angle = 10
+period = 75
+
+[layer11]
+texture = stage_light.png
+xres = 256
+yres = 256
+xpos = -0.69
+ypos = -0.8
+xscale = 3.0
+yscale = 3.0
+angle = -12.0
+src_blending = src_alpha
+dst_blending = one
+foreground = 1
+
+[layer11:fx1]
+type = light
+trigger = pick
+light_number = 1
+intensity = 0.7
+
+[layer11:fx2]
+type = rotate
+trigger = miss
+profile = sinstep
+angle = -10
+period = 100
+
+[layer12]
+texture = stage_light.png
+xres = 256
+yres = 256
+xpos = -0.48
+ypos = -0.83
+xscale = 3.0
+yscale = 3.0
+angle = -16.0
+src_blending = src_alpha
+dst_blending = one
+foreground = 1
+
+[layer12:fx1]
+type = light
+trigger = pick
+light_number = 2
+intensity = 0.7
+
+[layer12:fx2]
+type = rotate
+trigger = miss
+profile = sinstep
+angle = 7
+period = 150
+
+[layer13]
+texture = stage_light.png
+xres = 256
+yres = 256
+xpos = 0.83
+ypos = -0.75
+xscale = 3.0
+yscale = 3.0
+src_blending = src_alpha
+dst_blending = one
+foreground = 1
+
+[layer13:fx1]
+type = light
+trigger = pick
+intensity = 0.7
+light_number = 0
+
+[layer13:fx2]
+type = rotate
+trigger = miss
+profile = sinstep
+angle = 12
+period = 250
+
+[layer14]
+texture = stage_light.png
+xres = 256
+yres = 256
+xpos = 0.64
+ypos = -0.84
+xscale = 3.0
+yscale = 3.0
+angle = 12.0
+src_blending = src_alpha
+dst_blending = one
+foreground = 1
+
+[layer14:fx1]
+type = light
+trigger = pick
+light_number = 1
+intensity = 0.7
+
+[layer14:fx2]
+type = rotate
+trigger = miss
+profile = sinstep
+angle = -5
+period = 200
+
+[layer15]
+texture = stage_light.png
+xres = 256
+yres = 256
+xpos = 0.46
+ypos = -0.9
+xscale = 3.0
+yscale = 3.0
+angle = 19.0
+src_blending = src_alpha
+dst_blending = one
+foreground = 1
+
+[layer15:fx1]
+type = light
+trigger = pick
+light_number = 2
+intensity = 0.7
+
+[layer15:fx2]
+type = rotate
+trigger = miss
+angle = -5
+period = 150
diff --git a/data/themes/MegaLight V4/stage/light.png b/data/themes/MegaLight V4/stage/light.png
new file mode 100644
index 000000000..9e562751f
Binary files /dev/null and b/data/themes/MegaLight V4/stage/light.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_audience1.png b/data/themes/MegaLight V4/stage/stage_audience1.png
new file mode 100644
index 000000000..5fc96f60c
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_audience1.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_audience2.png b/data/themes/MegaLight V4/stage/stage_audience2.png
new file mode 100644
index 000000000..b7441c017
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_audience2.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_background.png b/data/themes/MegaLight V4/stage/stage_background.png
new file mode 100644
index 000000000..c63c5c6ef
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_background.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_bassdrum.png b/data/themes/MegaLight V4/stage/stage_bassdrum.png
new file mode 100644
index 000000000..fbacdbe4a
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_bassdrum.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_drums.png b/data/themes/MegaLight V4/stage/stage_drums.png
new file mode 100644
index 000000000..9835e1701
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_drums.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_light.png b/data/themes/MegaLight V4/stage/stage_light.png
new file mode 100644
index 000000000..c87cd427c
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_light.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_lights1.png b/data/themes/MegaLight V4/stage/stage_lights1.png
new file mode 100644
index 000000000..6828ff76c
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_lights1.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_lights2.png b/data/themes/MegaLight V4/stage/stage_lights2.png
new file mode 100644
index 000000000..f77ee00f6
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_lights2.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_speaker_cones.png b/data/themes/MegaLight V4/stage/stage_speaker_cones.png
new file mode 100644
index 000000000..fed6a419c
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_speaker_cones.png differ
diff --git a/data/themes/MegaLight V4/stage/stage_speakers.png b/data/themes/MegaLight V4/stage/stage_speakers.png
new file mode 100644
index 000000000..2704c7a10
Binary files /dev/null and b/data/themes/MegaLight V4/stage/stage_speakers.png differ
diff --git a/data/themes/MegaLight/rockmeter.ini b/data/themes/MegaLight/rockmeter.ini
index f58951731..503184ba1 100644
--- a/data/themes/MegaLight/rockmeter.ini
+++ b/data/themes/MegaLight/rockmeter.ini
@@ -1,22 +1,25 @@
[layer0:Image]
texture = rock_bottom.png
-xpos = 0.07
+xpos = 25
ypos = 0.5
xscale = .25
yscale = .25
+inPixels = xpos
[layer1:Image]
texture = rock_fill.png
-xpos = 0.07
-ypos = (.265*rock) + .235
+xpos = 25
+ypos = 0.5
xscale = .25
-yscale = -.25*rock
-rect = (1,0,rock,0)
+yscale = .25
+rect = (0.0,1.0,0,rock)
+valignment = bottom
+inPixels = xpos
[layer2:Image]
texture = rock_top.png
-xpos = 0.07
-ypos = 0.475
+xpos = 25
+ypos = 0.5
xscale = .25
yscale = .25
@@ -38,16 +41,17 @@ yscale = 0.41
texture = overdrive fill.png
xpos = 0.5
ypos = 0.032
-xscale = .41*power
+xscale = .41
yscale = .35
rect = (0.0, power, 0.25, 1.0)
+alignment = left
-; [layer6:image}
-; texture = overdrive top.png
-; xpos = .295
-; ypos = 0.065
-; xscale = 0.3
-; yscale = 0.3
+[layer6:Image]
+texture = overdrive top.png
+xpos = .5
+ypos = 0.065
+xscale = 0.3
+yscale = 0.3
[layer9:Text]
text = streak
@@ -95,122 +99,28 @@ ypos = 315
alignment = right
inPixels = xpos|ypos
-[layer18:Image]
-condition = not player.isBassGuitar and player.starPowerActive and streak >= streakMax
-texture = mult.png
-xpos = 600
-ypos = 0.7
-xscale = .41
-yscale = .41
-rect = (0.0, 1.0, float(multiplier-1)*0.125, float(multiplier)*0.125)
-inPixels = xpos
-
-[layer19:Image]
-condition = not player.isBassGuitar and player.starPowerActive and not streak >= streakMax
-texture = mult.png
-xpos = 600
-ypos = 0.7
-xscale = .41
-yscale = .41
-rect = (0.0, 1.0, float(multiplier-1)*0.125, float(multiplier)*0.125)
-inPixels = xpos
+[layer17:Image]
+texture = mult2.png
+xpos = .5
+ypos = 0.0395
+xscale = .4305
+yscale = .4305
+rect = (0.0, 1.0, (streak%10)*.1, ((streak%10)+1)*.1)
-[layer20:Image]
-condition = not player.isBassGuitar and not player.starPowerActive and streak >= streakMax
-texture = mult.png
-xpos = 600
-ypos = 0.7
-xscale = .41
-yscale = .41
-rect = (0.0, 1.0, float(multiplier-1)*0.125 + 1.000, float(multiplier)*0.125 + 1.000)
-inPixels = xpos
+[layer17:fx0:Replace]
+rect = (0.0, 1.0, 0.9, 1.0)
+condition = streak >= streakMax
-[layer21:Image]
-condition = not player.isBassGuitar and not player.starPowerActive and not streak >= streakMax
+[layer18:Image]
texture = mult.png
-xpos = 600
-ypos = 0.7
-xscale = .41
-yscale = .41
+xpos = .5
+ypos = 0.034
+xscale = .5
+yscale = .5
rect = (0.0, 1.0, float(multiplier-1)*0.125, float(multiplier)*0.125)
-inPixels = xpos
-[layer22:Image]
-condition = player.isBassGuitar and player.starPowerActive and streak >= streakMax
+[layer18:fx0:Replace]
texture = bassgroovemult.png
-xpos = 600
-ypos = 0.7
-xscale = .41
-yscale = .41
rect = (0.0, 1.0, float(multiplier-1)*0.083333333, float(multiplier)*0.083333333)
-inPixels = xpos
+condition = player.isBassGuitar and streak > 30
-[layer23:Image]
-condition = player.isBassGuitar and player.starPowerActive and not streak >= streakMax
-texture = bassgroovemult.png
-xpos = 600
-ypos = 0.7
-xscale = .41
-yscale = .41
-rect = (0.0, 1.0, float(multiplier-1)*0.083333333, float(multiplier)*0.083333333)
-inPixels = xpos
-
-[layer24:Image]
-condition = player.isBassGuitar and not player.starPowerActive and streak >= streakMax
-texture = bassgroovemult.png
-xpos = 600
-ypos = 0.7
-xscale = .41
-yscale = .41
-rect = (0.0, 1.0, float(multiplier-1)*0.083333333 + 1.000, float(multiplier)*0.083333333 + 1.000)
-inPixels = xpos
-
-[layer25:Image]
-condition = player.isBassGuitar and not player.starPowerActive and not streak >= streakMax
-texture = bassgroovemult.png
-xpos = 600
-ypos = 0.7
-xscale = .41
-yscale = .41
-rect = (0.0, 1.0, float(multiplier-1)*0.083333333, float(multiplier)*0.083333333)
-inPixels = xpos
-
-[layer26:Image]
-texture = mult2.png
-condition = streak > 0 and streak <= 10
-xpos = 600
-ypos = 0.7055
-xscale = .4305
-yscale = .4305
-rect = (0.0, .25, float(streak-1)*.1, float(streak)*.1)
-inPixels = xpos
-
-[layer27:Image]
-texture = mult2.png
-condition = streak > 10 and streak <= 20
-xpos = 600
-ypos = 0.7055
-xscale = .4305
-yscale = .4305
-rect = (.25, .5, float(streak-1)*.1, float(streak)*.1)
-inPixels = xpos
-
-[layer28:Image]
-texture = mult2.png
-condition = streak > 20 and streak <= 30
-xpos = 600
-ypos = 0.7055
-xscale = .4305
-yscale = .4305
-rect = (.5, .75, float(streak-1)*.1, float(streak)*.1)
-inPixels = xpos
-
-[layer29:Image]
-texture = mult2.png
-condition = streak > 30
-xpos = 600
-ypos = 0.7055
-xscale = .4305
-yscale = .4305
-rect = (.75, 1.0, 0.0, 0.10)
-inPixels = xpos
\ No newline at end of file
diff --git a/data/themes/MegaLight/rockmeter/mult2.png b/data/themes/MegaLight/rockmeter/mult2.png
index 729d37615..44139e9ac 100644
Binary files a/data/themes/MegaLight/rockmeter/mult2.png and b/data/themes/MegaLight/rockmeter/mult2.png differ
diff --git a/data/tutorials/bangbang/guitar.ogg b/data/tutorials/bangbang/guitar.ogg
deleted file mode 100644
index d63f2c8a5..000000000
Binary files a/data/tutorials/bangbang/guitar.ogg and /dev/null differ
diff --git a/data/tutorials/bangbang/label.png b/data/tutorials/bangbang/label.png
deleted file mode 100644
index ae2a5f0b7..000000000
Binary files a/data/tutorials/bangbang/label.png and /dev/null differ
diff --git a/data/tutorials/bangbang/notes.mid b/data/tutorials/bangbang/notes.mid
deleted file mode 100644
index 82afb220a..000000000
Binary files a/data/tutorials/bangbang/notes.mid and /dev/null differ
diff --git a/data/tutorials/bangbang/song.ini b/data/tutorials/bangbang/song.ini
deleted file mode 100644
index 4f55621cf..000000000
--- a/data/tutorials/bangbang/song.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[song]
-artist = Mary Jo feat. Tommi Inkila
-cassettecolor = #ff4411
-name = Bang Bang, Mystery Man
\ No newline at end of file
diff --git a/data/tutorials/bangbang/song.ogg b/data/tutorials/bangbang/song.ogg
deleted file mode 100644
index ce2c24dd9..000000000
Binary files a/data/tutorials/bangbang/song.ogg and /dev/null differ
diff --git a/doc/RunningFromSource.mkd b/doc/RunningFromSource.mkd
index d35da7df0..ff2f828dd 100644
--- a/doc/RunningFromSource.mkd
+++ b/doc/RunningFromSource.mkd
@@ -8,9 +8,8 @@ Table of Contents
3. [Notes on PyOpenGL versions](#Notes-on-PyOpenGL-versions)
4. [Setting up Python and third-party dependencies](#Setting-up-Python-and-third-party-dependencies)
5. [Compiling the native modules](#Compiling-the-native-modules)
-6. [Dependencies for video playback](#Dependencies-for-video-playback)
-7. [Starting the game](#Starting-the-game)
-8. [Making binaries](#Making-binaries)
+6. [Starting the game](#Starting-the-game)
+7. [Making binaries](#Making-binaries)
Checking out the latest code
@@ -103,7 +102,7 @@ under Windows.)
First, you will need Python itself. Go to [the Python download page][]
and select the most recent 2.6.x release. The most recent 2.6.x at the
-time of this writing is 2.6.5 ([direct link][py2.6.5-win32]). Again,
+time of this writing is 2.6.6 ([direct link][py2.6.6-win32]). Again,
_32-bit is recommended, even if you have a 64-bit system._ Install
by double-clicking the .msi file, and be sure to remember which folder
you install Python to, as you will need to know for the next step.
@@ -140,7 +139,7 @@ The following packages are required:
* Python Imaging Library ([direct link to 1.1.7+](http://github.com/downloads/fuzion/pil-2009-raclette/PIL-1.1.7-2009-raclette-r358.win32-py2.6.exe))
* pyogg ([direct link to 1.3](http://www.mediafire.com/file/qlw2hwyy0rz/pyogg-1.3.win32-py2.6.exe))
* pyvorbis ([direct link to 1.5a](http://www.mediafire.com/file/i21ewawljtw/pyvorbis-1.5.win32-py2.6.exe))
- * Cython ([direct link to 0.12.1](http://www.lfd.uci.edu/~gohlke/pythonlibs/Cython-0.12.1.win32-py2.6.exe))
+ * Cython ([link to 0.13](http://www.lfd.uci.edu/~gohlke/pythonlibs/#cython))
The following packages are optional:
@@ -179,6 +178,20 @@ The following packages are optional:
Install all packages by double-clicking the .exe or .msi files that
you downloaded.
+#### Installing the Win32 Dependency Pack
+
+Some code in FoFiX depends on external libraries written in C. The
+`win32/` directory contains build scripts, but it can be difficult to
+get the proper environment set up to use them.
+
+Since building and setting up these libraries can be difficult, we are
+making available a prebuilt archive of everything you need to compile
+FoFiX's native modules. Download the latest FoFiX Win32 Dependency Pack
+from [here](http://www.mediafire.com/?x0000ohmctblb) and unzip it into the
+`win32/` directory. (The `deps/` directory in the archive should become
+a subdirectory of the `win32/` directory.) Now you are ready to compile
+the native modules.
+
### Mac OS X
@@ -217,7 +230,8 @@ The following are required:
* Python's development headers
* A C++ compiler
* Cython
- * The OpenGL and GLU development headers
+ * pkg-config
+ * The OpenGL, GLU, GLib, libogg, libtheora, and libswscale (part of ffmpeg) development headers
The following are optional (refer to the Windows instructions to see
what each one is needed for):
@@ -230,9 +244,10 @@ what each one is needed for):
For those of you on Debian or Ubuntu, this means installing the
following packages: `python-pygame`, `python-opengl`, `python-numpy`,
`python-imaging`, `python-ogg`, `python-pyvorbis`, `python-dev`,
-`build-essential`, `cython`, `libgl1-mesa-dev`, `libglu1-mesa-dev`.
-If you're stuck without pygame 1.9, also install `python-numeric`.
-If you want Psyco, install `python-psyco`.
+`build-essential`, `cython`, `pkg-config`, `libgl1-mesa-dev`,
+`libglu1-mesa-dev`, `libglib2.0-dev`, `libogg-dev`, `libtheora-dev`,
+`libswscale-dev`. If you're stuck without pygame 1.9, also install
+`python-numeric`. If you want Psyco, install `python-psyco`.
Some packages can be troublesome, so we have notes below about certain
packages.
@@ -325,6 +340,10 @@ You will have to do this **every time** you receive changes to a `.c`,
are in danger of weird crashes, and our first question will probably be
whether or not you rebuilt the native modules.
+(If `setup.py` complains about any programs or libraries being missing,
+check that you have installed all of the dependencies, and for Windows
+users, that the Win32 Dependency Pack is unpacked in the proper location.)
+
As for making sure you have a compiler, read the section for your
operating system.
@@ -355,56 +374,6 @@ Install the appropriate package from your distribution's repository.
Under Debian and Ubuntu, you want `build-essential`.
-----
-
-Dependencies for video playback
--------------------------------
-
-_This section is **optional**, and it is only necessary if you want to
-try out the current implementation, which will be replaced soon._
-
-To play videos in FoFiX you currently need to install GStreamer.
-We'll be moving away from GStreamer soon (before 4.0), but for now,
-here are the instructions.
-
-_N.B.: While many types of video (i.e. anything the GStreamer installation
-knows how to decode) work with the current code, the replacement video
-code is likely not to support anything other than Theora._
-
-Follow the instructions for your operating system.
-
-### Windows
-
-#### GStreamer binaries
-Download a GStreamer binary repack:
-
-
-Use 7-Zip or a compatible archiving program to unpack it into the root of
-the FoFiX environment. (If done correctly, this should create a folder
-named `gstreamer` on the same level as `data`, `doc`, `pkg`, `src`, and
-`svg`, and the `gstreamer` folder should have as its direct children
-folders named `bin`, `etc`, `lib`, `share`, and `src` and a file named
-`COPYING`. Carefully check how you unpacked it if this is not the case.)
-
-#### PyGObject
-
-Download and run this:
-
-
-#### PyGst
-
-Download and run this:
-
-
-### Mac OS X
-
-Someone with a Mac will have to expand this section.
-
-### GNU/Linux
-
-Install the Python GStreamer bindings (pygst) and all of their
-dependencies. Under Debian and Ubuntu, the package to install is
-`python-gst0.10`.
----
diff --git a/doc/copyright b/doc/copyright
index ef8fa4e0b..22f3604b8 100644
--- a/doc/copyright
+++ b/doc/copyright
@@ -45,12 +45,10 @@ except:
Miriam font family is copyright 2004-2010 by Maxim Iorsh
(iorsh@users.sourceforge.net). All rights reserved.
-Distribution, modification or commercial usage of the songs is not allowed.
-
-All data files, excluding the songs and the font files mentioned above but
-including the tutorial are licensed under the GNU General Public License
-described below. Note that some source files derived from other sources might
-have differing licenses.
+All data files, including the tutorials, except the font files mentioned
+above, are licensed under the GNU General Public License described
+below. Note that some source files derived from other sources might have
+differing licenses.
----------------------------------------------------------------------------
diff --git a/pkg/CleanHashCache.py b/pkg/CleanHashCache.py
deleted file mode 100644
index 837a46afe..000000000
--- a/pkg/CleanHashCache.py
+++ /dev/null
@@ -1,108 +0,0 @@
-#####################################################################
-# -*- coding: iso-8859-1 -*- #
-# #
-# Frets on Fire X (FoFiX) #
-# Copyright (C) 2009 myfingershurt #
-# 2009 John Stumpo #
-# #
-# This program is free software; you can redistribute it and/or #
-# modify it under the terms of the GNU General Public License #
-# as published by the Free Software Foundation; either version 2 #
-# of the License, or (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
-# GNU General Public License for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program; if not, write to the Free Software #
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, #
-# MA 02110-1301, USA. #
-#####################################################################
-
-__version__ = '$Id$'
-
-import os
-import sys
-if os.name != 'nt':
- sys.stderr.write('This script only works on Windows.\n')
- sys.exit(1)
-
-import win32api
-import win32con
-import sha
-try:
- import sqlite3
-except ImportError:
- import pysqlite2.dbapi2 as sqlite3
-
-from Tkinter import *
-
-class CleanHashCacheWindow(object):
- def __init__(self, master, db):
- self.master = master
- self.db = db
- master.title('CleanHashCache')
- master.protocol('WM_DELETE_WINDOW', self.close)
- frame = Frame(master)
-
- lbl = Label(master, text='Double-click a version\nto delete its hashes.')
- lbl.pack(side=TOP, padx=10, pady=10)
-
- self.listbox = Listbox(master, exportselection=0)
- for version in [row[0] for row in self.db.execute('SELECT `version` FROM `verlist`').fetchall()]:
- self.listbox.insert(END, version)
- self.listbox.bind('', lambda e: self.delete())
- self.listbox.pack(side=TOP, padx=10, pady=0)
-
- okbutton = Button(master, text='Done', command=self.close)
- okbutton.pack(side=TOP, padx=10, pady=10, fill=BOTH, expand=1)
-
- master.resizable(0, 0)
-
- def run(self):
- self.master.mainloop()
-
- def delete(self):
- if len(self.listbox.curselection()):
- if win32api.MessageBox(self.master.winfo_id(),
- 'Really delete this version\'s hashes?\nThis cannot be undone!',
- 'CleanHashCache', win32con.MB_YESNO|win32con.MB_ICONQUESTION) == win32con.IDYES:
- vername = str(self.listbox.get(self.listbox.curselection()[0]))
- self.db.execute('DELETE FROM `verlist` WHERE `version` = ?', [vername])
- self.db.commit()
- self.db.execute('DROP TABLE `hashes_%s`' % sha.sha(vername).hexdigest())
- self.db.commit()
- self.listbox.delete(self.listbox.curselection()[0])
-
- def close(self):
- self.db.commit()
- self.db.execute('VACUUM')
- self.db.commit()
- self.db.close()
- self.master.withdraw()
- self.master.quit()
-
-def main():
- hashcache = sqlite3.Connection('HashCache')
- hashcache.execute('CREATE TABLE IF NOT EXISTS `verlist` (`version` STRING UNIQUE)')
- hashcache.commit()
- CleanHashCacheWindow(Tk(), hashcache).run()
- sys.exit(0)
-
-try:
- main()
-except (KeyboardInterrupt, SystemExit):
- raise
-except:
- import traceback
- import win32clipboard
- if win32api.MessageBox(0, 'A fatal error has occurred. This program will now terminate.\n\n' +
- traceback.format_exc() + '\n\nCopy traceback to Clipboard?',
- 'CleanHashCache', win32con.MB_YESNO|win32con.MB_ICONSTOP) == win32con.IDYES:
- win32clipboard.OpenClipboard()
- win32clipboard.EmptyClipboard()
- win32clipboard.SetClipboardText(traceback.format_exc())
- win32clipboard.CloseClipboard()
- raise
diff --git a/pkg/Dist-MegaLight-GNULinux.lst b/pkg/Dist-MegaLight-GNULinux.lst
deleted file mode 100644
index 4853c3ccc..000000000
--- a/pkg/Dist-MegaLight-GNULinux.lst
+++ /dev/null
@@ -1,24 +0,0 @@
-src:.
-doc:.
-data/*.png:data/
-data/*.dae:data/
-data/*.ttf:data/
-data/*.ini:data/
-data/mods:data/
-data/necks:data/
-data/shaders:data/
-data/sounds:data/
-data/themes:data/
-data/translations:data/
-data/tutorials/jurgenfof:data/tutorials/
-data/tutorials/drumtest:data/tutorials/
-data/users:data/
-dist/FoFiX:.
-dist/FoFiX.bin:.
-AUTHORS:.
-COPYING:.
-CREDITS:.
-NEWS:.
-README:.
-../FoFiX-wiki:.
-dist/*.so*:.
diff --git a/pkg/ListToNSIS.py b/pkg/ListToNSIS.py
index 0ad46201d..4b9e3a74a 100644
--- a/pkg/ListToNSIS.py
+++ b/pkg/ListToNSIS.py
@@ -32,23 +32,9 @@
import hashlib
class NsisScriptGenerator(object):
- def __init__(self, baseFolder='.', hashCache=None, oldTblName=None, newTblName=None):
+ def __init__(self, baseFolder='.'):
self.nodeList = []
self.baseFolder = baseFolder
- self.hashCache = hashCache
- self.oldTblName = None
- self.newTblName = None
- if oldTblName is not None:
- self.oldTblName = hashlib.sha1(oldTblName).hexdigest()
- if newTblName is not None:
- self.newTblName = hashlib.sha1(newTblName).hexdigest()
- if self.hashCache is not None:
- self.hashCache.execute('INSERT OR REPLACE INTO `verlist` (`version`) VALUES (?)', [newTblName])
- self.hashCache.execute('DROP TABLE IF EXISTS `hashes_%s`' % self.newTblName)
- self.hashCache.commit()
- self.hashCache.execute('VACUUM')
- self.hashCache.execute('CREATE TABLE `hashes_%s` (`path` STRING UNIQUE, `hash` STRING)' % self.newTblName)
- self.hashCache.commit()
def readList(self, listname):
l = open(listname, 'r')
for line in l:
@@ -63,13 +49,6 @@ def readList(self, listname):
for f in win32api.FindFiles(line):
path = os.path.join(os.path.dirname(line), f[8])
if os.path.isfile(path) and path.find('.svn') == -1: # omit .svn folders
- if self.hashCache is not None:
- newhash = hashlib.sha1(open(path, 'rb').read()).hexdigest()
- self.hashCache.execute('INSERT OR REPLACE INTO `hashes_%s` (`path`, `hash`) VALUES (?, ?)' % self.newTblName, [path, newhash])
- if self.oldTblName is not None:
- oldhash = self.hashCache.execute('SELECT `hash` FROM `hashes_%s` WHERE `path` = ?' % self.oldTblName, [path]).fetchone()
- if oldhash is not None and oldhash[0] == newhash:
- continue
self.nodeList.append(path)
os.chdir(oldpwd)
l.close()
diff --git a/pkg/MakeFoFiXInstaller.py b/pkg/MakeFoFiXInstaller.py
index 3ce1ae6b9..425417906 100644
--- a/pkg/MakeFoFiXInstaller.py
+++ b/pkg/MakeFoFiXInstaller.py
@@ -34,10 +34,6 @@
import shutil
import win32api
import hashlib
-try:
- import sqlite3
-except ImportError:
- import pysqlite2.dbapi2 as sqlite3
us = r'..\FoFiX.exe'
if not os.path.isfile(us):
@@ -48,14 +44,9 @@
FOFIX_VERSION = '%d.%d.%d.%d' % (vdict['FileVersionMS'] >> 16, vdict['FileVersionMS'] & 0xffff, vdict['FileVersionLS'] >> 16, vdict['FileVersionLS'] & 0xffff)
FOFIX_VERSION_FULL = str(win32api.GetFileVersionInfo(us, r'\StringFileInfo\%04x%04x\ProductVersion' % win32api.GetFileVersionInfo(us, r'\VarFileInfo\Translation')[0]))
-# Make the hashcache.
-hashcache = sqlite3.Connection('HashCache')
-hashcache.execute('CREATE TABLE IF NOT EXISTS `verlist` (`version` STRING UNIQUE)')
-hashcache.commit()
-MLDist = ListToNSIS.NsisScriptGenerator('..', hashcache, newTblName=FOFIX_VERSION_FULL)
+MLDist = ListToNSIS.NsisScriptGenerator('..')
MLDist.readList('Dist-All.lst')
MLDist.readExcludeList('filesToExclude.lst')
-hashcache.commit()
oldcwd = os.getcwd()
os.chdir('..')
@@ -225,137 +216,6 @@
try:
if os.spawnl(os.P_WAIT, makensis, 'makensis.exe', 'Setup.nsi') != 0:
raise RuntimeError, 'Installer generation failed.'
- # Yank out the new uninstaller.
- os.spawnl(os.P_WAIT, os.path.join(oldcwd, 'FoFiX v%s Setup.exe' % FOFIX_VERSION_FULL), 'FoFiX v%s Setup.exe' % FOFIX_VERSION_FULL, '/WriteUninstallerOnly')
-
- # Now we can go back through the hashcache and make the patches.
- versions = [row[0] for row in hashcache.execute('SELECT `version` FROM `verlist` WHERE `version` != ?', [FOFIX_VERSION_FULL])]
- for v in versions:
- os.chdir(oldcwd)
- MLDist = ListToNSIS.NsisScriptGenerator('..', hashcache, oldTblName=v, newTblName=FOFIX_VERSION_FULL)
- MLDist.readList('Dist-All.lst')
- MLDist.readExcludeList('filesToExclude.lst')
- hashcache.commit()
- os.chdir('..')
- os.unlink('Setup.nsi')
- oldExeSha1 = hashcache.execute("SELECT `hash` FROM `hashes_%s` WHERE `path` = 'FoFiX.exe'" % hashlib.sha1(v).hexdigest()).fetchone()[0]
- patcher = ListToNSIS.NsisScriptBuilder(r"""
-!define FOFIX_VERSION %s
-!define FOFIX_VERSION_FULL "%s"
-!define FOFIX_VERSION_OLD "%s"
-!define FOFIX_VERSION_OLD_EXE_SHA1 "%s"
-!include "MUI2.nsh"
-
-# Installer title and filename.
-Name 'FoFiX v${FOFIX_VERSION_OLD} to v${FOFIX_VERSION_FULL} Patch'
-Caption 'FoFiX v${FOFIX_VERSION_OLD} to v${FOFIX_VERSION_FULL} Patch'
-OutFile 'pkg\FoFiX v${FOFIX_VERSION_OLD} to v${FOFIX_VERSION_FULL} Patch.exe'
-
-# Installer parameters.
-SetCompressor /SOLID lzma
-RequestExecutionLevel user # no UAC on Vista
-ShowInstDetails show
-InstallButtonText "&Upgrade"
-
-# Where we're going (by default at least)
-InstallDir '$DOCUMENTS\FoFiX'
-# Where we stashed the install location.
-InstallDirRegKey HKCU 'SOFTWARE\myfingershurt\FoFiX' InstallRoot
-
-# Function to run FoFiX from the finish page.
-Function runFoFiX
- SetOutPath $INSTDIR
- Exec $INSTDIR\FoFiX.exe
-FunctionEnd
-
-# More installer parameters.
-!define MUI_ABORTWARNING
-!define MUI_ABORTWARNING_TEXT "Are you sure you want to quit FoFiX v${FOFIX_VERSION_OLD} to v${FOFIX_VERSION_FULL} Patch?"
-!define MUI_FINISHPAGE_NOAUTOCLOSE
-!define MUI_FINISHPAGE_NOREBOOTSUPPORT
-!define MUI_FINISHPAGE_RUN
-!define MUI_FINISHPAGE_RUN_TEXT "Run FoFiX v${FOFIX_VERSION_FULL}"
-!define MUI_FINISHPAGE_RUN_FUNCTION runFoFiX
-!define MUI_FINISHPAGE_LINK "FoFiX Development Home"
-!define MUI_FINISHPAGE_LINK_LOCATION "http://code.google.com/p/fofix/"
-!define MUI_LICENSEPAGE_RADIOBUTTONS
-!define MUI_HEADERIMAGE
-!define MUI_WELCOMEPAGE_TITLE "Welcome to the FoFiX v${FOFIX_VERSION_OLD} to v${FOFIX_VERSION_FULL} Patch Wizard"
-!define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the upgrade of FoFiX v${FOFIX_VERSION_OLD} to version ${FOFIX_VERSION_FULL}.$\r$\n$\r$\nBefore upgrading, make sure FoFiX v${FOFIX_VERSION_OLD} is not running.$\r$\n$\r$\nClick Next to continue."
-!define MUI_DIRECTORYPAGE_TEXT_TOP "Setup will upgrade the installation of FoFiX v${FOFIX_VERSION_OLD} in the following folder to version ${FOFIX_VERSION_FULL}. To perform the upgrade in a different folder, click Browse and select another folder. Click Upgrade to start the upgrade."
-!define MUI_INSTFILESPAGE_FINISHHEADER_TEXT "Upgrade Complete"
-!define MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT "Upgrade was completed successfully."
-!define MUI_INSTFILESPAGE_ABORTHEADER_TEXT "Upgrade Aborted"
-!define MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT "Upgrade was aborted."
-!define MUI_FINISHPAGE_TITLE "Completing the FoFiX v${FOFIX_VERSION_OLD} to v${FOFIX_VERSION_FULL} Patch Wizard"
-!define MUI_FINISHPAGE_TEXT "FoFiX v${FOFIX_VERSION_OLD} has been upgraded to version ${FOFIX_VERSION_FULL}.$\r$\n$\r$\nClick Finish to close this wizard.$\r$\n$\r$\nInstaller by John Stumpo.$\r$\nInstaller graphics by akedrou."
-!define MUI_FINISHPAGE_TEXT_LARGE
-!define MUI_HEADERIMAGE_BITMAP "pkg\installer_gfx\header.bmp"
-!define MUI_WELCOMEFINISHPAGE_BITMAP "pkg\installer_gfx\welcome.bmp"
-
-# Function to verify the install path.
-Function verifyFoFiXInstDir
- IfFileExists $INSTDIR haveDir
- Abort
-haveDir:
- IfFileExists $INSTDIR\FoFiX.exe haveFoFexe
- MessageBox MB_YESNO|MB_ICONEXCLAMATION "This does not look like a valid FoFiX installation folder.$\r$\n$\r$\nIf you would like to merely unpack the altered files into this folder, you may continue anyway.$\r$\n$\r$\nContinue?" IDYES allow
- Abort
-haveFoFexe:
- Crypto::HashFile "SHA1" $INSTDIR\FoFiX.exe
- Pop $0
- StrCmp $0 ${FOFIX_VERSION_OLD_EXE_SHA1} allow
- MessageBox MB_YESNO|MB_ICONEXCLAMATION "This looks like a valid FoFiX installation folder, but not version ${FOFIX_VERSION_OLD}.$\r$\n$\r$\nApplying this patch will more than likely break your installation!$\r$\n$\r$\nContinue anyway?" IDYES allow
- Abort
-allow:
-FunctionEnd
-
-# The pages of the installer...
-!insertmacro MUI_PAGE_WELCOME
-!insertmacro MUI_PAGE_LICENSE "COPYING"
-!define MUI_PAGE_HEADER_TEXT "Choose Upgrade Location"
-!define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to upgrade FoFiX v${FOFIX_VERSION_OLD} to version ${FOFIX_VERSION_FULL}."
-!define MUI_PAGE_CUSTOMFUNCTION_LEAVE verifyFoFiXInstDir
-!insertmacro MUI_PAGE_DIRECTORY
-!define MUI_PAGE_HEADER_TEXT "Upgrading"
-!define MUI_PAGE_HEADER_SUBTEXT "Please wait while FoFiX v${FOFIX_VERSION_OLD} is upgraded to version ${FOFIX_VERSION_FULL}."
-!insertmacro MUI_PAGE_INSTFILES
-!insertmacro MUI_PAGE_FINISH
-
-# Throw in a cool background image.
-#!define MUI_CUSTOMFUNCTION_GUIINIT startBackground
-#Function startBackground
-# InitPluginsDir
-# File /oname=$PLUGINSDIR\background.bmp pkg\installer_gfx\background.bmp
-# BgImage::SetBG /NOUNLOAD /FILLSCREEN $PLUGINSDIR\background.bmp
-# BgImage::Redraw /NOUNLOAD
-#FunctionEnd
-#Function .onGUIEnd
-# BgImage::Destroy
-#FunctionEnd
-
-!insertmacro MUI_LANGUAGE "English"
-
-# Add version info to the resulting installers.
-VIProductVersion "${FOFIX_VERSION}"
-VIAddVersionKey /LANG=1033 "CompanyName" "FoFiX Team"
-VIAddVersionKey /LANG=1033 "FileDescription" "FoFiX Patch Utility"
-VIAddVersionKey /LANG=1033 "FileVersion" "${FOFIX_VERSION_FULL}"
-VIAddVersionKey /LANG=1033 "InternalName" "FoFiX v${FOFIX_VERSION_OLD} to v${FOFIX_VERSION_FULL} Patch.exe"
-VIAddVersionKey /LANG=1033 "LegalCopyright" "© 2008-2010 FoFiX Team. GNU GPL v2 or later."
-VIAddVersionKey /LANG=1033 "OriginalFilename" "FoFiX v${FOFIX_VERSION_OLD} to v${FOFIX_VERSION_FULL} Patch.exe"
-VIAddVersionKey /LANG=1033 "ProductName" "FoFiX"
-VIAddVersionKey /LANG=1033 "ProductVersion" "${FOFIX_VERSION_FULL}"
-""" % (FOFIX_VERSION, FOFIX_VERSION_FULL, str(v), str(oldExeSha1)))
- patcher.addSection('Patch', r'''
-SectionIn RO
-%s
-SetOutPath $INSTDIR
-File "%s"
-''' % (MLDist.getInstallScript(), os.path.join(oldcwd, 'uninst.exe')), '', 'All altered files for this patch.')
- open('Setup.nsi', 'w').write(patcher.getScript())
- if os.spawnl(os.P_WAIT, makensis, 'makensis.exe', 'Setup.nsi') != 0:
- raise RuntimeError, 'Installer generation failed.'
finally:
if os.getcwd() == oldcwd:
diff --git a/pkg/MakeThemeInstaller.py b/pkg/MakeThemeInstaller.py
deleted file mode 100644
index 6cb9fe741..000000000
--- a/pkg/MakeThemeInstaller.py
+++ /dev/null
@@ -1,261 +0,0 @@
-#####################################################################
-# -*- coding: iso-8859-1 -*- #
-# #
-# Frets on Fire X (FoFiX) #
-# Copyright (C) 2009 myfingershurt #
-# 2009 John Stumpo #
-# #
-# This program is free software; you can redistribute it and/or #
-# modify it under the terms of the GNU General Public License #
-# as published by the Free Software Foundation; either version 2 #
-# of the License, or (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
-# GNU General Public License for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program; if not, write to the Free Software #
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, #
-# MA 02110-1301, USA. #
-#####################################################################
-
-__version__ = '$Id$'
-
-import os
-import sys
-if os.name != 'nt':
- sys.stderr.write('This script works only on Windows.\n')
- sys.exit(1)
-
-from Tkinter import *
-
-try:
- import sqlite3
-except ImportError:
- import pysqlite2.dbapi2 as sqlite3
-
-import _winreg
-import win32api
-import win32con
-import win32gui
-import pywintypes
-
-class ThemeSelectWindow(object):
- def __init__(self, master, themelist):
- self.master = master
- master.title('Theme Installer Generator')
- master.protocol('WM_DELETE_WINDOW', lambda: sys.exit(0))
- frame = Frame(master)
-
- lbl = Label(master, text='Choose theme to package:')
- lbl.pack(side=TOP, padx=10, pady=10)
-
- self.listbox = Listbox(master, exportselection=0)
- for theme in themelist:
- self.listbox.insert(END, theme)
- self.listbox.bind('', lambda e: self.ok())
- self.listbox.pack(side=TOP, padx=10, pady=0)
-
- okbutton = Button(master, text='OK', command=self.ok)
- okbutton.pack(side=TOP, padx=10, pady=10, fill=BOTH, expand=1)
-
- master.resizable(0, 0)
-
- def run(self):
- self.master.mainloop()
- return self.listbox.get(self.listbox.curselection()[0])
-
- def ok(self):
- if len(self.listbox.curselection()):
- self.master.withdraw()
- self.master.quit()
-
-
-class VersionSelectWindow(object):
- def __init__(self, master):
- self.master = master
- master.title('Theme Installer Generator')
- master.protocol('WM_DELETE_WINDOW', lambda: sys.exit(0))
- frame = Frame(master)
-
- lbl = Label(master, text='Version number:')
- lbl.pack(side=TOP, padx=10, pady=10)
-
- self.entry = Entry(master)
- self.entry.bind('', lambda e: self.ok())
- self.entry.pack(side=TOP, padx=10, pady=0)
-
- okbutton = Button(master, text='OK', command=self.ok)
- okbutton.pack(side=TOP, padx=10, pady=10, fill=BOTH, expand=1)
-
- master.resizable(0, 0)
-
- def run(self):
- self.master.mainloop()
- return self.entry.get()
-
- def ok(self):
- if self.entry.get() != '':
- self.master.withdraw()
- self.master.quit()
-
-
-def main():
- # Find NSIS.
- try:
- key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'Software\NSIS')
- nsisPath = _winreg.QueryValueEx(key, '')[0]
- _winreg.CloseKey(key)
- except WindowsError:
- win32api.MessageBox(0, '''To create theme installers, you must have NSIS installed on your system.
-
-NSIS is needed to create theme installers, but not to use them.
-
-NSIS is free software that can be downloaded from http://nsis.sourceforge.net/.''',
- 'Theme Installer Generator', win32con.MB_OK|win32con.MB_ICONSTOP)
- sys.exit(1)
- makensis = os.path.join(nsisPath, 'makensis.exe')
-
- # Find possible themes.
- themes = []
- os.chdir(os.path.join('..', 'data', 'themes'))
- for name in os.listdir('.'):
- if os.path.isfile(os.path.join(name, 'theme.ini')):
- themes.append(name)
-
- # Get the theme and its version number.
- themesel = ThemeSelectWindow(Tk(), themes)
- theme = themesel.run()
-
- versel = VersionSelectWindow(Tk())
- version = versel.run()
-
- # Allow a license agreement to be added.
- if win32api.MessageBox(0, 'Would you like to display a license agreement in the installer?',
- 'Theme Installer Generator', win32con.MB_YESNO|win32con.MB_ICONQUESTION) == win32con.IDYES:
- try:
- licensefile = win32gui.GetOpenFileNameW(
- Filter='License Agreements (COPYING,LICENSE,*.txt,*.rtf)\0COPYING;LICENSE;*.txt;*.rtf\0All Files (*.*)\0*.*\0',
- Title='Theme Installer Generator: Select license agreement',
- Flags=win32con.OFN_DONTADDTORECENT|win32con.OFN_FILEMUSTEXIST|win32con.OFN_HIDEREADONLY|win32con.OFN_NOCHANGEDIR)[0]
- except pywintypes.error:
- sys.exit(0)
- else:
- licensefile = None
-
- # Where are we putting this?
- try:
- destfile = win32gui.GetSaveFileNameW(
- Filter='Executable files (*.exe)\0*.exe\0All Files (*.*)\0*.*\0',
- Title='Theme Installer Generator: Save installer',
- Flags=win32con.OFN_DONTADDTORECENT|win32con.OFN_OVERWRITEPROMPT|win32con.OFN_HIDEREADONLY|win32con.OFN_NOCHANGEDIR)[0]
- except pywintypes.error:
- sys.exit(0)
-
- # Let's do this.
- script = r"""
-!define THEME_NAME "%s"
-!define THEME_VERSION "%s"
-!include "MUI2.nsh"
-
-# Installer title and filename.
-Name 'FoFiX Theme "${THEME_NAME}" v${THEME_VERSION}'
-OutFile '%s'
-
-# Installer parameters.
-SetCompressor /SOLID lzma
-RequestExecutionLevel user # no UAC on Vista
-ShowInstDetails show
-
-# Where we're going (by default at least)
-InstallDir '$DOCUMENTS\FoFiX'
-# Where we stashed the install location.
-InstallDirRegKey HKCU 'SOFTWARE\myfingershurt\FoFiX' InstallRoot
-
-# Function to run FoFiX from the finish page.
-Function runFoFiX
- SetOutPath $INSTDIR
- Exec $INSTDIR\FoFiX.exe
-FunctionEnd
-
-# More installer parameters.
-!define MUI_ABORTWARNING
-!define MUI_FINISHPAGE_NOAUTOCLOSE
-!define MUI_FINISHPAGE_NOREBOOTSUPPORT
-!define MUI_FINISHPAGE_RUN
-!define MUI_FINISHPAGE_RUN_TEXT "Run FoFiX"
-!define MUI_FINISHPAGE_RUN_FUNCTION runFoFiX
-!define MUI_LICENSEPAGE_RADIOBUTTONS
-!define MUI_HEADERIMAGE
-!define MUI_FINISHPAGE_TEXT "FoFiX Theme $\"${THEME_NAME}$\" v${THEME_VERSION} has been installed on your computer.$\r$\n$\r$\nClick Finish to close this wizard.$\r$\n$\r$\nInstaller framework by John Stumpo."
-!define MUI_FINISHPAGE_TEXT_LARGE
-# gfx pending
-#!define MUI_HEADERIMAGE_BITMAP "pkg\installer_gfx\header.bmp"
-
-# Function to verify the install path.
-Function verifyFoFiXInstDir
- IfFileExists $INSTDIR haveDir
- Abort
-haveDir:
- IfFileExists $INSTDIR\FoFiX.exe allow
- MessageBox MB_YESNO|MB_ICONEXCLAMATION "This does not look like a valid FoFiX installation folder.$\r$\n$\r$\nIf you would like to merely unpack the theme files into this folder, you may continue anyway.$\r$\n$\r$\nContinue?" IDYES allow
- Abort
-allow:
-FunctionEnd
-
-# The pages of the installer...
-!insertmacro MUI_PAGE_WELCOME
-%s
-!define MUI_PAGE_CUSTOMFUNCTION_LEAVE verifyFoFiXInstDir
-!insertmacro MUI_PAGE_DIRECTORY
-!insertmacro MUI_PAGE_INSTFILES
-!insertmacro MUI_PAGE_FINISH
-
-# Throw in a cool background image.
-# gfx pending
-#!define MUI_CUSTOMFUNCTION_GUIINIT startBackground
-#Function startBackground
-# InitPluginsDir
-# File /oname=$PLUGINSDIR\background.bmp pkg\installer_gfx\background.bmp
-# BgImage::SetBG /NOUNLOAD /FILLSCREEN $PLUGINSDIR\background.bmp
-# BgImage::Redraw /NOUNLOAD
-#FunctionEnd
-#Function .onGUIEnd
-# BgImage::Destroy
-#FunctionEnd
-
-!insertmacro MUI_LANGUAGE "English"
-
-Section
-""" % tuple(map(str, (theme, version, destfile, licensefile and ('!insertmacro MUI_PAGE_LICENSE "%s"' % licensefile) or '')))
- for root, dirs, files in os.walk(theme):
- if root.find('.svn') != -1: #stump: skip .svn folders
- continue
- script += 'SetOutPath "$INSTDIR\\data\\themes\\%s\\%s"\r\n' % (theme, root[len(theme):])
- for f in files:
- script += 'File "%s"\r\n' % os.path.join(root, f)
- script += 'SetOutPath $INSTDIR\r\nSectionEnd\r\n'
- open('Setup.nsi', 'w').write(script)
- if os.spawnl(os.P_WAIT, makensis, 'makensis.exe', 'Setup.nsi') != 0:
- raise RuntimeError, 'Installer generation failed.'
- os.unlink('Setup.nsi')
- win32api.MessageBox(0, 'Installer generation complete.', 'Theme Installer Generator',
- win32con.MB_OK|win32con.MB_ICONINFORMATION)
-
-try:
- main()
-except (KeyboardInterrupt, SystemExit):
- raise
-except:
- import traceback
- import win32clipboard
- if win32api.MessageBox(0, 'A fatal error has occurred. This program will now terminate.\n\n' +
- traceback.format_exc() + '\n\nCopy traceback to Clipboard?',
- 'Theme Installer Generator', win32con.MB_YESNO|win32con.MB_ICONSTOP) == win32con.IDYES:
- win32clipboard.OpenClipboard()
- win32clipboard.EmptyClipboard()
- win32clipboard.SetClipboardText(traceback.format_exc())
- win32clipboard.CloseClipboard()
- raise
diff --git a/pkg/Package-GNULinux.pl b/pkg/Package-GNULinux.pl
deleted file mode 100644
index ed78a25dd..000000000
--- a/pkg/Package-GNULinux.pl
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/perl
-#
-# Copyright 2008 Pascal Giard
-#
-# Create patch for FoFiX from a list of source:destination
-# Look at ../Makefile for a usage example.
-use strict;
-use warnings;
-use Getopt::Long;
-use File::Path;
-use File::Remove qw(remove);
-use File::NCopy;
-use File::Find;
-
-use vars qw(%opt @src @dest @tuple @svndirs);
-my $cwd = ( $0 =~ /(^.*\/)[^\/]+$/ ) ? $1 : "./";
-
-GetOptions( "dest=s" => \$opt{'dir'},
- "list=s" => \$opt{'list'} );
-
-die "Need destination directory!" unless( defined $opt{'dir'} );
-$opt{'list'} = "Dist-MegaLight-GNULinux.lst" unless( defined $opt{'list'} );
-
-sub delsvn {
- return unless ( $File::Find::name =~ /\.svn$/ );
- push @svndirs, $File::Find::name;
-}
-
-open(FH, "$opt{'list'}") or die $!;
-while( ) {
- chop();
- next if( /^$/ );
- @tuple = split( /:/, $_);
- push @src, $tuple[0];
- push @dest, $tuple[1];
-}
-close FH;
-
-my $cp = File::NCopy->new(recursive => 1,
- force_write => 1,
- follow_links => 1);
-
-mkpath("$opt{'dir'}");
-for(my $i = 0; $i < scalar(@src); $i++ ) {
- mkpath("$opt{'dir'}/$dest[$i]") unless ( -e "$opt{'dir'}/$dest[$i]" );
- $cp->copy("$src[$i]", "$opt{'dir'}/$dest[$i]")
- or die "Copy of $src[$i] to $opt{'dir'}/$dest[$i] failed: $!";
-}
-
-chdir $opt{'dir'};
-remove( \1, qw{
- src/*.pyc src/*.pyo src/*.bat
- src/midi/*.pyc src/midi/*.pyo } );
-find({ wanted => \&delsvn, no_chdir => 1 }, ".");
-remove( \1, @svndirs ) or die $! if( scalar(@svndirs) > 0 );
-chdir "..";
-
-__END__
diff --git a/src/Credits.py b/src/Credits.py
index b66ca5378..73c6ae5f7 100644
--- a/src/Credits.py
+++ b/src/Credits.py
@@ -39,11 +39,7 @@
import Config
import Dialogs
-try:
- from VideoPlayer import VideoPlayer
- videoAvailable = True
-except:
- videoAvailable = False
+from VideoPlayer import VideoLayer, VideoPlayerError
class Element:
"""A basic element in the credits scroller."""
@@ -172,24 +168,17 @@ def __init__(self, engine, songName = None):
self.videoLayer = False
self.background = None
- if videoAvailable:
- # TODO: Parameters to add to theme.ini:
- # - credits_video_file
- # - credits_video_start_time
- # - credits_video_end_time
- vidSource = os.path.join(Version.dataPath(), 'themes', self.themename, \
- 'menu', 'credits.avi')
- if os.path.exists(vidSource):
- winWidth, winHeight = self.engine.view.geometry[2:4]
- songVideoStartTime = 0
- songVideoEndTime = None
- self.vidPlayer = VideoPlayer(-1, vidSource, (winWidth, winHeight),
- mute = True, loop = True,
- startTime = songVideoStartTime,
- endTime = songVideoEndTime)
- if self.vidPlayer.validFile:
- self.engine.view.pushLayer(self.vidPlayer)
- self.videoLayer = True
+ vidSource = os.path.join(Version.dataPath(), 'themes', self.themename, \
+ 'menu', 'credits.ogv')
+ if os.path.isfile(vidSource):
+ try:
+ self.vidPlayer = VideoLayer(self.engine, vidSource, mute = True, loop = True)
+ except (IOError, VideoPlayerError):
+ Log.error('Error loading credits video:')
+ else:
+ self.vidPlayer.play()
+ self.engine.view.pushLayer(self.vidPlayer)
+ self.videoLayer = True
if not self.videoLayer and \
not self.engine.loadImgDrawing(self, 'background', os.path.join('themes', self.themename, 'menu', 'credits.png')):
diff --git a/src/Drum.py b/src/Drum.py
index 66fa27b41..e6bb93041 100644
--- a/src/Drum.py
+++ b/src/Drum.py
@@ -447,13 +447,12 @@ def renderNote(self, length, sustain, color, flat = False, tailOnly = False, isT
if self.twoDnote == True:
- if self.notedisappear == True:#Notes keep on going when missed
- notecol = (1,1,1,1)#capo
- else:
- if flat:#Notes disappear when missed
- notecol = (.1,.1,.1,1)
- else:
- notecol = (1,1,1,1)
+ if self.notedisappear ==0:#Notes keep on going when missed
+ notecol = (1,1,1)#capo
+ elif self.notedisappear == 1:#Notes disappear when missed
+ notecol = (.1,.1,.1)
+ elif self.notedisappear == 2: #Notes Turn Red when missed
+ notecol = (1,0,0,1)
tailOnly = True
@@ -837,27 +836,37 @@ def renderOpenNotes(self, visibility, song, pos):
isTappable = False
- if self.notedisappear == True:#Notes keep on going when missed
- if event.played or event.hopod:
+ if self.notedisappear == 0:#Notes keep on going when missed
+ if event.played or event.hopod:#if the note isnt missed
tailOnly = True
length += z
z = 0
if length <= 0:
continue
- if z < 0 and not (event.played or event.hopod):
+ if z < 0 and not (event.played or event.hopod):#if the note is missed
color = (.6, .6, .6, .5 * visibility * f)
- flat = False
- else:#Notes disappear when missed
- if z < 0:
- if event.played or event.hopod:
+ flat = False
+ elif self.notedisappear == 1:#Notes disappear when missed
+ if z < 0:#if note past frets
+ if event.played or event.hopod:#if note was hit
tailOnly = True
length += z
z = 0
if length <= 0:
continue
- else:
+ else:#note missed
color = (.6, .6, .6, .5 * visibility * f)
flat = False
+ if self.notedisappear == 2:#turn red when missed
+ if event.played or event.hopod: #if the note isnt missed
+ tailOnly = True
+ length += z
+ z = 0
+ if length <= 0:
+ continue
+ if z < 0 and not (event.played or event.hopod): #if the note is missed
+ color = (1, 0, 0, 1)#turn note red
+ flat = False
sustain = False
@@ -998,27 +1007,37 @@ def renderNotes(self, visibility, song, pos):
isTappable = False
- if self.notedisappear == True:#Notes keep on going when missed
- if event.played or event.hopod:
+ if self.notedisappear == 0:#Notes keep on going when missed
+ if event.played or event.hopod:#if the note isnt missed
tailOnly = True
length += z
z = 0
if length <= 0:
continue
- if z < 0 and not (event.played or event.hopod):
+ if z < 0 and not (event.played or event.hopod):#if the note is missed
color = (.6, .6, .6, .5 * visibility * f)
- flat = False
- else:#Notes disappear when missed
- if z < 0:
- if event.played or event.hopod:
+ flat = False
+ elif self.notedisappear == 1:#Notes disappear when missed
+ if z < 0:#if note past frets
+ if event.played or event.hopod:#if note was hit
tailOnly = True
length += z
z = 0
if length <= 0:
continue
- else:
+ else:#note missed
color = (.6, .6, .6, .5 * visibility * f)
flat = False
+ if self.notedisappear == 2:#turn red when missed
+ if event.played or event.hopod: #if the note isnt missed
+ tailOnly = True
+ length += z
+ z = 0
+ if length <= 0:
+ continue
+ if z < 0 and not (event.played or event.hopod): #if the note is missed
+ color = (1, 0, 0, 1)#turn note red
+ flat = False
sustain = False
diff --git a/src/FoFiX.py b/src/FoFiX.py
index cc162102b..bca5a9d38 100644
--- a/src/FoFiX.py
+++ b/src/FoFiX.py
@@ -40,6 +40,11 @@
import os
import Version
+# Add the directory of DLL dependencies to the PATH if we're running
+# from source on Windows so we pick them up when those bits are imported.
+if os.name == 'nt' and not hasattr(sys, 'frozen'):
+ os.environ['PATH'] = os.path.abspath(os.path.join('..', 'win32', 'deps', 'bin')) + os.pathsep + os.environ['PATH']
+
def _usage(errmsg=None):
usage = """Usage: %(prog)s [options]
@@ -104,12 +109,7 @@ def _usage(errmsg=None):
import Resource
import pygame
import traceback
-
-try:
- from VideoPlayer import VideoPlayer
- videoAvailable = True
-except:
- videoAvailable = False
+from VideoPlayer import VideoLayer, VideoPlayerError
def main():
try:
@@ -213,33 +213,24 @@ def main():
# Play the intro video if it is present, we have the capability, and
# we are not in one-shot mode.
videoLayer = False
- if videoAvailable and not engine.cmdPlay:
- # TODO: Parameters to add to theme.ini:
- # - intro_video_file
- # - intro_video_start_time
- # - intro_video_end_time
+ if not engine.cmdPlay:
themename = Config.get("coffee", "themename")
vidSource = os.path.join(Version.dataPath(), 'themes', themename, \
- 'menu', 'intro.avi')
+ 'menu', 'intro.ogv')
if os.path.isfile(vidSource):
- winWidth, winHeight = engine.view.geometry[2:4]
- songVideoStartTime = 0
- songVideoEndTime = None
- vidPlayer = VideoPlayer(-1, vidSource, (winWidth, winHeight),
- startTime = songVideoStartTime,
- endTime = songVideoEndTime)
- if vidPlayer.validFile:
+ try:
+ vidPlayer = VideoLayer(engine, vidSource, cancellable=True)
+ except (IOError, VideoPlayerError):
+ Log.error("Error loading intro video:")
+ else:
+ vidPlayer.play()
engine.view.pushLayer(vidPlayer)
videoLayer = True
- try:
- engine.ticksAtStart = pygame.time.get_ticks()
- while not vidPlayer.finished:
- engine.run()
- engine.view.popLayer(vidPlayer)
- engine.view.pushLayer(MainMenu(engine))
- except KeyboardInterrupt:
- engine.view.popLayer(vidPlayer)
- engine.view.pushLayer(MainMenu(engine))
+ engine.ticksAtStart = pygame.time.get_ticks()
+ while not vidPlayer.finished:
+ engine.run()
+ engine.view.popLayer(vidPlayer)
+ engine.view.pushLayer(MainMenu(engine))
if not videoLayer:
engine.setStartupLayer(MainMenu(engine))
diff --git a/src/GameEngine.py b/src/GameEngine.py
index fd0214f2a..c37af9b81 100644
--- a/src/GameEngine.py
+++ b/src/GameEngine.py
@@ -143,7 +143,7 @@ def sortOptionsByKey(dict):
Config.define("performance", "game_priority", int, 2, text = _("Process Priority"), options = sortOptionsByKey({0: _("Idle"), 1: _("Low"), 2: _("Normal"), 3:_("Above Normal"), 4:_("High"), 5:_("Realtime")}), tipText = _("Change this to increase the priority of the FoFiX process. Don't change this unless you know what you're doing. DO NOT set this to Realtime. Ever."))
Config.define("performance", "restrict_to_first_processor", bool, False, text=_("Restrict to First Core (Win32 Only)"), options={False: _("No"), True: _("Yes")}, tipText=_("Choose whether to restrict the game to running on only the first processor core on the system. Only has an effect under Windows.")) #stump
Config.define("performance", "use_psyco", bool, False, text=_("Use Psyco"), options={False: _("No"), True: _("Yes")}, tipText = _("Enable or disable the Psyco specializing compiler. Tests have indicated the game runs faster with it off.")) #stump
-Config.define("game", "notedisappear", bool, False, text = _("Missed Notes"), options = {False: _("Disappear"), True: _("Keep on going")}, tipText = _("When you miss a note, this sets whether they disappear from the fretboard or scroll off the bottom of the screen."))
+Config.define("game", "notedisappear", int, 1, text = _("Missed Notes"), options = {0: _("Disappear"), 1: _("Keep on going"), 2: _("Turn Red")}, tipText = _("When you miss a note, this sets whether they disappear from the fretboard, scroll off the bottom of the screen or turn red"))
#akedrou - Quickset (based on Fablaculp's Performance Autoset)
Config.define("quickset", "performance", int, 0, text = _("Performance"), options = sortOptionsByKey({0: _("Manual"), 1: _("Pure Speed"), 2: _("Fast"), 3: _("Quality (Recommended)"), 4: _("Max Quality (Slow)")}), tipText = _("Set the performance of your game. You can fine-tune in the advanced menus."))
@@ -599,7 +599,7 @@ def __init__(self, config = None):
self.stageFolders = []
currentTheme = themename
- stagespath = os.path.join(Version.dataPath(), "themes", currentTheme, "stages")
+ stagespath = os.path.join(Version.dataPath(), "themes", currentTheme, "backgrounds")
themepath = os.path.join(Version.dataPath(), "themes", currentTheme)
if os.path.exists(stagespath):
self.stageFolders = []
@@ -825,7 +825,7 @@ def drawStarScore(self, screenwidth, screenheight, xpos, ypos, stars, scale = No
def drawImage(self, image, scale = (1.0, -1.0), coord = (0, 0), rot = 0, \
color = (1,1,1,1), rect = (0,1,0,1), stretched = 0, fit = 0, \
- alignment = 1):
+ alignment = 1, valignment = 1):
"""
Draws the image/surface to screen
@@ -848,6 +848,8 @@ def drawImage(self, image, scale = (1.0, -1.0), coord = (0, 0), rot = 0, \
on the top side (1), bottom side (2), or center point (any other value) of the image
@param alignment: Adjusts the texture so the coordinate for x-axis placement can either be
on the left side (0), center point (1), or right(2) side of the image
+ @param valignment: Adjusts the texture so the coordinate for y-axis placement can either be
+ on the bottom side (0), center point (1), or top(2) side of the image
"""
width, height = scale
@@ -875,6 +877,7 @@ def drawImage(self, image, scale = (1.0, -1.0), coord = (0, 0), rot = 0, \
image.setScale(width, height)
image.setPosition(x, y)
image.setAlignment(alignment)
+ image.setVAlignment(valignment)
image.setAngle(rot)
image.setColor(color)
image.draw()
diff --git a/src/Guitar.py b/src/Guitar.py
index 4df42c270..d711cda8c 100644
--- a/src/Guitar.py
+++ b/src/Guitar.py
@@ -63,8 +63,6 @@ def __init__(self, engine, playerObj, editorMode = False, player = 0, bass = Fal
self.Animspeed = 30#Lower value = Faster animations
#For Animated Starnotes
self.indexCount = 0
- #Alarian, For animated hitglow
- self.HCountAni = False
#myfingershurt:
self.hopoStyle = self.engine.config.get("game", "hopo_system")
@@ -425,15 +423,15 @@ def renderNote(self, length, sustain, kill, color, flat = False, tailOnly = Fals
return
+
if self.twoDnote == True:
#myfingershurt: this should be retrieved once at init, not repeatedly in-game whenever tails are rendered.
- if self.notedisappear == True:#Notes keep on going when missed
+ if self.notedisappear ==0:#Notes keep on going when missed
notecol = (1,1,1)#capo
- else:
- if flat:#Notes disappear when missed
- notecol = (.1,.1,.1)
- else:
- notecol = (1,1,1)
+ elif self.notedisappear == 1:#Notes disappear when missed
+ notecol = (.1,.1,.1)
+ elif self.notedisappear == 2: #Notes Turn Red when missed
+ notecol = (1,0,0,1)
tailOnly = True
if self.theme < 2:
@@ -830,27 +828,37 @@ def renderNotes(self, visibility, song, pos, killswitch):
# Clip the played notes to the origin
#myfingershurt: this should be loaded once at init, not every render...
- if self.notedisappear == True:#Notes keep on going when missed
- if event.played or event.hopod:
+ if self.notedisappear == 0:#Notes keep on going when missed
+ if event.played or event.hopod:#if the note isnt missed
tailOnly = True
length += z
z = 0
if length <= 0:
continue
- if z < 0 and not (event.played or event.hopod):
+ if z < 0 and not (event.played or event.hopod):#if the note is missed
color = (.6, .6, .6, .5 * visibility * f)
flat = False
- else:#Notes disappear when missed
- if z < 0:
- if event.played or event.hopod:
+ elif self.notedisappear == 1:#Notes disappear when missed
+ if z < 0:#if note past frets
+ if event.played or event.hopod:#if note was hit
tailOnly = True
length += z
z = 0
if length <= 0:
continue
- else:
+ else:#note missed
color = (.6, .6, .6, .5 * visibility * f)
- flat = False
+ flat = False
+ if self.notedisappear == 2:#turn red when missed
+ if event.played or event.hopod: #if the note isnt missed
+ tailOnly = True
+ length += z
+ z = 0
+ if length <= 0:
+ continue
+ if z < 0 and not (event.played or event.hopod): #if the note is missed
+ color = (1, 0, 0, 1)#turn note red
+ flat = False
big = False
self.bigMax = 0
@@ -994,27 +1002,37 @@ def renderTails(self, visibility, song, pos, killswitch):
# Clip the played notes to the origin
#myfingershurt: this should be loaded once at init, not every render...
- if self.notedisappear == True:#Notes keep on going when missed
- if event.played or event.hopod:
+ if self.notedisappear == 0:#Notes keep on going when missed
+ if event.played or event.hopod:#if the note isnt missed
tailOnly = True
length += z
z = 0
if length <= 0:
continue
- if z < 0 and not (event.played or event.hopod):
+ if z < 0 and not (event.played or event.hopod):#if the note is missed
color = (.6, .6, .6, .5 * visibility * f)
flat = False
- else:#Notes disappear when missed
- if z < 0:
- if event.played or event.hopod:
+ elif self.notedisappear == 1:#Notes disappear when missed
+ if z < 0:#if note past frets
+ if event.played or event.hopod:#if note was hit
tailOnly = True
length += z
z = 0
if length <= 0:
continue
- else:
+ else:#note missed
color = (.6, .6, .6, .5 * visibility * f)
- flat = False
+ flat = False
+ if self.notedisappear == 2:#turn red when missed
+ if event.played or event.hopod: #if the note isnt missed
+ tailOnly = True
+ length += z
+ z = 0
+ if length <= 0:
+ continue
+ if z < 0 and not (event.played or event.hopod): #if the note is missed
+ color = (1, 0, 0, 1)#turn note red
+ flat = False
big = False
self.bigMax = 0
@@ -1594,37 +1612,6 @@ def getDoubleNotes(self, notes):
else:
noteCount += 1
return sorted(notes, key=lambda x: x[0])
-
- def getRequiredNotesForRender(self, song, pos):
- if self.battleStatus[2] and self.difficulty != 0:
- Log.debug(self.battleDiffUpValue)
- song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue]
- track0 = song.track[self.player]
- notes0 = [(time, event) for time, event in track0.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
-
- song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue - 1]
- track1 = song.track[self.player]
- notes1 = [(time, event) for time, event in track1.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
-
- notes = []
- for time,note in notes0:
- if time < self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard or time > self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
- notes.append((time,note))
- for time,note in notes1:
- if time > self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard and time < self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
- notes.append((time,note))
- notes0 = None
- notes1 = None
- track0 = None
- track1 = None
- notes = sorted(notes, key=lambda x: x[0])
- else:
- track = song.track[self.player]
- notes = [(time, event) for time, event in track.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
-
- if self.battleStatus[7]:
- notes = self.getDoubleNotes(notes)
- return notes
#MFH - corrected and optimized:
def getRequiredNotesForJurgenOnTime(self, song, pos):
diff --git a/src/GuitarScene.py b/src/GuitarScene.py
index 4eb9fe429..554021f10 100644
--- a/src/GuitarScene.py
+++ b/src/GuitarScene.py
@@ -1301,22 +1301,17 @@ def __init__(self, engine, libraryName, songName):
# evilynux - Load stage background(s)
if self.stage.mode == 3:
- if Stage.videoAvailable:
- songVideo = None
- if self.song.info.video is not None:
- songVideo = self.song.info.video
- songVideoStartTime = self.song.info.video_start_time
- songVideoEndTime = self.song.info.video_end_time
- if songVideoEndTime == -1:
- songVideoEndTime = None
- self.stage.loadVideo(self.libraryName, self.songName,
- songVideo = songVideo,
- songVideoStartTime = songVideoStartTime,
- songVideoEndTime = songVideoEndTime)
- else:
- Log.warn("Video playback is not supported. GStreamer or its python bindings can't be found")
- self.engine.config.set("game", "stage_mode", 1)
- self.stage.mode = 1
+ songVideo = None
+ if self.song.info.video is not None:
+ songVideo = self.song.info.video
+ songVideoStartTime = self.song.info.video_start_time
+ songVideoEndTime = self.song.info.video_end_time
+ if songVideoEndTime == -1:
+ songVideoEndTime = None
+ self.stage.loadVideo(self.libraryName, self.songName,
+ songVideo = songVideo,
+ songVideoStartTime = songVideoStartTime,
+ songVideoEndTime = songVideoEndTime)
self.stage.load(self.libraryName, self.songName, self.playerList[0].practiceMode)
@@ -1749,7 +1744,7 @@ def freeResources(self):
if self.coOpType:
self.coOpScoreCard.lastNoteEvent = None
- if self.stage.mode == 3 and Stage.videoAvailable:
+ if self.stage.mode == 3:
self.engine.view.popLayer(self.stage.vidPlayer)
def getHandicap(self):
@@ -3887,14 +3882,14 @@ def endPick(self, num):
self.scoring[num].addScore(scoreTemp)
def render3D(self):
- if self.stage.mode == 3 and Stage.videoAvailable:
+ if self.stage.mode == 3:
if self.countdown <= 0:
if self.pause == True or self.failed == True:
- self.stage.vidPlayer.paused = True
+ self.stage.vidPlayer.pause()
else:
- self.stage.vidPlayer.paused = False
+ self.stage.vidPlayer.play()
else:
- self.stage.vidPlayer.paused = True
+ self.stage.vidPlayer.pause()
self.stage.render(self.visibility)
diff --git a/src/Instrument.py b/src/Instrument.py
index b79cfa75e..16d02636f 100644
--- a/src/Instrument.py
+++ b/src/Instrument.py
@@ -180,7 +180,8 @@ def __init__(self, engine, playerObj, player = 0):
self.HCount2 = 0
self.Hitanim = True
self.Hitanim2 = True
-
+ self.HCountAni = False
+
#myfingershurt: to keep track of pause status here as well
self.paused = False
@@ -715,7 +716,7 @@ def renderFlames(self, visibility, song, pos, controls):
for step in range(4):
#draw lightning in GH themes on SP gain
- if step == 0 and event.finalStar and self.spEnabled and self.disableFlameSFX != True:
+ if step == 0 and event.finalStar and self.spEnabled and self.disableFlameSFX != True and self.hitlightning:
self.engine.draw3Dtex(self.hitlightning, coord = (xlightning, y, 3.3), rot = (90, 1, 0, 0),
scale = (.15 + .5 * ms * ff, event.flameCount / 3.0 + .6 * ms * ff, 2), vertex = (.4,-2,-.4,2),
texcoord = (0.0,0.0,1.0,1.0), multiples = True, alpha = True, color = (1,1,1))
@@ -805,3 +806,33 @@ def renderFlames(self, visibility, song, pos, controls):
multiples = True, alpha = True, color = flamecol)
event.flameCount += 1
+ def getRequiredNotesForRender(self, song, pos):
+ if self.battleStatus[2] and self.difficulty != 0:
+ Log.debug(self.battleDiffUpValue)
+ song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue]
+ track0 = song.track[self.player]
+ notes0 = [(time, event) for time, event in track0.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
+
+ song.difficulty[self.player] = Song.difficulties[self.battleDiffUpValue - 1]
+ track1 = song.track[self.player]
+ notes1 = [(time, event) for time, event in track1.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
+
+ notes = []
+ for time,note in notes0:
+ if time < self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard or time > self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
+ notes.append((time,note))
+ for time,note in notes1:
+ if time > self.battleStartTimes[2] + self.currentPeriod * self.beatsPerBoard and time < self.battleStartTimes[2] - self.currentPeriod * self.beatsPerBoard + self.battleDiffUpLength:
+ notes.append((time,note))
+ notes0 = None
+ notes1 = None
+ track0 = None
+ track1 = None
+ notes = sorted(notes, key=lambda x: x[0])
+ else:
+ track = song.track[self.player]
+ notes = [(time, event) for time, event in track.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard)]
+
+ if self.battleStatus[7]:
+ notes = self.getDoubleNotes(notes)
+ return notes
diff --git a/src/Neck.py b/src/Neck.py
index b2c7e52f4..fe5804699 100644
--- a/src/Neck.py
+++ b/src/Neck.py
@@ -63,6 +63,8 @@ def __init__(self, engine, instrument, playerObj):
self.boardLength = self.engine.theme.neckLength
self.shaderSolocolor = self.engine.theme.shaderSolocolor
+ self.boardFadeAmount = self.engine.theme.boardFade
+
#death_au: fixed neck size
if self.isDrum and self.engine.config.get("game", "large_drum_neck"):
@@ -81,8 +83,8 @@ def __init__(self, engine, instrument, playerObj):
[color[0],color[1],color[2], 0],
[color[0],color[1],color[2], self.vis],
[color[0],color[1],color[2], self.vis],
- [color[0],color[1],color[2], self.vis],
- [color[0],color[1],color[2], self.vis],
+ [color[0],color[1],color[2], self.vis/self.boardFadeAmount],
+ [color[0],color[1],color[2], self.vis/self.boardFadeAmount],
[color[0],color[1],color[2], 0],
[color[0],color[1],color[2], 0]], dtype=np.float32)
@@ -380,10 +382,7 @@ def renderNeckMethod(self, visibility, offset, neck, alpha = False): #blazingame
def project(beat):
return 0.125 * beat / self.beatsPerUnit # glorandwarf: was 0.12
-
- if self.instrument.starPowerActive and self.theme == 0:#8bit
- color = self.engine.theme.spNoteColor #self.spColor #(.3,.7,.9)
- elif self.instrument.starPowerActive and self.theme == 1:
+ if self.instrument.starPowerActive and (self.theme == 0 or self.theme == 1):#8bit
color = self.engine.theme.spNoteColor #self.spColor #(.3,.7,.9)
else:
color = (1,1,1)
@@ -407,8 +406,8 @@ def project(beat):
[color[0],color[1],color[2], 0],
[color[0],color[1],color[2], v],
[color[0],color[1],color[2], v],
- [color[0],color[1],color[2], v],
- [color[0],color[1],color[2], v],
+ [color[0],color[1],color[2], v/self.boardFadeAmount],
+ [color[0],color[1],color[2], v/self.boardFadeAmount],
[color[0],color[1],color[2], 0],
[color[0],color[1],color[2], 0]], dtype=np.float32)
@@ -585,8 +584,8 @@ def project(beat):
[color[0],color[1],color[2], v],
[color[0],color[1],color[2], v],
[color[0],color[1],color[2], v],
- [color[0],color[1],color[2], v],
- [color[0],color[1],color[2], v],
+ [color[0],color[1],color[2], v/self.boardFadeAmount],
+ [color[0],color[1],color[2], v/self.boardFadeAmount],
[color[0],color[1],color[2], 0],
[color[0],color[1],color[2], 0]], dtype=np.float32)
cmgl.drawArrays(GL_TRIANGLE_STRIP, vertices=track_vtx, colors=track_col, texcoords=track_tex)
diff --git a/src/Rockmeter.py b/src/Rockmeter.py
index 8d858b783..857011004 100644
--- a/src/Rockmeter.py
+++ b/src/Rockmeter.py
@@ -44,6 +44,8 @@
LEFT = 0
CENTER = 1
RIGHT = 2
+TOP = 0
+BOTTOM = 2
#by making these global for the class, all layers that rely on
#these numbers will no longer have to have them be independent per
@@ -114,14 +116,14 @@ def __init__(self, stage, section, drawing):
Layer.__init__(self, stage, section)
#these are the images that are drawn when the layer is visible
- self.drawing = [self.engine.loadImgDrawing(self, None, drawing)]
- self.rect = [[0,1,0,1]] #how much of the image do you want rendered (left, right, top, bottom)
+ self.drawing = self.engine.loadImgDrawing(self, None, drawing)
+ self.rect = [0,1,0,1] #how much of the image do you want rendered (left, right, top, bottom)
def updateLayer(self, playerNum):
w, h, = self.engine.view.geometry[2:4]
- texture = self.drawing[0]
+ texture = self.drawing
- self.rect = [list(eval(self.get("rect", str, "(0,1,0,1)")))]
+ self.rect = list(eval(self.get("rect", str, "(0,1,0,1)")))
#all of this has to be repeated instead of using the base method
#because now things can be calculated in relation to the image's properties
@@ -134,8 +136,9 @@ def updateLayer(self, playerNum):
self.condition = bool(eval(self.get("condition", str, "True")))
self.alignment = eval(self.get("alignment", str, "center").upper())
+ self.valignment = eval(self.get("valignment", str, "center").upper())
- rect = self.rect[0]
+ rect = self.rect
self.scale[0] *= (rect[1] - rect[0])
self.scale[1] *= (rect[3] - rect[2])
#this allows you to scale images in relation to pixels instead
@@ -166,19 +169,21 @@ def render(self, visibility, playerNum):
for effect in self.effects:
effect.update()
- coord = self.position
- scale = self.scale
- rot = self.angle
- color = self.engine.theme.hexToColor(self.color)
- alignment = self.alignment
- drawing = self.drawing[0]
- rect = self.rect[0]
+ coord = self.position
+ scale = self.scale
+ rot = self.angle
+ color = self.engine.theme.hexToColor(self.color)
+ alignment = self.alignment
+ valignment = self.valignment
+ drawing = self.drawing
+ rect = self.rect
#frameX = self.frameX
#frameY = self.frameY
if self.condition:
- self.engine.drawImage(drawing, scale, coord, rot, color, rect, alignment = alignment)
+ self.engine.drawImage(drawing, scale, coord, rot, color, rect,
+ alignment = alignment, valignment = valignment)
#defines layers that are just font instead of images
class FontLayer(Layer):
@@ -237,12 +242,12 @@ def render(self, visibility, playerNum):
self.updateLayer(playerNum)
for effect in self.effects:
effect.update()
-
+
glColor3f(*self.engine.theme.hexToColor(self.color))
if self.condition:
self.font.render(self.text, (self.position[0], self.position[1]), align = self.alignment)
-
+
#creates a layer that is shaped like a pie-slice/circle instead of a rectangle
class CircleLayer(Layer):
def __init__(self, stage, section, drawing):
@@ -419,45 +424,67 @@ def __init__(self, layer, section):
Effect.__init__(self, layer, section)
if isinstance(layer, ImageLayer):
- texture = self.get("texture")
- drawing = os.path.join("themes", layer.stage.themename, "rockmeter", texture)
- layer.drawing.append(layer.engine.loadImgDrawing(self, "drawing", drawing))
- self.rect = list(eval(self.get("rect", str, "(0,1,0,1)")))
- layer.rect.append(self.rect)
- self.index = len(layer.drawing)-1
+ self.drawings = []
+ self.rects = []
+ if not self.get("texture") == None:
+ texture = self.get("texture").strip().split("|")
+ for tex in texture:
+ path = os.path.join("themes", layer.stage.themename, "rockmeter", tex)
+ drawing = layer.engine.loadImgDrawing(self, None, path)
+ self.drawings.append(drawing)
+ self.drawings.append(layer.drawing)
+ if not self.get("rect") == None:
+ rects = self.get("rect").split("|")
+ for rect in rects:
+ self.rects.append(eval(rect))
+ self.rects.append(layer.rect)
self.type = "image"
elif isinstance(layer, FontLayer):
self.font = self.engine.data.fontDict[self.get("font")]
- self.text = ""
+ self.text = self.get("text").split("|")
self.type = "font"
-
- def replaceFont(self):
- pass
+ self.conditions = self.get("condition", str, "True").split("|")
+
+ #fixes the scale after the rect is changed
+ def fixScale(self):
+ w, h, = self.layer.engine.view.geometry[2:4]
+
+ rect = self.layer.rect
+ scale = [eval(self.layer.get("xscale", str, "0.5")),
+ eval(self.layer.get("yscale", str, "0.5"))]
+ scale[0] *= (rect[1] - rect[0])
+ scale[1] *= (rect[3] - rect[2])
+ #this allows you to scale images in relation to pixels instead
+ #of percentage of the size of the image.
+ if "xscale" in self.layer.inPixels:
+ scale[0] /= self.layer.drawing.pixelSize[0]
+ if "yscale" in self.layer.inPixels:
+ scale[1] /= self.layer.drawing.pixelSize[1]
+
+ scale[1] = -scale[1]
+ scale[0] *= w/640.0
+ scale[1] *= h/480.0
+
+ self.layer.scale = scale
- def replaceImage(self):
- if not self.layer.drawing.index(self.drawing) == 0:
- self.layer.drawing.insert(0, self.layer.drawing.pop(self.index))
- self.layer.rect.insert(0, self.layer.rect.pop(self.index))
-
- def resetImage(self):
- if self.layer.drawing.index(self.drawing) == 0:
- self.layer.drawing.insert(self.index, self.layer.drawing.pop(0))
- self.layer.rect.insert(self.index, self.layer.rect.pop(0))
-
def update(self):
- self.condition = bool(eval(self.get("condition", str, "True")))
- if self.condition:
- if type == "font":
- self.replaceFont()
- else:
- self.replaceImage()
+ if self.type == "font":
+ self.layer.text = self.text[-1]
else:
- if type == "font":
- pass
- else:
- self.resetImage()
+ self.layer.drawing = self.drawings[-1]
+ for i, cond in enumerate(self.conditions):
+ if bool(eval(cond)):
+ if self.type == "font":
+ self.layer.text = self.text[i]
+ else:
+ if len(self.drawings) > 1:
+ self.layer.drawing = self.drawings[i]
+ if len(self.rects) > 1:
+ self.layer.rect = self.rects[i]
+ self.fixScale()
+ break
class Rockmeter:
def get(self, value, type = str, default = None):
@@ -520,8 +547,8 @@ def loadLayerFX(self, layer, section):
layer.effects.append(Slide(layer, fxsection))
# elif t == types[1]:
# layer.effects.append(Rotate(layer, fxsection))
-# elif t == types[2]:
-# layer.effects.append(Replace(layer, fxsection))
+ elif t == types[2]:
+ layer.effects.append(Replace(layer, fxsection))
# else:
# layer.effects.append(Fade(layer, fxsection))
diff --git a/src/Stage.py b/src/Stage.py
index 27a046b0c..64fa55b63 100644
--- a/src/Stage.py
+++ b/src/Stage.py
@@ -30,21 +30,218 @@
import os
import random #MFH - needed for new stage background handling
from Language import _
+import math
+
+from VideoPlayer import VideoLayer, VideoPlayerError
-try:
- from VideoPlayer import VideoPlayer
- videoAvailable = True
-except:
- videoAvailable = False
-
import Rockmeter #blazingamer - new 4.0 code for rendering rockmeters through stage.ini
+
+class Layer(object):
+ """
+ A graphical stage layer that can have a number of animation effects associated with it.
+ """
+ def __init__(self, stage, drawing):
+ """
+ Constructor.
+
+ @param stage: Containing Stage
+ @param drawing: SvgDrawing for this layer. Make sure this drawing is rendered to
+ a texture for performance reasons.
+ """
+ self.stage = stage
+ self.drawing = drawing
+ self.position = (0.0, 0.0)
+ self.angle = 0.0
+ self.scale = (1.0, 1.0)
+ self.color = (1.0, 1.0, 1.0, 1.0)
+ self.srcBlending = GL_SRC_ALPHA
+ self.dstBlending = GL_ONE_MINUS_SRC_ALPHA
+ self.transforms = [[1,1], [1,1], 1, [1,1,1,1]] #scale, coord, angle, color
+ self.effects = []
+
+ def render(self, visibility):
+ """
+ Render the layer.
+
+ @param visibility: Floating point visibility factor (1 = opaque, 0 = invisibile)
+ """
+ w, h = self.stage.engine.view.geometry[2:4]
+ v = 1.0 - visibility ** 2
+
+ color = self.color
+
+ #coordinates are positioned with (0,0) being in the middle of the screen
+ coord = [w/2 + self.position[0] * w/2, h/2 - self.position[1] * h/2]
+ if v > .01:
+ color = [self.color[0], self.color[1], self.color[2], visibility]
+ scale = [self.scale[0], -self.scale[1]]
+ rot = self.angle
+
+ self.transforms = [scale, coord, rot, color]
+ # Blend in all the effects
+ for effect in self.effects:
+ effect.apply()
+
+ glBlendFunc(self.srcBlending, self.dstBlending)
+ self.stage.engine.drawImage(self.drawing, self.transforms[0], self.transforms[1],
+ self.transforms[2], self.transforms[3])
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+
+class Effect(object):
+ """
+ An animationn effect that can be attached to a Layer.
+ """
+ def __init__(self, layer, options):
+ """
+ Constructor.
+
+ @param layer: Layer to attach this effect to.
+ @param options: Effect options (default in parens):
+ intensity - Floating point effect intensity (1.0)
+ trigger - Effect trigger, one of "none", "beat",
+ "quarterbeat", "pick", "miss" ("none")
+ period - Trigger period in ms (200.0)
+ delay - Trigger delay in periods (0.0)
+ profile - Trigger profile, one of "step", "linstep",
+ "smoothstep"
+ """
+ self.layer = layer
+ self.stage = layer.stage
+ self.intensity = float(options.get("intensity", 1.0))
+ self.trigger = getattr(self, "trigger" + options.get("trigger", "none").capitalize())
+ self.period = float(options.get("period", 500.0))
+ self.delay = float(options.get("delay", 0.0))
+ self.triggerProf = getattr(self, options.get("profile", "linstep"))
+
+ def apply(self):
+ pass
+
+ def triggerNone(self):
+ return 0.0
+
+ def triggerBeat(self):
+ if not self.stage.lastBeatPos:
+ return 0.0
+ t = self.stage.pos - self.delay * self.stage.beatPeriod - self.stage.lastBeatPos
+ return self.intensity * (1.0 - self.triggerProf(0, self.stage.beatPeriod, t))
+
+ def triggerQuarterbeat(self):
+ if not self.stage.lastQuarterBeatPos:
+ return 0.0
+ t = self.stage.pos - self.delay * (self.stage.beatPeriod / 4) - self.stage.lastQuarterBeatPos
+ return self.intensity * (1.0 - self.triggerProf(0, self.stage.beatPeriod / 4, t))
+
+ def triggerPick(self):
+ if not self.stage.lastPickPos:
+ return 0.0
+ t = self.stage.pos - self.delay * self.period - self.stage.lastPickPos
+ return self.intensity * (1.0 - self.triggerProf(0, self.period, t))
+
+ def triggerMiss(self):
+ if not self.stage.lastMissPos:
+ return 0.0
+ t = self.stage.pos - self.delay * self.period - self.stage.lastMissPos
+ return self.intensity * (1.0 - self.triggerProf(0, self.period, t))
+
+ def step(self, threshold, x):
+ return (x > threshold) and 1 or 0
+
+ def linstep(self, min, max, x):
+ if x < min:
+ return 0
+ if x > max:
+ return 1
+ return (x - min) / (max - min)
+
+ def smoothstep(self, min, max, x):
+ if x < min:
+ return 0
+ if x > max:
+ return 1
+ def f(x):
+ return -2 * x**3 + 3*x**2
+ return f((x - min) / (max - min))
+
+ def sinstep(self, min, max, x):
+ return math.cos(math.pi * (1.0 - self.linstep(min, max, x)))
+
+ def getNoteColor(self, note):
+ if note >= len(self.stage.engine.theme.noteColors) - 1:
+ return self.stage.engine.theme.noteColors[-1]
+ elif note <= 0:
+ return self.stage.engine.theme.noteColors[0]
+ f2 = note % 1.0
+ f1 = 1.0 - f2
+ c1 = self.stage.engine.theme.noteColors[int(note)]
+ c2 = self.stage.engine.theme.noteColors[int(note) + 1]
+ return (c1[0] * f1 + c2[0] * f2, \
+ c1[1] * f1 + c2[1] * f2, \
+ c1[2] * f1 + c2[2] * f2)
+
+class LightEffect(Effect):
+ def __init__(self, layer, options):
+ Effect.__init__(self, layer, options)
+ self.lightNumber = int(options.get("light_number", 0))
+ self.ambient = float(options.get("ambient", 0.5))
+ self.contrast = float(options.get("contrast", 0.5))
+
+ def apply(self):
+ if len(self.stage.averageNotes) < self.lightNumber + 2:
+ self.layer.color = (0.0, 0.0, 0.0, 0.0)
+ return
+
+ t = self.trigger()
+ t = self.ambient + self.contrast * t
+ c = self.getNoteColor(self.stage.averageNotes[self.lightNumber])
+ self.layer.transforms[3] = (c[0] * t, c[1] * t, c[2] * t, self.intensity)
+
+class RotateEffect(Effect):
+ def __init__(self, layer, options):
+ Effect.__init__(self, layer, options)
+ self.angle = math.pi / 180.0 * float(options.get("angle", 45))
+
+ def apply(self):
+ if not self.stage.lastMissPos:
+ return
+
+ t = self.trigger()
+ self.layer.transforms[2] = t*self.angle
+
+class WiggleEffect(Effect):
+ def __init__(self, layer, options):
+ Effect.__init__(self, layer, options)
+ self.freq = float(options.get("frequency", 6))
+ self.xmag = float(options.get("xmagnitude", 0.1))
+ self.ymag = float(options.get("ymagnitude", 0.1))
+
+ def apply(self):
+ t = self.trigger()
+
+ w, h = self.stage.engine.view.geometry[2:4]
+ p = t * 2 * math.pi * self.freq
+ s, c = t * math.sin(p), t * math.cos(p)
+ self.layer.transforms[1][0] += self.xmag * w * s
+ self.layer.transforms[1][1] += self.ymag * h * c
+
+class ScaleEffect(Effect):
+ def __init__(self, layer, options):
+ Effect.__init__(self, layer, options)
+ self.xmag = float(options.get("xmagnitude", .1))
+ self.ymag = float(options.get("ymagnitude", .1))
+
+ def apply(self):
+ t = self.trigger()
+ self.layer.transforms[0] = (1.0 + self.xmag * t, -1.0 + self.ymag * t)
+
class Stage(object):
def __init__(self, guitarScene, configFileName):
self.scene = guitarScene
self.engine = guitarScene.engine
self.config = Config.MyConfigParser()
- self.layers = []
+ self.backgroundLayers = []
+ self.foregroundLayers = []
+ self.textures = {}
self.reset()
@@ -83,62 +280,117 @@ def __init__(self, guitarScene, configFileName):
# evilynux - Improved stage error handling
self.themename = self.engine.data.themeLabel
- self.path = os.path.join("themes",self.themename,"stages")
+ self.path = os.path.join("themes",self.themename,"backgrounds")
self.pathfull = self.engine.getPath(self.path)
if not os.path.exists(self.pathfull): # evilynux
Log.warn("Stage folder does not exist: %s" % self.pathfull)
self.mode = 1 # Fallback to song-specific stage
suffix = ".jpg"
+ self.loadLayers(configFileName)
+
+ def loadLayers(self, configFileName):
+ self.config.read(configFileName)
+ path = os.path.join("themes", self.themename, "stage")
+
+ # Build the layers
+ for i in range(32):
+ section = "layer%d" % i
+ if self.config.has_section(section):
+ def get(value, type = str, default = None):
+ if self.config.has_option(section, value):
+ return type(self.config.get(section, value))
+ return default
+
+ xres = get("xres", int, 256)
+ yres = get("yres", int, 256)
+ texture = get("texture")
+
+ try:
+ drawing = self.textures[texture]
+ except KeyError:
+ drawing = self.engine.loadImgDrawing(self, None, os.path.join(path, texture), textureSize = (xres, yres))
+ self.textures[texture] = drawing
+
+ layer = Layer(self, drawing)
+
+ layer.position = (get("xpos", float, 0.0), get("ypos", float, 0.0))
+ layer.scale = (get("xscale", float, 1.0), get("yscale", float, 1.0))
+ layer.angle = math.pi * get("angle", float, 0.0) / 180.0
+ layer.srcBlending = globals()["GL_%s" % get("src_blending", str, "src_alpha").upper()]
+ layer.dstBlending = globals()["GL_%s" % get("dst_blending", str, "one_minus_src_alpha").upper()]
+ layer.color = (get("color_r", float, 1.0), get("color_g", float, 1.0), get("color_b", float, 1.0), get("color_a", float, 1.0))
+
+ # Load any effects
+ fxClasses = {
+ "light": LightEffect,
+ "rotate": RotateEffect,
+ "wiggle": WiggleEffect,
+ "scale": ScaleEffect,
+ }
+
+ for j in range(32):
+ fxSection = "layer%d:fx%d" % (i, j)
+ if self.config.has_section(fxSection):
+ type = self.config.get(fxSection, "type")
+
+ if not type in fxClasses:
+ continue
+
+ options = self.config.options(fxSection)
+ options = dict([(opt, self.config.get(fxSection, opt)) for opt in options])
+
+ fx = fxClasses[type](layer, options)
+ layer.effects.append(fx)
+
+ if get("foreground", int):
+ self.foregroundLayers.append(layer)
+ else:
+ self.backgroundLayers.append(layer)
+
def loadVideo(self, libraryName, songName, songVideo = None,
songVideoStartTime = None, songVideoEndTime = None):
- if not videoAvailable:
- raise NameError('Video (gstreamer) is not available!')
self.vidSource = None
if self.songStage == 1:
songAbsPath = os.path.join(libraryName, songName)
if songVideo is not None and \
os.path.isfile(os.path.join(songAbsPath, songVideo)):
self.vidSource = os.path.join(songAbsPath, songVideo)
- elif os.path.exists(os.path.join(songAbsPath, "default.avi")):
+ elif os.path.exists(os.path.join(songAbsPath, "default.ogv")):
Log.warn("Video not found: %s" % \
os.path.join(songAbsPath, songVideo))
- self.vidSource = os.path.join(songAbsPath, "default.avi")
+ self.vidSource = os.path.join(songAbsPath, "default.ogv")
if self.vidSource is None:
if self.songStage == 1:
Log.warn("Video not found: %s" % \
- os.path.join(songAbsPath, "default.avi"))
+ os.path.join(songAbsPath, "default.ogv"))
songVideoStartTime = None
songVideoEndTime = None
- self.vidSource = os.path.join(self.pathfull, "default.avi")
+ self.vidSource = os.path.join(self.pathfull, "default.ogv")
if not os.path.exists(self.vidSource):
Log.warn("Video not found: %s" % \
- os.path.join(self.pathfull, "default.avi"))
+ os.path.join(self.pathfull, "default.ogv"))
Log.warn("No video found, falling back to default static image mode for now")
self.mode = 1 # Fallback
self.vidSource = None
return
-
- winWidth, winHeight = (self.engine.view.geometry[2],
- self.engine.view.geometry[3])
- Log.debug("Attempting to load video: %s" % self.vidSource)
+
try: # Catches invalid video files or unsupported formats
Log.debug("Attempting to load video: %s" % self.vidSource)
- self.vidPlayer = VideoPlayer(-1, self.vidSource, (winWidth, winHeight),
- mute = True, loop = True,
- startTime = songVideoStartTime,
- endTime = songVideoEndTime)
+ self.vidPlayer = VideoLayer(self.engine, self.vidSource,
+ mute = True, loop = True,
+ startTime = songVideoStartTime,
+ endTime = songVideoEndTime)
self.engine.view.pushLayer(self.vidPlayer)
- self.vidPlayer.paused = True
- except:
+ except (IOError, VideoPlayerError):
self.mode = 1
- Log.warn("Failed to load video, fallback to default stage mode.")
+ Log.error("Failed to load song video (falling back to default stage mode):")
def restartVideo(self):
- if not videoAvailable or not self.mode == 3:
+ if not self.mode == 3:
return
- self.vidPlayer.loadVideo(self.vidSource)
+ self.vidPlayer.restart()
def load(self, libraryName, songName, practiceMode = False):
rm = os.path.join("themes", self.themename, "rockmeter.ini")
@@ -169,9 +421,9 @@ def load(self, libraryName, songName, practiceMode = False):
background = "practicebass"
else:
background = "practice"
- if not self.engine.loadImgDrawing(self, "background", os.path.join("themes",self.themename,"stages",background)):
+ if not self.engine.loadImgDrawing(self, "background", os.path.join("themes",self.themename,"backgrounds",background)):
#MFH - must first fall back on the old practice.png before forcing blank stage mode!
- if not self.engine.loadImgDrawing(self, "background", os.path.join("themes",self.themename,"stages","practice")):
+ if not self.engine.loadImgDrawing(self, "background", os.path.join("themes",self.themename,"backgrounds","practice")):
Log.warn("No practice stage, falling back on a forced Blank stage mode") # evilynux
self.mode = 2 #if no practice stage, just fall back on a forced Blank stage mode
@@ -235,7 +487,7 @@ def load(self, libraryName, songName, practiceMode = False):
self.mode = 2
if self.animatedFolder != "Normal" and self.mode != 2: #just use the base Stages folder for rotation
- self.path = os.path.join("themes",self.themename,"stages",self.animatedFolder)
+ self.path = os.path.join("themes",self.themename,"backgrounds",self.animatedFolder)
self.pathfull = self.engine.getPath(self.path)
self.animation = True
@@ -334,11 +586,6 @@ def triggerBeat(self, pos, beat):
self.beat = beat
self.averageNotes = self.averageNotes[-4:] + self.averageNotes[-1:]
- def renderLayers(self, layers, visibility):
- with self.engine.view.orthogonalProjection(normalize = True):
- for layer in layers:
- layer.render(visibility)
-
def run(self, pos, period):
self.pos = pos
self.beatPeriod = period
@@ -352,10 +599,16 @@ def run(self, pos, period):
if beat > self.beat:
self.triggerBeat(pos, beat)
+ def renderLayers(self, layers, visibility):
+ if self.mode != 3:
+ with self.engine.view.orthogonalProjection(normalize = True):
+ for layer in layers:
+ layer.render(visibility)
+
def render(self, visibility):
if self.mode != 3:
self.renderBackground()
- self.renderLayers(self.layers, visibility)
+ self.renderLayers(self.backgroundLayers, visibility)
if shaders.enable("stage"):
height = 0.0
for i in shaders.var["color"].keys():
@@ -375,6 +628,5 @@ def render(self, visibility):
shaders.disable()
self.scene.renderGuitar()
+ self.renderLayers(self.foregroundLayers, visibility)
self.rockmeter.render(visibility)
-
-
diff --git a/src/Svg.py b/src/Svg.py
index 2c02236db..cf6e3efac 100644
--- a/src/Svg.py
+++ b/src/Svg.py
@@ -37,7 +37,6 @@
class SvgContext(object):
def __init__(self, geometry):
self.geometry = geometry
- self.transform = SvgTransform()
self.setGeometry(geometry)
self.setProjection(geometry)
glMatrixMode(GL_MODELVIEW)
@@ -59,35 +58,6 @@ def clear(self, r = 0, g = 0, b = 0, a = 0):
glClearColor(r, g, b, a)
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
-
-class SvgTransform(object):
- def __init__(self, baseTransform = None):
- self.reset()
- if baseTransform is not None:
- self.ops = baseTransform.ops[:]
-
- def transform(self, transform):
- self.ops.extend(transform.ops)
-
- def reset(self):
- self.ops = []
-
- def translate(self, dx, dy):
- # The old code did this with a matrix addition, not a multiplication.
- # We get the same effect by doing the translations before anything else.
- self.ops.insert(0, lambda: glTranslatef(dx, dy, 0))
-
- def rotate(self, angle):
- self.ops.append(lambda: glRotatef(math.degrees(angle), 0.0, 0.0, 1.0))
-
- def scale(self, sx, sy):
- self.ops.append(lambda: glScalef(sx, sy, 1.0))
-
- def applyGL(self):
- for op in self.ops:
- op()
-
-
class ImgDrawing(object):
def __init__(self, context, ImgData):
self.ImgData = None
@@ -114,13 +84,14 @@ def __init__(self, context, ImgData):
Log.error(e)
raise RuntimeError(e)
- self.pixelSize = self.texture.pixelSize
- self.position = [0.0,0.0]
- self.scale = [1.0,1.0]
- self.angle = 0
- self.color = (1.0,1.0,1.0)
- self.rect = (0,1,0,1)
- self.shift = -.5
+ self.pixelSize = self.texture.pixelSize #the size of the image in pixels (from texture)
+ self.position = [0.0,0.0] #position of the image in the viewport
+ self.scale = [1.0,1.0] #percentage scaling
+ self.angle = 0 #angle of rotation (degrees)
+ self.color = (1.0,1.0,1.0,1.0) #glColor rgba
+ self.rect = (0,1,0,1) #texture mapping coordinates
+ self.shift = -.5 #horizontal alignment
+ self.vshift = -.5 #vertical alignment
self.createArrays()
@@ -195,6 +166,14 @@ def setAlignment(self, alignment):
elif alignment == 2:#right
self.shift = -1.0
+ def setVAlignment(self, alignment):
+ if alignment == 0: #bottom
+ self.vshift = 0
+ elif alignment == 1:#center
+ self.vshift = -.5
+ elif alignment == 2:#top
+ self.vshift = -1.0
+
def setColor(self, color):
if len(color) == 3:
color = (color[0], color[1], color[2], 1.0)
@@ -213,9 +192,10 @@ def draw(self):
glTranslate(self.position[0], self.position[1], 0.0)
glScalef(self.scale[0], self.scale[1], 1.0)
glRotatef(self.angle, 0, 0, 1)
-
+
glScalef(self.pixelSize[0], self.pixelSize[1], 1)
- glTranslatef(self.shift, -.5, 0)
+ glTranslatef(self.shift, self.vshift, 0)
+
glColor4f(*self.color)
glEnable(GL_TEXTURE_2D)
diff --git a/src/Theme.py b/src/Theme.py
index 38eca5776..71323fab4 100644
--- a/src/Theme.py
+++ b/src/Theme.py
@@ -80,547 +80,548 @@ def __init__(self, path, name):
Log.error("Theme: %s does not exist!\nExiting.\n" % self.themePath)
sys.exit(1)
+ if os.path.exists(os.path.join(self.themePath, "theme.ini")):
+ self.config = Config.MyConfigParser()
+ self.config.read(os.path.join(self.themePath, "theme.ini"))
+ Log.debug("theme.ini loaded")
else:
-
- if os.path.exists(os.path.join(self.themePath, "theme.ini")):
- self.config = Config.MyConfigParser()
- self.config.read(os.path.join(self.themePath, "theme.ini"))
- Log.debug("theme.ini loaded")
+ self.config = None
+ Log.debug("no theme.ini")
+ config = self.config
+
+ def get(value, type = str, default = None):
+ if type == "color":
+ if self.config:
+ if self.config.has_option("theme", value):
+ return self.hexToColor(self.config.get("theme", value))
+ return self.hexToColor(default)
else:
- self.config = None
- Log.debug("no theme.ini")
- config = self.config
+ if self.config:
+ if self.config.has_option("theme", value):
+ return type(self.config.get("theme", value))
+ return default
+
+ #These colors are very important
+ #background_color defines what color openGL will clear too
+ # (the color that shows then no image is present)
+ #base_color is the default color of text in menus
+ #selected_color is the color of text when it is selected
+ # (like in the settings menu or when selecting a song)
+ self.backgroundColor = get("background_color", "color", "#000000")
+ self.baseColor = get("base_color", "color", "#FFFFFF")
+ self.selectedColor = get("selected_color", "color", "#FFBF00")
+
+ #notes that are not textured are drawn in 3 parts (Mesh, Mesh_001, Mesh_002, and occasionally Mesh_003)
+ #The color of mesh is set by mesh_color (on a GH note this is the black ring)
+ #The color of the Mesh_001 is the color of the note (green, red, yellow, etc)
+ #Mesh_002 is set by the hopo_color but if Mesh_003 is present it will be colored spot_color
+ #When Mesh_003 is present it will be colored hopo_color
+ self.meshColor = get("mesh_color", "color", "#000000")
+ self.hopoColor = get("hopo_color", "color", "#00AAAA")
+ self.spotColor = get("spot_color", "color", "#FFFFFF")
+
+ #keys when they are not textured are made of three parts (Mesh, Key_001, Key_002),
+ #two of which can be colored by the CustomTheme.py or the Theme.ini (Mesh, Mesh_002).
+ #These will only work if the object has a Glow_001 mesh in it, else it will render
+ #the whole object the color of the fret
+ #Key_001 is colored by key_color, Key_002 is colored by key2_color, pretty obvious, eh?
+ self.keyColor = get("key_color", "color", "#333333")
+ self.key2Color = get("key2_color", "color", "#000000")
+
+ #when a note is hit a glow will show aside from the hitflames, this has been around
+ #since the original Frets on Fire. What glow_color allows you to do is set it so
+ #the glow is either the color of the fret it's over or it can be the color the image
+ #actually is (if the image is white then no matter what key is hit the glow will be white)
+ self.glowColor = get("glow_color", str, "fret")
+ if not self.glowColor == "frets":
+ self.glowColor = self.hexToColor(self.glowColor)
- def get(value, type = str, default = None):
- if type == "color":
- if self.config:
- if self.config.has_option("theme", value):
- return self.hexToColor(self.config.get("theme", value))
- return self.hexToColor(default)
- else:
- if self.config:
- if self.config.has_option("theme", value):
- return type(self.config.get("theme", value))
- return default
-
- #These colors are very important
- #background_color defines what color openGL will clear too
- # (the color that shows then no image is present)
- #base_color is the default color of text in menus
- #selected_color is the color of text when it is selected
- # (like in the settings menu or when selecting a song)
- self.backgroundColor = get("background_color", "color", "#000000")
- self.baseColor = get("base_color", "color", "#FFFFFF")
- self.selectedColor = get("selected_color", "color", "#FFBF00")
-
- #notes that are not textured are drawn in 3 parts (Mesh, Mesh_001, Mesh_002, and occasionally Mesh_003)
- #The color of mesh is set by mesh_color (on a GH note this is the black ring)
- #The color of the Mesh_001 is the color of the note (green, red, yellow, etc)
- #Mesh_002 is set by the hopo_color but if Mesh_003 is present it will be colored spot_color
- #When Mesh_003 is present it will be colored hopo_color
- self.meshColor = get("mesh_color", "color", "#000000")
- self.hopoColor = get("hopo_color", "color", "#00AAAA")
- self.spotColor = get("spot_color", "color", "#FFFFFF")
-
- #keys when they are not textured are made of three parts (Mesh, Key_001, Key_002),
- #two of which can be colored by the CustomTheme.py or the Theme.ini (Mesh, Mesh_002).
- #These will only work if the object has a Glow_001 mesh in it, else it will render
- #the whole object the color of the fret
- #Key_001 is colored by key_color, Key_002 is colored by key2_color, pretty obvious, eh?
- self.keyColor = get("key_color", "color", "#333333")
- self.key2Color = get("key2_color", "color", "#000000")
-
- #when a note is hit a glow will show aside from the hitflames, this has been around
- #since the original Frets on Fire. What glow_color allows you to do is set it so
- #the glow is either the color of the fret it's over or it can be the color the image
- #actually is (if the image is white then no matter what key is hit the glow will be white)
- self.glowColor = get("glow_color", str, "fret")
- if not self.glowColor == "frets":
- self.glowColor = self.hexToColor(self.glowColor)
-
- #Note Colors (this applies to frets and notes)
- #default is green, red, yellow, blue, orange, purple (I don't know why there's a 6th color)
- default_color = ["#22FF22", "#FF2222", "#FFFF22", "#3333FF", "#FF9933", "#CC22CC"]
- self.noteColors = [get("fret%d_color" % i, "color", default_color[i]) for i in range(6)]
- self.spNoteColor = get("fretS_color", "color", "#4CB2E5")
- self.killNoteColor = get("fretK_color", "color", "#FFFFFF")
-
- #just like glow_color, this allows you to have tails use either the color of the note
- #or the actual color of the tail
- self.use_fret_colors = get("use_fret_colors", bool, False)
-
- self.fretPress = get("fretPress", bool, False)
-
- #Point of View (x, y, z)
- self.povTarget = (get("pov_target_x", float), get("pov_target_y", float), get("pov_target_z", float))
- self.povOrigin = (get("pov_origin_x", float), get("pov_origin_y", float), get("pov_origin_z", float))
-
- #Loading phrases
- self.loadingPhrase = get("loading_phrase", str, "Let's get this show on the Road_Impress the Crowd_" +
- "Don't forget to strum!_Rock the house!_Jurgen is watching").split("_")
- self.resultsPhrase = get("results_phrase", str, "").split("_")
-
- #crowd_loop_delay controls how long (in milliseconds) FoFiX needs to wait before
- #playing the crowd noise again in the results screen after it finishes
- self.crowdLoopDelay = get("crowd_loop_delay", int)
-
- #When a song starts up it displays the info of the song (artist, name, etc)
- #positioning and the size of the font are handled by these values respectively
- self.songInfoDisplayScale = get("song_info_display_scale", float, 0.0020)
- self.songInfoDisplayX = get("song_info_display_X", float, 0.05)
- self.songInfoDisplayY = get("song_info_display_Y", float, 0.05)
-
- #when AI is enabled, this value controls where in the player's window
- #it should say that "Jurgen is here" and how large the words need to be
- self.jurgTextPos = get("jurgen_text_pos", str, "1,1,.00035").split(",")
-
- #just a little misc option that allows you to change the name of what you
- #what starpower/overdrive to be called. Some enjoy the classic Jurgen Power
- #name from Hering's mod.
- self.power_up_name = get("power_up_name", str, "Jurgen Power")
-
- self.countdownPosX = get("countdown_pos_x", float, 0.5)
- self.countdownPosY = get("countdown_pos_y", float, 0.45)
-
- #These values determine the width of the neck as well as the length of it
- #width seems pretty obvious but length has an advantage in that by making
- #it shorter the fade away comes sooner. This is handy for unique POV because
- #sometimes static hud object (the lyric display) can get in the way.
- self.neckWidth = get("neck_width", float, 3.0)
- self.neckLength = get("neck_length", float, 9.0)
-
- #When in the neck choosing screen, these values determine the position of the
- #prompt that is usually at the top of the screen and says how to choose a neck
- self.neck_prompt_x = get("menu_neck_choose_x", float, 0.1)
- self.neck_prompt_y = get("menu_neck_choose_y", float, 0.05)
-
- #Setlist
- #This is really a bit of a mess but luckily most of the names are quite self
- #explanatory. These values are only necessary if your theme is using the old
- #default code that takes advantage of having the 4 different modes
- #list, cd, list/cd hybrid, rb2
- #if you're not using the default setlist display then don't bother with these values
- self.songListDisplay = get("song_list_display", int, 0)
-
- self.setlistguidebuttonsposX = get("setlistguidebuttonsposX", float, 0.408)
- self.setlistguidebuttonsposY = get("setlistguidebuttonsposY", float, 0.0322)
- self.setlistguidebuttonsscaleX = get("setlistguidebuttonsscaleX", float, 0.29)
- self.setlistguidebuttonsscaleY = get("setlistguidebuttonsscaleY", float, 0.308)
- self.setlistpreviewbuttonposX = get("setlistpreviewbuttonposX", float, 0.5)
- self.setlistpreviewbuttonposY = get("setlistpreviewbuttonposY", float, 0.5)
- self.setlistpreviewbuttonscaleX = get("setlistpreviewbuttonscaleX", float, 0.5)
- self.setlistpreviewbuttonscaleY = get("setlistpreviewbuttonscaleY", float, 0.5)
-
- self.songSelectSubmenuOffsetLines = get("song_select_submenu_offset_lines")
- self.songSelectSubmenuOffsetSpaces = get("song_select_submenu_offset_spaces")
- self.songSelectSubmenuX = get("song_select_submenu_x")
- self.songSelectSubmenuY = get("song_select_submenu_y")
-
- self.song_cd_Xpos = get("song_cd_x", float, 0.0)
- self.song_cdscore_Xpos = get("song_cdscore_x", float, 0.6)
-
- self.song_listcd_cd_Xpos = get("song_listcd_cd_x", float, .75)
- self.song_listcd_cd_Ypos = get("song_listcd_cd_y", float, .6)
- self.song_listcd_score_Xpos = get("song_listcd_score_x", float, .6)
- self.song_listcd_score_Ypos = get("song_listcd_score_y", float, .5)
- self.song_listcd_list_Xpos = get("song_listcd_list_x", float, .1)
-
- self.song_list_Xpos = get("song_list_x", float, 0.15)
- self.song_listscore_Xpos = get("song_listscore_x", float, 0.8)
-
- self.songlist_score_colorVar = get("songlist_score_color", "color", "#93C351")
- self.songlistcd_score_colorVar = get("songlistcd_score_color", "color", "#FFFFFF")
- self.career_title_colorVar = get("career_title_color", "color", "#000000")
- self.song_name_text_colorVar = get("song_name_text_color", "color", "#FFFFFF")
- self.song_name_selected_colorVar = get("song_name_selected_color", "color", "#FFBF00")
- self.artist_text_colorVar = get("artist_text_color", "color", "#4080FF")
- self.artist_selected_colorVar = get("artist_selected_color", "color", "#4080FF")
- self.library_text_colorVar = get("library_text_color", "color", "#FFFFFF")
- self.library_selected_colorVar = get("library_selected_color", "color", "#FFBF00")
- self.song_rb2_diff_colorVar = get("song_rb2_diff_color", "color", "#FFBF00")
-
- #These determine the position of the version tag on the main menu.
- #Usually it's easier to just create a 640x480 picture and position it
- #in that because by default the image is position in the middle of the window
- self.versiontagposX = get("versiontagposX", float, 0.5)
- self.versiontagposY = get("versiontagposY", float, 0.5)
-
- #pause menu and fail menu positions and text colors
- self.pause_bkg_pos = get("pause_bkg", str, "0.5,0.5,1.0,1.0").split(",")
- self.pause_text_xPos = get("pause_text_x", float)
- self.pause_text_yPos = get("pause_text_y", float)
- self.pause_text_colorVar = get("pause_text_color", "color", "#FFFFFF")
- self.pause_selected_colorVar = get("pause_selected_color", "color", "#FFBF00")
-
- self.fail_completed_colorVar = get("fail_completed_color", "color", "#FFFFFF")
- self.fail_text_colorVar = get("fail_text_color", "color", "#FFFFFF")
- self.fail_selected_colorVar = get("fail_selected_color", "color", "#FFBF00")
-
- self.fail_bkg_pos = get("fail_bkg", str, "0.5,0.5,1.0,1.0").split(",")
- self.fail_text_xPos = get("fail_text_x", float)
- self.fail_text_yPos = get("fail_text_y", float)
- self.fail_songname_xPos = get("fail_songname_x", float, 0.5)
- self.fail_songname_yPos = get("fail_songname_y", float, 0.35)
-
- self.opt_bkg_size = get("opt_bkg", str, "0.5,0.5,1.0,1.0").split(",")
- self.opt_text_xPos = get("opt_text_x", float)
- self.opt_text_yPos = get("opt_text_y", float)
- self.opt_text_colorVar = get("opt_text_color", "color", "#FFFFFF")
- self.opt_selected_colorVar = get("opt_selected_color", "color", "#FFBF00")
-
- #main menu system
- self.menuPos = [get("menu_x", float, 0.2), get("menu_y", float, 0.8)]
- self.menuRB = get("rbmenu", bool, False)
- self.main_menu_scaleVar = get("main_menu_scale", float, 0.5)
- self.main_menu_vspacingVar = get("main_menu_vspacing", float, .09)
- self.use_solo_submenu = get("use_solo_submenu", bool, True)
-
- #Settings option scale
- self.settingsmenuScale = get("settings_menu_scale", float, 0.002)
-
- #loading Parameters
- self.loadingX = get("loading_x", float, 0.5)
- self.loadingY = get("loading_y", float, 0.6)
- self.loadingFScale = get("loading_font_scale", float, 0.0015)
- self.loadingRMargin = get("loading_right_margin", float, 1.0)
- self.loadingLSpacing = get("loading_line_spacing", float, 1.0)
- self.loadingColor = get("loading_text_color", "color", "#FFFFFF")
+ #Note Colors (this applies to frets and notes)
+ #default is green, red, yellow, blue, orange, purple (I don't know why there's a 6th color)
+ default_color = ["#22FF22", "#FF2222", "#FFFF22", "#3333FF", "#FF9933", "#CC22CC"]
+ self.noteColors = [get("fret%d_color" % i, "color", default_color[i]) for i in range(6)]
+ self.spNoteColor = get("fretS_color", "color", "#4CB2E5")
+ self.killNoteColor = get("fretK_color", "color", "#FFFFFF")
+
+ #just like glow_color, this allows you to have tails use either the color of the note
+ #or the actual color of the tail
+ self.use_fret_colors = get("use_fret_colors", bool, False)
+
+ self.fretPress = get("fretPress", bool, False)
+
+ #Point of View (x, y, z)
+ self.povTarget = (get("pov_target_x", float), get("pov_target_y", float), get("pov_target_z", float))
+ self.povOrigin = (get("pov_origin_x", float), get("pov_origin_y", float), get("pov_origin_z", float))
+
+ #Fade settings for fretboard
+ self.boardFade = (get("fretboard_fade_amount", float, 1))
- #this is the amount you can offset the shadow in the loading screen text
- self.shadowoffsetx = get("shadowoffsetx", float, .0022)
- self.shadowoffsety = get("shadowoffsety", float, .0005)
-
- self.sub_menu_xVar = get("sub_menu_x", float, None)
- self.sub_menu_yVar = get("sub_menu_y", float, None)
- #self.songback = get("songback")
-
- self.versiontag = get("versiontag", bool, False)
-
- #these are the little help messages at the bottom of the
- #options screen when you hover over an item
- self.menuTipTextY = get("menu_tip_text_y", float, .7)
- self.menuTipTextFont = get("menu_tip_text_font", str, "font")
- self.menuTipTextScale = get("menu_tip_text_scale", float, .002)
- self.menuTipTextColor = get("menu_tip_text_color", "color", "#FFFFFF")
- self.menuTipTextScrollSpace = get("menu_tip_text_scroll_space", float, .25)
- self.menuTipTextScrollMode = get("menu_tip_text_scroll_mode", int, 0)
- self.menuTipTextDisplay = get("menu_tip_text_display", bool, False)
-
- #Lobby
- self.controlActivateX = get("control_activate_x", float, 0.645)
- self.controlActivateSelectX = get("control_activate_select_x", float, 0.5)
- self.controlActivatePartX = get("control_activate_part_x", float, 0.41)
- self.controlActivateY = get("control_activate_y", float, 0.18)
- self.controlActivateScale = get("control_activate_scale", float, 0.0018)
- self.controlActivateSpace = get("control_activate_part_size", float, 22.000)
- self.controlActivatePartSize = get("control_activate_space", float, 0.045)
- self.controlActivateFont = get("control_activate_font", str, "font")
- self.controlDescriptionX = get("control_description_x", float, 0.5)
- self.controlDescriptionY = get("control_description_y", float, 0.13)
- self.controlDescriptionScale = get("control_description_scale", float, 0.002)
- self.controlDescriptionFont = get("control_description_font", str, "font")
- self.controlCheckX = get("control_description_scale", float, 0.002)
- self.controlCheckY = get("control_check_x", float, 0.16)
- self.controlCheckTextY = get("control_check_text_y", float, 0.61)
- self.controlCheckPartMult = get("control_check_part_mult", float, 2.8)
- self.controlCheckScale = get("control_check_space", float, 0.23)
- self.controlCheckSpace = get("control_check_scale", float, 0.0018)
- self.controlCheckFont = get("control_check_font", str, "font")
-
- self.lobbyMode = get("lobby_mode", int, 0)
- self.lobbyPreviewX = get("lobby_preview_x", float, 0.7)
- self.lobbyPreviewY = get("lobby_preview_y", float, 0.0)
- self.lobbyPreviewSpacing = get("lobby_preview_spacing", float, 0.04)
-
- self.lobbyTitleX = get("lobby_title_x", float, 0.5)
- self.lobbyTitleY = get("lobby_title_y", float, 0.07)
- self.lobbyTitleCharacterX = get("lobby_title_character_x", float, 0.26)
- self.lobbyTitleCharacterY = get("lobby_title_character_y", float, 0.24)
- self.lobbyTitleScale = get("lobby_title_scale", float, 0.0024)
- self.lobbyTitleFont = get("lobby_title_font", str, "loadingFont")
-
- self.lobbyAvatarX = get("lobby_avatar_x", float, 0.7)
- self.lobbyAvatarY = get("lobby_avatar_y", float, 0.75)
- self.lobbyAvatarScale = get("lobby_avatar_scale", float, 1.0)
-
- self.lobbySelectX = get("lobby_select_x", float, 0.4)
- self.lobbySelectY = get("lobby_select_y", float, 0.32)
- self.lobbySelectImageX = get("lobby_select_image_x", float, 0.255)
- self.lobbySelectImageY = get("lobby_select_image_y", float, 0.335)
- self.lobbySelectScale = get("lobby_select_scale", float, 0.0018)
- self.lobbySelectSpace = get("lobby_select_space", float, 0.04)
- self.lobbySelectFont = get("lobby_select_font", str, "font")
- self.lobbySelectLength = get("lobby_select_length", int, 5)
-
- self.lobbyTitleColor = get("lobby_title_color", "color", "#FFFFFF")
- self.lobbyInfoColor = get("lobby_info_color", "color", "#FFFFFF")
- self.lobbyFontColor = get("lobby_font_color", "color", "#FFFFFF")
- self.lobbyPlayerColor = get("lobby_player_color", "color", "#FFFFFF")
- self.lobbySelectColor = get("lobby_select_color", "color", "#FFBF00")
- self.lobbyDisableColor = get("lobby_disable_color", "color", "#666666")
-
- self.characterCreateX = get("character_create_x", float, 0.25)
- self.characterCreateY = get("character_create_y", float, 0.15)
- self.characterCreateHelpX = get("character_create_help_x", float, 0.5)
- self.characterCreateHelpY = get("character_create_help_y", float, 0.73)
- self.characterCreateScale = get("character_create_scale", float, 0.0018)
- self.characterCreateSpace = get("character_create_space", float, 0.045)
- self.characterCreateHelpScale = get("character_create_help_scale", float, 0.0018)
- self.characterCreateOptionX = get("character_create_option_x", float, 0.75)
-
- self.characterCreateOptionFont = get("character_create_option_font", str, "font")
- self.characterCreateHelpFont = get("character_create_help_font", str, "loadingFont")
-
- self.characterCreateFontColor = get("character_create_font_color", "color", "#FFFFFF")
- self.characterCreateSelectColor = get("character_create_select_color", "color", "#FFBF00")
- self.characterCreateHelpColor = get("character_create_help_color", "color", "#FFFFFF")
-
- self.avatarSelectTextX = get("avatar_select_text_x", float, 0.44)
- self.avatarSelectTextY = get("avatar_select_text_y", float, 0.16)
- self.avatarSelectTextScale = get("avatar_select_text_scale", float, 0.0027)
-
- self.avatarSelectAvX = get("avatar_select_avatar_x", float, 0.667)
- self.avatarSelectAvY = get("avatar_select_avatar_y", float, 0.5)
-
- self.avatarSelectWheelY = get("avatar_select_wheel_y", float, 0.0)
-
- self.avatarSelectFont = get("avatar_select_font", str, "font")
-
- self.lobbyPanelAvatarDimension = (get("lobbyPanelAvatarWidth", float, 200.00),
- get("lobbyPanelAvatarHeight", float, 110.00))
- self.lobbyTitleText = get("lobbyTitleText", str, "Lobby")
- self.lobbyTitleTextPos = (get("lobbyTitleTextX", str, .5),
- get("lobbyTitleTextY", float, .1))
- self.lobbyTitleTextAlign = eval(get("lobbyTitleTextAlign", str, "CENTER"))
- self.lobbyTitleTextScale = get("lobbyTitleTextScale", float, .0025)
- self.lobbyTitleTextFont = get("lobbyTitleTextFont", str, "font")
-
- self.lobbySubtitleText = get("lobbySubtitleText", str, "Choose Your Character!")
- self.lobbySubtitleTextPos = (get("lobbySubtitleTextX", float, .5),
- get("lobbySubtitleTextY", float, .15))
- self.lobbySubtitleTextScale = get("lobbySubtitleTextScale", float, .0015)
- self.lobbySubtitleTextFont = get("lobbySubtitleTextFont", str, "font")
- self.lobbySubtitleTextAlign = eval(get("lobbySubtitleTextAlign", str, "LEFT"))
-
- self.lobbyOptionScale = get("lobbyOptionScale", float, .001)
- self.lobbyOptionAlign = eval(get("lobbyOptionAlign", str, "CENTER"))
- self.lobbyOptionFont = get("lobbyOptionFont", str, "font")
- self.lobbyOptionPos = (get("lobbyOptionX", float, .5),
- get("lobbyOptionY", float, .46))
- self.lobbyOptionSpace = get("lobbyOptionSpace", float, .04)
- self.lobbyOptionColor = get("lobbyOptionColor", "color", "#FFFFFF")
-
- self.lobbySaveCharScale = get("lobbySaveCharScale", float, .001)
- self.lobbySaveCharAlign = eval(get("lobbySaveCharAlign", str, "CENTER"))
- self.lobbySaveCharFont = get("lobbySaveCharFont", str, "font")
- self.lobbySaveCharColor = get("lobbySaveCharColor", "color", "#FFFFFF")
-
- self.lobbyGameModePos = (get("lobbyGameModeX", float, .985),
- get("lobbyGameModeY", float, .03))
- self.lobbyGameModeScale = get("lobbyGameModeScale", float, .001)
- self.lobbyGameModeAlign = eval(get("lobbyGameModeAlign", str, "RIGHT"))
- self.lobbyGameModeFont = get("lobbyGameModeFont", str, "font")
- self.lobbyGameModeColor = get("lobbyGameModeColor", "color", "#FFFFFF")
-
- self.lobbyPanelNamePos = (get("lobbyPanelNameX", float, 0.0),
- get("lobbyPanelNameY", float, 0.0))
- self.lobbyPanelNameFont = get("lobbyPanelNameFont", str, "font")
- self.lobbyPanelNameScale = get("lobbyPanelNameScale", float, .001)
- self.lobbyPanelNameAlign = eval(get("lobbyPanelNameAlign", str, "LEFT"))
- self.lobbyControlPos = (get("lobbyControlX", float, .5),
- get("lobbyControlY", float, .375))
- self.lobbyControlFont = get("lobbyControlFont", str, "font")
- self.lobbyControlScale = get("lobbyControlScale", float, .0025)
- self.lobbyControlAlign = eval(get("lobbyControlAlign", str, "CENTER"))
- self.lobbyHeaderColor = get("lobbyHeaderColor", "color", "#FFFFFF")
- self.lobbySelectLength = get("lobbySelectLength", int, 4)
-
- self.lobbyPartScale = get("lobbyPartScale", float, .25)
- self.lobbyPartPos = (get("lobbyPartX", float, .5),
- get("lobbyPartY", float, .52))
- self.lobbyControlImgScale = get("lobbyControlImgScale", float, .25)
- self.lobbyControlImgPos = (get("lobbyControlImgX", float, .5),
- get("lobbyControlImgY", float, .55))
-
- self.lobbyKeyboardImgScale = get("lobbyKeyboardImgScale", float, .1)
- self.lobbyKeyboardImgPos = (get("lobbyKeyboardImgX", float, .8),
- get("lobbyKeyboardImgY", float, .95))
- self.lobbySelectedColor = get("lobbySelectedColor", "color", "#FFFF66")
- self.lobbyDisabledColor = get("lobbyDisabledColor", "color", "#BBBBBB")
- self.lobbyPanelSize = (get("lobbyPanelWidth", float, .2),
- get("lobbyPanelHeight", float, .8))
- self.lobbyPanelPos = (get("lobbyPanelX", float, .04),
- get("lobbyPanelY", float, .1))
- self.lobbyPanelSpacing = get("lobbyPanelSpacing", float, .24)
-
- self.partDiffTitleText = get("partDiffTitleText", str, "Select a Part and Difficulty")
- self.partDiffTitleTextPos = (get("partDiffTitleTextX", float, .5),
- get("partDiffTitleTextY", float, .1))
- self.partDiffTitleTextAlign = eval(get("partDiffTitleTextAlign", str, "CENTER"))
- self.partDiffTitleTextScale = get("partDiffTitleTextScale", float, .0025)
- self.partDiffTitleTextFont = get("partDiffTitleTextFont", str, "font")
-
- self.partDiffSubtitleText = get("partDiffSubtitleText", str, "Ready to Play!")
- self.partDiffSubtitleTextPos = (get("partDiffSubtitleX", float, .5),
- get("partDiffSubtitleY", float, .15))
- self.partDiffSubtitleTextAlign = eval(get("partDiffSubtitleTextAlign", str, "CENTER"))
- self.partDiffSubtitleTextScale = get("partDiffSubtitleTextScale", float, .0015)
- self.partDiffSubtitleTextFont = get("partDiffSubtitleTextFont", str, "font")
-
- self.partDiffOptionScale = get("partDiffOptionScale", float, .001)
- self.partDiffOptionAlign = eval(get("partDiffOptionAlign", str, "CENTER"))
- self.partDiffOptionFont = get("partDiffOptionFont", str, "font")
- self.partDiffOptionPos = (get("partDiffOptionX", float, .5),
- get("partDiffOptionY", float, .46))
- self.partDiffOptionSpace = get("partDiffOptionScale", float, .04)
- self.partDiffOptionColor = get("partDiffOptionColor", "color", "#FFFFFF")
- self.partDiffSelectedColor = get("partDiffSelectedColor", "color", "#FFFF66")
-
- self.partDiffGameModeScale = get("partDiffGameModeScale", float, .001)
- self.partDiffGameModeAlign = eval(get("partDiffGameModeAlign", str, "RIGHT"))
- self.partDiffGameModeFont = get("partDiffGameModeFont", str, "font")
- self.partDiffGameModePos = (get("partDiffGameModeX", float, .985),
- get("partDiffGameModeY", float, .03))
- self.partDiffGameModeColor = get("partDiffGameModeColor", "color", "#FFFFFF")
-
- self.partDiffPanelNameScale = get("partDiffPanelNameScale", float, .001)
- self.partDiffPanelNameAlign = eval(get("partDiffPanelNameAlign", str, "LEFT"))
- self.partDiffPanelNameFont = get("partDiffPanelNameFont", str, "font")
- self.partDiffPanelNamePos = (get("partDiffPanelNameX", float, 0.0),
- get("partDiffPanelNameY", float, 0.0))
-
- self.partDiffControlScale = get("partDiffControlScale", float, .0025)
- self.partDiffControlAlign = eval(get("partDiffControlAlign", str, "CENTER"))
- self.partDiffControlFont = get("partDiffControlFont", str, "font")
- self.partDiffControlPos = (get("partDiffControlX", float, .5),
- get("partDiffControlY", float, .375))
- self.partDiffHeaderColor = get("partDiffHeaderColor", "color", "#FFFFFF")
-
- self.partDiffPartScale = get("partDiffPartScale", float, .5)
- self.partDiffPartPos = (get("partDiffPartX", float, .5),
- get("partDiffpartY", float, .65))
-
- self.partDiffKeyboardImgScale = get("partDiffKeyboardImgScale", float, .5)
- self.partDiffKeyboardImgPos = (get("partDiffKeyboardImgX", float, .8),
- get("partDiffKeyboardImgY", float, .95))
-
- self.partDiffPanelSpacing = get("partDiffPanelSpacing", float, .24)
- self.partDiffPanelPos = (get("partDiffPanelX", float, .8),
- get("partDiffPanelY", float, .95))
- self.partDiffPanelSize = (get("partDiffPanelWidth", float, .2),
- get("partDiffPanelHeight", float, .8))
-
- #Vocal mode
- self.vocalMeterSize = get("vocal_meter_size", float, 45.000)
- self.vocalMeterX = get("vocal_meter_x", float, .25)
- self.vocalMeterY = get("vocal_meter_y", float, .8)
- self.vocalMultX = get("vocal_mult_x", float, .28)
- self.vocalMultY = get("vocal_mult_y", float, .8)
-
- self.vocalPowerX = get("vocal_power_x", float, .5)
- self.vocalPowerY = get("vocal_power_y", float, .8)
-
- self.vocalFillupCenterX = get("vocal_fillup_center_x", int, 139)
- self.vocalFillupCenterY = get("vocal_fillup_center_y", int, 151)
- self.vocalFillupInRadius = get("vocal_fillup_in_radius", int, 25)
- self.vocalFillupOutRadius = get("vocal_fillup_out_radius", int, 139)
- self.vocalFillupFactor = get("vocal_fillup_factor", float, 300.000)
- self.vocalFillupColor = get("vocal_fillup_color", "color", "#DFDFDE")
- self.vocalCircularFillup = get("vocal_circular_fillup", bool, True)
-
- self.vocalLaneSize = get("vocal_lane_size", float, .002)
- self.vocalGlowSize = get("vocal_glow_size", float, .012)
- self.vocalGlowFade = get("vocal_glow_fade", float, .6)
-
- self.vocalLaneColor = get("vocal_lane_color", "color", "#99FF80")
- self.vocalShadowColor = get("vocal_shadow_color", "color", "#CCFFBF")
- self.vocalGlowColor = get("vocal_glow_color", "color", "#33FF00")
- self.vocalLaneColorStar = get("vocal_lane_color_star", "color", "#FFFF80")
- self.vocalShadowColorStar = get("vocal_shadow_color_star", "color", "#FFFFBF")
- self.vocalGlowColorStar = get("vocal_glow_color_star", "color", "#FFFF00")
-
- #3D Note/Fret rendering system
- self.twoDnote = get("twoDnote", bool, True)
- self.twoDkeys = get("twoDkeys", bool, True)
-
- self.threeDspin = get("threeDspin", bool, False)
-
- self.noterot = [get("noterot"+unicode(i+1), float, 0) for i in range(5)]
- self.keyrot = [get("keyrot"+unicode(i+1), float, 0) for i in range(5)]
- self.drumnoterot = [get("drumnoterot"+unicode(i+1), float, 0) for i in range(5)]
- self.drumkeyrot = [get("drumkeyrot"+unicode(i+1), float, 0) for i in range(5)]
- self.notepos = [get("notepos"+unicode(i+1), float, 0) for i in range(5)]
- self.keypos = [get("keypos"+unicode(i+1), float, 0) for i in range(5)]
- self.drumnotepos = [get("drumnotepos"+unicode(i+1), float, 0) for i in range(5)]
- self.drumkeypos = [get("drumkeypos"+unicode(i+1), float, 0) for i in range(5)]
-
- self.shaderSolocolor = get("shaderSoloColor", "color", "#0000FF")
-
- #In-game rendering
- self.hopoIndicatorX = get("hopo_indicator_x")
- self.hopoIndicatorY = get("hopo_indicator_y")
- self.hopoIndicatorActiveColor = get("hopo_indicator_active_color", "color", "#FFFFFF")
- self.hopoIndicatorInactiveColor = get("hopo_indicator_inactive_color", "color", "#666666")
- self.markSolos = get("mark_solo_sections", int, 2)
- self.ingame_stats_colorVar = get("ingame_stats_color", "color", "#FFFFFF")
-
- #Game results scene
- self.result_score = get("result_score", str, ".5,.11,0.0025,None,None").split(",")
- self.result_star = get("result_star", str, ".5,.4,0.15,1.1").split(",")
- self.result_song = get("result_song", str, ".05,.045,.002,None,None").split(",")
- self.result_song_form = get("result_song_form", int, 0)
- self.result_song_text = get("result_song_text", str, "%s Finished!").strip()
- self.result_stats_part = get("result_stats_part", str, ".5,.64,0.002,None,None").split(",")
- self.result_stats_part_text = get("result_stats_part_text", str, "Part: %s").strip()
- self.result_stats_name = get("result_stats_name", str, ".5,.73,0.002,None,None").split(",")
- self.result_stats_diff = get("result_stats_diff", str, ".5,.55,0.002,None,None").split(",")
- self.result_stats_diff_text = get("result_stats_diff_text", str, "Difficulty: %s").strip()
- self.result_stats_accuracy = get("result_stats_accuracy", str, ".5,.61,0.002,None,None").split(",")
- self.result_stats_accuracy_text = get("result_stats_accuracy_text", str, "Accuracy: %.1f%%").strip()
- self.result_stats_streak = get("result_stats_streak", str, ".5,.58,0.002,None,None").split(",")
- self.result_stats_streak_text = get("result_stats_streak_text", str, "Long Streak: %s").strip()
- self.result_stats_notes = get("result_stats_notes", str, ".5,.52,0.002,None,None").split(",")
- self.result_stats_notes_text = get("result_stats_notes_text", str, "%s Notes Hit").strip()
- self.result_cheats_info = get("result_cheats_info", str, ".5,.3,.002").split(",")
- self.result_cheats_numbers = get("result_cheats_numbers", str, ".5,.35,.0015").split(",")
- self.result_cheats_percent = get("result_cheats_percent", str, ".45,.4,.0015").split(",")
- self.result_cheats_score = get("result_cheats_score", str, ".75,.4,.0015").split(",")
- self.result_cheats_color = get("result_cheats_color", "color", "#FFFFFF")
- self.result_cheats_font = get("result_cheats_font", str, "font")
- self.result_high_score_font = get("result_high_score_font", str, "font")
- self.result_menu_x = get("result_menu_x", float, .5)
- self.result_menu_y = get("result_menu_y", float, .2)
- self.result_star_type = get("result_star_type", int, 0)
-
- #Submenus
- allfiles = os.listdir(os.path.join(self.themePath,"menu"))
- self.submenuScale = {}
- self.submenuX = {}
- self.submenuY = {}
- self.submenuVSpace = {}
- listmenu = []
- for name in allfiles:
- if name.find("text") > -1:
- found = os.path.splitext(name)[0]
- if found == "maintext":
- continue
- Config.define("theme", found, str, None)
- self.submenuScale[found] = None
- self.submenuX[found] = None
- self.submenuY[found] = None
- self.submenuVSpace[found] = None
- listmenu.append(found)
- for i in listmenu:
- if i == "maintext":
+ #Loading phrases
+ self.loadingPhrase = get("loading_phrase", str, "Let's get this show on the Road_Impress the Crowd_" +
+ "Don't forget to strum!_Rock the house!_Jurgen is watching").split("_")
+ self.resultsPhrase = get("results_phrase", str, "").split("_")
+
+ #crowd_loop_delay controls how long (in milliseconds) FoFiX needs to wait before
+ #playing the crowd noise again in the results screen after it finishes
+ self.crowdLoopDelay = get("crowd_loop_delay", int)
+
+ #When a song starts up it displays the info of the song (artist, name, etc)
+ #positioning and the size of the font are handled by these values respectively
+ self.songInfoDisplayScale = get("song_info_display_scale", float, 0.0020)
+ self.songInfoDisplayX = get("song_info_display_X", float, 0.05)
+ self.songInfoDisplayY = get("song_info_display_Y", float, 0.05)
+
+ #when AI is enabled, this value controls where in the player's window
+ #it should say that "Jurgen is here" and how large the words need to be
+ self.jurgTextPos = get("jurgen_text_pos", str, "1,1,.00035").split(",")
+
+ #just a little misc option that allows you to change the name of what you
+ #what starpower/overdrive to be called. Some enjoy the classic Jurgen Power
+ #name from Hering's mod.
+ self.power_up_name = get("power_up_name", str, "Jurgen Power")
+
+ self.countdownPosX = get("countdown_pos_x", float, 0.5)
+ self.countdownPosY = get("countdown_pos_y", float, 0.45)
+
+ #These values determine the width of the neck as well as the length of it
+ #width seems pretty obvious but length has an advantage in that by making
+ #it shorter the fade away comes sooner. This is handy for unique POV because
+ #sometimes static hud object (the lyric display) can get in the way.
+ self.neckWidth = get("neck_width", float, 3.0)
+ self.neckLength = get("neck_length", float, 9.0)
+
+ #When in the neck choosing screen, these values determine the position of the
+ #prompt that is usually at the top of the screen and says how to choose a neck
+ self.neck_prompt_x = get("menu_neck_choose_x", float, 0.1)
+ self.neck_prompt_y = get("menu_neck_choose_y", float, 0.05)
+
+ #Setlist
+ #This is really a bit of a mess but luckily most of the names are quite self
+ #explanatory. These values are only necessary if your theme is using the old
+ #default code that takes advantage of having the 4 different modes
+ #list, cd, list/cd hybrid, rb2
+ #if you're not using the default setlist display then don't bother with these values
+ self.songListDisplay = get("song_list_display", int, 0)
+
+ self.setlistguidebuttonsposX = get("setlistguidebuttonsposX", float, 0.408)
+ self.setlistguidebuttonsposY = get("setlistguidebuttonsposY", float, 0.0322)
+ self.setlistguidebuttonsscaleX = get("setlistguidebuttonsscaleX", float, 0.29)
+ self.setlistguidebuttonsscaleY = get("setlistguidebuttonsscaleY", float, 0.308)
+ self.setlistpreviewbuttonposX = get("setlistpreviewbuttonposX", float, 0.5)
+ self.setlistpreviewbuttonposY = get("setlistpreviewbuttonposY", float, 0.5)
+ self.setlistpreviewbuttonscaleX = get("setlistpreviewbuttonscaleX", float, 0.5)
+ self.setlistpreviewbuttonscaleY = get("setlistpreviewbuttonscaleY", float, 0.5)
+
+ self.songSelectSubmenuOffsetLines = get("song_select_submenu_offset_lines")
+ self.songSelectSubmenuOffsetSpaces = get("song_select_submenu_offset_spaces")
+ self.songSelectSubmenuX = get("song_select_submenu_x")
+ self.songSelectSubmenuY = get("song_select_submenu_y")
+
+ self.song_cd_Xpos = get("song_cd_x", float, 0.0)
+ self.song_cdscore_Xpos = get("song_cdscore_x", float, 0.6)
+
+ self.song_listcd_cd_Xpos = get("song_listcd_cd_x", float, .75)
+ self.song_listcd_cd_Ypos = get("song_listcd_cd_y", float, .6)
+ self.song_listcd_score_Xpos = get("song_listcd_score_x", float, .6)
+ self.song_listcd_score_Ypos = get("song_listcd_score_y", float, .5)
+ self.song_listcd_list_Xpos = get("song_listcd_list_x", float, .1)
+
+ self.song_list_Xpos = get("song_list_x", float, 0.15)
+ self.song_listscore_Xpos = get("song_listscore_x", float, 0.8)
+
+ self.songlist_score_colorVar = get("songlist_score_color", "color", "#93C351")
+ self.songlistcd_score_colorVar = get("songlistcd_score_color", "color", "#FFFFFF")
+ self.career_title_colorVar = get("career_title_color", "color", "#000000")
+ self.song_name_text_colorVar = get("song_name_text_color", "color", "#FFFFFF")
+ self.song_name_selected_colorVar = get("song_name_selected_color", "color", "#FFBF00")
+ self.artist_text_colorVar = get("artist_text_color", "color", "#4080FF")
+ self.artist_selected_colorVar = get("artist_selected_color", "color", "#4080FF")
+ self.library_text_colorVar = get("library_text_color", "color", "#FFFFFF")
+ self.library_selected_colorVar = get("library_selected_color", "color", "#FFBF00")
+ self.song_rb2_diff_colorVar = get("song_rb2_diff_color", "color", "#FFBF00")
+
+ #These determine the position of the version tag on the main menu.
+ #Usually it's easier to just create a 640x480 picture and position it
+ #in that because by default the image is position in the middle of the window
+ self.versiontagposX = get("versiontagposX", float, 0.5)
+ self.versiontagposY = get("versiontagposY", float, 0.5)
+
+ #pause menu and fail menu positions and text colors
+ self.pause_bkg_pos = get("pause_bkg", str, "0.5,0.5,1.0,1.0").split(",")
+ self.pause_text_xPos = get("pause_text_x", float)
+ self.pause_text_yPos = get("pause_text_y", float)
+ self.pause_text_colorVar = get("pause_text_color", "color", "#FFFFFF")
+ self.pause_selected_colorVar = get("pause_selected_color", "color", "#FFBF00")
+
+ self.fail_completed_colorVar = get("fail_completed_color", "color", "#FFFFFF")
+ self.fail_text_colorVar = get("fail_text_color", "color", "#FFFFFF")
+ self.fail_selected_colorVar = get("fail_selected_color", "color", "#FFBF00")
+
+ self.fail_bkg_pos = get("fail_bkg", str, "0.5,0.5,1.0,1.0").split(",")
+ self.fail_text_xPos = get("fail_text_x", float)
+ self.fail_text_yPos = get("fail_text_y", float)
+ self.fail_songname_xPos = get("fail_songname_x", float, 0.5)
+ self.fail_songname_yPos = get("fail_songname_y", float, 0.35)
+
+ self.opt_bkg_size = get("opt_bkg", str, "0.5,0.5,1.0,1.0").split(",")
+ self.opt_text_xPos = get("opt_text_x", float)
+ self.opt_text_yPos = get("opt_text_y", float)
+ self.opt_text_colorVar = get("opt_text_color", "color", "#FFFFFF")
+ self.opt_selected_colorVar = get("opt_selected_color", "color", "#FFBF00")
+
+ #main menu system
+ self.menuPos = [get("menu_x", float, 0.2), get("menu_y", float, 0.8)]
+ self.menuRB = get("rbmenu", bool, False)
+ self.main_menu_scaleVar = get("main_menu_scale", float, 0.5)
+ self.main_menu_vspacingVar = get("main_menu_vspacing", float, .09)
+ self.use_solo_submenu = get("use_solo_submenu", bool, True)
+
+ #Settings option scale
+ self.settingsmenuScale = get("settings_menu_scale", float, 0.002)
+
+ #loading Parameters
+ self.loadingX = get("loading_x", float, 0.5)
+ self.loadingY = get("loading_y", float, 0.6)
+ self.loadingFScale = get("loading_font_scale", float, 0.0015)
+ self.loadingRMargin = get("loading_right_margin", float, 1.0)
+ self.loadingLSpacing = get("loading_line_spacing", float, 1.0)
+ self.loadingColor = get("loading_text_color", "color", "#FFFFFF")
+
+ #this is the amount you can offset the shadow in the loading screen text
+ self.shadowoffsetx = get("shadowoffsetx", float, .0022)
+ self.shadowoffsety = get("shadowoffsety", float, .0005)
+
+ self.sub_menu_xVar = get("sub_menu_x", float, None)
+ self.sub_menu_yVar = get("sub_menu_y", float, None)
+ #self.songback = get("songback")
+
+ self.versiontag = get("versiontag", bool, False)
+
+ #these are the little help messages at the bottom of the
+ #options screen when you hover over an item
+ self.menuTipTextY = get("menu_tip_text_y", float, .7)
+ self.menuTipTextFont = get("menu_tip_text_font", str, "font")
+ self.menuTipTextScale = get("menu_tip_text_scale", float, .002)
+ self.menuTipTextColor = get("menu_tip_text_color", "color", "#FFFFFF")
+ self.menuTipTextScrollSpace = get("menu_tip_text_scroll_space", float, .25)
+ self.menuTipTextScrollMode = get("menu_tip_text_scroll_mode", int, 0)
+ self.menuTipTextDisplay = get("menu_tip_text_display", bool, False)
+
+ #Lobby
+ self.controlActivateX = get("control_activate_x", float, 0.645)
+ self.controlActivateSelectX = get("control_activate_select_x", float, 0.5)
+ self.controlActivatePartX = get("control_activate_part_x", float, 0.41)
+ self.controlActivateY = get("control_activate_y", float, 0.18)
+ self.controlActivateScale = get("control_activate_scale", float, 0.0018)
+ self.controlActivateSpace = get("control_activate_part_size", float, 22.000)
+ self.controlActivatePartSize = get("control_activate_space", float, 0.045)
+ self.controlActivateFont = get("control_activate_font", str, "font")
+ self.controlDescriptionX = get("control_description_x", float, 0.5)
+ self.controlDescriptionY = get("control_description_y", float, 0.13)
+ self.controlDescriptionScale = get("control_description_scale", float, 0.002)
+ self.controlDescriptionFont = get("control_description_font", str, "font")
+ self.controlCheckX = get("control_description_scale", float, 0.002)
+ self.controlCheckY = get("control_check_x", float, 0.16)
+ self.controlCheckTextY = get("control_check_text_y", float, 0.61)
+ self.controlCheckPartMult = get("control_check_part_mult", float, 2.8)
+ self.controlCheckScale = get("control_check_space", float, 0.23)
+ self.controlCheckSpace = get("control_check_scale", float, 0.0018)
+ self.controlCheckFont = get("control_check_font", str, "font")
+
+ self.lobbyMode = get("lobby_mode", int, 0)
+ self.lobbyPreviewX = get("lobby_preview_x", float, 0.7)
+ self.lobbyPreviewY = get("lobby_preview_y", float, 0.0)
+ self.lobbyPreviewSpacing = get("lobby_preview_spacing", float, 0.04)
+
+ self.lobbyTitleX = get("lobby_title_x", float, 0.5)
+ self.lobbyTitleY = get("lobby_title_y", float, 0.07)
+ self.lobbyTitleCharacterX = get("lobby_title_character_x", float, 0.26)
+ self.lobbyTitleCharacterY = get("lobby_title_character_y", float, 0.24)
+ self.lobbyTitleScale = get("lobby_title_scale", float, 0.0024)
+ self.lobbyTitleFont = get("lobby_title_font", str, "loadingFont")
+
+ self.lobbyAvatarX = get("lobby_avatar_x", float, 0.7)
+ self.lobbyAvatarY = get("lobby_avatar_y", float, 0.75)
+ self.lobbyAvatarScale = get("lobby_avatar_scale", float, 1.0)
+
+ self.lobbySelectX = get("lobby_select_x", float, 0.4)
+ self.lobbySelectY = get("lobby_select_y", float, 0.32)
+ self.lobbySelectImageX = get("lobby_select_image_x", float, 0.255)
+ self.lobbySelectImageY = get("lobby_select_image_y", float, 0.335)
+ self.lobbySelectScale = get("lobby_select_scale", float, 0.0018)
+ self.lobbySelectSpace = get("lobby_select_space", float, 0.04)
+ self.lobbySelectFont = get("lobby_select_font", str, "font")
+ self.lobbySelectLength = get("lobby_select_length", int, 5)
+
+ self.lobbyTitleColor = get("lobby_title_color", "color", "#FFFFFF")
+ self.lobbyInfoColor = get("lobby_info_color", "color", "#FFFFFF")
+ self.lobbyFontColor = get("lobby_font_color", "color", "#FFFFFF")
+ self.lobbyPlayerColor = get("lobby_player_color", "color", "#FFFFFF")
+ self.lobbySelectColor = get("lobby_select_color", "color", "#FFBF00")
+ self.lobbyDisableColor = get("lobby_disable_color", "color", "#666666")
+
+ self.characterCreateX = get("character_create_x", float, 0.25)
+ self.characterCreateY = get("character_create_y", float, 0.15)
+ self.characterCreateHelpX = get("character_create_help_x", float, 0.5)
+ self.characterCreateHelpY = get("character_create_help_y", float, 0.73)
+ self.characterCreateScale = get("character_create_scale", float, 0.0018)
+ self.characterCreateSpace = get("character_create_space", float, 0.045)
+ self.characterCreateHelpScale = get("character_create_help_scale", float, 0.0018)
+ self.characterCreateOptionX = get("character_create_option_x", float, 0.75)
+
+ self.characterCreateOptionFont = get("character_create_option_font", str, "font")
+ self.characterCreateHelpFont = get("character_create_help_font", str, "loadingFont")
+
+ self.characterCreateFontColor = get("character_create_font_color", "color", "#FFFFFF")
+ self.characterCreateSelectColor = get("character_create_select_color", "color", "#FFBF00")
+ self.characterCreateHelpColor = get("character_create_help_color", "color", "#FFFFFF")
+
+ self.avatarSelectTextX = get("avatar_select_text_x", float, 0.44)
+ self.avatarSelectTextY = get("avatar_select_text_y", float, 0.16)
+ self.avatarSelectTextScale = get("avatar_select_text_scale", float, 0.0027)
+
+ self.avatarSelectAvX = get("avatar_select_avatar_x", float, 0.667)
+ self.avatarSelectAvY = get("avatar_select_avatar_y", float, 0.5)
+
+ self.avatarSelectWheelY = get("avatar_select_wheel_y", float, 0.0)
+
+ self.avatarSelectFont = get("avatar_select_font", str, "font")
+
+ self.lobbyPanelAvatarDimension = (get("lobbyPanelAvatarWidth", float, 200.00),
+ get("lobbyPanelAvatarHeight", float, 110.00))
+ self.lobbyTitleText = get("lobbyTitleText", str, "Lobby")
+ self.lobbyTitleTextPos = (get("lobbyTitleTextX", str, 0.3),
+ get("lobbyTitleTextY", float, 0.015))
+ self.lobbyTitleTextAlign = eval(get("lobbyTitleTextAlign", str, "CENTER"))
+ self.lobbyTitleTextScale = get("lobbyTitleTextScale", float, .001)
+ self.lobbyTitleTextFont = get("lobbyTitleTextFont", str, "font")
+
+ self.lobbySubtitleText = get("lobbySubtitleText", str, "Choose Your Character!")
+ self.lobbySubtitleTextPos = (get("lobbySubtitleTextX", float, 0.5),
+ get("lobbySubtitleTextY", float, 0.015))
+ self.lobbySubtitleTextScale = get("lobbySubtitleTextScale", float, .0015)
+ self.lobbySubtitleTextFont = get("lobbySubtitleTextFont", str, "font")
+ self.lobbySubtitleTextAlign = eval(get("lobbySubtitleTextAlign", str, "CENTER"))
+
+ self.lobbyOptionScale = get("lobbyOptionScale", float, .001)
+ self.lobbyOptionAlign = eval(get("lobbyOptionAlign", str, "CENTER"))
+ self.lobbyOptionFont = get("lobbyOptionFont", str, "font")
+ self.lobbyOptionPos = (get("lobbyOptionX", float, .5),
+ get("lobbyOptionY", float, .46))
+ self.lobbyOptionSpace = get("lobbyOptionSpace", float, .04)
+ self.lobbyOptionColor = get("lobbyOptionColor", "color", "#FFFFFF")
+
+ self.lobbySaveCharScale = get("lobbySaveCharScale", float, .001)
+ self.lobbySaveCharAlign = eval(get("lobbySaveCharAlign", str, "CENTER"))
+ self.lobbySaveCharFont = get("lobbySaveCharFont", str, "font")
+ self.lobbySaveCharColor = get("lobbySaveCharColor", "color", "#FFFFFF")
+
+ self.lobbyGameModePos = (get("lobbyGameModeX", float, 0.7),
+ get("lobbyGameModeY", float, 0.015))
+ self.lobbyGameModeScale = get("lobbyGameModeScale", float, .001)
+ self.lobbyGameModeAlign = eval(get("lobbyGameModeAlign", str, "CENTER"))
+ self.lobbyGameModeFont = get("lobbyGameModeFont", str, "font")
+ self.lobbyGameModeColor = get("lobbyGameModeColor", "color", "#FFFFFF")
+
+ self.lobbyPanelNamePos = (get("lobbyPanelNameX", float, 0.0),
+ get("lobbyPanelNameY", float, 0.0))
+ self.lobbyPanelNameFont = get("lobbyPanelNameFont", str, "font")
+ self.lobbyPanelNameScale = get("lobbyPanelNameScale", float, .001)
+ self.lobbyPanelNameAlign = eval(get("lobbyPanelNameAlign", str, "LEFT"))
+ self.lobbyControlPos = (get("lobbyControlX", float, .5),
+ get("lobbyControlY", float, .375))
+ self.lobbyControlFont = get("lobbyControlFont", str, "font")
+ self.lobbyControlScale = get("lobbyControlScale", float, .0025)
+ self.lobbyControlAlign = eval(get("lobbyControlAlign", str, "CENTER"))
+ self.lobbyHeaderColor = get("lobbyHeaderColor", "color", "#FFFFFF")
+ self.lobbySelectLength = get("lobbySelectLength", int, 4)
+
+ self.lobbyPartScale = get("lobbyPartScale", float, .25)
+ self.lobbyPartPos = (get("lobbyPartX", float, .5),
+ get("lobbyPartY", float, .52))
+ self.lobbyControlImgScale = get("lobbyControlImgScale", float, .25)
+ self.lobbyControlImgPos = (get("lobbyControlImgX", float, .5),
+ get("lobbyControlImgY", float, .55))
+
+ self.lobbyKeyboardImgScale = get("lobbyKeyboardImgScale", float, .1)
+ self.lobbyKeyboardImgPos = (get("lobbyKeyboardImgX", float, .8),
+ get("lobbyKeyboardImgY", float, .95))
+ self.lobbySelectedColor = get("lobbySelectedColor", "color", "#FFFF66")
+ self.lobbyDisabledColor = get("lobbyDisabledColor", "color", "#BBBBBB")
+ self.lobbyPanelSize = (get("lobbyPanelWidth", float, .2),
+ get("lobbyPanelHeight", float, .8))
+ self.lobbyPanelPos = (get("lobbyPanelX", float, .04),
+ get("lobbyPanelY", float, .1))
+ self.lobbyPanelSpacing = get("lobbyPanelSpacing", float, .24)
+
+ self.partDiffTitleText = get("partDiffTitleText", str, "Select a Part and Difficulty")
+ self.partDiffTitleTextPos = (get("partDiffTitleTextX", float, .5),
+ get("partDiffTitleTextY", float, .1))
+ self.partDiffTitleTextAlign = eval(get("partDiffTitleTextAlign", str, "CENTER"))
+ self.partDiffTitleTextScale = get("partDiffTitleTextScale", float, .0025)
+ self.partDiffTitleTextFont = get("partDiffTitleTextFont", str, "font")
+
+ self.partDiffSubtitleText = get("partDiffSubtitleText", str, "Ready to Play!")
+ self.partDiffSubtitleTextPos = (get("partDiffSubtitleX", float, .5),
+ get("partDiffSubtitleY", float, .15))
+ self.partDiffSubtitleTextAlign = eval(get("partDiffSubtitleTextAlign", str, "CENTER"))
+ self.partDiffSubtitleTextScale = get("partDiffSubtitleTextScale", float, .0015)
+ self.partDiffSubtitleTextFont = get("partDiffSubtitleTextFont", str, "font")
+
+ self.partDiffOptionScale = get("partDiffOptionScale", float, .001)
+ self.partDiffOptionAlign = eval(get("partDiffOptionAlign", str, "CENTER"))
+ self.partDiffOptionFont = get("partDiffOptionFont", str, "font")
+ self.partDiffOptionPos = (get("partDiffOptionX", float, .5),
+ get("partDiffOptionY", float, .46))
+ self.partDiffOptionSpace = get("partDiffOptionScale", float, .04)
+ self.partDiffOptionColor = get("partDiffOptionColor", "color", "#FFFFFF")
+ self.partDiffSelectedColor = get("partDiffSelectedColor", "color", "#FFFF66")
+
+ self.partDiffGameModeScale = get("partDiffGameModeScale", float, .001)
+ self.partDiffGameModeAlign = eval(get("partDiffGameModeAlign", str, "CENTER"))
+ self.partDiffGameModeFont = get("partDiffGameModeFont", str, "font")
+ self.partDiffGameModePos = (get("partDiffGameModeX", float, .07),
+ get("partDiffGameModeY", float, .015))
+ self.partDiffGameModeColor = get("partDiffGameModeColor", "color", "#FFFFFF")
+
+ self.partDiffPanelNameScale = get("partDiffPanelNameScale", float, .001)
+ self.partDiffPanelNameAlign = eval(get("partDiffPanelNameAlign", str, "LEFT"))
+ self.partDiffPanelNameFont = get("partDiffPanelNameFont", str, "font")
+ self.partDiffPanelNamePos = (get("partDiffPanelNameX", float, 0.0),
+ get("partDiffPanelNameY", float, 0.0))
+
+ self.partDiffControlScale = get("partDiffControlScale", float, .0025)
+ self.partDiffControlAlign = eval(get("partDiffControlAlign", str, "CENTER"))
+ self.partDiffControlFont = get("partDiffControlFont", str, "font")
+ self.partDiffControlPos = (get("partDiffControlX", float, .5),
+ get("partDiffControlY", float, .375))
+ self.partDiffHeaderColor = get("partDiffHeaderColor", "color", "#FFFFFF")
+
+ self.partDiffPartScale = get("partDiffPartScale", float, .25)
+ self.partDiffPartPos = (get("partDiffPartX", float, .5),
+ get("partDiffpartY", float, .52))
+
+ self.partDiffKeyboardImgScale = get("partDiffKeyboardImgScale", float, .1)
+ self.partDiffKeyboardImgPos = (get("partDiffKeyboardImgX", float, .8),
+ get("partDiffKeyboardImgY", float, .95))
+
+ self.partDiffPanelSpacing = get("partDiffPanelSpacing", float, .24)
+ self.partDiffPanelPos = (get("partDiffPanelX", float, .04),
+ get("partDiffPanelY", float, .1))
+ self.partDiffPanelSize = (get("partDiffPanelWidth", float, .2),
+ get("partDiffPanelHeight", float, .8))
+
+ #Vocal mode
+ self.vocalMeterSize = get("vocal_meter_size", float, 45.000)
+ self.vocalMeterX = get("vocal_meter_x", float, .25)
+ self.vocalMeterY = get("vocal_meter_y", float, .8)
+ self.vocalMultX = get("vocal_mult_x", float, .28)
+ self.vocalMultY = get("vocal_mult_y", float, .8)
+
+ self.vocalPowerX = get("vocal_power_x", float, .5)
+ self.vocalPowerY = get("vocal_power_y", float, .8)
+
+ self.vocalFillupCenterX = get("vocal_fillup_center_x", int, 139)
+ self.vocalFillupCenterY = get("vocal_fillup_center_y", int, 151)
+ self.vocalFillupInRadius = get("vocal_fillup_in_radius", int, 25)
+ self.vocalFillupOutRadius = get("vocal_fillup_out_radius", int, 139)
+ self.vocalFillupFactor = get("vocal_fillup_factor", float, 300.000)
+ self.vocalFillupColor = get("vocal_fillup_color", "color", "#DFDFDE")
+ self.vocalCircularFillup = get("vocal_circular_fillup", bool, True)
+
+ self.vocalLaneSize = get("vocal_lane_size", float, .002)
+ self.vocalGlowSize = get("vocal_glow_size", float, .012)
+ self.vocalGlowFade = get("vocal_glow_fade", float, .6)
+
+ self.vocalLaneColor = get("vocal_lane_color", "color", "#99FF80")
+ self.vocalShadowColor = get("vocal_shadow_color", "color", "#CCFFBF")
+ self.vocalGlowColor = get("vocal_glow_color", "color", "#33FF00")
+ self.vocalLaneColorStar = get("vocal_lane_color_star", "color", "#FFFF80")
+ self.vocalShadowColorStar = get("vocal_shadow_color_star", "color", "#FFFFBF")
+ self.vocalGlowColorStar = get("vocal_glow_color_star", "color", "#FFFF00")
+
+ #3D Note/Fret rendering system
+ self.twoDnote = get("twoDnote", bool, True)
+ self.twoDkeys = get("twoDkeys", bool, True)
+
+ self.threeDspin = get("threeDspin", bool, False)
+
+ self.noterot = [get("noterot"+str(i+1), float, 0) for i in range(5)]
+ self.keyrot = [get("keyrot"+str(i+1), float, 0) for i in range(5)]
+ self.drumnoterot = [get("drumnoterot"+str(i+1), float, 0) for i in range(5)]
+ self.drumkeyrot = [get("drumkeyrot"+str(i+1), float, 0) for i in range(5)]
+ self.notepos = [get("notepos"+str(i+1), float, 0) for i in range(5)]
+ self.keypos = [get("keypos"+str(i+1), float, 0) for i in range(5)]
+ self.drumnotepos = [get("drumnotepos"+str(i+1), float, 0) for i in range(5)]
+ self.drumkeypos = [get("drumkeypos"+str(i+1), float, 0) for i in range(5)]
+
+ self.shaderSolocolor = get("shaderSoloColor", "color", "#0000FF")
+
+ #In-game rendering
+ self.hopoIndicatorX = get("hopo_indicator_x")
+ self.hopoIndicatorY = get("hopo_indicator_y")
+ self.hopoIndicatorActiveColor = get("hopo_indicator_active_color", "color", "#FFFFFF")
+ self.hopoIndicatorInactiveColor = get("hopo_indicator_inactive_color", "color", "#666666")
+ self.markSolos = get("mark_solo_sections", int, 2)
+ self.ingame_stats_colorVar = get("ingame_stats_color", "color", "#FFFFFF")
+
+ #Game results scene
+ self.result_score = get("result_score", str, ".5,.11,0.0025,None,None").split(",")
+ self.result_star = get("result_star", str, ".5,.4,0.15,1.1").split(",")
+ self.result_song = get("result_song", str, ".05,.045,.002,None,None").split(",")
+ self.result_song_form = get("result_song_form", int, 0)
+ self.result_song_text = get("result_song_text", str, "%s Finished!").strip()
+ self.result_stats_part = get("result_stats_part", str, ".5,.64,0.002,None,None").split(",")
+ self.result_stats_part_text = get("result_stats_part_text", str, "Part: %s").strip()
+ self.result_stats_name = get("result_stats_name", str, ".5,.73,0.002,None,None").split(",")
+ self.result_stats_diff = get("result_stats_diff", str, ".5,.55,0.002,None,None").split(",")
+ self.result_stats_diff_text = get("result_stats_diff_text", str, "Difficulty: %s").strip()
+ self.result_stats_accuracy = get("result_stats_accuracy", str, ".5,.61,0.002,None,None").split(",")
+ self.result_stats_accuracy_text = get("result_stats_accuracy_text", str, "Accuracy: %.1f%%").strip()
+ self.result_stats_streak = get("result_stats_streak", str, ".5,.58,0.002,None,None").split(",")
+ self.result_stats_streak_text = get("result_stats_streak_text", str, "Long Streak: %s").strip()
+ self.result_stats_notes = get("result_stats_notes", str, ".5,.52,0.002,None,None").split(",")
+ self.result_stats_notes_text = get("result_stats_notes_text", str, "%s Notes Hit").strip()
+ self.result_cheats_info = get("result_cheats_info", str, ".5,.3,.002").split(",")
+ self.result_cheats_numbers = get("result_cheats_numbers", str, ".5,.35,.0015").split(",")
+ self.result_cheats_percent = get("result_cheats_percent", str, ".45,.4,.0015").split(",")
+ self.result_cheats_score = get("result_cheats_score", str, ".75,.4,.0015").split(",")
+ self.result_cheats_color = get("result_cheats_color", "color", "#FFFFFF")
+ self.result_cheats_font = get("result_cheats_font", str, "font")
+ self.result_high_score_font = get("result_high_score_font", str, "font")
+ self.result_menu_x = get("result_menu_x", float, .5)
+ self.result_menu_y = get("result_menu_y", float, .2)
+ self.result_star_type = get("result_star_type", int, 0)
+
+ #Submenus
+ allfiles = os.listdir(os.path.join(self.themePath,"menu"))
+ self.submenuScale = {}
+ self.submenuX = {}
+ self.submenuY = {}
+ self.submenuVSpace = {}
+ listmenu = []
+ for name in allfiles:
+ if name.find("text") > -1:
+ found = os.path.splitext(name)[0]
+ if found == "maintext":
continue
- if self.submenuX[i]:
- self.submenuX[i] = get(i).split(",")[0].strip()
- if self.submenuY[i]:
- self.submenuY[i] = get(i).split(",")[1].strip()
- if self.submenuScale[i]:
- self.submenuScale[i] = get(i).split(",")[2].strip()
- if self.submenuVSpace[i]:
- self.submenuVSpace[i] = get(i).split(",")[3].strip()
+ Config.define("theme", found, str, None)
+ self.submenuScale[found] = None
+ self.submenuX[found] = None
+ self.submenuY[found] = None
+ self.submenuVSpace[found] = None
+ listmenu.append(found)
+ for i in listmenu:
+ if i == "maintext":
+ continue
+ if self.submenuX[i]:
+ self.submenuX[i] = get(i).split(",")[0].strip()
+ if self.submenuY[i]:
+ self.submenuY[i] = get(i).split(",")[1].strip()
+ if self.submenuScale[i]:
+ self.submenuScale[i] = get(i).split(",")[2].strip()
+ if self.submenuVSpace[i]:
+ self.submenuVSpace[i] = get(i).split(",")[3].strip()
def setSelectedColor(self, alpha = 1.0):
@@ -850,6 +851,7 @@ def renderPanels(self, dialog):
wP = w*self.theme.partDiffPanelSize[0]
hP = h*self.theme.partDiffPanelSize[1]
glColor3f(*self.theme.partDiffHeaderColor)
+ self.theme.fadeScreen(-2.00)
if self.theme.partDiffTitleText:
dialog.fontDict[self.theme.partDiffTitleTextFont].render(self.theme.partDiffTitleText, self.theme.partDiffTitleTextPos, scale = self.theme.partDiffTitleTextScale, align = self.theme.partDiffTitleTextAlign)
if self.theme.partDiffSubtitleText:
diff --git a/src/Version.py b/src/Version.py
index 67d8f375d..adca3c52e 100644
--- a/src/Version.py
+++ b/src/Version.py
@@ -31,7 +31,7 @@
PROGRAM_NAME = 'FoFiX'
PROGRAM_UNIXSTYLE_NAME = 'fofix'
URL = 'http://fofix.googlecode.com'
-RELEASE_ID = 'development'
+RELEASE_ID = 'alpha 1'
def _getTagLine():
import VFS # can't be done at top level due to circular import issues...
diff --git a/src/VideoPlayer.h b/src/VideoPlayer.h
new file mode 100644
index 000000000..c68ddeaae
--- /dev/null
+++ b/src/VideoPlayer.h
@@ -0,0 +1,48 @@
+/* Frets on Fire X (FoFiX)
+ * Copyright (C) 2010 Team FoFiX
+ * 2010 John Stumpo
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#ifndef VIDEOPLAYER_H
+#define VIDEOPLAYER_H
+
+#include
+
+typedef struct _VideoPlayer VideoPlayer;
+
+VideoPlayer* video_player_new(const char* filename, GError** err);
+void video_player_destroy(VideoPlayer* player);
+
+void video_player_play(VideoPlayer* player);
+void video_player_pause(VideoPlayer* player);
+
+gboolean video_player_bind_frame(VideoPlayer* player, GError** err);
+
+gboolean video_player_eof(const VideoPlayer* player);
+double video_player_aspect_ratio(const VideoPlayer* player);
+
+GQuark video_player_error_quark(void);
+#define VIDEO_PLAYER_ERROR video_player_error_quark()
+
+typedef enum {
+ VIDEO_PLAYER_NO_VIDEO,
+ VIDEO_PLAYER_BAD_HEADERS,
+ VIDEO_PLAYER_BAD_DATA
+} VideoPlayerError;
+
+#endif
diff --git a/src/VideoPlayer.py b/src/VideoPlayer.py
deleted file mode 100644
index ec393de3c..000000000
--- a/src/VideoPlayer.py
+++ /dev/null
@@ -1,334 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#####################################################################
-# #
-# Frets on Fire X (FoFiX) #
-# Copyright (C) 2009-2010 FoFiX Team #
-# See CREDITS for a full list of contributors #
-# #
-# This program is free software; you can redistribute it and/or #
-# modify it under the terms of the GNU General Public License #
-# as published by the Free Software Foundation; either version 2 #
-# of the License, or (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
-# GNU General Public License for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program; if not, write to the Free Software #
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, #
-# MA 02110-1301, USA. #
-#####################################################################
-from __future__ import with_statement
-import os
-import sys
-
-# Twiddle the appropriate envvars under Windows so we can load gstreamer
-# directly from [FoFiX root]/gstreamer and not have the ugliness of
-# requiring the user to install and configure it separately...
-if os.name == 'nt':
- if hasattr(sys, 'frozen'):
- _gstpath = 'gstreamer'
- else:
- _gstpath = os.path.join('..', 'gstreamer')
- if os.path.isdir(_gstpath):
- os.environ['PATH'] = os.path.abspath(os.path.join(_gstpath, 'bin')) + os.pathsep + os.environ['PATH']
- os.environ['GST_PLUGIN_PATH'] = os.path.abspath(os.path.join(_gstpath, 'lib', 'gstreamer-0.10'))
-
-# Almighty GStreamer
-import gobject
-import pygst
-pygst.require('0.10')
-import gst
-from gst.extend import discoverer # Video property detection
-
-import pygame
-
-from OpenGL.GL import *
-from OpenGL.GLU import *
-# Array-based drawing
-import numpy as np
-
-import cmgl
-
-from View import View, BackgroundLayer
-import Log
-from Texture import Texture
-
-FUTUREPROOF_VIDEO_CODECS = ['Theora']
-FUTUREPROOF_AUDIO_CODECS = ['Vorbis']
-FUTUREPROOF_CONTAINERS = ['Ogg']
-
-# Simple video player
-class VideoPlayer(BackgroundLayer):
- def __init__(self, framerate, vidSource, (winWidth, winHeight) = (None, None), mute = False, loop = False, startTime = None, endTime = None):
- self.updated = False
- self.videoList = None
- self.videoTex = None
- self.videoBuffer = None
- self.videoSrc = vidSource
- self.mute = mute
- self.loop = loop
- self.startTime = startTime
- self.endTime = endTime
- if winWidth is not None and winHeight is not None:
- self.winWidth, self.winHeight = winWidth, winHeight
- else: # default
- self.winWidth, self.winHeight = (640, 480)
- Log.warn("VideoPlayer: No resolution specified (default %dx%d)" %
- (self.winWidth, self.winHeight))
- self.vidWidth, self.vidHeight = -1, -1
- self.fps = framerate
- self.clock = pygame.time.Clock()
- self.paused = False
- self.finished = False
- self.discovered = False
- self.timeFormat = gst.Format(gst.FORMAT_TIME)
-
- self.loadVideo(vidSource) # Load the video
-
- # Load a new video:
- # 1) Detect video resolution
- # 2) Setup OpenGL texture
- # 3) Setup GStreamer pipeline
- def loadVideo(self, vidSource):
- Log.debug("Attempting to load video: %s" % self.videoSrc)
- if not os.path.exists(vidSource):
- Log.error("Video %s does not exist!" % vidSource)
- self.videoSrc = vidSource
- d = discoverer.Discoverer(self.videoSrc)
- d.connect('discovered', self.videoDiscover)
- d.discover()
- gobject.threads_init() # Start C threads
- while not self.discovered:
- # Force C threads iteration
- gobject.MainLoop().get_context().iteration(True)
- if not self.validFile:
- Log.error("Invalid video file: %s\n" % self.videoSrc)
- return False
- self.textureSetup()
- self.videoSetup()
- return True
-
- # Use GStreamer's video discoverer to autodetect video properties
- def videoDiscover(self, d, isMedia):
- self.validFile = True
- if isMedia and d.is_video:
-
- # Warn about codecs that might not be supported in the future.
- vcodec = d.tags.get('video-codec', '[plugin did not give a name]')
- Log.debug('Video codec: ' + vcodec)
- if vcodec not in FUTUREPROOF_VIDEO_CODECS:
- Log.warn('Support for %s is not guaranteed in the future; try one of: %s' % (vcodec, ', '.join(FUTUREPROOF_VIDEO_CODECS)))
-
- if d.is_audio:
- acodec = d.tags.get('audio-codec', '[plugin did not give a name]')
- Log.debug('Audio codec: ' + acodec)
- if acodec not in FUTUREPROOF_AUDIO_CODECS:
- Log.warn('Support for %s is not guaranteed in the future; try one of: %s' % (acodec, ', '.join(FUTUREPROOF_AUDIO_CODECS)))
-
- container = d.tags.get('container-format', '[plugin did not give a name]')
- Log.debug('Container format: ' + container)
- if container not in FUTUREPROOF_CONTAINERS:
- Log.warn('Support for %s is not guaranteed in the future; try one of: %s' % (container, ', '.join(FUTUREPROOF_CONTAINERS)))
-
- self.vidWidth, self.vidHeight = d.videowidth, d.videoheight
- # Force mute if no sound track is available or
- # else you'll get nothing but a black screen!
- if not d.is_audio and not self.mute:
- Log.warn("Video has no sound ==> forcing mute.")
- self.mute = True
- else:
- self.validFile = False
-
- self.discovered = True
-
- def textureSetup(self):
- if not self.validFile:
- return
-
- self.videoTex = Texture(useMipmaps=False)
- self.videoBuffer = '\x00\x00\x00' * self.vidWidth * self.vidHeight
- self.updated = True
- self.textureUpdate()
-
- # Resize video (polygon) to respect resolution ratio
- # (The math is actually simple, take the time to draw it down if required)
- winRes = float(self.winWidth)/float(self.winHeight)
- vidRes = float(self.vidWidth)/float(self.vidHeight)
- vtxX = 1.0
- vtxY = 1.0
- if winRes > vidRes:
- r = float(self.winHeight)/float(self.vidHeight)
- vtxX = 1.0 - abs(self.winWidth-r*self.vidWidth) / (float(self.winWidth))
- elif winRes < vidRes:
- r = float(self.winWidth)/float(self.vidWidth)
- vtxY = 1.0 - abs(self.winHeight-r*self.vidHeight) / (float(self.winHeight))
-
- # Vertices
- videoVtx = np.array([[-vtxX, vtxY],
- [ vtxX, -vtxY],
- [ vtxX, vtxY],
- [-vtxX, vtxY],
- [-vtxX, -vtxY],
- [ vtxX, -vtxY]], dtype=np.float32)
- backVtx = np.array([[-1.0, 1.0],
- [ 1.0, -1.0],
- [ 1.0, 1.0],
- [-1.0, 1.0],
- [-1.0, -1.0],
- [ 1.0, -1.0]], dtype=np.float32)
- # Texture coordinates
- videoTex = np.array([[0.0, self.videoTex.size[1]],
- [self.videoTex.size[0], 0.0],
- [self.videoTex.size[0], self.videoTex.size[1]],
- [0.0, self.videoTex.size[1]],
- [0.0, 0.0],
- [self.videoTex.size[0], 0.0]], dtype=np.float32)
-
- # Create a compiled OpenGL call list and do array-based drawing
- # Could have used GL_QUADS but IIRC triangles are recommended
- self.videoList = cmgl.List()
- with self.videoList:
- # Draw borders where video aspect is different than specified width/height
- glColor3f(0., 0., 0.)
- cmgl.drawArrays(GL_TRIANGLE_STRIP, vertices=backVtx)
- # Draw video
- glEnable(GL_TEXTURE_2D)
- glColor3f(1., 1., 1.)
- cmgl.drawArrays(GL_TRIANGLE_STRIP, vertices=videoVtx, texcoords=videoTex)
- glDisable(GL_TEXTURE_2D)
-
- # Setup GStreamer's pipeline
- # Note: playbin2 seems also suitable, we might want to experiment with it
- # if decodebin is proven problematic
- def videoSetup(self):
- if self.startTime is not None or self.endTime is not None:
- self.setTime = True
- else:
- self.setTime = False
- if self.startTime is not None:
- self.startNs = self.startTime * 1000000 # From ms to ns
- else:
- self.startNs = 0
- if self.endTime is not None:
- self.endNs = self.endTime * 1000000
- else:
- self.endNs = -1
-
- with_audio = ""
- if not self.mute:
- with_audio = "! queue ! audioconvert ! audiorate ! audioresample ! autoaudiosink"
- s = "filesrc name=input ! decodebin2 name=dbin dbin. ! ffmpegcolorspace ! video/x-raw-rgb ! fakesink name=output signal-handoffs=true sync=true dbin. %s" % with_audio
- self.player = gst.parse_launch(s)
- self.input = self.player.get_by_name('input')
- self.fakeSink = self.player.get_by_name('output')
- self.input.set_property("location", self.videoSrc)
- self.fakeSink.connect ("handoff", self.newFrame)
-
- # Catch the end of file as well as errors
- # FIXME:
- # Messages are sent if i use the following in run():
- # gobject.MainLoop().get_context().iteration(True)
- # BUT the main python thread then freezes after ~5 seconds...
- # unless we use gobject.idle_add(self.player.elements)
- bus = self.player.get_bus()
- bus.add_signal_watch()
- bus.enable_sync_message_emission()
- bus.connect("message", self.onMessage)
- # Required to prevent the main python thread from freezing, why?!
- # Thanks to max26199 for finding this!
- gobject.idle_add(self.player.elements)
-
- # Handle bus event e.g. end of video or unsupported formats/codecs
- def onMessage(self, bus, message):
- type = message.type
-# print "Message %s" % type
- # End of video
- if type == gst.MESSAGE_EOS:
- if self.loop:
- # Seek back to start time and set end time
- self.player.seek(1, self.timeFormat, gst.SEEK_FLAG_FLUSH,
- gst.SEEK_TYPE_SET, self.startNs,
- gst.SEEK_TYPE_SET, self.endNs)
- else:
- self.player.set_state(gst.STATE_NULL)
- self.finished = True
- # Error
- elif type == gst.MESSAGE_ERROR:
- err = message.parse_error()
- Log.error("GStreamer error: %s" % err)
- self.player.set_state(gst.STATE_NULL)
- self.finished = True
-# raise NameError("GStreamer error: %s" % err)
- elif type == gst.MESSAGE_WARNING:
- warning, debug = message.parse_warning()
- Log.warn("GStreamer warning: %s\n(---) %s" % (warning, debug))
- elif type == gst.MESSAGE_STATE_CHANGED:
- oldstate, newstate, pending = message.parse_state_changed()
-# Log.debug("GStreamer state: %s" % newstate)
- if newstate == gst.STATE_READY:
- # Set start and end time
- if self.setTime:
- # Note: Weirdly, contrary to loop logic, i need a wait here!
- # Moreover, at the beginning, we're ready more than once!?
- pygame.time.wait(1000) # Why, oh why... isn't ready, READY?!
- self.player.seek(1, self.timeFormat, gst.SEEK_FLAG_FLUSH,
- gst.SEEK_TYPE_SET, self.startNs,
- gst.SEEK_TYPE_SET, self.endNs)
- self.setTime = False # Execute just once
-
- # Handle new video frames coming from the decoder
- def newFrame(self, sink, buffer, pad):
- self.videoBuffer = buffer
- self.updated = True
-
- def textureUpdate(self):
- if self.updated:
- img = pygame.image.frombuffer(self.videoBuffer,
- (self.vidWidth, self.vidHeight),
- 'RGB')
- self.videoTex.loadSurface(img)
- self.videoTex.setFilter()
- self.updated = False
-
- def shown(self):
- gobject.threads_init()
-
- def hidden(self):
- self.player.set_state(gst.STATE_NULL)
-
- def run(self, ticks = None):
- if not self.validFile:
- return
- if self.paused == True:
- self.player.set_state(gst.STATE_PAUSED)
- else:
- self.player.set_state(gst.STATE_PLAYING)
- self.finished = False
- gobject.MainLoop().get_context().iteration(True)
- self.clock.tick(self.fps)
-
- # Render texture to polygon
- # Note: Both visibility and topMost are currently unused.
- def render(self, visibility = 1.0, topMost = False):
- try:
- self.textureUpdate()
- # Save and clear both transformation matrices
- glMatrixMode(GL_PROJECTION)
- glPushMatrix()
- glLoadIdentity()
- glMatrixMode(GL_MODELVIEW)
- glPushMatrix()
- glLoadIdentity()
- # Draw the polygon and apply texture
- self.videoTex.bind()
- self.videoList()
- # Restore both transformation matrices
- glPopMatrix()
- glMatrixMode(GL_PROJECTION)
- glPopMatrix()
- except Exception:
- Log.error("Error attempting to play video")
diff --git a/src/VideoPlayer.pyx b/src/VideoPlayer.pyx
new file mode 100644
index 000000000..3345079c7
--- /dev/null
+++ b/src/VideoPlayer.pyx
@@ -0,0 +1,204 @@
+#####################################################################
+# -*- coding: iso-8859-1 -*- #
+# #
+# Frets on Fire X (FoFiX) #
+# Copyright (C) 2010 FoFiX Team #
+# 2010 John Stumpo #
+# #
+# This program is free software; you can redistribute it and/or #
+# modify it under the terms of the GNU General Public License #
+# as published by the Free Software Foundation; either version 2 #
+# of the License, or (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program; if not, write to the Free Software #
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, #
+# MA 02110-1301, USA. #
+#####################################################################
+
+# First a thin wrapper around VideoPlayer from VideoPlayerCore.c...
+
+cdef extern from "VideoPlayer.h":
+ ctypedef struct CVideoPlayer "VideoPlayer":
+ pass
+
+ ctypedef int GQuark
+ ctypedef struct GError:
+ GQuark domain
+ char* message
+ GQuark G_FILE_ERROR
+ GQuark VIDEO_PLAYER_ERROR
+ void g_error_free(GError*)
+
+ CVideoPlayer* video_player_new(char*, GError**)
+ void video_player_destroy(CVideoPlayer*)
+ void video_player_play(CVideoPlayer*)
+ void video_player_pause(CVideoPlayer*)
+ bint video_player_bind_frame(CVideoPlayer*, GError**)
+ bint video_player_eof(CVideoPlayer*)
+ double video_player_aspect_ratio(CVideoPlayer*)
+
+class VideoPlayerError(Exception):
+ pass
+
+cdef object raise_from_gerror(GError* err):
+ assert err is not NULL
+ if err.domain == VIDEO_PLAYER_ERROR:
+ exc = VideoPlayerError(err.message)
+ elif err.domain == G_FILE_ERROR:
+ exc = IOError(err.message)
+ else:
+ exc = Exception(err.message)
+ g_error_free(err)
+ raise exc
+
+cdef class VideoPlayer(object):
+ cdef CVideoPlayer* player
+
+ def __cinit__(self, char* filename):
+ cdef GError* err = NULL
+ self.player = video_player_new(filename, &err)
+ if self.player is NULL:
+ raise_from_gerror(err)
+
+ def __dealloc__(self):
+ if self.player is not NULL:
+ video_player_destroy(self.player)
+
+ def play(self):
+ video_player_play(self.player)
+
+ def pause(self):
+ video_player_pause(self.player)
+
+ def bind_frame(self):
+ cdef GError* err = NULL
+ if not video_player_bind_frame(self.player, &err):
+ raise_from_gerror(err)
+
+ def eof(self):
+ return video_player_eof(self.player)
+
+ def aspect_ratio(self):
+ return video_player_aspect_ratio(self.player)
+
+
+# And now, a layer for playing a video.
+
+from View import BackgroundLayer
+from Input import KeyListener
+import numpy as np
+import cmgl
+
+# This is Cython after all, so we may as well directly bind to OpenGL for the video layer implementation.
+cdef extern from "glwrap.h":
+ enum:
+ GL_PROJECTION
+ GL_MODELVIEW
+ GL_QUADS
+ GL_TEXTURE_2D
+ void glMatrixMode(int)
+ void glPushMatrix()
+ void glPopMatrix()
+ void glLoadIdentity()
+ void glEnable(int)
+ void glDisable(int)
+ void glColor4f(double, double, double, double)
+
+class VideoLayer(BackgroundLayer, KeyListener):
+ def __init__(self, engine, filename, mute = False, loop = False, startTime = None, endTime = None, cancellable = False):
+ self.engine = engine
+ self.filename = filename
+ self.mute = mute # TODO: audio
+ self.loop = loop # TODO: seeking
+ self.startTime = startTime # TODO: seeking
+ self.endTime = endTime # TODO: seeking
+ self.cancellable = cancellable
+
+ self.finished = False
+
+ self.player = VideoPlayer(self.filename)
+
+ def shown(self):
+ if self.cancellable:
+ self.engine.input.addKeyListener(self)
+
+ def hidden(self):
+ if self.cancellable:
+ self.engine.input.removeKeyListener(self)
+
+ def keyPressed(self, key, unicode):
+ self.finished = True
+
+ def play(self):
+ self.player.play()
+
+ def pause(self):
+ self.player.pause()
+
+ def restart(self):
+ # XXX: do this less hackishly
+ del self.player
+ self.player = VideoPlayer(self.filename)
+ self.player.play()
+
+ def render(self, visibility, topMost):
+ screen_aspect_ratio = float(self.engine.view.geometry[2]) / self.engine.view.geometry[3]
+ video_aspect_ratio = self.player.aspect_ratio()
+
+ # Figure out the area of the acreen to cover with video.
+ if screen_aspect_ratio > video_aspect_ratio:
+ width_fraction = video_aspect_ratio / screen_aspect_ratio
+ height_fraction = 1.0
+ else:
+ width_fraction = 1.0
+ height_fraction = screen_aspect_ratio / video_aspect_ratio
+
+ self.player.bind_frame()
+
+ glMatrixMode(GL_PROJECTION)
+ glPushMatrix()
+ glLoadIdentity()
+ glMatrixMode(GL_MODELVIEW)
+ glPushMatrix()
+ glLoadIdentity()
+
+ # Prepare to draw the video over a black rectangle that will fill the rest of the space.
+ background_vertices = np.array([[ 1.0, 1.0],
+ [-1.0, 1.0],
+ [-1.0, -1.0],
+ [ 1.0, -1.0]], dtype=np.float32)
+ video_vertices = np.array([[ width_fraction, height_fraction],
+ [-width_fraction, height_fraction],
+ [-width_fraction, -height_fraction],
+ [ width_fraction, -height_fraction]], dtype=np.float32)
+ texcoords = np.array([[1.0, 0.0],
+ [0.0, 0.0],
+ [0.0, 1.0],
+ [1.0, 1.0]], dtype=np.float32)
+
+ # The black rectangle.
+ glColor4f(0.0, 0.0, 0.0, 1.0)
+ cmgl.drawArrays(GL_QUADS, vertices=background_vertices)
+
+ # The actual video.
+ glEnable(GL_TEXTURE_2D)
+ glColor4f(1.0, 1.0, 1.0, 1.0)
+ cmgl.drawArrays(GL_QUADS, vertices=video_vertices, texcoords=texcoords)
+ glDisable(GL_TEXTURE_2D)
+
+ glPopMatrix()
+ glMatrixMode(GL_PROJECTION)
+ glPopMatrix()
+ glMatrixMode(GL_MODELVIEW)
+
+ if self.player.eof():
+ if self.loop:
+ self.restart()
+ else:
+ self.finished = True
diff --git a/src/VideoPlayerCore.c b/src/VideoPlayerCore.c
new file mode 100644
index 000000000..b50975165
--- /dev/null
+++ b/src/VideoPlayerCore.c
@@ -0,0 +1,349 @@
+/* Frets on Fire X (FoFiX)
+ * Copyright (C) 2010 Team FoFiX
+ * 2010 John Stumpo
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include "VideoPlayer.h"
+
+#include "glwrap.h"
+#include
+#include
+#include
+
+#include
+#include
+
+struct _VideoPlayer {
+ FILE* file;
+ ogg_sync_state osync;
+ GHashTable* stream_table;
+ ogg_page current_page;
+ gboolean have_video;
+ ogg_stream_state* vstream;
+ th_info tinfo;
+ th_comment tcomment;
+ th_setup_info* tsetup;
+ gboolean eof;
+ th_dec_ctx* vdecoder;
+ gboolean playing;
+ glong playback_position; /* microseconds */
+ GTimeVal playback_start_time;
+ GLuint video_texture;
+ ogg_int64_t decode_granpos;
+ th_ycbcr_buffer frame_buffer;
+ struct SwsContext* sws_context;
+ int tex_width;
+ int tex_height;
+ guchar* tex_buffer;
+};
+
+static void destroy_stream(gpointer data)
+{
+ ogg_stream_clear(data);
+ g_free(data);
+}
+
+static gboolean demux_next_page(VideoPlayer* player, GError** err)
+{
+ int serialno;
+ ogg_stream_state* ostream;
+
+ /* Demux the next page into player->current_page. */
+ while (ogg_sync_pageout(&player->osync, &player->current_page) != 1) {
+ char* buf = ogg_sync_buffer(&player->osync, 65536);
+ int bytes = fread(buf, 1, 65536, player->file);
+ if (bytes == 0) {
+ player->eof = TRUE;
+ return TRUE;
+ } else if (bytes < 0) {
+ g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errno),
+ "Failed to read video: %s", g_strerror(errno));
+ return FALSE;
+ }
+ ogg_sync_wrote(&player->osync, bytes);
+ }
+
+ /* Dispatch it to the correct ogg_stream_state. */
+ serialno = ogg_page_serialno(&player->current_page);
+ ostream = g_hash_table_lookup(player->stream_table, &serialno);
+ if (ostream != NULL) {
+ ogg_stream_pagein(ostream, &player->current_page);
+ } else if (ogg_page_bos(&player->current_page)) {
+ int* key = g_new(int, 1);
+ *key = serialno;
+ ostream = g_new(ogg_stream_state, 1);
+ ogg_stream_init(ostream, serialno);
+ g_hash_table_insert(player->stream_table, key, ostream);
+ ogg_stream_pagein(ostream, &player->current_page);
+ }
+ return TRUE;
+}
+
+static guint32 next_power_of_two(guint32 n)
+{
+ n--;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ n++;
+ return n;
+}
+
+static void update_texture(VideoPlayer* player)
+{
+ /* TODO: handle pic_[xy] correctly so the whole Theora testsuite works */
+ const uint8_t* const src[] = {player->frame_buffer[0].data, player->frame_buffer[1].data, player->frame_buffer[2].data};
+ int src_stride[] = {player->frame_buffer[0].stride, player->frame_buffer[1].stride, player->frame_buffer[2].stride};
+ uint8_t* dest[] = {player->tex_buffer};
+ int dest_stride[] = {player->tex_width * 4};
+ sws_scale(player->sws_context, src, src_stride, 0, player->tinfo.pic_height, dest, dest_stride);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, player->tex_width, player->tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, player->tex_buffer);
+}
+
+static gboolean demux_headers(VideoPlayer* player, GError** err)
+{
+ /* Go through the stream header pages, looking for one that starts a Theora stream. */
+ while (demux_next_page(player, err)) {
+ /* If the page isn't a header, we're done this step. */
+ if (player->eof || !ogg_page_bos(&player->current_page))
+ goto got_all_headers;
+
+ if (!player->have_video) {
+ /* Grab the first packet and check it for Theoraness.
+ Otherwise forget about the stream. */
+ int header_status;
+ ogg_packet pkt;
+
+ int serialno = ogg_page_serialno(&player->current_page);
+ ogg_stream_state* ostream = g_hash_table_lookup(player->stream_table, &serialno);
+ if (ogg_stream_packetout(ostream, &pkt) != 1) {
+ g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS,
+ "Bad headers in video file.");
+ return FALSE;
+ }
+
+ header_status = th_decode_headerin(&player->tinfo, &player->tcomment, &player->tsetup, &pkt);
+ if (header_status == TH_ENOTFORMAT) {
+ /* Forget the stream - it's not Theora. */
+ g_hash_table_remove(player->stream_table, &serialno);
+ } else if (header_status < 0) {
+ g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS,
+ "Bad headers in Theora stream.");
+ return FALSE;
+ } else {
+ player->have_video = TRUE;
+ player->vstream = ostream;
+ /* And keep looping through the header pages so we can throw out the other streams. */
+ }
+ } else {
+ /* Throw it out - we already found the stream. */
+ int serialno = ogg_page_serialno(&player->current_page);
+ g_hash_table_remove(player->stream_table, &serialno);
+ }
+ }
+ /* If we got here, demux_next_page exploded before we even finished the stream headers. */
+ return FALSE;
+
+got_all_headers:
+ if (!player->have_video) {
+ g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_NO_VIDEO,
+ "Failed to find a Theora stream in the video file.");
+ return FALSE;
+ }
+
+ /* Get the rest of the headers. */
+ while (!player->eof) {
+ ogg_packet pkt;
+ while (ogg_stream_packetout(player->vstream, &pkt) == 1) {
+ int header_status = th_decode_headerin(&player->tinfo, &player->tcomment, &player->tsetup, &pkt);
+ if (header_status < 0) {
+ g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS,
+ "Bad headers in Theora stream.");
+ return FALSE;
+ } else if (header_status == 0) {
+ /* We have everything we need to start decoding, and we have the first video packet. */
+ int decode_status;
+ int pix_format;
+ player->vdecoder = th_decode_alloc(&player->tinfo, player->tsetup);
+ player->playing = FALSE;
+ player->playback_position = 0;
+ decode_status = th_decode_packetin(player->vdecoder, &pkt, &player->decode_granpos);
+ if (decode_status != 0) {
+ g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_DATA,
+ "An error occurred decoding a Theora packet.");
+ return FALSE;
+ }
+ th_decode_ycbcr_out(player->vdecoder, player->frame_buffer);
+
+ player->tex_width = next_power_of_two(player->tinfo.pic_width);
+ player->tex_height = next_power_of_two(player->tinfo.pic_height);
+ player->tex_buffer = g_malloc(player->tex_width * player->tex_height * 4);
+ switch (player->tinfo.pixel_fmt) {
+ case TH_PF_420:
+ pix_format = PIX_FMT_YUV420P;
+ break;
+ case TH_PF_422:
+ pix_format = PIX_FMT_YUV422P;
+ break;
+ case TH_PF_444:
+ pix_format = PIX_FMT_YUV444P;
+ break;
+ default:
+ g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS,
+ "Bad pixel format in Theora stream.");
+ return FALSE;
+ }
+ player->sws_context = sws_getContext(player->tinfo.pic_width, player->tinfo.pic_height, pix_format, player->tex_width, player->tex_height, PIX_FMT_RGBA, SWS_FAST_BILINEAR, NULL, NULL, NULL);
+
+ glBindTexture(GL_TEXTURE_2D, player->video_texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ update_texture(player);
+ return TRUE;
+ }
+ /* Otherwise, there are still more header packets needed. */
+ }
+ if (!demux_next_page(player, err))
+ return FALSE;
+ }
+
+ g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_HEADERS,
+ "Failed to find all necessary Theora headers.");
+ return FALSE;
+}
+
+VideoPlayer* video_player_new(const char* filename, GError** err)
+{
+ VideoPlayer* player = g_new0(VideoPlayer, 1);
+
+ player->file = fopen(filename, "rb");
+ if (player->file == NULL) {
+ g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errno),
+ "Failed to open video: %s", g_strerror(errno));
+ g_free(player);
+ return NULL;
+ }
+
+ player->stream_table = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, destroy_stream);
+ ogg_sync_init(&player->osync);
+ th_info_init(&player->tinfo);
+ th_comment_init(&player->tcomment);
+ glGenTextures(1, &player->video_texture);
+ if (!demux_headers(player, err)) {
+ video_player_destroy(player);
+ return NULL;
+ }
+ return player;
+}
+
+void video_player_destroy(VideoPlayer* player)
+{
+ if (player->vdecoder != NULL)
+ th_decode_free(player->vdecoder);
+ if (player->tsetup != NULL)
+ th_setup_free(player->tsetup);
+ if (player->sws_context != NULL)
+ sws_freeContext(player->sws_context);
+ if (player->tex_buffer != NULL)
+ g_free(player->tex_buffer);
+ glDeleteTextures(1, &player->video_texture);
+ th_comment_clear(&player->tcomment);
+ th_info_clear(&player->tinfo);
+ g_hash_table_destroy(player->stream_table);
+ ogg_sync_clear(&player->osync);
+ fclose(player->file);
+ g_free(player);
+}
+
+void video_player_play(VideoPlayer* player)
+{
+ g_get_current_time(&player->playback_start_time);
+ g_time_val_add(&player->playback_start_time, -player->playback_position);
+ player->playing = TRUE;
+}
+
+void video_player_pause(VideoPlayer* player)
+{
+ player->playing = FALSE;
+}
+
+gboolean video_player_bind_frame(VideoPlayer* player, GError** err)
+{
+ gboolean must_update_texture = FALSE;
+
+ glBindTexture(GL_TEXTURE_2D, player->video_texture);
+
+ /* Advance the playback position if we're playing. */
+ if (player->playing) {
+ GTimeVal now;
+ g_get_current_time(&now);
+ player->playback_position = (1000000 * (now.tv_sec - player->playback_start_time.tv_sec)) + (now.tv_usec - player->playback_start_time.tv_usec);
+ }
+
+ while (th_granule_time(player->vdecoder, player->decode_granpos) * 1000000 < player->playback_position) {
+ ogg_packet pkt;
+ int decode_status;
+
+ /* Get the next packet. */
+ if (ogg_stream_packetout(player->vstream, &pkt) != 1) {
+ if (player->eof) {
+ video_player_pause(player);
+ break;
+ }
+ if (!demux_next_page(player, err))
+ return FALSE;
+ continue;
+ }
+
+ decode_status = th_decode_packetin(player->vdecoder, &pkt, &player->decode_granpos);
+ if (decode_status != 0 && decode_status != TH_DUPFRAME) {
+ g_set_error(err, VIDEO_PLAYER_ERROR, VIDEO_PLAYER_BAD_DATA,
+ "An error occurred decoding a Theora packet.");
+ return FALSE;
+ }
+
+ if (decode_status == 0) {
+ th_decode_ycbcr_out(player->vdecoder, player->frame_buffer);
+ must_update_texture = TRUE;
+ }
+ }
+
+ if (must_update_texture)
+ update_texture(player);
+
+ return TRUE;
+}
+
+gboolean video_player_eof(const VideoPlayer* player)
+{
+ return player->eof;
+}
+
+double video_player_aspect_ratio(const VideoPlayer* player)
+{
+ return player->tinfo.pic_width / (double)player->tinfo.pic_height;
+}
+
+GQuark video_player_error_quark(void)
+{
+ return g_quark_from_static_string("video-player-error-quark");
+}
diff --git a/src/setup.py b/src/setup.py
index 9bad38ea3..fbe20c0e7 100755
--- a/src/setup.py
+++ b/src/setup.py
@@ -24,9 +24,13 @@
# FoFiX fully unified setup script
from distutils.core import setup, Extension
-from Cython.Distutils import build_ext
+import distutils.ccompiler
+from distutils.dep_util import newer
+from Cython.Distutils import build_ext as _build_ext
import sys, SceneFactory, Version, glob, os
import numpy as np
+import shlex
+import subprocess
# Start setting up py2{exe,app} and building the argument set for setup().
@@ -34,40 +38,52 @@
# the script, just before the actual call to setup().
setup_args = {}
if os.name == 'nt':
- import py2exe
- from py2exe.resources.VersionInfo import RT_VERSION
- from py2exe.resources.VersionInfo import Version as VersionResource
- #stump: sometimes py2.6 py2exe thinks parts of pygame are "system" DLLs...
- __orig_isSystemDLL = py2exe.build_exe.isSystemDLL
- def isSystemDLL(pathname):
- if pathname.lower().find('pygame') != -1:
- return 0
- return __orig_isSystemDLL(pathname)
- py2exe.build_exe.isSystemDLL = isSystemDLL
+ try:
+ import py2exe
+ except ImportError:
+ if 'py2exe' in sys.argv:
+ print >>sys.stderr, 'py2exe must be installed to create .exe files.'
+ sys.exit(1)
+ else:
+ from py2exe.resources.VersionInfo import RT_VERSION
+ from py2exe.resources.VersionInfo import Version as VersionResource
+ #stump: sometimes py2.6 py2exe thinks parts of pygame are "system" DLLs...
+ __orig_isSystemDLL = py2exe.build_exe.isSystemDLL
+ def isSystemDLL(pathname):
+ if pathname.lower().find('pygame') != -1:
+ return 0
+ return __orig_isSystemDLL(pathname)
+ py2exe.build_exe.isSystemDLL = isSystemDLL
- setup_args.update({
- 'zipfile': "data/library.zip",
- 'windows': [
- {
- "script": "FoFiX.py",
- "icon_resources": [(1, "fofix.ico")],
- "other_resources": [(RT_VERSION, 1, VersionResource(
- #stump: the parameter below must consist only of up to four numerical fields separated by dots
- Version.versionNum(),
- file_description="Frets on Fire X",
- legal_copyright=r"© 2008-2010 FoFiX Team. GNU GPL v2 or later.",
- company_name="FoFiX Team",
- internal_name="FoFiX.exe",
- original_filename="FoFiX.exe",
- product_name=Version.PROGRAM_NAME,
- #stump: when run from the exe, FoFiX will claim to be "FoFiX v" + product_version
- product_version=Version.version()
- ).resource_bytes())]
- }
- ]
- })
+ setup_args.update({
+ 'zipfile': "data/library.zip",
+ 'windows': [
+ {
+ "script": "FoFiX.py",
+ "icon_resources": [(1, "fofix.ico")],
+ "other_resources": [(RT_VERSION, 1, VersionResource(
+ #stump: the parameter below must consist only of up to four numerical fields separated by dots
+ Version.versionNum(),
+ file_description="Frets on Fire X",
+ legal_copyright=r"© 2008-2010 FoFiX Team. GNU GPL v2 or later.",
+ company_name="FoFiX Team",
+ internal_name="FoFiX.exe",
+ original_filename="FoFiX.exe",
+ product_name=Version.PROGRAM_NAME,
+ #stump: when run from the exe, FoFiX will claim to be "FoFiX v" + product_version
+ product_version=Version.version()
+ ).resource_bytes())]
+ }
+ ]
+ })
elif sys.platform == 'darwin':
- import py2app
+ try:
+ import py2app
+ except ImportError:
+ if 'py2app' in sys.argv:
+ print >>sys.stderr, 'py2app must be installed to create .app bundles.'
+ sys.exit(1)
+
setup_args.update({
'app': ['FoFiX.py'],
'data_files': [
@@ -114,17 +130,7 @@ def isSystemDLL(pathname):
except ImportError:
pass
-# A couple things we need for pygst under Windows...
extraDllExcludes = []
-if os.name == 'nt':
- try:
- # Get the pygst dirs into sys.path if they're there.
- import pygst
- pygst.require('0.10')
- # Skip the DLLs used by pygst; the player code handles them itself.
- extraDllExcludes += [os.path.basename(d) for d in glob.glob(os.path.join('..', 'gstreamer', 'bin', 'libgst*.dll'))]
- except ImportError:
- pass
# Command-specific options shared between py2exe and py2app.
common_options = {
@@ -170,11 +176,6 @@ def isSystemDLL(pathname):
"mswsock.dll",
"powrprof.dll",
"w9xpopen.exe",
- "libgio-2.0-0.dll",
- "libglib-2.0-0.dll",
- "libgmodule-2.0-0.dll",
- "libgobject-2.0-0.dll",
- "libgthread-2.0-0.dll",
] + extraDllExcludes,
"optimize": 2
})
@@ -191,19 +192,169 @@ def isSystemDLL(pathname):
}
})
+
+def find_command(cmd):
+ '''Find a program on the PATH, or, on win32, in the dependency pack.'''
+
+ print 'checking for program %s...' % cmd,
+
+ if os.name == 'nt':
+ # Only accept something from the dependency pack.
+ path = os.path.join('..', 'win32', 'deps', 'bin', cmd+'.exe')
+ else:
+ # Search the PATH.
+ path = None
+ for dir in os.environ['PATH'].split(os.pathsep):
+ if os.access(os.path.join(dir, cmd), os.X_OK):
+ path = os.path.join(dir, cmd)
+ break
+
+ if path is None or not os.path.isfile(path):
+ print 'not found'
+ print >>sys.stderr, 'Could not find required program "%s".' % cmd
+ if os.name == 'nt':
+ print >>sys.stderr, '(Check that you have the latest version of the dependency pack installed.)'
+ sys.exit(1)
+
+ print path
+ return path
+
+
+# Find pkg-config so we can find the libraries we need.
+pkg_config = find_command('pkg-config')
+
+
+def pc_exists(pkg):
+ '''Check whether pkg-config thinks a library exists.'''
+ if os.spawnl(os.P_WAIT, pkg_config, 'pkg-config', '--exists', pkg) == 0:
+ return True
+ else:
+ return False
+
+
+# {py26hack} - Python 2.7 has subprocess.check_output for this purpose.
+def grab_stdout(*args, **kw):
+ '''Obtain standard output from a subprocess invocation, raising an exception
+ if the subprocess fails.'''
+
+ kw['stdout'] = subprocess.PIPE
+ proc = subprocess.Popen(*args, **kw)
+ stdout = proc.communicate()[0]
+ if proc.returncode != 0:
+ raise RuntimeError, 'subprocess %r returned %d' % (args[0], proc.returncode)
+ return stdout
+
+
+def pc_info(pkg):
+ '''Obtain build options for a library from pkg-config and
+ return a dict that can be expanded into the argument list for
+ L{distutils.core.Extension}.'''
+
+ print 'checking for library %s...' % pkg,
+ if not pc_exists(pkg):
+ print 'not found'
+ print >>sys.stderr, 'Could not find required library "%s".' % pkg
+ if os.name == 'nt':
+ print >>sys.stderr, '(Check that you have the latest version of the dependency pack installed.)'
+ else:
+ print >>sys.stderr, '(Check that you have the appropriate development package installed.)'
+ sys.exit(1)
+
+ cflags = shlex.split(grab_stdout([pkg_config, '--cflags', pkg]))
+ libs = shlex.split(grab_stdout([pkg_config, '--libs', pkg]))
+
+ # Pick out anything interesting in the cflags and libs, and
+ # silently drop the rest.
+ def def_split(x):
+ pair = list(x.split('=', 1))
+ if len(pair) == 1:
+ pair.append(None)
+ return tuple(pair)
+ info = {
+ 'define_macros': [def_split(x[2:]) for x in cflags if x[:2] == '-D'],
+ 'include_dirs': [x[2:] for x in cflags if x[:2] == '-I'],
+ 'libraries': [x[2:] for x in libs if x[:2] == '-l'],
+ 'library_dirs': [x[2:] for x in libs if x[:2] == '-L'],
+ }
+
+ print 'ok'
+ return info
+
+
+ogg_info = pc_info('ogg')
+theoradec_info = pc_info('theoradec')
+glib_info = pc_info('glib-2.0')
+swscale_info = pc_info('libswscale')
+if os.name == 'nt':
+ # Windows systems: we just know what the OpenGL library is.
+ gl_info = {'libraries': ['opengl32']}
+ # And glib needs a slight hack to work correctly.
+ glib_info['define_macros'].append(('inline', '__inline'))
+else:
+ # Other systems: we ask pkg-config.
+ gl_info = pc_info('gl')
+# Build a similar info record for the numpy headers.
+numpy_info = {'include_dirs': [np.get_include()]}
+
+
+def combine_info(*args):
+ '''Combine multiple result dicts from L{pc_info} into one.'''
+
+ info = {
+ 'define_macros': [],
+ 'include_dirs': [],
+ 'libraries': [],
+ 'library_dirs': [],
+ }
+
+ for a in args:
+ info['define_macros'].extend(a.get('define_macros', []))
+ info['include_dirs'].extend(a.get('include_dirs', []))
+ info['libraries'].extend(a.get('libraries', []))
+ info['library_dirs'].extend(a.get('library_dirs', []))
+
+ return info
+
+
+# Extend the build_ext command further to rebuild the import libraries on
+# an MSVC build under Windows so they actually work.
+class build_ext(_build_ext):
+ def run(self, *args, **kw):
+ if self.compiler is None:
+ self.compiler = distutils.ccompiler.get_default_compiler()
+ if self.compiler == 'msvc':
+ msvc = distutils.ccompiler.new_compiler(compiler='msvc', verbose=self.verbose, dry_run=self.dry_run, force=self.force)
+ msvc.initialize()
+ for deffile in glob.glob(os.path.join('..', 'win32', 'deps', 'lib', '*.def')):
+ libfile = os.path.splitext(deffile)[0] + '.lib'
+ if newer(deffile, libfile):
+ msvc.spawn([msvc.lib, '/nologo', '/machine:x86', '/out:'+libfile, '/def:'+deffile])
+
+ # Also add the directory containing the msinttypes headers to the include path.
+ self.include_dirs.append(os.path.join('..', 'win32', 'deps', 'include', 'msinttypes'))
+
+ return _build_ext.run(self, *args, **kw)
+
# Add the common arguments to setup().
# This includes arguments to cause FoFiX's extension modules to be built.
setup_args.update({
'options': options,
'ext_modules': [
- Extension('cmgl', ['cmgl.pyx'], include_dirs=[np.get_include()], libraries=['opengl32' if os.name == 'nt' else 'GL']),
+ Extension('cmgl', ['cmgl.pyx'], **combine_info(numpy_info, gl_info)),
Extension('pypitch._pypitch',
language='c++',
sources=['pypitch/_pypitch.pyx', 'pypitch/pitch.cpp',
- 'pypitch/AnalyzerInput.cpp'])
+ 'pypitch/AnalyzerInput.cpp']),
+ Extension('VideoPlayer', ['VideoPlayer.pyx', 'VideoPlayerCore.c'],
+ **combine_info(gl_info, ogg_info, theoradec_info, glib_info, swscale_info))
],
'cmdclass': {'build_ext': build_ext},
})
+# If we're on Windows, add the dependency directory to the PATH so py2exe will
+# pick up necessary DLLs from there.
+if os.name == 'nt':
+ os.environ['PATH'] = os.path.abspath(os.path.join('..', 'win32', 'deps', 'bin')) + os.pathsep + os.environ['PATH']
+
# And finally...
setup(**setup_args)
diff --git a/src/tests/VideoPlayerTest.py b/src/tests/VideoPlayerTest.py
deleted file mode 100644
index f4d0782c3..000000000
--- a/src/tests/VideoPlayerTest.py
+++ /dev/null
@@ -1,152 +0,0 @@
-#####################################################################
-# -*- coding: iso-8859-1 -*- #
-# #
-# FoFiX #
-# Copyright (C) 2009 Pascal Giard #
-# #
-# This program is free software; you can redistribute it and/or #
-# modify it under the terms of the GNU General Public License #
-# as published by the Free Software Foundation; either version 2 #
-# of the License, or (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
-# GNU General Public License for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program; if not, write to the Free Software #
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, #
-# MA 02110-1301, USA. #
-#####################################################################
-
-import unittest
-import os
-import pygame
-from pygame.locals import *
-from OpenGL.GL import *
-
-from GameEngine import GameEngine
-import Config
-import Version
-from VideoPlayer import VideoPlayer
-
-# Please change these values to fit your needs.
-# Note that video codecs/format supported depends on the gstreamer plugins
-# you've installed. On my machine that means pretty much anything e.g. XviD,
-# Ogg Theora, Flash, FFmpeg H.264, etc.).
-framerate = 24 # Number of frames per seconds (-1 = as fast as it can)
-# vidSource # Path to video; relative to FoFiX data/ directory
-
-# FIXME: Should autodetect video width and height values.
-
-# Video examples
-#=====================
-# XviD/MPEG-4
-vidSource = "t1.avi"
-
-# FFmpeg H.264
-# vidSource = "t2.m4v"
-
-# Macromedia Flash
-# vidSource = "t3.flv"
-# vidSource = "t3.1.flv" # 24 seconds
-
-# Xiph Ogg Theora
-# vidSource = "t4.ogv"
-
-class VideoPlayerTest(unittest.TestCase):
- # Simplest way to use the video player, use it as a Layer
- def testVideoPlayerLayer(self):
- config = Config.load(Version.PROGRAM_UNIXSTYLE_NAME + ".ini", setAsDefault = True)
- self.e = GameEngine(config)
- winWidth, winHeight = (self.e.view.geometry[2], self.e.view.geometry[3])
- vidPlayer = VideoPlayer(framerate, self.src, (winWidth, winHeight),
- loop = False, startTime = 10000, endTime = 14000)
- self.e.view.pushLayer(vidPlayer)
- while not vidPlayer.finished:
- self.e.run()
- self.e.view.popLayer(vidPlayer)
- self.e.audio.close()
- self.e.quit()
-
- # Keep tight control over the video player
- def testVideoPlayerSlave(self):
- winWidth, winHeight = 800, 600
- pygame.init()
- flags = DOUBLEBUF|OPENGL|HWPALETTE|HWSURFACE
- pygame.display.set_mode((winWidth, winHeight), flags)
- vidPlayer = VideoPlayer(framerate, self.src, (winWidth, winHeight))
- glViewport(0, 0, winWidth, winHeight) # Both required as...
- glScissor(0, 0, winWidth, winHeight) # ...GameEngine changes it
- glClearColor(0, 0, 0, 1.)
- while not vidPlayer.finished:
- vidPlayer.run()
- vidPlayer.render()
- pygame.display.flip()
- pygame.quit()
-
- # Grab the texture, use the CallList and do whatever we want with it;
- # We could also _just_ use the texture and take care of the polygons ourselves
- def testVideoPlayerSlaveShowOff(self):
- winWidth, winHeight = 500, 500
- pygame.init()
- flags = DOUBLEBUF|OPENGL|HWPALETTE|HWSURFACE
- pygame.display.set_mode((winWidth, winHeight), flags)
- vidPlayer = VideoPlayer(-1, self.src, (winWidth, winHeight))
- glViewport(0, 0, winWidth, winHeight) # Both required as...
- glScissor(0, 0, winWidth, winHeight) # ...GameEngine changes it
- glClearColor(0, 0, 0, 1.)
- x, y = 0.0, 1.0
- fx, fy, ftheta = 1, 1, 1
- theta = 0.0
- time = 0.0
- clock = pygame.time.Clock()
- while not vidPlayer.finished:
- vidPlayer.run()
- vidPlayer.textureUpdate()
- # Save and clear both transformation matrices
- glMatrixMode(GL_PROJECTION)
- glPushMatrix()
- glLoadIdentity()
- glMatrixMode(GL_MODELVIEW)
- glPushMatrix()
- glLoadIdentity()
-
- glClear(GL_COLOR_BUFFER_BIT)
- glColor3f(1., 1., 1.)
- vidPlayer.videoTex.bind()
- glTranslatef(x, y, 0)
- glRotatef(theta, 0, 0, 1.)
- glScalef(.5, .5, 1.)
- glCallList(vidPlayer.videoList)
-
- # Restore both transformation matrices
- glPopMatrix()
- glMatrixMode(GL_PROJECTION)
- glPopMatrix()
-
- pygame.display.flip()
-
- x = (x + fx*time)
- y = (y + fy*time)
- theta = theta + ftheta
- if x > 1.0 or x < -1.0:
- fx = fx * -1
- if y > 1.0 or y < -1.0:
- fy = fy * -1
- if theta > 90 or theta < -90:
- ftheta = ftheta * -1
- time = time + 0.00001
- clock.tick(60)
- pygame.quit()
-
- def setUp(self):
- self.src = os.path.join(Version.dataPath(), vidSource)
- self.assert_(os.path.exists(self.src), "File %s does not exist!" % self.src)
-
- def tearDown(self):
- pass
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/win32/makedefs.py b/win32/makedefs.py
new file mode 100755
index 000000000..f55f324cb
--- /dev/null
+++ b/win32/makedefs.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# Script to create .defs from a folder of MinGW implibs and a folder of DLLs.
+# Copyright (C) 2010 John Stumpo
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+import struct
+import sys
+import glob
+import shlex
+import re
+import subprocess
+
+if len(sys.argv) != 4:
+ sys.stderr.write('''Usage: %s [implib-dir] [dll-dir] [identify-cmd]
+For each MinGW-style import library in implib-dir, locates the corresponding
+DLL in dll-dir and creates a .def file for it in implib-dir, which MSVC
+tools can be run on to create an MSVC-compatible implib.
+
+identify-cmd is run on each implib to discover what DLL it goes with. It is
+split into an argument list using sh-style rules, then the implib name is
+added to the end of the argument list.
+
+The .def files are named [whatever goes after -l to link to the lib].def
+''' % sys.argv[0])
+ sys.exit(1)
+
+def make_def(file):
+ f = open(file, 'rb')
+ if f.read(2) != 'MZ':
+ raise ValueError, 'Incorrect magic number in file.'
+ f.seek(60)
+ pe_header_offset = struct.unpack('>sys.stderr, 'Could not get a unique DLL name from %s.' % implib
+ continue
+ dllname = dllnames[0].rstrip('\n')
+
+ for dll in dlls:
+ if dll.lower() == dllname.lower():
+ def_contents = make_def(os.path.join(sys.argv[2], dll))
+ open(os.path.join(sys.argv[1], dash_l_name+'.def'), 'w').write(def_contents)
diff --git a/win32/makedeps-cross.sh b/win32/makedeps-cross.sh
new file mode 100755
index 000000000..31e1481e0
--- /dev/null
+++ b/win32/makedeps-cross.sh
@@ -0,0 +1,294 @@
+#!/bin/sh -e
+# Script to cross-compile FoFiX's dependency libraries for Win32.
+# (Derived from a similar script I wrote for Performous.)
+# Copyright (C) 2010 John Stumpo
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+die () { echo "$@" >&2 ; exit 1 ; }
+
+assert_binary_on_path () {
+ if which "$1" >/dev/null 2>&1; then
+ echo found program "$1"
+ else
+ echo did not find "$1", which is required
+ exit 1
+ fi
+}
+
+if test -z "$CROSS_TOOL_PREFIX"; then
+ export CROSS_TOOL_PREFIX=i586-mingw32msvc
+fi
+echo "Using cross compilers prefixed with '$CROSS_TOOL_PREFIX-'."
+echo "(Set CROSS_TOOL_PREFIX to change this; don't include the trailing hyphen.)"
+if test -z "$CROSS_GCC"; then
+ assert_binary_on_path "$CROSS_TOOL_PREFIX"-gcc
+ export CROSS_GCC="$CROSS_TOOL_PREFIX"-gcc
+ assert_binary_on_path "$CROSS_TOOL_PREFIX"-g++
+ export CROSS_GXX="$CROSS_TOOL_PREFIX"-g++
+ assert_binary_on_path "$CROSS_TOOL_PREFIX"-ar
+ export CROSS_AR="$CROSS_TOOL_PREFIX"-ar
+ assert_binary_on_path "$CROSS_TOOL_PREFIX"-ranlib
+ export CROSS_RANLIB="$CROSS_TOOL_PREFIX"-ranlib
+ assert_binary_on_path "$CROSS_TOOL_PREFIX"-ld
+ export CROSS_LD="$CROSS_TOOL_PREFIX"-ld
+ assert_binary_on_path "$CROSS_TOOL_PREFIX"-dlltool
+ export CROSS_DLLTOOL="$CROSS_TOOL_PREFIX"-dlltool
+ assert_binary_on_path "$CROSS_TOOL_PREFIX"-nm
+ export CROSS_NM="$CROSS_TOOL_PREFIX"-nm
+ assert_binary_on_path "$CROSS_TOOL_PREFIX"-windres
+ export CROSS_WINDRES="$CROSS_TOOL_PREFIX"-windres
+fi
+if test -z "$WINE"; then
+ assert_binary_on_path wine
+ export WINE=wine
+fi
+echo "wine: $WINE"
+
+assert_binary_on_path autoreconf
+assert_binary_on_path libtoolize
+assert_binary_on_path make
+assert_binary_on_path pkg-config
+assert_binary_on_path python
+assert_binary_on_path svn
+assert_binary_on_path tar
+assert_binary_on_path unzip
+assert_binary_on_path wget
+
+SCRIPTDIR="`pwd`"
+export PREFIX="`pwd`"/deps
+export WINEPREFIX="`pwd`"/wine
+mkdir -pv "$PREFIX"/bin "$PREFIX"/lib "$PREFIX"/include "$PREFIX"/lib/pkgconfig "$PREFIX"/build-stamps
+if test -n "$KEEPTEMP"; then
+ RM_RF=true
+ echo 'Keeping the built source trees, as you requested.'
+else
+ RM_RF="rm -rf"
+ echo 'Unpacked source trees will be removed after compilation.'
+ echo '(Set KEEPTEMP to any value to preserve them.)'
+fi
+
+echo 'setting up wine environment'
+$WINE reg add 'HKCU\Environment' /v PATH /d Z:"`echo "$PREFIX" | tr '/' '\\'`"\\bin
+
+echo 'creating pkg-config wrapper for cross-compiled environment'
+cat >"$PREFIX"/bin/pkg-config <"$PREFIX"/bin/wine-shwrap <<"EOF"
+#!/bin/sh -e
+path="`(cd $(dirname "$1") && pwd)`/`basename "$1"`"
+echo '#!/bin/bash -e' >"$1"
+echo '$WINE '"$path"'.exe "$@" | tr -d '"'\\\015'" >>"$1"
+echo 'exit ${PIPESTATUS[0]}' >>"$1"
+chmod 0755 "$1"
+EOF
+chmod 0755 $PREFIX/bin/wine-shwrap
+
+export PATH="$PREFIX"/bin:"$PATH"
+
+download () {
+ basename="`basename "$1"`"
+ if test ! -f "$basename"; then
+ wget -c -O "$basename".part "$1"
+ mv -v "$basename".part "$basename"
+ fi
+}
+
+# We use win-iconv instead of full-fledged GNU libiconv because it still does
+# everything the other deps need and is far smaller.
+WINICONV="win-iconv-0.0.2"
+if test ! -f "$PREFIX"/build-stamps/win-iconv; then
+ download http://win-iconv.googlecode.com/files/$WINICONV.tar.bz2
+ tar jxvf $WINICONV.tar.bz2
+ cd $WINICONV
+ make clean
+ make -n iconv.dll win_iconv.exe | sed -e 's/^/$CROSS_TOOL_PREFIX-/' | sh -ex
+ $CROSS_GCC -mdll -o iconv.dll -Wl,--out-implib,libiconv.a iconv.def win_iconv.o
+ cp -v iconv.dll win_iconv.exe "$PREFIX"/bin
+ cp -v iconv.h "$PREFIX"/include
+ echo '' >>"$PREFIX"/include/iconv.h # squelch warnings about no newline at the end
+ sed -i -e 's://.*$::' "$PREFIX"/include/iconv.h # squelch warnings about C++ comments
+ cp -v libiconv.a "$PREFIX"/lib
+ cd ..
+ touch "$PREFIX"/build-stamps/win-iconv
+ $RM_RF $WINICONV
+fi
+
+# zlib
+ZLIB="zlib-1.2.5"
+if test ! -f "$PREFIX"/build-stamps/zlib; then
+ download http://www.zlib.net/$ZLIB.tar.bz2
+ tar jxvf $ZLIB.tar.bz2
+ cd $ZLIB
+ make -f win32/Makefile.gcc PREFIX="$CROSS_TOOL_PREFIX"- zlib1.dll
+ cp -v zlib.h zconf.h "$PREFIX"/include
+ cp -v zlib1.dll "$PREFIX"/bin
+ cp -v libzdll.a "$PREFIX"/lib/libz.a
+ cd ..
+ touch "$PREFIX"/build-stamps/zlib
+ $RM_RF $ZLIB
+fi
+
+# Flags passed to every dependency's ./configure script, for those deps that use autoconf and friends.
+COMMON_AUTOCONF_FLAGS="--prefix=$PREFIX --host=$CROSS_TOOL_PREFIX --disable-static --enable-shared CPPFLAGS=-I$PREFIX/include LDFLAGS=-L$PREFIX/lib"
+
+# Runtime (libintl) of GNU Gettext
+GETTEXT="gettext-0.18.1.1"
+if test ! -f "$PREFIX"/build-stamps/gettext-runtime; then
+ download http://ftp.gnu.org/gnu/gettext/$GETTEXT.tar.gz
+ tar zxvf $GETTEXT.tar.gz
+ cd $GETTEXT/gettext-runtime
+ ./configure $COMMON_AUTOCONF_FLAGS --enable-relocatable --disable-libasprintf --disable-java --disable-csharp
+ make
+ make install
+ cd ../..
+ touch "$PREFIX"/build-stamps/gettext-runtime
+ $RM_RF $GETTEXT
+fi
+
+# GLib
+GLIB="glib-2.26.1"
+if test ! -f "$PREFIX"/build-stamps/glib; then
+ download http://ftp.gnome.org/pub/GNOME/sources/glib/2.26/$GLIB.tar.bz2
+ tar jxvf $GLIB.tar.bz2
+ cd $GLIB
+ ./configure $COMMON_AUTOCONF_FLAGS
+ make -C glib
+ make -C gthread
+ make -C gobject glib-genmarshal.exe
+ wine-shwrap gobject/glib-genmarshal
+ make
+ make install
+ cd ..
+ touch "$PREFIX"/build-stamps/glib
+ $RM_RF $GLIB
+fi
+
+# pkg-config
+PKGCONFIG="pkg-config-0.25"
+if test ! -f "$PREFIX"/build-stamps/pkg-config; then
+ download http://pkgconfig.freedesktop.org/releases/$PKGCONFIG.tar.gz
+ tar zxvf $PKGCONFIG.tar.gz
+ cd $PKGCONFIG
+ ./configure $COMMON_AUTOCONF_FLAGS
+ make
+ make install
+ cd ..
+ touch "$PREFIX"/build-stamps/pkg-config
+ $RM_RF $PKGCONFIG
+fi
+
+# The rest of GNU Gettext
+if test ! -f "$PREFIX"/build-stamps/gettext; then
+ download http://ftp.gnu.org/gnu/gettext/$GETTEXT.tar.gz
+ tar zxvf $GETTEXT.tar.gz
+ cd $GETTEXT
+ ./configure $COMMON_AUTOCONF_FLAGS --enable-relocatable --disable-libasprintf --disable-java --disable-csharp CXX="$CROSS_GXX"
+ make
+ make install
+ cd ..
+ touch "$PREFIX"/build-stamps/gettext
+ $RM_RF $GETTEXT
+fi
+
+# libogg
+LIBOGG="libogg-1.2.1"
+if test ! -f "$PREFIX"/build-stamps/libogg; then
+ download http://downloads.xiph.org/releases/ogg/$LIBOGG.tar.gz
+ tar zxvf $LIBOGG.tar.gz
+ cd $LIBOGG
+ libtoolize
+ autoreconf # fix buggy configure test for 16-bit types
+ ./configure $COMMON_AUTOCONF_FLAGS
+ make
+ make install
+ cd ..
+ touch "$PREFIX"/build-stamps/libogg
+ $RM_RF $LIBOGG
+fi
+
+# libvorbis
+LIBVORBIS="libvorbis-1.3.2"
+if test ! -f "$PREFIX"/build-stamps/libvorbis; then
+ download http://downloads.xiph.org/releases/vorbis/$LIBVORBIS.tar.bz2
+ tar jxvf $LIBVORBIS.tar.bz2
+ cd $LIBVORBIS
+ ./configure $COMMON_AUTOCONF_FLAGS
+ make
+ make install
+ cd ..
+ touch "$PREFIX"/build-stamps/libvorbis
+ $RM_RF $LIBVORBIS
+fi
+
+# libtheora
+LIBTHEORA="libtheora-1.1.1"
+if test ! -f "$PREFIX"/build-stamps/libtheora; then
+ download http://downloads.xiph.org/releases/theora/$LIBTHEORA.tar.bz2
+ tar jxvf $LIBTHEORA.tar.bz2
+ cd $LIBTHEORA
+ ./configure $COMMON_AUTOCONF_FLAGS
+ make
+ make install
+ cd ..
+ touch "$PREFIX"/build-stamps/libtheora
+ $RM_RF $LIBTHEORA
+fi
+
+# ffmpeg
+# We only need libswscale.
+if test ! -f "$PREFIX"/build-stamps/ffmpeg; then
+ if test ! -d ffmpeg; then
+ svn co svn://svn.ffmpeg.org/ffmpeg/trunk ffmpeg
+ else
+ svn up ffmpeg
+ fi
+ cd ffmpeg
+ ./configure --prefix="$PREFIX" --cc="$CROSS_GCC" --nm="$CROSS_NM" --target-os=mingw32 --arch=i386 --disable-static --enable-shared --enable-gpl --enable-runtime-cpudetect --enable-memalign-hack --disable-everything --disable-ffmpeg --disable-ffplay --disable-ffserver --disable-ffprobe --disable-avdevice --disable-avcodec --disable-avcore --disable-avformat --disable-avfilter
+ sed -i -e 's/-Werror=[^ ]*//g' config.mak
+ make
+ make install
+ for lib in avutil swscale; do
+ # FFmpeg symlinks its DLLs to a few different names, differing in the level
+ # of detail of their version number, rather like what is done with ELF shared
+ # libraries. Unfortunately, the real DLL for each one is *not* the one that
+ # the implibs reference (that is, the one that will be required at runtime),
+ # so we must rename it after we nuke the symlinks.
+ find "$PREFIX"/bin -type l -name "${lib}*.dll" -print0 | xargs -0 rm -f
+ libfile="`find "$PREFIX"/bin -name "${lib}*.dll" | sed -e 1q`"
+ mv -v "$libfile" "`echo "$libfile" | sed -e "s/\($lib-[0-9]*\)[.0-9]*\.dll/\1.dll/"`"
+ done
+ cd ..
+ touch "$PREFIX"/build-stamps/ffmpeg
+ $RM_RF ffmpeg
+fi
+
+# msinttypes
+# MSVC needs these to compile stuff that uses ffmpeg. Since they intentionally
+# do not work with gcc, we install them to a subdirectory of deps/include
+# and feed MSVC an appropriate include_dir option in setup.py.
+if test ! -f "$PREFIX"/build-stamps/msinttypes; then
+ download http://msinttypes.googlecode.com/files/msinttypes-r26.zip
+ unzip -od deps/include/msinttypes msinttypes-r26.zip stdint.h inttypes.h
+ touch "$PREFIX"/build-stamps/msinttypes
+fi
+
+echo "All dependencies done."
+
+echo -n "Creating .def files... "
+python makedefs.py deps/lib deps/bin "$CROSS_DLLTOOL -I"
+echo "done"
diff --git a/win32/makedist.sh b/win32/makedist.sh
new file mode 100755
index 000000000..f37582bcd
--- /dev/null
+++ b/win32/makedist.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -e
+
+mkdir -pv dist
+cp -av deps dist
+
+rm -rf dist/deps/build-stamps dist/deps/etc dist/deps/share dist/deps/lib/gettext
+rm -vf dist/deps/lib/*.la dist/deps/bin/wine-shwrap dist/deps/bin/pkg-config
+i586-mingw32msvc-strip --strip-all dist/deps/bin/*.exe dist/deps/bin/*.dll
+ZIPFILE="fofix-win32-deppack-`date +%Y%m%d`.zip"
+rm -f "$ZIPFILE"
+(cd dist && zip -9r ../"$ZIPFILE" deps)