Skip to content

Commit 0945e03

Browse files
committed
Move SC2Reader back to the module file.
1 parent 5a80a14 commit 0945e03

File tree

2 files changed

+318
-314
lines changed

2 files changed

+318
-314
lines changed

sc2reader/__init__.py

Lines changed: 318 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,323 @@
11
from __future__ import absolute_import
22

3-
from sc2reader.sc import SC2Reader
3+
import os
4+
from sc2reader import utils
5+
from sc2reader import readers
6+
from sc2reader import data
7+
from sc2reader import exceptions
8+
from sc2reader.replay import Replay
9+
from collections import defaultdict
10+
11+
class SC2Reader(object):
12+
"""
13+
The primary interface to the sc2reader library. Acts as a configurable
14+
factory for :class:`Replay` objects. Maintains a set of registered readers,
15+
datapacks, and listeners with filterfuncs that allow the factory to apply
16+
a replay specific context to each replay as it loads.
17+
18+
#TODO: Include some examples here...
19+
20+
See the specific functions below for details.
21+
22+
:param True register_defaults: Automatically registers default readers
23+
and datapacks. Only disable if you know what you are doing.
24+
25+
:param True load_events: Enables parsing of game events. If you are do
26+
not need this information setting to false will reduce replay load
27+
time.
28+
29+
:param True autoplay: Enables auto playing of replays after loading game
30+
events. Playing events triggers enables registered listeners to add
31+
new data features to replays. Option ignored if load_events is false.
32+
33+
:param False load_map: Triggers downloading and parsing of map files
34+
associated with replays as they are loaded. When false, only the map
35+
url and name are available.
36+
37+
:param False verbose: Causes many steps in the replay loading process
38+
to produce more verbose output.
39+
40+
:param string directory: Specifies a base directory to prepend to all paths
41+
before attempting to load the replay.
42+
43+
:param -1 depth: Indicates the maximum search depth when loading replays
44+
from directories. -1 indicates no limit, 0 turns recursion off.
45+
46+
:param list exclude: A list of directory names (not paths) to exclude when
47+
performing recursive searches while loading replays from directories.
48+
49+
:param False followlinks: Enables symlink following when recursing through
50+
directories to load replay files.
51+
52+
"""
53+
54+
default_options = dict(
55+
# General use
56+
verbose=False,
57+
debug=False,
58+
59+
# Related to the SC2Reader class only
60+
register_defaults=True,
61+
62+
# Related to creating new replay objects
63+
autoplay=True,
64+
load_events=True,
65+
load_map=False,
66+
67+
# Related to passing paths into load_replay(s)
68+
directory='',
69+
70+
# Related to passing a directory to load_replays
71+
depth=-1,
72+
exclude=[],
73+
followlinks=False
74+
)
75+
76+
def __init__(self, **options):
77+
self.reset()
78+
self.configure(**options)
79+
80+
if self.options.get('register_defaults',None):
81+
self.register_defaults()
82+
83+
84+
85+
def configure(self, **new_options):
86+
"""
87+
Update the factory settings with the specified overrides.
88+
89+
See :class:`SC2Reader` for a list of available options.
90+
91+
:param new_options: Option values to override current factory settings.
92+
"""
93+
self.options.update(new_options)
94+
95+
def reset(self):
96+
"""
97+
Resets the current factory to default settings and removes all
98+
registered readers, datapacks, and listeners.
99+
"""
100+
self.options = utils.AttributeDict(self.default_options)
101+
self.registered_readers = defaultdict(list)
102+
self.registered_datapacks = list()
103+
self.registered_listeners = defaultdict(list)
104+
105+
106+
def load_replays(self, collection, options=None, **new_options):
107+
"""
108+
Loads the specified collection of replays using the current factory
109+
settings with specified overrides.
110+
111+
:param collection: Either a directory path or a mixed collection of
112+
directories, file paths, and open file objects.
113+
114+
:param None options: When options are passed directly into the options
115+
parameter the current factory settings are ignored and only the
116+
specified options are used during replay load.
117+
118+
:param new_options: Options values to override current factory settings
119+
for the collection of replays to be loaded.
120+
121+
:rtype: generator(:class:`Replay`)
122+
"""
123+
options = options or utils.merged_dict(self.options, new_options)
124+
125+
# Get the directory and hide it from nested calls
126+
directory = options.get('directory','')
127+
if 'directory' in options: del options['directory']
128+
129+
if isinstance(collection, basestring):
130+
full_path = os.path.join(directory, collection)
131+
for replay_path in utils.get_replay_files(full_path, **options):
132+
with open(replay_path) as replay_file:
133+
try:
134+
yield self.load_replay(replay_file, options=options)
135+
except exceptions.MPQError as e:
136+
print e
137+
138+
else:
139+
for replay_file in collection:
140+
if isinstance(replay_file, basestring):
141+
full_path = os.path.join(directory, replay_file)
142+
if os.path.isdir(full_path):
143+
for replay in self.load_replays(full_path, options=options):
144+
yield replay
145+
else:
146+
yield self.load_replay(full_path, options=options)
147+
148+
else:
149+
yield self.load_replay(replay_file, options=options)
150+
151+
def load_replay(self, replay_file, options=None, **new_options):
152+
"""
153+
Loads the specified replay using current factory settings with the
154+
specified overrides.
155+
156+
:param replay: An open file object or a path to a single file.
157+
158+
:param None options: When options are passed directly into the options
159+
parameter the current factory settings are ignored and only the
160+
specified options are used during replay load.
161+
162+
:param new_options: Options values to override current factory settings
163+
while loading this replay.
164+
165+
:rtype: :class:`Replay`
166+
"""
167+
options = options or utils.merged_dict(self.options, new_options)
168+
load_events = options.get('load_events',True)
169+
load_map = options.get('load_map',False)
170+
autoplay = options.get('autoplay',True)
171+
172+
if isinstance(replay_file, basestring):
173+
location = os.path.join(options.get('directory',''), replay_file)
174+
with open(location, 'rb') as replay_file:
175+
return self.load_replay(replay_file, options=options)
176+
177+
if options['verbose']:
178+
print replay_file.name
179+
180+
replay = Replay(replay_file, **options)
181+
182+
for data_file in ('replay.initData',
183+
'replay.details',
184+
'replay.attributes.events',
185+
'replay.message.events',):
186+
reader = self.get_reader(data_file, replay)
187+
replay.read_data(data_file, reader)
188+
189+
replay.load_details()
190+
replay.load_players()
191+
192+
if load_map:
193+
replay.load_map()
194+
195+
if load_events:
196+
for data_file in ('replay.game.events',):
197+
reader = self.get_reader(data_file, replay)
198+
replay.read_data(data_file, reader)
199+
200+
replay.load_events(self.get_datapack(replay))
201+
202+
if autoplay:
203+
replay.listeners = self.get_listeners(replay)
204+
replay.start()
205+
206+
return replay
207+
208+
209+
def get_reader(self, data_file, replay):
210+
for callback, reader in self.registered_readers[data_file]:
211+
if callback(replay):
212+
return reader
213+
214+
def get_datapack(self, replay):
215+
for callback, datapack in self.registered_datapacks:
216+
if callback(replay):
217+
return datapack
218+
return None
219+
220+
def get_listeners(self, replay):
221+
listeners = defaultdict(list)
222+
for event_class in self.registered_listeners.keys():
223+
for callback, listener in self.registered_listeners[event_class]:
224+
if callback(replay):
225+
listeners[event_class].append(listener)
226+
return listeners
227+
228+
229+
def register_listener(self, events, listener, filterfunc=lambda r: True):
230+
"""
231+
Allows you to specify event listeners for adding new features to the
232+
:class:`Replay` objects on :meth:`~Replay.play`. sc2reader comes with a
233+
small collection of :class:`Listener` classes that you can apply to your
234+
replays as needed.
235+
236+
Events are sent to listeners in registration order as they come up. By
237+
specifying a parent class you can register a listener to a set of events
238+
at once instead of listing them out individually. See the tutorials for
239+
more information.
240+
241+
:param events: A list of event classes you want sent to this listener.
242+
Registration to a single event can be done by specifying a single
243+
event class instead of a list. An isinstance() check is used so
244+
you can catch sets of classes at once by supplying a parent class.
245+
246+
:param listener: The :class:`Listener` object you want events sent to.
247+
248+
:param filterfunc: A function that accepts a partially loaded
249+
:class:`Replay` object as an argument and returns true if the
250+
reader should be used on this replay.
251+
"""
252+
try:
253+
for event in events:
254+
self.registered_listeners[event].append((filterfunc, listener))
255+
except TypeError:
256+
self.registered_listeners[event].append((filterfunc, listener))
257+
258+
def register_reader(self, data_file, reader, filterfunc=lambda r: True):
259+
"""
260+
Allows you to specify your own reader for use when reading the data
261+
files packed into the .SC2Replay archives. Datapacks are checked for
262+
use with the supplied filterfunc in reverse registration order to give
263+
user registered datapacks preference over factory default datapacks.
264+
265+
Don't use this unless you know what you are doing.
266+
267+
:param data_file: The full file name that you would like this reader to
268+
parse.
269+
270+
:param reader: The :class:`Reader` object you wish to use to read the
271+
data file.
272+
273+
:param filterfunc: A function that accepts a partially loaded
274+
:class:`Replay` object as an argument and returns true if the
275+
reader should be used on this replay.
276+
"""
277+
self.registered_readers[data_file].insert(0,(filterfunc, reader))
278+
279+
def register_datapack(self, datapack, filterfunc=lambda r: True):
280+
"""
281+
Allows you to specify your own datapacks for use when loading replays.
282+
Datapacks are checked for use with the supplied filterfunc in reverse
283+
registration order to give user registered datapacks preference over
284+
factory default datapacks.
285+
286+
This is how you would add mappings for your favorite custom map.
287+
288+
:param datapack: A :class:`BaseData` object to use for mapping unit
289+
types and ability codes to their corresponding classes.
290+
291+
:param filterfunc: A function that accepts a partially loaded
292+
:class:`Replay` object as an argument and returns true if the
293+
datapack should be used on this replay.
294+
"""
295+
self.registered_datapacks.insert(0,(filterfunc, datapack))
296+
297+
298+
def register_defaults(self):
299+
"""Registers all factory default objects."""
300+
self.register_default_readers()
301+
self.register_default_datapacks()
302+
303+
def register_default_readers(self):
304+
"""Registers factory default readers."""
305+
self.register_reader('replay.details', readers.DetailsReader_Base())
306+
self.register_reader('replay.initData', readers.InitDataReader_Base())
307+
self.register_reader('replay.message.events', readers.MessageEventsReader_Base())
308+
self.register_reader('replay.attributes.events', readers.AttributesEventsReader_Base(), lambda r: r.build < 17326)
309+
self.register_reader('replay.attributes.events', readers.AttributesEventsReader_17326(), lambda r: r.build >= 17326)
310+
self.register_reader('replay.game.events', readers.GameEventsReader_Base(), lambda r: r.build < 16561)
311+
self.register_reader('replay.game.events', readers.GameEventsReader_16561(), lambda r: 16561 <= r.build < 18574)
312+
self.register_reader('replay.game.events', readers.GameEventsReader_18574(), lambda r: 18574 <= r.build < 19595)
313+
self.register_reader('replay.game.events', readers.GameEventsReader_19595(), lambda r: 19595 <= r.build)
314+
315+
def register_default_datapacks(self):
316+
"""Registers factory default datapacks."""
317+
self.register_datapack(data.Data_16561, lambda r: 16561 <= r.build < 17326)
318+
self.register_datapack(data.Data_17326, lambda r: 17326 <= r.build < 18317)
319+
self.register_datapack(data.Data_18317, lambda r: 18317 <= r.build < 19595)
320+
self.register_datapack(data.Data_19595, lambda r: 19595 <= r.build)
4321

5322
__defaultSC2Reader = SC2Reader()
6323

0 commit comments

Comments
 (0)