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()