changeset 546:94dd9862c470

Rename the eosd script into pytouhou, and remove the obsolete pcb one.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Wed, 21 May 2014 20:52:42 +0200
parents bcff39c920ab
children e35bef07290d
files anmviewer eosd pcb pytouhou/games/pcb.py scripts/anmviewer scripts/pytouhou setup.py
diffstat 7 files changed, 332 insertions(+), 511 deletions(-) [+]
line wrap: on
line diff
--- a/anmviewer	Thu May 15 20:14:54 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-#!/usr/bin/env python2
-# -*- encoding: utf-8 -*-
-##
-## Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
-##
-## 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; version 3 only.
-##
-## 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.
-##
-
-import argparse
-import os
-
-from pytouhou.ui.window import Window
-from pytouhou.resource.loader import Loader
-from pytouhou.ui.anmrenderer import ANMRenderer
-
-
-def main(path, data, name, script, entry, sprites, fixed_pipeline):
-    resource_loader = Loader()
-    resource_loader.scan_archives(os.path.join(path, name) for name in data)
-
-    window = Window((384, 448), fixed_pipeline=fixed_pipeline, sound=False)
-
-    # Get out animation
-    anm = resource_loader.get_anm(name)
-    renderer = ANMRenderer(window, resource_loader, anm[entry], script, sprites)
-    window.set_runner(renderer)
-    window.run()
-
-
-parser = argparse.ArgumentParser(description='Viewer of ANM files, archives containing animations used in Touhou games.')
-
-parser.add_argument('data', metavar='DAT', default=('CM.DAT', 'ST.DAT'), nargs='*', help='Game’s .DAT data files')
-parser.add_argument('-p', '--path', metavar='DIRECTORY', default='.', help='Game directory path.')
-parser.add_argument('--anm', metavar='ANM', required=True, help='Select an ANM')
-parser.add_argument('--script', metavar='SCRIPT', type=int, default=0, help='First script to play')
-parser.add_argument('--entry', metavar='ENTRY', type=int, default=0, help='Entry to display, in multi-entries ANMs.')
-parser.add_argument('--sprites', action='store_true', default=False, help='Display sprites instead of scripts.')
-parser.add_argument('--fixed-pipeline', action='store_true', help='Use the fixed pipeline instead of the new programmable one.')
-
-args = parser.parse_args()
-
-main(args.path, tuple(args.data), args.anm, args.script, args.entry, args.sprites,
-     args.fixed_pipeline)
--- a/eosd	Thu May 15 20:14:54 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,280 +0,0 @@
-#!/usr/bin/env python2
-# -*- encoding: utf-8 -*-
-##
-## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
-##
-## 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; version 3 only.
-##
-## 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.
-##
-
-import argparse
-import os
-
-
-pathsep = os.path.pathsep
-default_data = (pathsep.join(('CM.DAT', 'th06*_CM.DAT', '*CM.DAT', '*cm.dat')),
-                pathsep.join(('ST.DAT', 'th6*ST.DAT', '*ST.DAT', '*st.dat')),
-                pathsep.join(('IN.DAT', 'th6*IN.DAT', '*IN.DAT', '*in.dat')),
-                pathsep.join(('MD.DAT', 'th6*MD.DAT', '*MD.DAT', '*md.dat')),
-                pathsep.join(('102h.exe', '102*.exe', '東方紅魔郷.exe', '*.exe')))
-
-
-parser = argparse.ArgumentParser(description='Libre reimplementation of the Touhou 6 engine.')
-
-parser.add_argument('data', metavar='DAT', default=default_data, nargs='*', help='Game’s data files')
-parser.add_argument('-p', '--path', metavar='DIRECTORY', default='.', help='Game directory path.')
-parser.add_argument('--debug', action='store_true', help='Set unlimited continues, and perhaps other debug features.')
-parser.add_argument('--verbosity', metavar='VERBOSITY', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], help='Select the wanted logging level.')
-
-game_group = parser.add_argument_group('Game options')
-game_group.add_argument('-s', '--stage', metavar='STAGE', type=int, default=None, help='Stage, 1 to 7 (Extra), nothing means story mode.')
-game_group.add_argument('-r', '--rank', metavar='RANK', type=int, default=0, help='Rank, from 0 (Easy, default) to 3 (Lunatic).')
-game_group.add_argument('-c', '--character', metavar='CHARACTER', type=int, default=0, help='Select the character to use, from 0 (ReimuA, default) to 3 (MarisaB).')
-game_group.add_argument('-b', '--boss-rush', action='store_true', help='Fight only bosses.')
-game_group.add_argument('--game', metavar='GAME', choices=['EoSD'], default='EoSD', help='Select the game engine to use.')
-game_group.add_argument('--hints', metavar='HINTS', default=None, help='Hints file, to display text while playing.')
-
-replay_group = parser.add_argument_group('Replay options')
-replay_group.add_argument('--replay', metavar='REPLAY', help='Select a file to replay.')
-replay_group.add_argument('--save-replay', metavar='REPLAY', help='Save the upcoming game into a replay file.')
-replay_group.add_argument('--skip-replay', action='store_true', help='Skip the replay and start to play when it’s finished.')
-
-netplay_group = parser.add_argument_group('Netplay options')
-netplay_group.add_argument('--port', metavar='PORT', type=int, default=0, help='Local port to use.')
-netplay_group.add_argument('--remote', metavar='REMOTE', default=None, help='Remote address.')
-netplay_group.add_argument('--friendly-fire', action='store_true', help='Allow friendly-fire during netplay.')
-
-graphics_group = parser.add_argument_group('Graphics options')
-graphics_group.add_argument('--backend', metavar='BACKEND', choices=['opengl', 'sdl'], default='opengl', help='Which backend to use (opengl or sdl for now).')
-graphics_group.add_argument('--fixed-pipeline', action='store_true', help='Use the fixed pipeline instead of the new programmable one.')
-graphics_group.add_argument('--single-buffer', action='store_true', help='Disable double buffering.')
-graphics_group.add_argument('--fps-limit', metavar='FPS', default=-1, type=int, help='Set fps limit. A value of 0 disables fps limiting, while a negative value limits to 60 fps if and only if vsync doesn’t work.')
-graphics_group.add_argument('--no-background', action='store_false', help='Disable background display (huge performance boost on slow systems).')
-graphics_group.add_argument('--no-particles', action='store_false', help='Disable particles handling (huge performance boost on slow systems).')
-graphics_group.add_argument('--no-sound', action='store_false', help='Disable music and sound effects.')
-
-args = parser.parse_args()
-
-
-import sys
-import logging
-
-if args.backend == 'opengl':
-    try:
-        from pytouhou.ui.opengl.gamerenderer import GameRenderer
-        opengl = True
-    except ImportError:
-        args.backend = 'sdl'
-
-if args.backend == 'sdl':
-    from pytouhou.ui.sdl.gamerenderer import GameRenderer
-    opengl = False
-
-from pytouhou.lib.sdl import SDL, show_simple_message_box
-from pytouhou.ui.window import Window
-from pytouhou.resource.loader import Loader
-from pytouhou.ui.gamerunner import GameRunner
-from pytouhou.game.player import GameOver
-from pytouhou.formats.t6rp import T6RP, Level
-from pytouhou.utils.random import Random
-from pytouhou.vm.msgrunner import NextStage
-from pytouhou.formats.hint import Hint
-from pytouhou.network import Network
-
-
-if args.game == 'EoSD':
-    from pytouhou.games.eosd import EoSDCommon as Common, EoSDGame as Game
-
-
-class GameBossRush(Game):
-    def run_iter(self, keystates):
-        for i in range(20):
-            skip = not (self.enemies or self.items or self.lasers
-                        or self.bullets or self.cancelled_bullets)
-            if skip:
-                keystates = [k & ~1 for k in keystates]
-            Game.run_iter(self, [0 if i else k | 256 for k in keystates])
-            if not self.enemies and self.frame % 90 == 0:
-                for player in self.players:
-                    if player.power < 128:
-                        player.power += 1
-            if not skip:
-                break
-
-    def cleanup(self):
-        boss_wait = any(ecl_runner.boss_wait for ecl_runner in self.ecl_runners)
-        if not (self.boss or self.msg_wait or boss_wait):
-            self.enemies = [enemy for enemy in self.enemies
-                            if enemy.boss_callback or enemy.frame > 1]
-            for laser in self.lasers:
-                if laser.frame <= 1:
-                    laser.removed = True
-            self.lasers = [laser for laser in self.lasers if laser.frame > 1]
-            self.bullets = [bullet for bullet in self.bullets if bullet.frame > 1]
-        Game.cleanup(self)
-
-
-def main(window, path, data, stage_num, rank, character, replay, save_filename,
-         skip_replay, boss_rush, debug, enable_background, enable_particles,
-         hints, verbosity, port, remote, friendly_fire):
-
-    resource_loader = Loader(path)
-
-    try:
-        resource_loader.scan_archives(data)
-    except IOError:
-        show_simple_message_box(u'Some data files were not found, did you forget the -p option?')
-        sys.exit(1)
-
-    if stage_num is None:
-        story = True
-        stage_num = 1
-        continues = 3
-    else:
-        story = False
-        continues = 0
-
-    if debug:
-        if not verbosity:
-            verbosity = 'DEBUG'
-        continues = -1  # Infinite lives
-
-    if verbosity:
-        logging.basicConfig(level=logging.__getattribute__(verbosity))
-
-    if replay:
-        with open(replay, 'rb') as file:
-            replay = T6RP.read(file)
-        rank = replay.rank
-        character = replay.character
-
-    save_keystates = None
-    if save_filename:
-        save_replay = T6RP()
-        save_replay.rank = rank
-        save_replay.character = character
-
-    difficulty = 16
-
-    if port != 0:
-        if remote:
-            remote_addr, remote_port = remote.split(':')
-            addr = remote_addr, int(remote_port)
-            selected_player = 0
-        else:
-            addr = None
-            selected_player = 1
-
-        prng = Random(0)
-        con = Network(port, addr, selected_player)
-        characters = [1, 3]
-    else:
-        con = None
-        selected_player = 0
-        characters = [character]
-
-    if hints:
-        with open(hints, 'rb') as file:
-            hints = Hint.read(file)
-
-    game_class = GameBossRush if boss_rush else Game
-
-    common = Common(resource_loader, characters, continues, stage_num - 1)
-    renderer = GameRenderer(resource_loader, window)
-    runner = GameRunner(window, renderer, common, resource_loader, skip_replay, con)
-    window.set_runner(runner)
-
-    while True:
-        first_player = common.players[0]
-
-        if replay:
-            level = replay.levels[stage_num - 1]
-            if not level:
-                raise Exception
-
-            prng = Random(level.random_seed)
-
-            #TODO: apply the replay to the other players.
-            #TODO: see if the stored score is used or if it’s the one from the previous stage.
-            if stage_num != 1 and stage_num - 2 in replay.levels:
-                previous_level = replay.levels[stage_num - 1]
-                first_player.score = previous_level.score
-                first_player.effective_score = previous_level.score
-            first_player.points = level.point_items
-            first_player.power = level.power
-            first_player.lives = level.lives
-            first_player.bombs = level.bombs
-            difficulty = level.difficulty
-        elif port == 0:
-            prng = Random()
-
-        if save_filename:
-            if not replay:
-                save_replay.levels[stage_num - 1] = level = Level()
-                level.random_seed = prng.seed
-                level.score = first_player.score
-                level.point_items = first_player.points
-                level.power = first_player.power
-                level.lives = first_player.lives
-                level.bombs = first_player.bombs
-                level.difficulty = difficulty
-            save_keystates = []
-
-        hints_stage = hints.stages[stage_num - 1] if hints else None
-
-        game = game_class(resource_loader, stage_num, rank, difficulty,
-                          common, prng, hints_stage, friendly_fire)
-
-        if not enable_particles:
-            def new_particle(pos, anim, amp, number=1, reverse=False, duration=24):
-                pass
-            game.new_particle = new_particle
-
-        background = game.background if enable_background else None
-        runner.load_game(game, background, game.std.bgms, replay, save_keystates)
-
-        try:
-            # Main loop
-            window.run()
-            break
-        except NextStage:
-            if not story or stage_num == (7 if boss_rush else 6 if rank > 0 else 5):
-                break
-            stage_num += 1
-        except GameOver:
-            show_simple_message_box(u'Game over!')
-            break
-        finally:
-            if save_filename:
-                last_key = -1
-                for time, key in enumerate(save_keystates):
-                    if key != last_key:
-                        level.keys.append((time, key, 0))
-                    last_key = key
-
-    window.set_runner(None)
-
-    if save_filename:
-        with open(save_filename, 'wb+') as file:
-            save_replay.write(file)
-
-
-with SDL(sound=args.no_sound):
-    window = Window(double_buffer=(not args.single_buffer),
-                    fps_limit=args.fps_limit,
-                    fixed_pipeline=args.fixed_pipeline, opengl=opengl)
-
-    main(window, args.path, tuple(args.data), args.stage, args.rank,
-         args.character, args.replay, args.save_replay, args.skip_replay,
-         args.boss_rush, args.debug, args.no_background, args.no_particles,
-         args.hints, args.verbosity, args.port, args.remote,
-         args.friendly_fire)
-
-    import gc
-    gc.collect()
--- a/pcb	Thu May 15 20:14:54 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-#!/usr/bin/env python2
-# -*- encoding: utf-8 -*-
-##
-## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
-##
-## 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; version 3 only.
-##
-## 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.
-##
-
-import argparse
-import os
-
-import pyximport
-pyximport.install()
-
-from pytouhou.resource.loader import Loader
-from pytouhou.game.background import Background
-from pytouhou.ui.gamerunner import GameRunner
-from pytouhou.games.pcb import PCBGame
-from pytouhou.game.player import PlayerState
-
-
-def main(path, stage_num, rank, character, data):
-    resource_loader = Loader(path)
-    resource_loader.scan_archives(data)
-    game = PCBGame(resource_loader, [PlayerState(character=character)], stage_num, rank, 16)
-
-    # Load stage data
-    stage = resource_loader.get_stage('stage%d.std' % stage_num)
-
-    background_anm_wrapper = resource_loader.get_anm_wrapper(('stg%dbg.anm' % stage_num,))
-    background = Background(stage, background_anm_wrapper)
-
-    # Main loop
-    runner = GameRunner(resource_loader, game, background)
-    runner.start()
-
-
-parser = argparse.ArgumentParser(description='Libre reimplementation of the Touhou 6 engine.')
-
-parser.add_argument('data', metavar='DAT', default=('Th07.dat'), nargs='*', help='Game’s .DAT data files')
-parser.add_argument('-p', '--path', metavar='DIRECTORY', default='.', help='Game directory path.')
-parser.add_argument('-s', '--stage', metavar='STAGE', type=int, required=True, help='Stage, 1 to 6, 7 (Extra) and 8 (Phantasm).')
-parser.add_argument('-r', '--rank', metavar='RANK', type=int, default=0, help='Rank, from 0 (Easy, default) to 3 (Lunatic).')
-parser.add_argument('-c', '--character', metavar='CHARACTER', type=int, default=0, help='Select the character to use, from 0 (ReimuA, default) to 5 (SakuyaB).')
-
-args = parser.parse_args()
-
-main(args.path, args.stage, args.rank, args.character, tuple(args.data))
--- a/pytouhou/games/pcb.py	Thu May 15 20:14:54 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-# -*- encoding: utf-8 -*-
-##
-## Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
-##
-## 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; version 3 only.
-##
-## 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.
-##
-
-from pytouhou.utils.interpolator import Interpolator
-
-from pytouhou.game.game import Game
-from pytouhou.game.bullettype import BulletType
-from pytouhou.game.bullettype import LaserType
-from pytouhou.game.itemtype import ItemType
-from pytouhou.game.player import Player
-from pytouhou.game.orb import Orb
-
-from math import pi
-
-
-class PCBGame(Game):
-    def __init__(self, resource_loader, player_states, stage, rank, difficulty,
-                 bullet_types=None, laser_types=None, item_types=None,
-                 nb_bullets_max=640, width=384, height=448, prng=None):
-        if not bullet_types:
-            etama3 = resource_loader.get_anm_wrapper(('etama3.anm',))
-            etama4 = resource_loader.get_anm_wrapper(('etama4.anm',))
-            bullet_types = [BulletType(etama3, 0, 11, 14, 15, 16, hitbox_size=4),
-                            BulletType(etama3, 1, 12, 17, 18, 19, hitbox_size=6),
-                            BulletType(etama3, 2, 12, 17, 18, 19, hitbox_size=4),
-                            BulletType(etama3, 3, 12, 17, 18, 19, hitbox_size=6),
-                            BulletType(etama3, 4, 12, 17, 18, 19, hitbox_size=5),
-                            BulletType(etama3, 5, 12, 17, 18, 19, hitbox_size=4),
-                            BulletType(etama3, 6, 13, 20, 20, 20, hitbox_size=16),
-                            BulletType(etama3, 7, 13, 20, 20, 20, hitbox_size=11),
-                            BulletType(etama3, 8, 13, 20, 20, 20, hitbox_size=9),
-                            BulletType(etama4, 0, 1, 2, 2, 2, hitbox_size=32)]
-
-        if not laser_types:
-            laser_types = [] #TODO
-
-        if not item_types:
-            item_types = [ItemType(etama3, 0, 7), #Power
-                          ItemType(etama3, 1, 8), #Point
-                          ItemType(etama3, 2, 9), #Big power
-                          ItemType(etama3, 3, 10), #Bomb
-                          ItemType(etama3, 4, 11), #Full power
-                          ItemType(etama3, 5, 12), #1up
-                          ItemType(etama3, 6, 13)] #Star
-
-        players = [PCBPlayer(state, self, resource_loader) for state in player_states]
-
-        Game.__init__(self, resource_loader, players, stage, rank, difficulty,
-                      bullet_types, laser_types, item_types, nb_bullets_max,
-                      width, height, prng)
-
-
-
-class PCBPlayer(Player):
-    def __init__(self, state, game, resource_loader):
-        number = '%d%s' % (state.character // 2, 'b' if state.character % 2 else 'a')
-        self.sht = resource_loader.get_sht('ply0%s.sht' % number)
-        self.focused_sht = resource_loader.get_sht('ply0%ss.sht' % number)
-        anm_wrapper = resource_loader.get_anm_wrapper(('player0%d.anm' % (state.character // 2),))
-        self.anm_wrapper = anm_wrapper
-
-        Player.__init__(self, state, game, anm_wrapper)
-
-        self.orbs = [Orb(self.anm_wrapper, 128, self.state, None),
-                     Orb(self.anm_wrapper, 129, self.state, None)]
-
-        self.orbs[0].offset_x = -24
-        self.orbs[1].offset_x = 24
-
-        self.orb_dx_interpolator = None
-        self.orb_dy_interpolator = None
-
-
-    def start_focusing(self):
-        self.orb_dx_interpolator = Interpolator((24,), self._game.frame,
-                                                (8,), self._game.frame + 8,
-                                                lambda x: x ** 2)
-        self.orb_dy_interpolator = Interpolator((0,), self._game.frame,
-                                                (-32,), self._game.frame + 8)
-        self.state.focused = True
-
-
-    def stop_focusing(self):
-        self.orb_dx_interpolator = Interpolator((8,), self._game.frame,
-                                                (24,), self._game.frame + 8,
-                                                lambda x: x ** 2)
-        self.orb_dy_interpolator = Interpolator((-32,), self._game.frame,
-                                                (0,), self._game.frame + 8)
-        self.state.focused = False
-
-
-    def objects(self):
-        return self.orbs
-
-
-    def update(self, keystate):
-        Player.update(self, keystate)
-
-        if self.death_time == 0 or self._game.frame - self.death_time > 60:
-            if self.orb_dx_interpolator:
-                self.orb_dx_interpolator.update(self._game.frame)
-                dx, = self.orb_dx_interpolator.values
-                self.orbs[0].offset_x = -dx
-                self.orbs[1].offset_x = dx
-            if self.orb_dy_interpolator:
-                self.orb_dy_interpolator.update(self._game.frame)
-                dy, = self.orb_dy_interpolator.values
-                self.orbs[0].offset_y = dy
-                self.orbs[1].offset_y = dy
-
-        for orb in self.orbs:
-            orb.update()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/anmviewer	Wed May 21 20:52:42 2014 +0200
@@ -0,0 +1,50 @@
+#!/usr/bin/env python2
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+##
+## 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; version 3 only.
+##
+## 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.
+##
+
+import argparse
+import os
+
+from pytouhou.ui.window import Window
+from pytouhou.resource.loader import Loader
+from pytouhou.ui.anmrenderer import ANMRenderer
+
+
+def main(path, data, name, script, entry, sprites, fixed_pipeline):
+    resource_loader = Loader()
+    resource_loader.scan_archives(os.path.join(path, name) for name in data)
+
+    window = Window((384, 448), fixed_pipeline=fixed_pipeline, sound=False)
+
+    # Get out animation
+    anm = resource_loader.get_anm(name)
+    renderer = ANMRenderer(window, resource_loader, anm[entry], script, sprites)
+    window.set_runner(renderer)
+    window.run()
+
+
+parser = argparse.ArgumentParser(description='Viewer of ANM files, archives containing animations used in Touhou games.')
+
+parser.add_argument('data', metavar='DAT', default=('CM.DAT', 'ST.DAT'), nargs='*', help='Game’s .DAT data files')
+parser.add_argument('-p', '--path', metavar='DIRECTORY', default='.', help='Game directory path.')
+parser.add_argument('--anm', metavar='ANM', required=True, help='Select an ANM')
+parser.add_argument('--script', metavar='SCRIPT', type=int, default=0, help='First script to play')
+parser.add_argument('--entry', metavar='ENTRY', type=int, default=0, help='Entry to display, in multi-entries ANMs.')
+parser.add_argument('--sprites', action='store_true', default=False, help='Display sprites instead of scripts.')
+parser.add_argument('--fixed-pipeline', action='store_true', help='Use the fixed pipeline instead of the new programmable one.')
+
+args = parser.parse_args()
+
+main(args.path, tuple(args.data), args.anm, args.script, args.entry, args.sprites,
+     args.fixed_pipeline)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/pytouhou	Wed May 21 20:52:42 2014 +0200
@@ -0,0 +1,280 @@
+#!/usr/bin/env python2
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
+##
+## 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; version 3 only.
+##
+## 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.
+##
+
+import argparse
+import os
+
+
+pathsep = os.path.pathsep
+default_data = (pathsep.join(('CM.DAT', 'th06*_CM.DAT', '*CM.DAT', '*cm.dat')),
+                pathsep.join(('ST.DAT', 'th6*ST.DAT', '*ST.DAT', '*st.dat')),
+                pathsep.join(('IN.DAT', 'th6*IN.DAT', '*IN.DAT', '*in.dat')),
+                pathsep.join(('MD.DAT', 'th6*MD.DAT', '*MD.DAT', '*md.dat')),
+                pathsep.join(('102h.exe', '102*.exe', '東方紅魔郷.exe', '*.exe')))
+
+
+parser = argparse.ArgumentParser(description='Libre reimplementation of the Touhou 6 engine.')
+
+parser.add_argument('data', metavar='DAT', default=default_data, nargs='*', help='Game’s data files')
+parser.add_argument('-p', '--path', metavar='DIRECTORY', default='.', help='Game directory path.')
+parser.add_argument('--debug', action='store_true', help='Set unlimited continues, and perhaps other debug features.')
+parser.add_argument('--verbosity', metavar='VERBOSITY', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], help='Select the wanted logging level.')
+
+game_group = parser.add_argument_group('Game options')
+game_group.add_argument('-s', '--stage', metavar='STAGE', type=int, default=None, help='Stage, 1 to 7 (Extra), nothing means story mode.')
+game_group.add_argument('-r', '--rank', metavar='RANK', type=int, default=0, help='Rank, from 0 (Easy, default) to 3 (Lunatic).')
+game_group.add_argument('-c', '--character', metavar='CHARACTER', type=int, default=0, help='Select the character to use, from 0 (ReimuA, default) to 3 (MarisaB).')
+game_group.add_argument('-b', '--boss-rush', action='store_true', help='Fight only bosses.')
+game_group.add_argument('--game', metavar='GAME', choices=['EoSD'], default='EoSD', help='Select the game engine to use.')
+game_group.add_argument('--hints', metavar='HINTS', default=None, help='Hints file, to display text while playing.')
+
+replay_group = parser.add_argument_group('Replay options')
+replay_group.add_argument('--replay', metavar='REPLAY', help='Select a file to replay.')
+replay_group.add_argument('--save-replay', metavar='REPLAY', help='Save the upcoming game into a replay file.')
+replay_group.add_argument('--skip-replay', action='store_true', help='Skip the replay and start to play when it’s finished.')
+
+netplay_group = parser.add_argument_group('Netplay options')
+netplay_group.add_argument('--port', metavar='PORT', type=int, default=0, help='Local port to use.')
+netplay_group.add_argument('--remote', metavar='REMOTE', default=None, help='Remote address.')
+netplay_group.add_argument('--friendly-fire', action='store_true', help='Allow friendly-fire during netplay.')
+
+graphics_group = parser.add_argument_group('Graphics options')
+graphics_group.add_argument('--backend', metavar='BACKEND', choices=['opengl', 'sdl'], default='opengl', help='Which backend to use (opengl or sdl for now).')
+graphics_group.add_argument('--fixed-pipeline', action='store_true', help='Use the fixed pipeline instead of the new programmable one.')
+graphics_group.add_argument('--single-buffer', action='store_true', help='Disable double buffering.')
+graphics_group.add_argument('--fps-limit', metavar='FPS', default=-1, type=int, help='Set fps limit. A value of 0 disables fps limiting, while a negative value limits to 60 fps if and only if vsync doesn’t work.')
+graphics_group.add_argument('--no-background', action='store_false', help='Disable background display (huge performance boost on slow systems).')
+graphics_group.add_argument('--no-particles', action='store_false', help='Disable particles handling (huge performance boost on slow systems).')
+graphics_group.add_argument('--no-sound', action='store_false', help='Disable music and sound effects.')
+
+args = parser.parse_args()
+
+
+import sys
+import logging
+
+if args.backend == 'opengl':
+    try:
+        from pytouhou.ui.opengl.gamerenderer import GameRenderer
+        opengl = True
+    except ImportError:
+        args.backend = 'sdl'
+
+if args.backend == 'sdl':
+    from pytouhou.ui.sdl.gamerenderer import GameRenderer
+    opengl = False
+
+from pytouhou.lib.sdl import SDL, show_simple_message_box
+from pytouhou.ui.window import Window
+from pytouhou.resource.loader import Loader
+from pytouhou.ui.gamerunner import GameRunner
+from pytouhou.game.player import GameOver
+from pytouhou.formats.t6rp import T6RP, Level
+from pytouhou.utils.random import Random
+from pytouhou.vm.msgrunner import NextStage
+from pytouhou.formats.hint import Hint
+from pytouhou.network import Network
+
+
+if args.game == 'EoSD':
+    from pytouhou.games.eosd import EoSDCommon as Common, EoSDGame as Game
+
+
+class GameBossRush(Game):
+    def run_iter(self, keystates):
+        for i in range(20):
+            skip = not (self.enemies or self.items or self.lasers
+                        or self.bullets or self.cancelled_bullets)
+            if skip:
+                keystates = [k & ~1 for k in keystates]
+            Game.run_iter(self, [0 if i else k | 256 for k in keystates])
+            if not self.enemies and self.frame % 90 == 0:
+                for player in self.players:
+                    if player.power < 128:
+                        player.power += 1
+            if not skip:
+                break
+
+    def cleanup(self):
+        boss_wait = any(ecl_runner.boss_wait for ecl_runner in self.ecl_runners)
+        if not (self.boss or self.msg_wait or boss_wait):
+            self.enemies = [enemy for enemy in self.enemies
+                            if enemy.boss_callback or enemy.frame > 1]
+            for laser in self.lasers:
+                if laser.frame <= 1:
+                    laser.removed = True
+            self.lasers = [laser for laser in self.lasers if laser.frame > 1]
+            self.bullets = [bullet for bullet in self.bullets if bullet.frame > 1]
+        Game.cleanup(self)
+
+
+def main(window, path, data, stage_num, rank, character, replay, save_filename,
+         skip_replay, boss_rush, debug, enable_background, enable_particles,
+         hints, verbosity, port, remote, friendly_fire):
+
+    resource_loader = Loader(path)
+
+    try:
+        resource_loader.scan_archives(data)
+    except IOError:
+        show_simple_message_box(u'Some data files were not found, did you forget the -p option?')
+        sys.exit(1)
+
+    if stage_num is None:
+        story = True
+        stage_num = 1
+        continues = 3
+    else:
+        story = False
+        continues = 0
+
+    if debug:
+        if not verbosity:
+            verbosity = 'DEBUG'
+        continues = -1  # Infinite lives
+
+    if verbosity:
+        logging.basicConfig(level=logging.__getattribute__(verbosity))
+
+    if replay:
+        with open(replay, 'rb') as file:
+            replay = T6RP.read(file)
+        rank = replay.rank
+        character = replay.character
+
+    save_keystates = None
+    if save_filename:
+        save_replay = T6RP()
+        save_replay.rank = rank
+        save_replay.character = character
+
+    difficulty = 16
+
+    if port != 0:
+        if remote:
+            remote_addr, remote_port = remote.split(':')
+            addr = remote_addr, int(remote_port)
+            selected_player = 0
+        else:
+            addr = None
+            selected_player = 1
+
+        prng = Random(0)
+        con = Network(port, addr, selected_player)
+        characters = [1, 3]
+    else:
+        con = None
+        selected_player = 0
+        characters = [character]
+
+    if hints:
+        with open(hints, 'rb') as file:
+            hints = Hint.read(file)
+
+    game_class = GameBossRush if boss_rush else Game
+
+    common = Common(resource_loader, characters, continues, stage_num - 1)
+    renderer = GameRenderer(resource_loader, window)
+    runner = GameRunner(window, renderer, common, resource_loader, skip_replay, con)
+    window.set_runner(runner)
+
+    while True:
+        first_player = common.players[0]
+
+        if replay:
+            level = replay.levels[stage_num - 1]
+            if not level:
+                raise Exception
+
+            prng = Random(level.random_seed)
+
+            #TODO: apply the replay to the other players.
+            #TODO: see if the stored score is used or if it’s the one from the previous stage.
+            if stage_num != 1 and stage_num - 2 in replay.levels:
+                previous_level = replay.levels[stage_num - 1]
+                first_player.score = previous_level.score
+                first_player.effective_score = previous_level.score
+            first_player.points = level.point_items
+            first_player.power = level.power
+            first_player.lives = level.lives
+            first_player.bombs = level.bombs
+            difficulty = level.difficulty
+        elif port == 0:
+            prng = Random()
+
+        if save_filename:
+            if not replay:
+                save_replay.levels[stage_num - 1] = level = Level()
+                level.random_seed = prng.seed
+                level.score = first_player.score
+                level.point_items = first_player.points
+                level.power = first_player.power
+                level.lives = first_player.lives
+                level.bombs = first_player.bombs
+                level.difficulty = difficulty
+            save_keystates = []
+
+        hints_stage = hints.stages[stage_num - 1] if hints else None
+
+        game = game_class(resource_loader, stage_num, rank, difficulty,
+                          common, prng, hints_stage, friendly_fire)
+
+        if not enable_particles:
+            def new_particle(pos, anim, amp, number=1, reverse=False, duration=24):
+                pass
+            game.new_particle = new_particle
+
+        background = game.background if enable_background else None
+        runner.load_game(game, background, game.std.bgms, replay, save_keystates)
+
+        try:
+            # Main loop
+            window.run()
+            break
+        except NextStage:
+            if not story or stage_num == (7 if boss_rush else 6 if rank > 0 else 5):
+                break
+            stage_num += 1
+        except GameOver:
+            show_simple_message_box(u'Game over!')
+            break
+        finally:
+            if save_filename:
+                last_key = -1
+                for time, key in enumerate(save_keystates):
+                    if key != last_key:
+                        level.keys.append((time, key, 0))
+                    last_key = key
+
+    window.set_runner(None)
+
+    if save_filename:
+        with open(save_filename, 'wb+') as file:
+            save_replay.write(file)
+
+
+with SDL(sound=args.no_sound):
+    window = Window(double_buffer=(not args.single_buffer),
+                    fps_limit=args.fps_limit,
+                    fixed_pipeline=args.fixed_pipeline, opengl=opengl)
+
+    main(window, args.path, tuple(args.data), args.stage, args.rank,
+         args.character, args.replay, args.save_replay, args.skip_replay,
+         args.boss_rush, args.debug, args.no_background, args.no_particles,
+         args.hints, args.verbosity, args.port, args.remote,
+         args.friendly_fire)
+
+    import gc
+    gc.collect()
--- a/setup.py	Thu May 15 20:14:54 2014 +0200
+++ b/setup.py	Wed May 21 20:52:42 2014 +0200
@@ -129,7 +129,7 @@
     nthreads = None  # It seems Windows can’t compile in parallel.
     base = 'Win32GUI' if sys.platform == 'win32' else None
     extra = {'options': {'build_exe': {'includes': extension_names + ['glob', 'socket', 'select']}},
-             'executables': [Executable(script='eosd', base=base)]}
+             'executables': [Executable(script='scripts/pytouhou', base=base)]}
 
 
 setup(name='PyTouhou',
@@ -148,5 +148,5 @@
                                               'MAX_SOUNDS': 26,
                                               'USE_OPENGL': use_opengl,
                                               'USE_GLEW': is_windows}),
-      scripts=['eosd'] + (['anmviewer'] if anmviewer else []),
+      scripts=['scripts/pytouhou'] + (['scripts/anmviewer'] if anmviewer else []),
       **extra)