Mercurial > touhou
comparison scripts/pytouhou @ 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 | eosd@fb837b32c3dd |
| children | e35bef07290d |
comparison
equal
deleted
inserted
replaced
| 545:bcff39c920ab | 546:94dd9862c470 |
|---|---|
| 1 #!/usr/bin/env python2 | |
| 2 # -*- encoding: utf-8 -*- | |
| 3 ## | |
| 4 ## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> | |
| 5 ## | |
| 6 ## This program is free software; you can redistribute it and/or modify | |
| 7 ## it under the terms of the GNU General Public License as published | |
| 8 ## by the Free Software Foundation; version 3 only. | |
| 9 ## | |
| 10 ## This program is distributed in the hope that it will be useful, | |
| 11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 ## GNU General Public License for more details. | |
| 14 ## | |
| 15 | |
| 16 import argparse | |
| 17 import os | |
| 18 | |
| 19 | |
| 20 pathsep = os.path.pathsep | |
| 21 default_data = (pathsep.join(('CM.DAT', 'th06*_CM.DAT', '*CM.DAT', '*cm.dat')), | |
| 22 pathsep.join(('ST.DAT', 'th6*ST.DAT', '*ST.DAT', '*st.dat')), | |
| 23 pathsep.join(('IN.DAT', 'th6*IN.DAT', '*IN.DAT', '*in.dat')), | |
| 24 pathsep.join(('MD.DAT', 'th6*MD.DAT', '*MD.DAT', '*md.dat')), | |
| 25 pathsep.join(('102h.exe', '102*.exe', '東方紅魔郷.exe', '*.exe'))) | |
| 26 | |
| 27 | |
| 28 parser = argparse.ArgumentParser(description='Libre reimplementation of the Touhou 6 engine.') | |
| 29 | |
| 30 parser.add_argument('data', metavar='DAT', default=default_data, nargs='*', help='Game’s data files') | |
| 31 parser.add_argument('-p', '--path', metavar='DIRECTORY', default='.', help='Game directory path.') | |
| 32 parser.add_argument('--debug', action='store_true', help='Set unlimited continues, and perhaps other debug features.') | |
| 33 parser.add_argument('--verbosity', metavar='VERBOSITY', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], help='Select the wanted logging level.') | |
| 34 | |
| 35 game_group = parser.add_argument_group('Game options') | |
| 36 game_group.add_argument('-s', '--stage', metavar='STAGE', type=int, default=None, help='Stage, 1 to 7 (Extra), nothing means story mode.') | |
| 37 game_group.add_argument('-r', '--rank', metavar='RANK', type=int, default=0, help='Rank, from 0 (Easy, default) to 3 (Lunatic).') | |
| 38 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).') | |
| 39 game_group.add_argument('-b', '--boss-rush', action='store_true', help='Fight only bosses.') | |
| 40 game_group.add_argument('--game', metavar='GAME', choices=['EoSD'], default='EoSD', help='Select the game engine to use.') | |
| 41 game_group.add_argument('--hints', metavar='HINTS', default=None, help='Hints file, to display text while playing.') | |
| 42 | |
| 43 replay_group = parser.add_argument_group('Replay options') | |
| 44 replay_group.add_argument('--replay', metavar='REPLAY', help='Select a file to replay.') | |
| 45 replay_group.add_argument('--save-replay', metavar='REPLAY', help='Save the upcoming game into a replay file.') | |
| 46 replay_group.add_argument('--skip-replay', action='store_true', help='Skip the replay and start to play when it’s finished.') | |
| 47 | |
| 48 netplay_group = parser.add_argument_group('Netplay options') | |
| 49 netplay_group.add_argument('--port', metavar='PORT', type=int, default=0, help='Local port to use.') | |
| 50 netplay_group.add_argument('--remote', metavar='REMOTE', default=None, help='Remote address.') | |
| 51 netplay_group.add_argument('--friendly-fire', action='store_true', help='Allow friendly-fire during netplay.') | |
| 52 | |
| 53 graphics_group = parser.add_argument_group('Graphics options') | |
| 54 graphics_group.add_argument('--backend', metavar='BACKEND', choices=['opengl', 'sdl'], default='opengl', help='Which backend to use (opengl or sdl for now).') | |
| 55 graphics_group.add_argument('--fixed-pipeline', action='store_true', help='Use the fixed pipeline instead of the new programmable one.') | |
| 56 graphics_group.add_argument('--single-buffer', action='store_true', help='Disable double buffering.') | |
| 57 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.') | |
| 58 graphics_group.add_argument('--no-background', action='store_false', help='Disable background display (huge performance boost on slow systems).') | |
| 59 graphics_group.add_argument('--no-particles', action='store_false', help='Disable particles handling (huge performance boost on slow systems).') | |
| 60 graphics_group.add_argument('--no-sound', action='store_false', help='Disable music and sound effects.') | |
| 61 | |
| 62 args = parser.parse_args() | |
| 63 | |
| 64 | |
| 65 import sys | |
| 66 import logging | |
| 67 | |
| 68 if args.backend == 'opengl': | |
| 69 try: | |
| 70 from pytouhou.ui.opengl.gamerenderer import GameRenderer | |
| 71 opengl = True | |
| 72 except ImportError: | |
| 73 args.backend = 'sdl' | |
| 74 | |
| 75 if args.backend == 'sdl': | |
| 76 from pytouhou.ui.sdl.gamerenderer import GameRenderer | |
| 77 opengl = False | |
| 78 | |
| 79 from pytouhou.lib.sdl import SDL, show_simple_message_box | |
| 80 from pytouhou.ui.window import Window | |
| 81 from pytouhou.resource.loader import Loader | |
| 82 from pytouhou.ui.gamerunner import GameRunner | |
| 83 from pytouhou.game.player import GameOver | |
| 84 from pytouhou.formats.t6rp import T6RP, Level | |
| 85 from pytouhou.utils.random import Random | |
| 86 from pytouhou.vm.msgrunner import NextStage | |
| 87 from pytouhou.formats.hint import Hint | |
| 88 from pytouhou.network import Network | |
| 89 | |
| 90 | |
| 91 if args.game == 'EoSD': | |
| 92 from pytouhou.games.eosd import EoSDCommon as Common, EoSDGame as Game | |
| 93 | |
| 94 | |
| 95 class GameBossRush(Game): | |
| 96 def run_iter(self, keystates): | |
| 97 for i in range(20): | |
| 98 skip = not (self.enemies or self.items or self.lasers | |
| 99 or self.bullets or self.cancelled_bullets) | |
| 100 if skip: | |
| 101 keystates = [k & ~1 for k in keystates] | |
| 102 Game.run_iter(self, [0 if i else k | 256 for k in keystates]) | |
| 103 if not self.enemies and self.frame % 90 == 0: | |
| 104 for player in self.players: | |
| 105 if player.power < 128: | |
| 106 player.power += 1 | |
| 107 if not skip: | |
| 108 break | |
| 109 | |
| 110 def cleanup(self): | |
| 111 boss_wait = any(ecl_runner.boss_wait for ecl_runner in self.ecl_runners) | |
| 112 if not (self.boss or self.msg_wait or boss_wait): | |
| 113 self.enemies = [enemy for enemy in self.enemies | |
| 114 if enemy.boss_callback or enemy.frame > 1] | |
| 115 for laser in self.lasers: | |
| 116 if laser.frame <= 1: | |
| 117 laser.removed = True | |
| 118 self.lasers = [laser for laser in self.lasers if laser.frame > 1] | |
| 119 self.bullets = [bullet for bullet in self.bullets if bullet.frame > 1] | |
| 120 Game.cleanup(self) | |
| 121 | |
| 122 | |
| 123 def main(window, path, data, stage_num, rank, character, replay, save_filename, | |
| 124 skip_replay, boss_rush, debug, enable_background, enable_particles, | |
| 125 hints, verbosity, port, remote, friendly_fire): | |
| 126 | |
| 127 resource_loader = Loader(path) | |
| 128 | |
| 129 try: | |
| 130 resource_loader.scan_archives(data) | |
| 131 except IOError: | |
| 132 show_simple_message_box(u'Some data files were not found, did you forget the -p option?') | |
| 133 sys.exit(1) | |
| 134 | |
| 135 if stage_num is None: | |
| 136 story = True | |
| 137 stage_num = 1 | |
| 138 continues = 3 | |
| 139 else: | |
| 140 story = False | |
| 141 continues = 0 | |
| 142 | |
| 143 if debug: | |
| 144 if not verbosity: | |
| 145 verbosity = 'DEBUG' | |
| 146 continues = -1 # Infinite lives | |
| 147 | |
| 148 if verbosity: | |
| 149 logging.basicConfig(level=logging.__getattribute__(verbosity)) | |
| 150 | |
| 151 if replay: | |
| 152 with open(replay, 'rb') as file: | |
| 153 replay = T6RP.read(file) | |
| 154 rank = replay.rank | |
| 155 character = replay.character | |
| 156 | |
| 157 save_keystates = None | |
| 158 if save_filename: | |
| 159 save_replay = T6RP() | |
| 160 save_replay.rank = rank | |
| 161 save_replay.character = character | |
| 162 | |
| 163 difficulty = 16 | |
| 164 | |
| 165 if port != 0: | |
| 166 if remote: | |
| 167 remote_addr, remote_port = remote.split(':') | |
| 168 addr = remote_addr, int(remote_port) | |
| 169 selected_player = 0 | |
| 170 else: | |
| 171 addr = None | |
| 172 selected_player = 1 | |
| 173 | |
| 174 prng = Random(0) | |
| 175 con = Network(port, addr, selected_player) | |
| 176 characters = [1, 3] | |
| 177 else: | |
| 178 con = None | |
| 179 selected_player = 0 | |
| 180 characters = [character] | |
| 181 | |
| 182 if hints: | |
| 183 with open(hints, 'rb') as file: | |
| 184 hints = Hint.read(file) | |
| 185 | |
| 186 game_class = GameBossRush if boss_rush else Game | |
| 187 | |
| 188 common = Common(resource_loader, characters, continues, stage_num - 1) | |
| 189 renderer = GameRenderer(resource_loader, window) | |
| 190 runner = GameRunner(window, renderer, common, resource_loader, skip_replay, con) | |
| 191 window.set_runner(runner) | |
| 192 | |
| 193 while True: | |
| 194 first_player = common.players[0] | |
| 195 | |
| 196 if replay: | |
| 197 level = replay.levels[stage_num - 1] | |
| 198 if not level: | |
| 199 raise Exception | |
| 200 | |
| 201 prng = Random(level.random_seed) | |
| 202 | |
| 203 #TODO: apply the replay to the other players. | |
| 204 #TODO: see if the stored score is used or if it’s the one from the previous stage. | |
| 205 if stage_num != 1 and stage_num - 2 in replay.levels: | |
| 206 previous_level = replay.levels[stage_num - 1] | |
| 207 first_player.score = previous_level.score | |
| 208 first_player.effective_score = previous_level.score | |
| 209 first_player.points = level.point_items | |
| 210 first_player.power = level.power | |
| 211 first_player.lives = level.lives | |
| 212 first_player.bombs = level.bombs | |
| 213 difficulty = level.difficulty | |
| 214 elif port == 0: | |
| 215 prng = Random() | |
| 216 | |
| 217 if save_filename: | |
| 218 if not replay: | |
| 219 save_replay.levels[stage_num - 1] = level = Level() | |
| 220 level.random_seed = prng.seed | |
| 221 level.score = first_player.score | |
| 222 level.point_items = first_player.points | |
| 223 level.power = first_player.power | |
| 224 level.lives = first_player.lives | |
| 225 level.bombs = first_player.bombs | |
| 226 level.difficulty = difficulty | |
| 227 save_keystates = [] | |
| 228 | |
| 229 hints_stage = hints.stages[stage_num - 1] if hints else None | |
| 230 | |
| 231 game = game_class(resource_loader, stage_num, rank, difficulty, | |
| 232 common, prng, hints_stage, friendly_fire) | |
| 233 | |
| 234 if not enable_particles: | |
| 235 def new_particle(pos, anim, amp, number=1, reverse=False, duration=24): | |
| 236 pass | |
| 237 game.new_particle = new_particle | |
| 238 | |
| 239 background = game.background if enable_background else None | |
| 240 runner.load_game(game, background, game.std.bgms, replay, save_keystates) | |
| 241 | |
| 242 try: | |
| 243 # Main loop | |
| 244 window.run() | |
| 245 break | |
| 246 except NextStage: | |
| 247 if not story or stage_num == (7 if boss_rush else 6 if rank > 0 else 5): | |
| 248 break | |
| 249 stage_num += 1 | |
| 250 except GameOver: | |
| 251 show_simple_message_box(u'Game over!') | |
| 252 break | |
| 253 finally: | |
| 254 if save_filename: | |
| 255 last_key = -1 | |
| 256 for time, key in enumerate(save_keystates): | |
| 257 if key != last_key: | |
| 258 level.keys.append((time, key, 0)) | |
| 259 last_key = key | |
| 260 | |
| 261 window.set_runner(None) | |
| 262 | |
| 263 if save_filename: | |
| 264 with open(save_filename, 'wb+') as file: | |
| 265 save_replay.write(file) | |
| 266 | |
| 267 | |
| 268 with SDL(sound=args.no_sound): | |
| 269 window = Window(double_buffer=(not args.single_buffer), | |
| 270 fps_limit=args.fps_limit, | |
| 271 fixed_pipeline=args.fixed_pipeline, opengl=opengl) | |
| 272 | |
| 273 main(window, args.path, tuple(args.data), args.stage, args.rank, | |
| 274 args.character, args.replay, args.save_replay, args.skip_replay, | |
| 275 args.boss_rush, args.debug, args.no_background, args.no_particles, | |
| 276 args.hints, args.verbosity, args.port, args.remote, | |
| 277 args.friendly_fire) | |
| 278 | |
| 279 import gc | |
| 280 gc.collect() |
