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)