|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
""" |
|
3
|
|
|
This module provides a set of parsers which can be used to obtain information |
|
4
|
|
|
about IL-2 FB missions. Every parser is a one-pass parser. They can be used to |
|
5
|
|
|
parse a whole mission file or to parse a single given section as well. |
|
6
|
|
|
""" |
|
7
|
|
|
|
|
8
|
1 |
|
import datetime |
|
9
|
1 |
|
import math |
|
10
|
1 |
|
import six |
|
11
|
1 |
|
import sys |
|
12
|
|
|
|
|
13
|
1 |
|
from abc import ABCMeta, abstractmethod |
|
14
|
|
|
|
|
15
|
1 |
|
from il2fb.commons.flight import Formations, RoutePointTypes |
|
16
|
1 |
|
from il2fb.commons.organization import AirForces, Belligerents, Regiments |
|
17
|
1 |
|
from il2fb.commons.spatial import Point2D, Point3D |
|
18
|
1 |
|
from il2fb.commons.targets import TargetTypes, TargetPriorities |
|
19
|
1 |
|
from il2fb.commons.weather import Conditions, Gust, Turbulence |
|
20
|
|
|
|
|
21
|
1 |
|
from .constants import ( |
|
22
|
|
|
IS_STATIONARY_AIRCRAFT_RESTORABLE, NULL, WEAPONS_CONTINUATION_MARK, |
|
23
|
|
|
ROUTE_POINT_EXTRA_PARAMETERS_MARK, ROUTE_POINT_RADIO_SILENCE_ON, |
|
24
|
|
|
ROUTE_POINT_RADIO_SILENCE_OFF, |
|
25
|
|
|
) |
|
26
|
1 |
|
from .converters import ( |
|
27
|
|
|
to_bool, to_belligerent, to_skill, to_unit_type, to_air_force, |
|
28
|
|
|
) |
|
29
|
1 |
|
from .exceptions import MissionParsingError |
|
30
|
1 |
|
from .utils import move_if_present, set_if_present, strip_comments |
|
31
|
1 |
|
from .structures import ( |
|
32
|
|
|
GroundRoutePoint, Building, StaticCamera, FrontMarker, Rocket, |
|
33
|
|
|
StationaryObject, StationaryArtillery, StationaryAircraft, StationaryShip, |
|
34
|
|
|
FlightRoutePoint, FlightRouteTakeoffPoint, FlightRoutePatrolPoint, |
|
35
|
|
|
FlightRouteAttackPoint, |
|
36
|
|
|
) |
|
37
|
|
|
|
|
38
|
|
|
|
|
39
|
1 |
|
class SectionParser(object): |
|
40
|
|
|
""" |
|
41
|
|
|
Abstract base parser of a single section in a mission file. |
|
42
|
|
|
|
|
43
|
|
|
A common approach to parse a section can be described in the following way: |
|
44
|
|
|
|
|
45
|
|
|
#. Pass a section name (e.g. 'MAIN') to :meth:`start` method. If parser can |
|
46
|
|
|
process a section with such name, it will return `True` and then you can |
|
47
|
|
|
proceed. |
|
48
|
|
|
#. Pass section lines one-by-one to :meth:`parse_line`. |
|
49
|
|
|
#. When you are done, get your parsed data by calling :meth:`stop`. This |
|
50
|
|
|
will tell the parser that no more data will be given and the parsing can |
|
51
|
|
|
be finished. |
|
52
|
|
|
|
|
53
|
|
|
| |
|
54
|
|
|
**Example**: |
|
55
|
|
|
|
|
56
|
|
|
.. code-block:: python |
|
57
|
|
|
|
|
58
|
|
|
section_name = "Test section" |
|
59
|
|
|
lines = ["foo", "bar", "baz", "qux", ] |
|
60
|
|
|
parser = SomeParser() |
|
61
|
|
|
|
|
62
|
|
|
if parser.start(section_name): |
|
63
|
|
|
for line in lines: |
|
64
|
|
|
parser.parse_line(line) |
|
65
|
|
|
result = parser.stop() |
|
66
|
|
|
|
|
67
|
|
|
""" |
|
68
|
|
|
|
|
69
|
1 |
|
__metaclass__ = ABCMeta |
|
70
|
|
|
|
|
71
|
|
|
#: Tells whether a parser was started. |
|
72
|
1 |
|
running = False |
|
73
|
|
|
|
|
74
|
|
|
#: An internal buffer which can be redefined. |
|
75
|
1 |
|
data = None |
|
76
|
|
|
|
|
77
|
1 |
|
def start(self, section_name): |
|
78
|
|
|
""" |
|
79
|
|
|
Try to start a parser. If a section with given name can be parsed, the |
|
80
|
|
|
parser will initialize it's internal data structures and set |
|
81
|
|
|
:attr:`running` to `True`. |
|
82
|
|
|
|
|
83
|
|
|
:param str section_name: a name of section which is going to be parsed |
|
84
|
|
|
|
|
85
|
|
|
:returns: `True` if section with a given name can be parsed by parser, |
|
86
|
|
|
`False` otherwise |
|
87
|
|
|
:rtype: :class:`bool` |
|
88
|
|
|
""" |
|
89
|
1 |
|
result = self.check_section_name(section_name) |
|
90
|
1 |
|
if result: |
|
91
|
1 |
|
self.running = True |
|
92
|
1 |
|
self.init_parser(section_name) |
|
93
|
1 |
|
return result |
|
94
|
|
|
|
|
95
|
1 |
|
@abstractmethod |
|
96
|
|
|
def check_section_name(self, section_name): |
|
97
|
|
|
""" |
|
98
|
|
|
Check whether a section with a given name can be parsed. |
|
99
|
|
|
|
|
100
|
|
|
:param str section_name: a name of section which is going to be parsed |
|
101
|
|
|
|
|
102
|
|
|
:returns: `True` if section with a given name can be parsed by parser, |
|
103
|
|
|
`False` otherwise |
|
104
|
|
|
:rtype: :class:`bool` |
|
105
|
|
|
""" |
|
106
|
|
|
|
|
107
|
1 |
|
@abstractmethod |
|
108
|
|
|
def init_parser(self, section_name): |
|
109
|
|
|
""" |
|
110
|
|
|
Abstract method which is called by :meth:`start` to initialize |
|
111
|
|
|
internal data structures. |
|
112
|
|
|
|
|
113
|
|
|
:param str section_name: a name of section which is going to be parsed |
|
114
|
|
|
|
|
115
|
|
|
:returns: ``None`` |
|
116
|
|
|
""" |
|
117
|
|
|
|
|
118
|
1 |
|
@abstractmethod |
|
119
|
|
|
def parse_line(self, line): |
|
120
|
|
|
""" |
|
121
|
|
|
Abstract method which is called manually to parse a line from mission |
|
122
|
|
|
section. |
|
123
|
|
|
|
|
124
|
|
|
:param str line: a single line to parse |
|
125
|
|
|
|
|
126
|
|
|
:returns: ``None`` |
|
127
|
|
|
""" |
|
128
|
|
|
|
|
129
|
1 |
|
def stop(self): |
|
130
|
|
|
""" |
|
131
|
|
|
Stops parser and returns fully processed data. |
|
132
|
|
|
|
|
133
|
|
|
:returns: a data structure returned by :meth:`clean` method |
|
134
|
|
|
|
|
135
|
|
|
:raises RuntimeError: if parser was not started |
|
136
|
|
|
""" |
|
137
|
1 |
|
if not self.running: |
|
138
|
1 |
|
raise RuntimeError("Cannot stop parser which is not running") |
|
139
|
|
|
|
|
140
|
1 |
|
self.running = False |
|
141
|
1 |
|
return self.clean() |
|
142
|
|
|
|
|
143
|
1 |
|
def clean(self): |
|
144
|
|
|
""" |
|
145
|
|
|
Returns fully parsed data. Is called by :meth:`stop` method. |
|
146
|
|
|
|
|
147
|
|
|
:returns: a data structure which is specific for every subclass |
|
148
|
|
|
""" |
|
149
|
1 |
|
return self.data |
|
150
|
|
|
|
|
151
|
|
|
|
|
152
|
1 |
|
class ValuesParser(SectionParser): |
|
153
|
|
|
""" |
|
154
|
|
|
This is a base class for parsers which assume that a section, which is |
|
155
|
|
|
going to be parsed, consists of key-value pairs with unique keys, one pair |
|
156
|
|
|
per line. |
|
157
|
|
|
|
|
158
|
|
|
**Section definition example**:: |
|
159
|
|
|
|
|
160
|
|
|
[some section name] |
|
161
|
|
|
key1 value1 |
|
162
|
|
|
key2 value2 |
|
163
|
|
|
key3 value3 |
|
164
|
|
|
""" |
|
165
|
|
|
|
|
166
|
1 |
|
def init_parser(self, section_name): |
|
167
|
|
|
""" |
|
168
|
|
|
Implements abstract method. See :meth:`SectionParser.init_parser` for |
|
169
|
|
|
semantics. |
|
170
|
|
|
|
|
171
|
|
|
Initializes a dictionary to store raw keys and their values. |
|
172
|
|
|
""" |
|
173
|
1 |
|
self.data = {} |
|
174
|
|
|
|
|
175
|
1 |
|
def parse_line(self, line): |
|
176
|
|
|
""" |
|
177
|
|
|
Implements abstract method. See :meth:`SectionParser.parse_line` for |
|
178
|
|
|
semantics. |
|
179
|
|
|
|
|
180
|
|
|
Splits line into key-value pair and puts it into internal dictionary. |
|
181
|
|
|
""" |
|
182
|
1 |
|
key, value = line.strip().split() |
|
183
|
1 |
|
self.data.update({key: value}) |
|
184
|
|
|
|
|
185
|
|
|
|
|
186
|
1 |
|
class CollectingParser(SectionParser): |
|
187
|
|
|
""" |
|
188
|
|
|
This is a base class for parsers which assume that a section, which is |
|
189
|
|
|
going to be parsed, consists of homogeneous lines which describe different |
|
190
|
|
|
objects with one set of attributes. |
|
191
|
|
|
|
|
192
|
|
|
**Section definition example**:: |
|
193
|
|
|
|
|
194
|
|
|
[some section name] |
|
195
|
|
|
object1_attr1 object1_attr2 object1_attr3 object1_attr4 |
|
196
|
|
|
object2_attr1 object2_attr2 object2_attr3 object2_attr4 |
|
197
|
|
|
object3_attr1 object3_attr2 object3_attr3 object3_attr4 |
|
198
|
|
|
""" |
|
199
|
|
|
|
|
200
|
1 |
|
def init_parser(self, section_name): |
|
201
|
|
|
""" |
|
202
|
|
|
Implements abstract method. See :meth:`SectionParser.init_parser` for |
|
203
|
|
|
semantics. |
|
204
|
|
|
|
|
205
|
|
|
Initializes a list for storing collection of objects. |
|
206
|
|
|
""" |
|
207
|
1 |
|
self.data = [] |
|
208
|
|
|
|
|
209
|
1 |
|
def parse_line(self, line): |
|
210
|
|
|
""" |
|
211
|
|
|
Implements abstract method. See :meth:`SectionParser.parse_line` for |
|
212
|
|
|
semantics. |
|
213
|
|
|
|
|
214
|
|
|
Just puts entire line to internal buffer. You probably will want to |
|
215
|
|
|
redefine this method to do some extra job on each line. |
|
216
|
|
|
""" |
|
217
|
1 |
|
self.data.append(line.strip()) |
|
218
|
|
|
|
|
219
|
|
|
|
|
220
|
1 |
|
class MainParser(ValuesParser): |
|
221
|
|
|
""" |
|
222
|
|
|
Parses ``MAIN`` section. |
|
223
|
|
|
View :ref:`detailed description <main-section>`. |
|
224
|
|
|
""" |
|
225
|
|
|
|
|
226
|
1 |
|
def check_section_name(self, section_name): |
|
227
|
|
|
""" |
|
228
|
|
|
Implements abstract method. See |
|
229
|
|
|
:meth:`SectionParser.check_section_name` for semantics. |
|
230
|
|
|
""" |
|
231
|
1 |
|
return section_name == "MAIN" |
|
232
|
|
|
|
|
233
|
1 |
|
def clean(self): |
|
234
|
|
|
""" |
|
235
|
|
|
Redefines base method. See :meth:`SectionParser.clean` for |
|
236
|
|
|
semantics. |
|
237
|
|
|
""" |
|
238
|
1 |
|
weather_conditions = int(self.data['CloudType']) |
|
239
|
1 |
|
return { |
|
240
|
|
|
'location_loader': self.data['MAP'], |
|
241
|
|
|
'time': { |
|
242
|
|
|
'value': self._to_time(self.data['TIME']), |
|
243
|
|
|
'is_fixed': 'TIMECONSTANT' in self.data, |
|
244
|
|
|
}, |
|
245
|
|
|
'weather_conditions': Conditions.get_by_value(weather_conditions), |
|
246
|
|
|
'cloud_base': int(float(self.data['CloudHeight'])), |
|
247
|
|
|
'player': { |
|
248
|
|
|
'belligerent': to_belligerent(self.data['army']), |
|
249
|
|
|
'flight_id': self.data.get('player'), |
|
250
|
|
|
'aircraft_index': int(self.data['playerNum']), |
|
251
|
|
|
'fixed_weapons': 'WEAPONSCONSTANT' in self.data, |
|
252
|
|
|
}, |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
1 |
|
def _to_time(self, value): |
|
256
|
1 |
|
time = float(value) |
|
257
|
1 |
|
minutes, hours = math.modf(time) |
|
258
|
1 |
|
return datetime.time(int(hours), int(minutes * 60)) |
|
259
|
|
|
|
|
260
|
|
|
|
|
261
|
1 |
|
class SeasonParser(ValuesParser): |
|
262
|
|
|
""" |
|
263
|
|
|
Parses ``SEASON`` section. |
|
264
|
|
|
View :ref:`detailed description <season-section>`. |
|
265
|
|
|
""" |
|
266
|
|
|
|
|
267
|
1 |
|
def check_section_name(self, section_name): |
|
268
|
|
|
""" |
|
269
|
|
|
Implements abstract method. See |
|
270
|
|
|
:meth:`SectionParser.check_section_name` for semantics. |
|
271
|
|
|
""" |
|
272
|
1 |
|
return section_name == "SEASON" |
|
273
|
|
|
|
|
274
|
1 |
|
def clean(self): |
|
275
|
|
|
""" |
|
276
|
|
|
Redefines base method. See :meth:`SectionParser.clean` for |
|
277
|
|
|
semantics. |
|
278
|
|
|
|
|
279
|
|
|
Combines day, time and year into :class:`datetime.date` object. |
|
280
|
|
|
""" |
|
281
|
1 |
|
date = datetime.date(int(self.data['Year']), |
|
282
|
|
|
int(self.data['Month']), |
|
283
|
|
|
int(self.data['Day'])) |
|
284
|
1 |
|
return {'date': date, } |
|
285
|
|
|
|
|
286
|
|
|
|
|
287
|
1 |
|
class WeatherParser(ValuesParser): |
|
288
|
|
|
""" |
|
289
|
|
|
Parses ``WEATHER`` section. |
|
290
|
|
|
View :ref:`detailed description <weather-section>`. |
|
291
|
|
|
""" |
|
292
|
|
|
|
|
293
|
1 |
|
def check_section_name(self, section_name): |
|
294
|
|
|
""" |
|
295
|
|
|
Implements abstract method. See |
|
296
|
|
|
:meth:`SectionParser.check_section_name` for semantics. |
|
297
|
|
|
""" |
|
298
|
1 |
|
return section_name == "WEATHER" |
|
299
|
|
|
|
|
300
|
1 |
|
def clean(self): |
|
301
|
|
|
""" |
|
302
|
|
|
Redefines base method. See :meth:`SectionParser.clean` for |
|
303
|
|
|
semantics. |
|
304
|
|
|
""" |
|
305
|
1 |
|
gust = int(self.data['Gust']) |
|
306
|
1 |
|
turbulence = int(self.data['Turbulence']) |
|
307
|
1 |
|
return { |
|
308
|
|
|
'weather': { |
|
309
|
|
|
'wind': { |
|
310
|
|
|
'direction': float(self.data['WindDirection']), |
|
311
|
|
|
'speed': float(self.data['WindSpeed']), |
|
312
|
|
|
}, |
|
313
|
|
|
'gust': Gust.get_by_value(gust), |
|
314
|
|
|
'turbulence': Turbulence.get_by_value(turbulence), |
|
315
|
|
|
}, |
|
316
|
|
|
} |
|
317
|
|
|
|
|
318
|
|
|
|
|
319
|
1 |
|
class MDSParser(ValuesParser): |
|
320
|
|
|
""" |
|
321
|
|
|
Parses ``MDS`` section. |
|
322
|
|
|
View :ref:`detailed description <mds-section>`. |
|
323
|
|
|
""" |
|
324
|
|
|
|
|
325
|
1 |
|
def check_section_name(self, section_name): |
|
326
|
1 |
|
return section_name == "MDS" |
|
327
|
|
|
|
|
328
|
1 |
|
def parse_line(self, line): |
|
329
|
1 |
|
super(MDSParser, self).parse_line(line.replace('MDS_', '')) |
|
330
|
|
|
|
|
331
|
1 |
|
def clean(self): |
|
332
|
1 |
|
return { |
|
333
|
|
|
'conditions': { |
|
334
|
|
|
'radar': { |
|
335
|
|
|
'advanced_mode': to_bool(self.data['Radar_SetRadarToAdvanceMode']), |
|
336
|
|
|
'refresh_interval': int(self.data['Radar_RefreshInterval']), |
|
337
|
|
|
'ships': { |
|
338
|
|
|
'big': { |
|
339
|
|
|
'max_range': int(self.data['Radar_ShipRadar_MaxRange']), |
|
340
|
|
|
'min_height': int(self.data['Radar_ShipRadar_MinHeight']), |
|
341
|
|
|
'max_height': int(self.data['Radar_ShipRadar_MaxHeight']), |
|
342
|
|
|
}, |
|
343
|
|
|
'small': { |
|
344
|
|
|
'max_range': int(self.data['Radar_ShipSmallRadar_MaxRange']), |
|
345
|
|
|
'min_height': int(self.data['Radar_ShipSmallRadar_MinHeight']), |
|
346
|
|
|
'max_height': int(self.data['Radar_ShipSmallRadar_MaxHeight']), |
|
347
|
|
|
}, |
|
348
|
|
|
}, |
|
349
|
|
|
'scouts': { |
|
350
|
|
|
'max_range': int(self.data['Radar_ScoutRadar_MaxRange']), |
|
351
|
|
|
'max_height': int(self.data['Radar_ScoutRadar_DeltaHeight']), |
|
352
|
|
|
'alpha': int(self.data['Radar_ScoutGroundObjects_Alpha']), |
|
353
|
|
|
}, |
|
354
|
|
|
}, |
|
355
|
|
|
'scouting': { |
|
356
|
|
|
'ships_affect_radar': to_bool(self.data['Radar_ShipsAsRadar']), |
|
357
|
|
|
'scouts_affect_radar': to_bool(self.data['Radar_ScoutsAsRadar']), |
|
358
|
|
|
'only_scouts_complete_targets': to_bool(self.data['Radar_ScoutCompleteRecon']), |
|
359
|
|
|
}, |
|
360
|
|
|
'communication': { |
|
361
|
|
|
'tower_communication': to_bool(self.data['Radar_EnableTowerCommunications']), |
|
362
|
|
|
'vectoring': not to_bool(self.data['Radar_DisableVectoring']), |
|
363
|
|
|
'ai_radio_silence': to_bool(self.data['Misc_DisableAIRadioChatter']), |
|
364
|
|
|
}, |
|
365
|
|
|
'home_bases': { |
|
366
|
|
|
'hide_ai_aircrafts_after_landing': to_bool(self.data['Misc_DespawnAIPlanesAfterLanding']), |
|
367
|
|
|
'hide_unpopulated': to_bool(self.data['Radar_HideUnpopulatedAirstripsFromMinimap']), |
|
368
|
|
|
'hide_players_count': to_bool(self.data['Misc_HidePlayersCountOnHomeBase']), |
|
369
|
|
|
}, |
|
370
|
|
|
'crater_visibility_muptipliers': { |
|
371
|
|
|
'le_100kg': float(self.data['Misc_BombsCat1_CratersVisibilityMultiplier']), |
|
372
|
|
|
'le_1000kg': float(self.data['Misc_BombsCat2_CratersVisibilityMultiplier']), |
|
373
|
|
|
'gt_1000kg': float(self.data['Misc_BombsCat3_CratersVisibilityMultiplier']), |
|
374
|
|
|
}, |
|
375
|
|
|
}, |
|
376
|
|
|
} |
|
377
|
|
|
|
|
378
|
|
|
|
|
379
|
1 |
|
class MDSScoutsParser(CollectingParser): |
|
380
|
|
|
""" |
|
381
|
|
|
Parses ``MDS_Scouts`` section. |
|
382
|
|
|
View :ref:`detailed description <mds-scouts-section>`. |
|
383
|
|
|
""" |
|
384
|
1 |
|
input_prefix = "MDS_Scouts_" |
|
385
|
1 |
|
output_prefix = "scouts_" |
|
386
|
|
|
|
|
387
|
1 |
|
def check_section_name(self, section_name): |
|
388
|
1 |
|
if not section_name.startswith(self.input_prefix): |
|
389
|
1 |
|
return False |
|
390
|
1 |
|
belligerent_name = self._get_belligerent_name(section_name) |
|
391
|
1 |
|
return bool(belligerent_name) |
|
392
|
|
|
|
|
393
|
1 |
|
def init_parser(self, section_name): |
|
394
|
1 |
|
super(MDSScoutsParser, self).init_parser(section_name) |
|
395
|
1 |
|
belligerent_name = self._get_belligerent_name(section_name) |
|
396
|
1 |
|
self.belligerent = Belligerents[belligerent_name] |
|
397
|
1 |
|
self.output_key = "{}{}".format(self.output_prefix, belligerent_name) |
|
398
|
|
|
|
|
399
|
1 |
|
def _get_belligerent_name(self, section_name): |
|
400
|
1 |
|
return section_name[len(self.input_prefix):].lower() |
|
401
|
|
|
|
|
402
|
1 |
|
def clean(self): |
|
403
|
1 |
|
return { |
|
404
|
|
|
self.output_key: { |
|
405
|
|
|
'belligerent': self.belligerent, |
|
406
|
|
|
'aircrafts': self.data, |
|
407
|
|
|
}, |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
|
|
411
|
1 |
|
class RespawnTimeParser(ValuesParser): |
|
412
|
|
|
""" |
|
413
|
|
|
Parses ``RespawnTime`` section. |
|
414
|
|
|
View :ref:`detailed description <respawn-time-section>`. |
|
415
|
|
|
""" |
|
416
|
|
|
|
|
417
|
1 |
|
def check_section_name(self, section_name): |
|
418
|
1 |
|
return section_name == "RespawnTime" |
|
419
|
|
|
|
|
420
|
1 |
|
def clean(self): |
|
421
|
1 |
|
return { |
|
422
|
|
|
'respawn_time': { |
|
423
|
|
|
'ships': { |
|
424
|
|
|
'big': int(self.data['Bigship']), |
|
425
|
|
|
'small': int(self.data['Ship']), |
|
426
|
|
|
}, |
|
427
|
|
|
'balloons': int(self.data['Aeroanchored']), |
|
428
|
|
|
'artillery': int(self.data['Artillery']), |
|
429
|
|
|
'searchlights': int(self.data['Searchlight']), |
|
430
|
|
|
}, |
|
431
|
|
|
} |
|
432
|
|
|
|
|
433
|
|
|
|
|
434
|
1 |
|
class ChiefsParser(CollectingParser): |
|
435
|
|
|
""" |
|
436
|
|
|
Parses ``Chiefs`` section. |
|
437
|
|
|
View :ref:`detailed description <chiefs-section>`. |
|
438
|
|
|
""" |
|
439
|
|
|
|
|
440
|
1 |
|
def check_section_name(self, section_name): |
|
441
|
1 |
|
return section_name == "Chiefs" |
|
442
|
|
|
|
|
443
|
1 |
|
def parse_line(self, line): |
|
444
|
1 |
|
params = line.split() |
|
445
|
1 |
|
(uid, type_code, belligerent), params = params[0:3], params[3:] |
|
446
|
|
|
|
|
447
|
1 |
|
chief_type, code = type_code.split('.') |
|
448
|
1 |
|
try: |
|
449
|
1 |
|
chief_type = to_unit_type(chief_type) |
|
450
|
1 |
|
except Exception: |
|
451
|
|
|
# Use original string as unit type |
|
452
|
|
|
pass |
|
453
|
|
|
|
|
454
|
1 |
|
unit = { |
|
455
|
|
|
'id': uid, |
|
456
|
|
|
'code': code, |
|
457
|
|
|
'type': chief_type, |
|
458
|
|
|
'belligerent': to_belligerent(belligerent), |
|
459
|
|
|
} |
|
460
|
1 |
|
if params: |
|
461
|
1 |
|
hibernation, skill, recharge_time = params |
|
462
|
1 |
|
unit.update({ |
|
463
|
|
|
'hibernation': int(hibernation), |
|
464
|
|
|
'skill': to_skill(skill), |
|
465
|
|
|
'recharge_time': float(recharge_time), |
|
466
|
|
|
}) |
|
467
|
1 |
|
self.data.append(unit) |
|
468
|
|
|
|
|
469
|
1 |
|
def clean(self): |
|
470
|
1 |
|
return {'moving_units': self.data, } |
|
471
|
|
|
|
|
472
|
|
|
|
|
473
|
1 |
|
class ChiefRoadParser(CollectingParser): |
|
474
|
|
|
""" |
|
475
|
|
|
Parses ``N_Chief_Road`` section. |
|
476
|
|
|
View :ref:`detailed description <chief-road-section>`. |
|
477
|
|
|
""" |
|
478
|
1 |
|
id_suffix = "_Chief" |
|
479
|
1 |
|
section_suffix = "_Road" |
|
480
|
1 |
|
input_suffix = id_suffix + section_suffix |
|
481
|
1 |
|
output_prefix = 'route_' |
|
482
|
|
|
|
|
483
|
1 |
|
def check_section_name(self, section_name): |
|
484
|
1 |
|
if not section_name.endswith(self.input_suffix): |
|
485
|
1 |
|
return False |
|
486
|
1 |
|
unit_id = self._extract_unit_id(section_name) |
|
487
|
1 |
|
stop = unit_id.index(self.id_suffix) |
|
488
|
1 |
|
return unit_id[:stop].isdigit() |
|
489
|
|
|
|
|
490
|
1 |
|
def init_parser(self, section_name): |
|
491
|
1 |
|
super(ChiefRoadParser, self).init_parser(section_name) |
|
492
|
1 |
|
unit_id = self._extract_unit_id(section_name) |
|
493
|
1 |
|
self.output_key = "{}{}".format(self.output_prefix, unit_id) |
|
494
|
|
|
|
|
495
|
1 |
|
def _extract_unit_id(self, section_name): |
|
496
|
1 |
|
stop = section_name.index(self.section_suffix) |
|
497
|
1 |
|
return section_name[:stop] |
|
498
|
|
|
|
|
499
|
1 |
|
def parse_line(self, line): |
|
500
|
1 |
|
params = line.split() |
|
501
|
1 |
|
pos, params = params[0:2], params[3:] |
|
502
|
|
|
|
|
503
|
1 |
|
args = { |
|
504
|
|
|
'pos': Point2D(*pos), |
|
505
|
|
|
} |
|
506
|
1 |
|
is_checkpoint = bool(params) |
|
507
|
1 |
|
args['is_checkpoint'] = is_checkpoint |
|
508
|
1 |
|
if is_checkpoint: |
|
509
|
1 |
|
args['delay'] = int(params[0]) |
|
510
|
1 |
|
args['section_length'] = int(params[1]) |
|
511
|
1 |
|
args['speed'] = float(params[2]) |
|
512
|
|
|
|
|
513
|
1 |
|
point = GroundRoutePoint(**args) |
|
514
|
1 |
|
self.data.append(point) |
|
515
|
|
|
|
|
516
|
1 |
|
def clean(self): |
|
517
|
1 |
|
return {self.output_key: self.data} |
|
518
|
|
|
|
|
519
|
|
|
|
|
520
|
1 |
|
class NStationaryParser(CollectingParser): |
|
521
|
|
|
""" |
|
522
|
|
|
Parses ``NStationary`` section. |
|
523
|
|
|
View :ref:`detailed description <nstationary-section>`. |
|
524
|
|
|
""" |
|
525
|
|
|
|
|
526
|
1 |
|
def __parse_artillery(params): |
|
527
|
|
|
""" |
|
528
|
|
|
Parse additional options for ``artillery`` type |
|
529
|
|
|
""" |
|
530
|
1 |
|
try: |
|
531
|
1 |
|
awakening_time, the_range, skill, use_spotter = params |
|
532
|
1 |
|
skill = to_skill(skill) |
|
533
|
1 |
|
use_spotter = to_bool(use_spotter) |
|
534
|
1 |
|
except ValueError: |
|
535
|
1 |
|
try: |
|
536
|
1 |
|
awakening_time, the_range = params |
|
537
|
1 |
|
except ValueError: |
|
538
|
1 |
|
awakening_time, the_range = params[0], 0 |
|
539
|
1 |
|
skill, use_spotter = None, False |
|
540
|
|
|
|
|
541
|
1 |
|
return { |
|
542
|
|
|
'awakening_time': float(awakening_time), |
|
543
|
|
|
'range': int(the_range), |
|
544
|
|
|
'skill': skill, |
|
545
|
|
|
'use_spotter': use_spotter, |
|
546
|
|
|
} |
|
547
|
|
|
|
|
548
|
1 |
|
def __parse_aircraft(params): |
|
549
|
|
|
""" |
|
550
|
|
|
Parse additional options for ``planes`` type |
|
551
|
|
|
""" |
|
552
|
1 |
|
try: |
|
553
|
1 |
|
air_force, allows_spawning__restorable = params[1:3] |
|
554
|
1 |
|
skin, show_markings = params[4:] |
|
555
|
1 |
|
except ValueError: |
|
556
|
1 |
|
air_force, allows_spawning__restorable = None, 0 |
|
557
|
1 |
|
skin, show_markings = params[1:] |
|
558
|
|
|
|
|
559
|
1 |
|
is_restorable = allows_spawning__restorable == IS_STATIONARY_AIRCRAFT_RESTORABLE |
|
560
|
1 |
|
skin = None if skin == NULL else skin |
|
561
|
|
|
|
|
562
|
1 |
|
return { |
|
563
|
|
|
'air_force': to_air_force(air_force), |
|
564
|
|
|
'allows_spawning': to_bool(allows_spawning__restorable), |
|
565
|
|
|
'is_restorable': is_restorable, |
|
566
|
|
|
'skin': skin, |
|
567
|
|
|
'show_markings': to_bool(show_markings), |
|
568
|
|
|
} |
|
569
|
|
|
|
|
570
|
1 |
|
def __parse_ship(params): |
|
571
|
|
|
""" |
|
572
|
|
|
Parse additional options for ``ships`` type |
|
573
|
|
|
""" |
|
574
|
1 |
|
awakening_time, skill, recharge_time = params[1:] |
|
575
|
1 |
|
return { |
|
576
|
|
|
'awakening_time': float(awakening_time), |
|
577
|
|
|
'recharge_time': float(recharge_time), |
|
578
|
|
|
'skill': to_skill(skill), |
|
579
|
|
|
} |
|
580
|
|
|
|
|
581
|
1 |
|
subparsers = { |
|
582
|
|
|
'artillery': (StationaryArtillery, __parse_artillery), |
|
583
|
|
|
'planes': (StationaryAircraft, __parse_aircraft), |
|
584
|
|
|
'ships': (StationaryShip, __parse_ship), |
|
585
|
|
|
} |
|
586
|
|
|
|
|
587
|
1 |
|
def check_section_name(self, section_name): |
|
588
|
1 |
|
return section_name == "NStationary" |
|
589
|
|
|
|
|
590
|
1 |
|
def parse_line(self, line): |
|
591
|
1 |
|
params = line.split() |
|
592
|
|
|
|
|
593
|
1 |
|
oid, object_name, belligerent = params[0], params[1], params[2] |
|
594
|
1 |
|
pos = params[3:5] |
|
595
|
1 |
|
rotation_angle = params[5] |
|
596
|
1 |
|
params = params[6:] |
|
597
|
|
|
|
|
598
|
1 |
|
type_name = self._get_type_name(object_name) |
|
599
|
1 |
|
try: |
|
600
|
1 |
|
object_type = to_unit_type(type_name) |
|
601
|
1 |
|
except Exception: |
|
602
|
|
|
# Use original string as object's type |
|
603
|
1 |
|
object_type = type_name |
|
604
|
|
|
|
|
605
|
1 |
|
the_object = { |
|
606
|
|
|
'id': oid, |
|
607
|
|
|
'belligerent': to_belligerent(belligerent), |
|
608
|
|
|
'code': self._get_code(object_name), |
|
609
|
|
|
'pos': Point2D(*pos), |
|
610
|
|
|
'rotation_angle': float(rotation_angle), |
|
611
|
|
|
'type': object_type, |
|
612
|
|
|
} |
|
613
|
|
|
|
|
614
|
1 |
|
if type_name in self.subparsers: |
|
615
|
1 |
|
structure, subparser = self.subparsers.get(type_name) |
|
616
|
1 |
|
the_object.update(subparser(params)) |
|
617
|
|
|
else: |
|
618
|
1 |
|
structure = StationaryObject |
|
619
|
|
|
|
|
620
|
1 |
|
self.data.append(structure(**the_object)) |
|
621
|
|
|
|
|
622
|
1 |
|
def _get_type_name(self, object_name): |
|
623
|
1 |
|
if object_name.startswith('ships'): |
|
624
|
1 |
|
return "ships" |
|
625
|
|
|
else: |
|
626
|
1 |
|
start = object_name.index('.') + 1 |
|
627
|
1 |
|
stop = object_name.rindex('.') |
|
628
|
1 |
|
return object_name[start:stop] |
|
629
|
|
|
|
|
630
|
1 |
|
def _get_code(self, code): |
|
631
|
1 |
|
start = code.index('$') + 1 |
|
632
|
1 |
|
return code[start:] |
|
633
|
|
|
|
|
634
|
1 |
|
def clean(self): |
|
635
|
1 |
|
return {'stationary': self.data, } |
|
636
|
|
|
|
|
637
|
|
|
|
|
638
|
1 |
|
class BuildingsParser(CollectingParser): |
|
639
|
|
|
""" |
|
640
|
|
|
Parses ``Buildings`` section. |
|
641
|
|
|
View :ref:`detailed description <buildings-section>`. |
|
642
|
|
|
""" |
|
643
|
|
|
|
|
644
|
1 |
|
def check_section_name(self, section_name): |
|
645
|
1 |
|
return section_name == "Buildings" |
|
646
|
|
|
|
|
647
|
1 |
|
def parse_line(self, line): |
|
648
|
1 |
|
params = line.split() |
|
649
|
1 |
|
oid, building_object, belligerent = params[:3] |
|
650
|
1 |
|
pos_x, pos_y, rotation_angle = params[3:] |
|
651
|
1 |
|
code = building_object.split('$')[1] |
|
652
|
1 |
|
self.data.append(Building( |
|
653
|
|
|
id=oid, |
|
654
|
|
|
belligerent=to_belligerent(belligerent), |
|
655
|
|
|
code=code, |
|
656
|
|
|
pos=Point2D(pos_x, pos_y), |
|
657
|
|
|
rotation_angle=float(rotation_angle), |
|
658
|
|
|
)) |
|
659
|
|
|
|
|
660
|
1 |
|
def clean(self): |
|
661
|
1 |
|
return {'buildings': self.data, } |
|
662
|
|
|
|
|
663
|
|
|
|
|
664
|
1 |
|
class TargetParser(CollectingParser): |
|
665
|
|
|
""" |
|
666
|
|
|
Parses ``Target`` section. |
|
667
|
|
|
View :ref:`detailed description <target-section>`. |
|
668
|
|
|
""" |
|
669
|
|
|
|
|
670
|
1 |
|
def check_section_name(self, section_name): |
|
671
|
1 |
|
return section_name == "Target" |
|
672
|
|
|
|
|
673
|
1 |
|
def parse_line(self, line): |
|
674
|
1 |
|
params = line.split() |
|
675
|
|
|
|
|
676
|
1 |
|
type_code, priority, in_sleep_mode, delay = params[:4] |
|
677
|
1 |
|
params = params[4:] |
|
678
|
|
|
|
|
679
|
1 |
|
target_type = TargetTypes.get_by_value(int(type_code)) |
|
680
|
1 |
|
target = { |
|
681
|
|
|
'type': target_type, |
|
682
|
|
|
'priority': TargetPriorities.get_by_value(int(priority)), |
|
683
|
|
|
'in_sleep_mode': to_bool(in_sleep_mode), |
|
684
|
|
|
'delay': int(delay), |
|
685
|
|
|
} |
|
686
|
|
|
|
|
687
|
1 |
|
subparser = TargetParser._subparsers.get(target_type) |
|
688
|
1 |
|
if subparser is not None: |
|
689
|
1 |
|
target.update(subparser(params)) |
|
690
|
|
|
|
|
691
|
1 |
|
self.data.append(target) |
|
692
|
|
|
|
|
693
|
1 |
|
@staticmethod |
|
694
|
|
|
def to_destruction_level(value): |
|
695
|
1 |
|
return int(value) / 10 |
|
696
|
|
|
|
|
697
|
1 |
|
def parse_destroy_or_cover_or_escort(params): |
|
698
|
|
|
""" |
|
699
|
|
|
Parse extra parameters for targets with type 'destroy' or 'cover' or |
|
700
|
|
|
'escort'. |
|
701
|
|
|
""" |
|
702
|
1 |
|
destruction_level = TargetParser.to_destruction_level(params[0]) |
|
703
|
1 |
|
pos, waypoint, object_code = params[1:3], params[4], params[5] |
|
704
|
1 |
|
object_pos = params[6:8] |
|
705
|
1 |
|
return { |
|
706
|
|
|
'destruction_level': destruction_level, |
|
707
|
|
|
'pos': Point2D(*pos), |
|
708
|
|
|
'object': { |
|
709
|
|
|
'waypoint': int(waypoint), |
|
710
|
|
|
'id': object_code, |
|
711
|
|
|
'pos': Point2D(*object_pos), |
|
712
|
|
|
}, |
|
713
|
|
|
} |
|
714
|
|
|
|
|
715
|
1 |
|
def parse_destroy_or_cover_bridge(params): |
|
716
|
|
|
""" |
|
717
|
|
|
Parse extra parameters for targets with type 'destroy bridge' or |
|
718
|
|
|
'cover bridge'. |
|
719
|
|
|
""" |
|
720
|
1 |
|
pos, object_code, object_pos = params[1:3], params[5], params[6:8] |
|
721
|
1 |
|
return { |
|
722
|
|
|
'pos': Point2D(*pos), |
|
723
|
|
|
'object': { |
|
724
|
|
|
'id': object_code, |
|
725
|
|
|
'pos': Point2D(*object_pos), |
|
726
|
|
|
}, |
|
727
|
|
|
} |
|
728
|
|
|
|
|
729
|
1 |
|
def parse_destroy_or_cover_area(params): |
|
730
|
|
|
""" |
|
731
|
|
|
Parse extra parameters for targets with type 'destroy area' or |
|
732
|
|
|
'cover area'. |
|
733
|
|
|
""" |
|
734
|
1 |
|
destruction_level = TargetParser.to_destruction_level(params[0]) |
|
735
|
1 |
|
pos_x, pos_y, radius = params[1:] |
|
736
|
1 |
|
return { |
|
737
|
|
|
'destruction_level': destruction_level, |
|
738
|
|
|
'pos': Point2D(pos_x, pos_y), |
|
739
|
|
|
'radius': int(radius), |
|
740
|
|
|
} |
|
741
|
|
|
|
|
742
|
1 |
|
def parse_recon(params): |
|
743
|
|
|
""" |
|
744
|
|
|
Parse extra parameters for targets with 'recon' type. |
|
745
|
|
|
""" |
|
746
|
1 |
|
requires_landing = params[0] != '500' |
|
747
|
1 |
|
pos, radius, params = params[1:3], params[3], params[4:] |
|
748
|
1 |
|
data = { |
|
749
|
|
|
'radius': int(radius), |
|
750
|
|
|
'requires_landing': requires_landing, |
|
751
|
|
|
'pos': Point2D(*pos), |
|
752
|
|
|
} |
|
753
|
1 |
|
if params: |
|
754
|
1 |
|
waypoint, object_code = params[:2] |
|
755
|
1 |
|
object_pos = params[2:] |
|
756
|
1 |
|
data['object'] = { |
|
757
|
|
|
'waypoint': int(waypoint), |
|
758
|
|
|
'id': object_code, |
|
759
|
|
|
'pos': Point2D(*object_pos), |
|
760
|
|
|
} |
|
761
|
1 |
|
return data |
|
762
|
|
|
|
|
763
|
1 |
|
_subparsers = { |
|
764
|
|
|
TargetTypes.destroy: parse_destroy_or_cover_or_escort, |
|
765
|
|
|
TargetTypes.destroy_bridge: parse_destroy_or_cover_bridge, |
|
766
|
|
|
TargetTypes.destroy_area: parse_destroy_or_cover_area, |
|
767
|
|
|
TargetTypes.recon: parse_recon, |
|
768
|
|
|
TargetTypes.escort: parse_destroy_or_cover_or_escort, |
|
769
|
|
|
TargetTypes.cover: parse_destroy_or_cover_or_escort, |
|
770
|
|
|
TargetTypes.cover_area: parse_destroy_or_cover_area, |
|
771
|
|
|
TargetTypes.cover_bridge: parse_destroy_or_cover_bridge, |
|
772
|
|
|
} |
|
773
|
|
|
|
|
774
|
1 |
|
def clean(self): |
|
775
|
1 |
|
return {'targets': self.data, } |
|
776
|
|
|
|
|
777
|
|
|
|
|
778
|
1 |
|
class BornPlaceParser(CollectingParser): |
|
779
|
|
|
""" |
|
780
|
|
|
Parses ``BornPlace`` section. |
|
781
|
|
|
View :ref:`detailed description <bornplace-section>`. |
|
782
|
|
|
""" |
|
783
|
|
|
|
|
784
|
1 |
|
def check_section_name(self, section_name): |
|
785
|
1 |
|
return section_name == "BornPlace" |
|
786
|
|
|
|
|
787
|
1 |
|
def parse_line(self, line): |
|
788
|
1 |
|
( |
|
789
|
|
|
belligerent, the_range, pos_x, pos_y, has_parachutes, |
|
790
|
|
|
air_spawn_height, air_spawn_speed, air_spawn_heading, max_pilots, |
|
791
|
|
|
radar_min_height, radar_max_height, radar_range, air_spawn_always, |
|
792
|
|
|
enable_aircraft_limits, aircraft_limits_consider_lost, |
|
793
|
|
|
disable_spawning, friction_enabled, friction_value, |
|
794
|
|
|
aircraft_limits_consider_stationary, show_default_icon, |
|
795
|
|
|
air_spawn_if_deck_is_full, spawn_in_stationary, |
|
796
|
|
|
return_to_start_position |
|
797
|
|
|
) = line.split() |
|
798
|
|
|
|
|
799
|
1 |
|
self.data.append({ |
|
800
|
|
|
'range': int(the_range), |
|
801
|
|
|
'belligerent': to_belligerent(belligerent), |
|
802
|
|
|
'show_default_icon': to_bool(show_default_icon), |
|
803
|
|
|
'friction': { |
|
804
|
|
|
'enabled': to_bool(friction_enabled), |
|
805
|
|
|
'value': float(friction_value), |
|
806
|
|
|
}, |
|
807
|
|
|
'spawning': { |
|
808
|
|
|
'enabled': not to_bool(disable_spawning), |
|
809
|
|
|
'with_parachutes': to_bool(has_parachutes), |
|
810
|
|
|
'max_pilots': int(max_pilots), |
|
811
|
|
|
'in_stationary': { |
|
812
|
|
|
'enabled': to_bool(spawn_in_stationary), |
|
813
|
|
|
'return_to_start_position': to_bool(return_to_start_position), |
|
814
|
|
|
}, |
|
815
|
|
|
'in_air': { |
|
816
|
|
|
'height': int(air_spawn_height), |
|
817
|
|
|
'speed': int(air_spawn_speed), |
|
818
|
|
|
'heading': int(air_spawn_heading), |
|
819
|
|
|
'conditions': { |
|
820
|
|
|
'always': to_bool(air_spawn_always), |
|
821
|
|
|
'if_deck_is_full': to_bool(air_spawn_if_deck_is_full), |
|
822
|
|
|
}, |
|
823
|
|
|
}, |
|
824
|
|
|
'aircraft_limitations': { |
|
825
|
|
|
'enabled': to_bool(enable_aircraft_limits), |
|
826
|
|
|
'consider_lost': to_bool(aircraft_limits_consider_lost), |
|
827
|
|
|
'consider_stationary': to_bool(aircraft_limits_consider_stationary), |
|
828
|
|
|
}, |
|
829
|
|
|
}, |
|
830
|
|
|
'radar': { |
|
831
|
|
|
'range': int(radar_range), |
|
832
|
|
|
'min_height': int(radar_min_height), |
|
833
|
|
|
'max_height': int(radar_max_height), |
|
834
|
|
|
}, |
|
835
|
|
|
'pos': Point2D(pos_x, pos_y), |
|
836
|
|
|
}) |
|
837
|
|
|
|
|
838
|
1 |
|
def clean(self): |
|
839
|
1 |
|
return {'home_bases': self.data, } |
|
840
|
|
|
|
|
841
|
|
|
|
|
842
|
1 |
|
class BornPlaceAircraftsParser(CollectingParser): |
|
843
|
|
|
""" |
|
844
|
|
|
Parses ``BornPlaceN`` section. |
|
845
|
|
|
View :ref:`detailed description <bornplace-aircrafts-section>`. |
|
846
|
|
|
""" |
|
847
|
1 |
|
input_prefix = 'BornPlace' |
|
848
|
1 |
|
output_prefix = 'home_base_aircrafts_' |
|
849
|
|
|
|
|
850
|
1 |
|
def check_section_name(self, section_name): |
|
851
|
1 |
|
if not section_name.startswith(self.input_prefix): |
|
852
|
1 |
|
return False |
|
853
|
1 |
|
try: |
|
854
|
1 |
|
self._extract_section_number(section_name) |
|
855
|
1 |
|
except ValueError: |
|
856
|
1 |
|
return False |
|
857
|
|
|
else: |
|
858
|
1 |
|
return True |
|
859
|
|
|
|
|
860
|
1 |
|
def init_parser(self, section_name): |
|
861
|
1 |
|
super(BornPlaceAircraftsParser, self).init_parser(section_name) |
|
862
|
1 |
|
self.output_key = ( |
|
863
|
|
|
"{}{}".format(self.output_prefix, |
|
864
|
|
|
self._extract_section_number(section_name))) |
|
865
|
1 |
|
self.aircraft = None |
|
866
|
|
|
|
|
867
|
1 |
|
def _extract_section_number(self, section_name): |
|
868
|
1 |
|
start = len(self.input_prefix) |
|
869
|
1 |
|
return int(section_name[start:]) |
|
870
|
|
|
|
|
871
|
1 |
|
def parse_line(self, line): |
|
872
|
1 |
|
parts = line.split() |
|
873
|
|
|
|
|
874
|
1 |
|
if parts[0] == WEAPONS_CONTINUATION_MARK: |
|
875
|
1 |
|
self.aircraft['weapon_limitations'].extend(parts[1:]) |
|
876
|
|
|
else: |
|
877
|
1 |
|
if self.aircraft: |
|
878
|
|
|
# Finalize previous aircraft |
|
879
|
1 |
|
self.data.append(self.aircraft) |
|
880
|
1 |
|
self.aircraft = BornPlaceAircraftsParser._parse_new_item(parts) |
|
881
|
|
|
|
|
882
|
1 |
|
@staticmethod |
|
883
|
|
|
def _parse_new_item(parts): |
|
884
|
1 |
|
code = parts.pop(0) |
|
885
|
1 |
|
limit = BornPlaceAircraftsParser._extract_limit(parts) |
|
886
|
1 |
|
return { |
|
887
|
|
|
'code': code, |
|
888
|
|
|
'limit': limit, |
|
889
|
|
|
'weapon_limitations': parts, |
|
890
|
|
|
} |
|
891
|
|
|
|
|
892
|
1 |
|
@staticmethod |
|
893
|
|
|
def _extract_limit(parts): |
|
894
|
1 |
|
if parts: |
|
895
|
1 |
|
limit = int(parts.pop(0)) |
|
896
|
1 |
|
limit = limit if limit >= 0 else None |
|
897
|
|
|
else: |
|
898
|
1 |
|
limit = None |
|
899
|
1 |
|
return limit |
|
900
|
|
|
|
|
901
|
1 |
|
def clean(self): |
|
902
|
1 |
|
if self.aircraft: |
|
903
|
1 |
|
aircraft, self.aircraft = self.aircraft, None |
|
904
|
1 |
|
self.data.append(aircraft) |
|
905
|
|
|
|
|
906
|
1 |
|
return {self.output_key: self.data, } |
|
907
|
|
|
|
|
908
|
|
|
|
|
909
|
1 |
|
class BornPlaceAirForcesParser(CollectingParser): |
|
910
|
|
|
""" |
|
911
|
|
|
Parses ``BornPlaceCountriesN`` section. |
|
912
|
|
|
View :ref:`detailed description <bornplace-air-forces-section>`. |
|
913
|
|
|
""" |
|
914
|
1 |
|
input_prefix = 'BornPlaceCountries' |
|
915
|
1 |
|
output_prefix = 'home_base_air_forces_' |
|
916
|
|
|
|
|
917
|
1 |
|
def check_section_name(self, section_name): |
|
918
|
1 |
|
if not section_name.startswith(self.input_prefix): |
|
919
|
1 |
|
return False |
|
920
|
1 |
|
try: |
|
921
|
1 |
|
self._extract_section_number(section_name) |
|
922
|
1 |
|
except ValueError: |
|
923
|
1 |
|
return False |
|
924
|
|
|
else: |
|
925
|
1 |
|
return True |
|
926
|
|
|
|
|
927
|
1 |
|
def init_parser(self, section_name): |
|
928
|
1 |
|
super(BornPlaceAirForcesParser, self).init_parser(section_name) |
|
929
|
1 |
|
self.output_key = ( |
|
930
|
|
|
"{}{}".format(self.output_prefix, |
|
931
|
|
|
self._extract_section_number(section_name))) |
|
932
|
1 |
|
self.countries = {} |
|
933
|
|
|
|
|
934
|
1 |
|
def _extract_section_number(self, section_name): |
|
935
|
1 |
|
start = len(self.input_prefix) |
|
936
|
1 |
|
return int(section_name[start:]) |
|
937
|
|
|
|
|
938
|
1 |
|
def parse_line(self, line): |
|
939
|
1 |
|
air_force = to_air_force(line.strip()) |
|
940
|
1 |
|
self.data.append(air_force) |
|
941
|
|
|
|
|
942
|
1 |
|
def clean(self): |
|
943
|
1 |
|
return {self.output_key: self.data, } |
|
944
|
|
|
|
|
945
|
|
|
|
|
946
|
1 |
|
class StaticCameraParser(CollectingParser): |
|
947
|
|
|
""" |
|
948
|
|
|
Parses ``StaticCamera`` section. |
|
949
|
|
|
View :ref:`detailed description <static-camera-section>`. |
|
950
|
|
|
""" |
|
951
|
|
|
|
|
952
|
1 |
|
def check_section_name(self, section_name): |
|
953
|
1 |
|
return section_name == "StaticCamera" |
|
954
|
|
|
|
|
955
|
1 |
|
def parse_line(self, line): |
|
956
|
1 |
|
pos_x, pos_y, pos_z, belligerent = line.split() |
|
957
|
1 |
|
self.data.append(StaticCamera( |
|
958
|
|
|
belligerent=to_belligerent(belligerent), |
|
959
|
|
|
pos=Point3D(pos_x, pos_y, pos_z), |
|
960
|
|
|
)) |
|
961
|
|
|
|
|
962
|
1 |
|
def clean(self): |
|
963
|
1 |
|
return {'cameras': self.data, } |
|
964
|
|
|
|
|
965
|
|
|
|
|
966
|
1 |
|
class FrontMarkerParser(CollectingParser): |
|
967
|
|
|
""" |
|
968
|
|
|
Parses ``FrontMarker`` section. |
|
969
|
|
|
View :ref:`detailed description <front-marker-section>`. |
|
970
|
|
|
""" |
|
971
|
|
|
|
|
972
|
1 |
|
def check_section_name(self, section_name): |
|
973
|
1 |
|
return section_name == "FrontMarker" |
|
974
|
|
|
|
|
975
|
1 |
|
def parse_line(self, line): |
|
976
|
1 |
|
oid, pos_x, pos_y, belligerent = line.split() |
|
977
|
1 |
|
self.data.append(FrontMarker( |
|
978
|
|
|
id=oid, |
|
979
|
|
|
belligerent=to_belligerent(belligerent), |
|
980
|
|
|
pos=Point2D(pos_x, pos_y), |
|
981
|
|
|
)) |
|
982
|
|
|
|
|
983
|
1 |
|
def clean(self): |
|
984
|
1 |
|
return {'markers': self.data, } |
|
985
|
|
|
|
|
986
|
|
|
|
|
987
|
1 |
|
class RocketParser(CollectingParser): |
|
988
|
|
|
""" |
|
989
|
|
|
Parses ``Rocket`` section. |
|
990
|
|
|
View :ref:`detailed description <rocket-section>`. |
|
991
|
|
|
""" |
|
992
|
|
|
|
|
993
|
1 |
|
def check_section_name(self, section_name): |
|
994
|
1 |
|
return section_name == "Rocket" |
|
995
|
|
|
|
|
996
|
1 |
|
def parse_line(self, line): |
|
997
|
1 |
|
params = line.split() |
|
998
|
|
|
|
|
999
|
1 |
|
oid, code, belligerent = params[0:3] |
|
1000
|
1 |
|
pos = params[3:5] |
|
1001
|
1 |
|
rotation_angle, delay, count, period = params[5:9] |
|
1002
|
1 |
|
destination = params[9:] |
|
1003
|
|
|
|
|
1004
|
1 |
|
self.data.append(Rocket( |
|
1005
|
|
|
id=oid, |
|
1006
|
|
|
code=code, |
|
1007
|
|
|
belligerent=to_belligerent(belligerent), |
|
1008
|
|
|
pos=Point2D(*pos), |
|
1009
|
|
|
rotation_angle=float(rotation_angle), |
|
1010
|
|
|
delay=float(delay), |
|
1011
|
|
|
count=int(count), |
|
1012
|
|
|
period=float(period), |
|
1013
|
|
|
destination=Point2D(*destination) if destination else None |
|
1014
|
|
|
)) |
|
1015
|
|
|
|
|
1016
|
1 |
|
def clean(self): |
|
1017
|
1 |
|
return {'rockets': self.data} |
|
1018
|
|
|
|
|
1019
|
|
|
|
|
1020
|
1 |
|
class WingParser(CollectingParser): |
|
1021
|
|
|
""" |
|
1022
|
|
|
Parses ``Wing`` section. |
|
1023
|
|
|
View :ref:`detailed description <wing-section>`. |
|
1024
|
|
|
""" |
|
1025
|
|
|
|
|
1026
|
1 |
|
def check_section_name(self, section_name): |
|
1027
|
1 |
|
return section_name == "Wing" |
|
1028
|
|
|
|
|
1029
|
1 |
|
def clean(self): |
|
1030
|
1 |
|
return {'flights': self.data} |
|
1031
|
|
|
|
|
1032
|
|
|
|
|
1033
|
1 |
|
class FlightInfoParser(ValuesParser): |
|
1034
|
|
|
""" |
|
1035
|
|
|
Parses settings for a moving flight group. |
|
1036
|
|
|
View :ref:`detailed description <flight-info-section>`. |
|
1037
|
|
|
""" |
|
1038
|
|
|
|
|
1039
|
1 |
|
def check_section_name(self, section_name): |
|
1040
|
1 |
|
try: |
|
1041
|
1 |
|
self._decompose_section_name(section_name) |
|
1042
|
1 |
|
except Exception: |
|
1043
|
1 |
|
return False |
|
1044
|
|
|
else: |
|
1045
|
1 |
|
return True |
|
1046
|
|
|
|
|
1047
|
1 |
|
def init_parser(self, section_name): |
|
1048
|
1 |
|
super(FlightInfoParser, self).init_parser(section_name) |
|
1049
|
1 |
|
self.output_key = section_name |
|
1050
|
1 |
|
self.flight_info = self._decompose_section_name(section_name) |
|
1051
|
|
|
|
|
1052
|
1 |
|
def _decompose_section_name(self, section_name): |
|
1053
|
1 |
|
prefix = section_name[:-2] |
|
1054
|
1 |
|
squadron, flight = section_name[-2:] |
|
1055
|
|
|
|
|
1056
|
1 |
|
try: |
|
1057
|
1 |
|
regiment = None |
|
1058
|
1 |
|
air_force = AirForces.get_by_flight_prefix(prefix) |
|
1059
|
1 |
|
except ValueError: |
|
1060
|
1 |
|
regiment = Regiments.get_by_code_name(prefix) |
|
1061
|
1 |
|
air_force = regiment.air_force |
|
1062
|
|
|
|
|
1063
|
1 |
|
return { |
|
1064
|
|
|
'id': section_name, |
|
1065
|
|
|
'air_force': air_force, |
|
1066
|
|
|
'regiment': regiment, |
|
1067
|
|
|
'squadron_index': int(squadron), |
|
1068
|
|
|
'flight_index': int(flight), |
|
1069
|
|
|
} |
|
1070
|
|
|
|
|
1071
|
1 |
|
def clean(self): |
|
1072
|
1 |
|
count = int(self.data['Planes']) |
|
1073
|
1 |
|
code = self.data['Class'].split('.', 1)[1] |
|
1074
|
1 |
|
aircrafts = [] |
|
1075
|
|
|
|
|
1076
|
1 |
|
def _add_if_present(target, key, value): |
|
1077
|
1 |
|
if value: |
|
1078
|
1 |
|
target[key] = value |
|
1079
|
|
|
|
|
1080
|
1 |
|
for i in range(count): |
|
1081
|
1 |
|
aircraft = { |
|
1082
|
|
|
'index': i, |
|
1083
|
|
|
'has_markings': self._has_markings(i), |
|
1084
|
|
|
'skill': self._get_skill(i), |
|
1085
|
|
|
} |
|
1086
|
1 |
|
_add_if_present( |
|
1087
|
|
|
aircraft, 'aircraft_skin', self._get_skin('skin', i)) |
|
1088
|
1 |
|
_add_if_present( |
|
1089
|
|
|
aircraft, 'nose_art', self._get_skin('nose_art', i)) |
|
1090
|
1 |
|
_add_if_present( |
|
1091
|
|
|
aircraft, 'pilot_skin', self._get_skin('pilot', i)) |
|
1092
|
1 |
|
_add_if_present( |
|
1093
|
|
|
aircraft, 'spawn_object', self._get_spawn_object_id(i)) |
|
1094
|
1 |
|
aircrafts.append(aircraft) |
|
1095
|
|
|
|
|
1096
|
1 |
|
self.flight_info.update({ |
|
1097
|
|
|
'ai_only': 'OnlyAI' in self.data, |
|
1098
|
|
|
'aircrafts': aircrafts, |
|
1099
|
|
|
'code': code, |
|
1100
|
|
|
'fuel': int(self.data['Fuel']), |
|
1101
|
|
|
'with_parachutes': 'Parachute' not in self.data, |
|
1102
|
|
|
'count': count, |
|
1103
|
|
|
'weapons': self.data['weapons'], |
|
1104
|
|
|
}) |
|
1105
|
|
|
|
|
1106
|
1 |
|
return {self.output_key: self.flight_info} |
|
1107
|
|
|
|
|
1108
|
1 |
|
def _get_skill(self, aircraft_id): |
|
1109
|
1 |
|
if 'Skill' in self.data: |
|
1110
|
1 |
|
return to_skill(self.data['Skill']) |
|
1111
|
|
|
else: |
|
1112
|
1 |
|
return to_skill(self.data['Skill{:}'.format(aircraft_id)]) |
|
1113
|
|
|
|
|
1114
|
1 |
|
def _has_markings(self, aircraft_id): |
|
1115
|
1 |
|
return 'numberOn{:}'.format(aircraft_id) not in self.data |
|
1116
|
|
|
|
|
1117
|
1 |
|
def _get_skin(self, prefix, aircraft_id): |
|
1118
|
1 |
|
return self.data.get('{:}{:}'.format(prefix, aircraft_id)) |
|
1119
|
|
|
|
|
1120
|
1 |
|
def _get_spawn_object_id(self, aircraft_id): |
|
1121
|
1 |
|
return self.data.get('spawn{:}'.format(aircraft_id)) |
|
1122
|
|
|
|
|
1123
|
|
|
|
|
1124
|
1 |
|
class FlightRouteParser(CollectingParser): |
|
1125
|
|
|
""" |
|
1126
|
|
|
Parses ``*_Way`` section. |
|
1127
|
|
|
View :ref:`detailed description <flight-route-section>`. |
|
1128
|
|
|
""" |
|
1129
|
1 |
|
input_suffix = "_Way" |
|
1130
|
1 |
|
output_prefix = 'flight_route_' |
|
1131
|
|
|
|
|
1132
|
1 |
|
def check_section_name(self, section_name): |
|
1133
|
1 |
|
return section_name.endswith(self.input_suffix) |
|
1134
|
|
|
|
|
1135
|
1 |
|
def _extract_flight_code(self, section_name): |
|
1136
|
1 |
|
return section_name[:-len(self.input_suffix)] |
|
1137
|
|
|
|
|
1138
|
1 |
|
def init_parser(self, section_name): |
|
1139
|
1 |
|
super(FlightRouteParser, self).init_parser(section_name) |
|
1140
|
1 |
|
flight_code = self._extract_flight_code(section_name) |
|
1141
|
1 |
|
self.output_key = "{}{}".format(self.output_prefix, flight_code) |
|
1142
|
1 |
|
self.point = None |
|
1143
|
1 |
|
self.point_class = None |
|
1144
|
|
|
|
|
1145
|
1 |
|
def parse_line(self, line): |
|
1146
|
1 |
|
params = line.split() |
|
1147
|
1 |
|
type_code, params = params[0], params[1:] |
|
1148
|
1 |
|
if type_code == ROUTE_POINT_EXTRA_PARAMETERS_MARK: |
|
1149
|
1 |
|
self._parse_options(params) |
|
1150
|
|
|
else: |
|
1151
|
1 |
|
self._finalize_current_point() |
|
1152
|
1 |
|
pos, speed, params = params[0:3], params[3], params[4:] |
|
1153
|
1 |
|
self.point = { |
|
1154
|
|
|
'type': RoutePointTypes.get_by_value(type_code), |
|
1155
|
|
|
'pos': Point3D(*pos), |
|
1156
|
|
|
'speed': float(speed), |
|
1157
|
|
|
} |
|
1158
|
1 |
|
self._parse_extra(params) |
|
1159
|
|
|
|
|
1160
|
1 |
|
def _parse_options(self, params): |
|
1161
|
1 |
|
try: |
|
1162
|
1 |
|
cycles, timeout, angle, side_size, altitude_difference = params |
|
1163
|
1 |
|
self.point.update({ |
|
1164
|
|
|
'patrol_cycles': int(cycles), |
|
1165
|
|
|
'patrol_timeout': int(timeout), |
|
1166
|
|
|
'pattern_angle': int(angle), |
|
1167
|
|
|
'pattern_side_size': int(side_size), |
|
1168
|
|
|
'pattern_altitude_difference': int(altitude_difference), |
|
1169
|
|
|
}) |
|
1170
|
1 |
|
self.point_class = FlightRoutePatrolPoint |
|
1171
|
1 |
|
except ValueError: |
|
1172
|
1 |
|
delay, spacing = params[1:3] |
|
1173
|
1 |
|
self.point.update({ |
|
1174
|
|
|
'delay': int(delay), |
|
1175
|
|
|
'spacing': int(spacing), |
|
1176
|
|
|
}) |
|
1177
|
1 |
|
self.point_class = FlightRouteTakeoffPoint |
|
1178
|
|
|
|
|
1179
|
1 |
|
def _parse_extra(self, params): |
|
1180
|
1 |
|
if FlightRouteParser._is_new_game_version(params): |
|
1181
|
1 |
|
radio_silence, formation, params = FlightRouteParser._parse_new_version_extra(params) |
|
1182
|
1 |
|
if params: |
|
1183
|
1 |
|
self._parse_target(params) |
|
1184
|
|
|
else: |
|
1185
|
1 |
|
radio_silence = False |
|
1186
|
1 |
|
formation = None |
|
1187
|
|
|
|
|
1188
|
1 |
|
self.point.update({ |
|
1189
|
|
|
'radio_silence': radio_silence, |
|
1190
|
|
|
'formation': formation, |
|
1191
|
|
|
}) |
|
1192
|
|
|
|
|
1193
|
1 |
|
@staticmethod |
|
1194
|
|
|
def _is_new_game_version(params): |
|
1195
|
1 |
|
return ( |
|
1196
|
|
|
ROUTE_POINT_RADIO_SILENCE_ON in params |
|
1197
|
|
|
or ROUTE_POINT_RADIO_SILENCE_OFF in params |
|
1198
|
|
|
) |
|
1199
|
|
|
|
|
1200
|
1 |
|
@staticmethod |
|
1201
|
|
|
def _parse_new_version_extra(params): |
|
1202
|
1 |
|
try: |
|
1203
|
1 |
|
index = params.index(ROUTE_POINT_RADIO_SILENCE_ON) |
|
1204
|
1 |
|
except ValueError: |
|
1205
|
1 |
|
index = params.index(ROUTE_POINT_RADIO_SILENCE_OFF) |
|
1206
|
|
|
|
|
1207
|
1 |
|
params, radio_silence, extra = params[:index], params[index], params[index+1:] |
|
1208
|
|
|
|
|
1209
|
1 |
|
radio_silence = radio_silence == ROUTE_POINT_RADIO_SILENCE_ON |
|
1210
|
1 |
|
formation = Formations.get_by_value(extra[0]) if extra else None |
|
1211
|
|
|
|
|
1212
|
1 |
|
return radio_silence, formation, params |
|
1213
|
|
|
|
|
1214
|
1 |
|
def _parse_target(self, params): |
|
1215
|
1 |
|
target_id, target_route_point = params[:2] |
|
1216
|
|
|
|
|
1217
|
1 |
|
self.point.update({ |
|
1218
|
|
|
'target_id': target_id, |
|
1219
|
|
|
'target_route_point': int(target_route_point), |
|
1220
|
|
|
}) |
|
1221
|
|
|
|
|
1222
|
1 |
|
if self.point['type'] is RoutePointTypes.normal: |
|
1223
|
1 |
|
self.point['type'] = RoutePointTypes.air_attack |
|
1224
|
|
|
|
|
1225
|
1 |
|
self.point_class = FlightRouteAttackPoint |
|
1226
|
|
|
|
|
1227
|
1 |
|
def clean(self): |
|
1228
|
1 |
|
self._finalize_current_point() |
|
1229
|
1 |
|
return {self.output_key: self.data} |
|
1230
|
|
|
|
|
1231
|
1 |
|
def _finalize_current_point(self): |
|
1232
|
1 |
|
if self.point: |
|
1233
|
1 |
|
point_class = getattr(self, 'point_class') or FlightRoutePoint |
|
1234
|
1 |
|
self.data.append(point_class(**self.point)) |
|
1235
|
1 |
|
self.point = None |
|
1236
|
1 |
|
self.point_class = None |
|
1237
|
|
|
|
|
1238
|
|
|
|
|
1239
|
1 |
|
class FileParser(object): |
|
1240
|
|
|
""" |
|
1241
|
|
|
Parses a whole mission file. |
|
1242
|
|
|
View :ref:`detailed description <file-parser>`. |
|
1243
|
|
|
""" |
|
1244
|
|
|
|
|
1245
|
1 |
|
def __init__(self): |
|
1246
|
1 |
|
self.parsers = [ |
|
1247
|
|
|
MainParser(), |
|
1248
|
|
|
SeasonParser(), |
|
1249
|
|
|
WeatherParser(), |
|
1250
|
|
|
RespawnTimeParser(), |
|
1251
|
|
|
MDSParser(), |
|
1252
|
|
|
MDSScoutsParser(), |
|
1253
|
|
|
ChiefsParser(), |
|
1254
|
|
|
ChiefRoadParser(), |
|
1255
|
|
|
NStationaryParser(), |
|
1256
|
|
|
BuildingsParser(), |
|
1257
|
|
|
TargetParser(), |
|
1258
|
|
|
BornPlaceParser(), |
|
1259
|
|
|
BornPlaceAircraftsParser(), |
|
1260
|
|
|
BornPlaceAirForcesParser(), |
|
1261
|
|
|
StaticCameraParser(), |
|
1262
|
|
|
FrontMarkerParser(), |
|
1263
|
|
|
RocketParser(), |
|
1264
|
|
|
WingParser(), |
|
1265
|
|
|
FlightRouteParser(), |
|
1266
|
|
|
] |
|
1267
|
1 |
|
self.flight_info_parser = FlightInfoParser() |
|
1268
|
|
|
|
|
1269
|
1 |
|
def parse(self, mission): |
|
1270
|
1 |
|
if isinstance(mission, six.string_types): |
|
1271
|
1 |
|
with open(mission, 'r') as f: |
|
1272
|
1 |
|
return self.parse_stream(f) |
|
1273
|
|
|
else: |
|
1274
|
1 |
|
return self.parse_stream(mission) |
|
1275
|
|
|
|
|
1276
|
1 |
|
def parse_stream(self, sequence): |
|
1277
|
1 |
|
self._current_parser = None |
|
1278
|
1 |
|
self.data = {} |
|
1279
|
|
|
|
|
1280
|
1 |
|
for i, line in enumerate(sequence): |
|
1281
|
1 |
|
line = strip_comments(line) |
|
1282
|
1 |
|
if FileParser.is_section_name(line): |
|
1283
|
1 |
|
self._finalize_current_parser() |
|
1284
|
1 |
|
section_name = FileParser.get_section_name(line) |
|
1285
|
1 |
|
self._current_parser = self._get_parser(section_name) |
|
1286
|
1 |
|
elif self._current_parser: |
|
1287
|
1 |
|
self._try_to_parse_line(i, line) |
|
1288
|
|
|
|
|
1289
|
1 |
|
self._finalize_current_parser() |
|
1290
|
1 |
|
return self._clean() |
|
1291
|
|
|
|
|
1292
|
1 |
|
@staticmethod |
|
1293
|
|
|
def is_section_name(line): |
|
1294
|
1 |
|
return line.startswith('[') and line.endswith(']') |
|
1295
|
|
|
|
|
1296
|
1 |
|
@staticmethod |
|
1297
|
|
|
def get_section_name(line): |
|
1298
|
1 |
|
return line.strip('[]') |
|
1299
|
|
|
|
|
1300
|
1 |
|
def _get_parser(self, section_name): |
|
1301
|
1 |
|
parser = self.flight_info_parser |
|
1302
|
1 |
|
flights = self.data.get('flights') |
|
1303
|
|
|
|
|
1304
|
1 |
|
if flights is not None and parser.start(section_name): |
|
1305
|
1 |
|
return parser |
|
1306
|
|
|
|
|
1307
|
1 |
|
for parser in self.parsers: |
|
1308
|
1 |
|
if parser.start(section_name): |
|
1309
|
1 |
|
return parser |
|
1310
|
|
|
|
|
1311
|
1 |
|
return None |
|
1312
|
|
|
|
|
1313
|
1 |
|
def _finalize_current_parser(self): |
|
1314
|
1 |
|
if not self._current_parser: |
|
1315
|
1 |
|
return |
|
1316
|
1 |
|
try: |
|
1317
|
1 |
|
data = self._current_parser.stop() |
|
1318
|
1 |
|
except Exception: |
|
1319
|
1 |
|
error_type, original_msg, traceback = sys.exc_info() |
|
1320
|
1 |
|
msg = ( |
|
1321
|
|
|
"{0} during finalization of \"{1}\": {2}" |
|
1322
|
|
|
.format(error_type.__name__, |
|
1323
|
|
|
self._current_parser.__class__.__name__, |
|
1324
|
|
|
original_msg)) |
|
1325
|
1 |
|
FileParser._raise_error(msg, traceback) |
|
1326
|
|
|
else: |
|
1327
|
1 |
|
self.data.update(data) |
|
1328
|
|
|
finally: |
|
1329
|
1 |
|
self._current_parser = None |
|
1330
|
|
|
|
|
1331
|
1 |
|
def _try_to_parse_line(self, line_number, line): |
|
1332
|
1 |
|
try: |
|
1333
|
1 |
|
self._current_parser.parse_line(line) |
|
1334
|
1 |
|
except Exception: |
|
1335
|
1 |
|
error_type, original_msg, traceback = sys.exc_info() |
|
1336
|
1 |
|
msg = ( |
|
1337
|
|
|
"{0} in line #{1} (\"{2}\"): {3}" |
|
1338
|
|
|
.format(error_type.__name__, line_number, line, original_msg)) |
|
1339
|
1 |
|
FileParser._raise_error(msg, traceback) |
|
1340
|
|
|
|
|
1341
|
1 |
|
@staticmethod |
|
1342
|
|
|
def _raise_error(message, traceback): |
|
1343
|
1 |
|
error = MissionParsingError(message) |
|
1344
|
1 |
|
six.reraise(MissionParsingError, error, traceback) |
|
1345
|
|
|
|
|
1346
|
1 |
|
def _clean(self): |
|
1347
|
1 |
|
result = {} |
|
1348
|
|
|
|
|
1349
|
1 |
|
move_if_present(result, self.data, 'location_loader') |
|
1350
|
1 |
|
move_if_present(result, self.data, 'player') |
|
1351
|
1 |
|
move_if_present(result, self.data, 'targets') |
|
1352
|
|
|
|
|
1353
|
1 |
|
set_if_present(result, 'conditions', self._get_conditions()) |
|
1354
|
1 |
|
set_if_present(result, 'objects', self._get_objects()) |
|
1355
|
|
|
|
|
1356
|
1 |
|
return result |
|
1357
|
|
|
|
|
1358
|
1 |
|
def _get_conditions(self): |
|
1359
|
1 |
|
result = {} |
|
1360
|
|
|
|
|
1361
|
1 |
|
set_if_present(result, 'time_info', self._get_time_info()) |
|
1362
|
1 |
|
set_if_present(result, 'meteorology', self._get_meteorology()) |
|
1363
|
1 |
|
set_if_present(result, 'scouting', self._get_scouting()) |
|
1364
|
|
|
|
|
1365
|
1 |
|
move_if_present(result, self.data, 'respawn_time') |
|
1366
|
|
|
|
|
1367
|
1 |
|
if 'conditions' in self.data: |
|
1368
|
1 |
|
conditions = self.data['conditions'] |
|
1369
|
|
|
|
|
1370
|
1 |
|
move_if_present(result, conditions, 'radar') |
|
1371
|
1 |
|
move_if_present(result, conditions, 'communication') |
|
1372
|
1 |
|
move_if_present(result, conditions, 'home_bases') |
|
1373
|
1 |
|
move_if_present(result, conditions, 'crater_visibility_muptipliers') |
|
1374
|
|
|
|
|
1375
|
1 |
|
return result |
|
1376
|
|
|
|
|
1377
|
1 |
|
def _get_time_info(self): |
|
1378
|
1 |
|
result = {} |
|
1379
|
|
|
|
|
1380
|
1 |
|
move_if_present(result, self.data, 'date') |
|
1381
|
1 |
|
if 'time' in self.data: |
|
1382
|
1 |
|
result.update({ |
|
1383
|
|
|
'time': self.data['time']['value'], |
|
1384
|
|
|
'is_fixed': self.data['time']['is_fixed'], |
|
1385
|
|
|
}) |
|
1386
|
|
|
|
|
1387
|
1 |
|
return result |
|
1388
|
|
|
|
|
1389
|
1 |
|
def _get_meteorology(self): |
|
1390
|
1 |
|
result = {} |
|
1391
|
|
|
|
|
1392
|
1 |
|
move_if_present(result, self.data, 'weather', 'weather_conditions') |
|
1393
|
1 |
|
move_if_present(result, self.data, 'cloud_base') |
|
1394
|
|
|
|
|
1395
|
1 |
|
if 'weather' in self.data: |
|
1396
|
1 |
|
result.update(self.data.pop('weather')) |
|
1397
|
|
|
|
|
1398
|
1 |
|
return result |
|
1399
|
|
|
|
|
1400
|
1 |
|
def _get_scouting(self): |
|
1401
|
1 |
|
result = {} |
|
1402
|
|
|
|
|
1403
|
1 |
|
try: |
|
1404
|
1 |
|
conditions = self.data['conditions'].pop('scouting') |
|
1405
|
1 |
|
result.update(conditions) |
|
1406
|
1 |
|
except KeyError: |
|
1407
|
|
|
pass |
|
1408
|
|
|
|
|
1409
|
1 |
|
keys = filter( |
|
1410
|
|
|
lambda x: x.startswith(MDSScoutsParser.output_prefix), |
|
1411
|
|
|
self.data.keys() |
|
1412
|
|
|
) |
|
1413
|
1 |
|
scouts = { |
|
1414
|
|
|
self.data[key]['belligerent']: self.data[key]['aircrafts'] |
|
1415
|
|
|
for key in keys |
|
1416
|
|
|
} |
|
1417
|
1 |
|
set_if_present(result, 'scouts', scouts) |
|
1418
|
|
|
|
|
1419
|
1 |
|
return result |
|
1420
|
|
|
|
|
1421
|
1 |
|
def _get_objects(self): |
|
1422
|
1 |
|
result = {} |
|
1423
|
|
|
|
|
1424
|
1 |
|
set_if_present(result, 'moving_units', self._get_moving_units()) |
|
1425
|
1 |
|
set_if_present(result, 'flights', self._get_flights()) |
|
1426
|
1 |
|
set_if_present(result, 'home_bases', self._get_home_bases()) |
|
1427
|
|
|
|
|
1428
|
1 |
|
move_if_present(result, self.data, 'stationary') |
|
1429
|
1 |
|
move_if_present(result, self.data, 'buildings') |
|
1430
|
1 |
|
move_if_present(result, self.data, 'cameras') |
|
1431
|
1 |
|
move_if_present(result, self.data, 'markers') |
|
1432
|
1 |
|
move_if_present(result, self.data, 'rockets') |
|
1433
|
|
|
|
|
1434
|
1 |
|
return result |
|
1435
|
|
|
|
|
1436
|
1 |
|
def _get_moving_units(self): |
|
1437
|
1 |
|
units = self.data.pop('moving_units', []) |
|
1438
|
1 |
|
for unit in units: |
|
1439
|
1 |
|
key = "{}{}".format(ChiefRoadParser.output_prefix, unit['id']) |
|
1440
|
1 |
|
unit['route'] = self.data.pop(key, []) |
|
1441
|
1 |
|
return units |
|
1442
|
|
|
|
|
1443
|
1 |
|
def _get_flights(self): |
|
1444
|
1 |
|
keys = self.data.pop('flights', []) |
|
1445
|
1 |
|
flights = [self.data.pop(key) for key in keys if key in self.data] |
|
1446
|
1 |
|
for flight in flights: |
|
1447
|
1 |
|
key = "{}{}".format(FlightRouteParser.output_prefix, flight['id']) |
|
1448
|
1 |
|
flight['route'] = self.data.pop(key, []) |
|
1449
|
1 |
|
return flights |
|
1450
|
|
|
|
|
1451
|
1 |
|
def _get_home_bases(self): |
|
1452
|
1 |
|
home_bases = self.data.pop('home_bases', []) |
|
1453
|
1 |
|
for i, home_base in enumerate(home_bases): |
|
1454
|
1 |
|
key = "{}{}".format(BornPlaceAircraftsParser.output_prefix, i) |
|
1455
|
1 |
|
home_base['spawning']['aircraft_limitations']['allowed_aircrafts'] = self.data.pop(key, []) |
|
1456
|
|
|
|
|
1457
|
1 |
|
key = "{}{}".format(BornPlaceAirForcesParser.output_prefix, i) |
|
1458
|
1 |
|
home_base['spawning']['allowed_air_forces'] = self.data.pop(key, []) |
|
1459
|
|
|
return home_bases |
|
1460
|
|
|
|