Completed
Push — master ( d2ce03...62163d )
by Oleksandr
01:51
created

clean()   A

Complexity

Conditions 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1
Metric Value
cc 1
dl 0
loc 16
ccs 6
cts 6
cp 1
crap 1
rs 9.4286
1
# -*- coding: utf-8 -*-
2 1
"""
3
This module contains parsers which work with sections related to air flights.
4
5
List of flights is defined in "Wing" section of game's mission file. However,
6
it contains list of flights, not wings. Wings stay above flight in air force
7
unit organization. See example for USA:
8
http://usmilitary.about.com/cs/airforce/a/aforganization.htm
9
10
Despite inconsistency between section name (Wing) and its contents (list of
11
flights), this module was named after section name to keep clear the origin of
12
the data.
13
"""
14
15 1
from il2fb.commons.flight import Formations, RoutePointTypes
16 1
from il2fb.commons.organization import AirForces, Regiments
17 1
from il2fb.commons.spatial import Point3D
18 1
from il2fb.commons.structures import BaseStructure
19
20 1
from ..constants import (
21
    ROUTE_POINT_EXTRA_PARAMETERS_MARK, ROUTE_POINT_RADIO_SILENCE_ON,
22
    ROUTE_POINT_RADIO_SILENCE_OFF,
23
)
24 1
from ..converters import to_skill
25 1
from ..utils import set_if_present
26 1
from . import CollectingParser, ValuesParser
27
28
29 1
class FlightSectionParser(CollectingParser):
30
    """
31
    Parses ``Wing`` section.
32
    View :ref:`detailed description <wing-section>`.
33
    """
34
35 1
    def check_section_name(self, section_name):
36 1
        return section_name == "Wing"
37
38 1
    def clean(self):
39 1
        return {'flights': self.data}
40
41
42 1
class FlightInfoSectionParser(ValuesParser):
43
    """
44
    Parses settings for a moving flight group.
45
    View :ref:`detailed description <flight-info-section>`.
46
    """
47
48 1
    def check_section_name(self, section_name):
49 1
        try:
50 1
            self._decompose_section_name(section_name)
51 1
        except Exception:
52 1
            return False
53
        else:
54 1
            return True
55
56 1
    def init_parser(self, section_name):
57 1
        super(FlightInfoSectionParser, self).init_parser(section_name)
58 1
        self.output_key = section_name
59 1
        self.flight_info = self._decompose_section_name(section_name)
60
61 1
    def _decompose_section_name(self, section_name):
62 1
        prefix = section_name[:-2]
63 1
        squadron, flight = section_name[-2:]
64
65 1
        try:
66 1
            regiment = None
67 1
            air_force = AirForces.get_by_flight_prefix(prefix)
68 1
        except ValueError:
69 1
            regiment = Regiments.get_by_code_name(prefix)
70 1
            air_force = regiment.air_force
71
72 1
        return {
73
            'id': section_name,
74
            'air_force': air_force,
75
            'regiment': regiment,
76
            'squadron_index': int(squadron),
77
            'flight_index': int(flight),
78
        }
79
80 1
    def clean(self):
81 1
        count = int(self.data['Planes'])
82 1
        code = self.data['Class'].split('.', 1)[1]
83 1
        aircrafts = self._get_aircrafts(count)
84
85 1
        self.flight_info.update({
86
            'ai_only': 'OnlyAI' in self.data,
87
            'code': code,
88
            'fuel': int(self.data['Fuel']),
89
            'with_parachutes': 'Parachute' not in self.data,
90
            'count': count,
91
            'weapons': self.data['weapons'],
92
            'aircrafts': aircrafts,
93
        })
94
95 1
        return {self.output_key: self.flight_info}
96
97 1
    def _get_aircrafts(self, aircrafts_count):
98 1
        results = []
99
100 1
        for i in range(aircrafts_count):
101 1
            info = {
102
                'index': i,
103
                'has_markings': self._has_markings(i),
104
                'skill': self._get_skill(i),
105
            }
106 1
            set_if_present(info, 'aircraft_skin', self._get_skin('skin', i))
107 1
            set_if_present(info, 'nose_art', self._get_skin('nose_art', i))
108 1
            set_if_present(info, 'pilot_skin', self._get_skin('pilot', i))
109 1
            set_if_present(info, 'spawn_object', self._get_spawn_object_id(i))
110 1
            results.append(info)
111
112 1
        return results
113
114 1
    def _get_skill(self, aircraft_id):
115 1
        if 'Skill' in self.data:
116 1
            return to_skill(self.data['Skill'])
117
        else:
118 1
            return to_skill(self.data['Skill{:}'.format(aircraft_id)])
119
120 1
    def _has_markings(self, aircraft_id):
121 1
        return 'numberOn{:}'.format(aircraft_id) not in self.data
122
123 1
    def _get_skin(self, prefix, aircraft_id):
124 1
        return self.data.get('{:}{:}'.format(prefix, aircraft_id))
125
126 1
    def _get_spawn_object_id(self, aircraft_id):
127 1
        return self.data.get('spawn{:}'.format(aircraft_id))
128
129
130 1
class FlightRoutePoint(BaseStructure):
131 1
    __slots__ = ['type', 'pos', 'speed', 'formation', 'radio_silence', ]
132
133 1
    def __init__(self, type, pos, speed, formation, radio_silence):
134 1
        self.type = type
135 1
        self.pos = pos
136 1
        self.speed = speed
137 1
        self.formation = formation
138 1
        self.radio_silence = radio_silence
139
140 1
    def __repr__(self):
141 1
        return ("<{0} '{1};{2};{3}'>"
142
                .format(self.__class__.__name__,
143
                        self.pos.x, self.pos.y, self.pos.z))
144
145
146 1
class FlightRouteTakeoffPoint(FlightRoutePoint):
147 1
    __slots__ = FlightRoutePoint.__slots__ + ['delay', 'spacing', ]
148
149 1
    def __init__(self, type, pos, speed, formation, radio_silence, delay,
150
                 spacing):
151 1
        super(FlightRouteTakeoffPoint, self).__init__(
152
            type, pos, speed, formation, radio_silence)
153 1
        self.delay = delay
154 1
        self.spacing = spacing
155
156
157 1
class FlightRoutePatrolPoint(FlightRoutePoint):
158 1
    __slots__ = FlightRoutePoint.__slots__ + [
159
        'patrol_cycles', 'patrol_timeout',
160
        'pattern_angle', 'pattern_side_size', 'pattern_altitude_difference',
161
    ]
162
163 1
    def __init__(self, type, pos, speed, formation, radio_silence,
164
                 patrol_cycles, patrol_timeout, pattern_angle,
165
                 pattern_side_size, pattern_altitude_difference):
166 1
        super(FlightRoutePatrolPoint, self).__init__(
167
            type, pos, speed, formation, radio_silence)
168 1
        self.patrol_cycles = patrol_cycles
169 1
        self.patrol_timeout = patrol_timeout
170 1
        self.pattern_angle = pattern_angle
171 1
        self.pattern_side_size = pattern_side_size
172 1
        self.pattern_altitude_difference = pattern_altitude_difference
173
174
175 1
class FlightRouteAttackPoint(FlightRoutePoint):
176 1
    __slots__ = FlightRoutePoint.__slots__ + [
177
        'target_id', 'target_route_point',
178
    ]
179
180 1
    def __init__(self, type, pos, speed, formation, radio_silence, target_id,
181
                 target_route_point):
182 1
        super(FlightRouteAttackPoint, self).__init__(
183
            type, pos, speed, formation, radio_silence)
184 1
        self.target_id = target_id
185 1
        self.target_route_point = target_route_point
186
187
188 1
class FlightRouteSectionParser(CollectingParser):
189
    """
190
    Parses ``*_Way`` section.
191
    View :ref:`detailed description <flight-route-section>`.
192
    """
193 1
    input_suffix = "_Way"
194 1
    output_prefix = 'flight_route_'
195
196 1
    def check_section_name(self, section_name):
197 1
        return section_name.endswith(self.input_suffix)
198
199 1
    def _extract_flight_code(self, section_name):
200 1
        return section_name[:-len(self.input_suffix)]
201
202 1
    def init_parser(self, section_name):
203 1
        super(FlightRouteSectionParser, self).init_parser(section_name)
204 1
        flight_code = self._extract_flight_code(section_name)
205 1
        self.output_key = "{}{}".format(self.output_prefix, flight_code)
206 1
        self.point = None
207 1
        self.point_class = None
208
209 1
    def parse_line(self, line):
210 1
        params = line.split()
211 1
        type_code, params = params[0], params[1:]
212 1
        if type_code == ROUTE_POINT_EXTRA_PARAMETERS_MARK:
213 1
            self._parse_options(params)
214
        else:
215 1
            self._finalize_current_point()
216 1
            pos, speed, params = params[0:3], params[3], params[4:]
217 1
            self.point = {
218
                'type': RoutePointTypes.get_by_value(type_code),
219
                'pos': Point3D(*pos),
220
                'speed': float(speed),
221
            }
222 1
            self._parse_extra(params)
223
224 1
    def _parse_options(self, params):
225 1
        try:
226 1
            cycles, timeout, angle, side_size, altitude_difference = params
227 1
            self.point.update({
228
                'patrol_cycles': int(cycles),
229
                'patrol_timeout': int(timeout),
230
                'pattern_angle': int(angle),
231
                'pattern_side_size': int(side_size),
232
                'pattern_altitude_difference': int(altitude_difference),
233
            })
234 1
            self.point_class = FlightRoutePatrolPoint
235 1
        except ValueError:
236 1
            delay, spacing = params[1:3]
237 1
            self.point.update({
238
                'delay': int(delay),
239
                'spacing': int(spacing),
240
            })
241 1
            self.point_class = FlightRouteTakeoffPoint
242
243 1
    def _parse_extra(self, params):
244 1
        if FlightRouteSectionParser._is_new_game_version(params):
245 1
            radio_silence, formation, params = FlightRouteSectionParser._parse_new_version_extra(params)
246 1
            if params:
247 1
                self._parse_target(params)
248
        else:
249 1
            radio_silence = False
250 1
            formation = None
251
252 1
        self.point.update({
253
            'radio_silence': radio_silence,
254
            'formation': formation,
255
        })
256
257 1
    @staticmethod
258
    def _is_new_game_version(params):
259 1
        return (
260
            ROUTE_POINT_RADIO_SILENCE_ON in params
261
            or ROUTE_POINT_RADIO_SILENCE_OFF in params
262
        )
263
264 1
    @staticmethod
265
    def _parse_new_version_extra(params):
266 1
        try:
267 1
            index = params.index(ROUTE_POINT_RADIO_SILENCE_ON)
268 1
        except ValueError:
269 1
            index = params.index(ROUTE_POINT_RADIO_SILENCE_OFF)
270
271 1
        params, radio_silence, extra = params[:index], params[index], params[index+1:]
272
273 1
        radio_silence = radio_silence == ROUTE_POINT_RADIO_SILENCE_ON
274 1
        formation = Formations.get_by_value(extra[0]) if extra else None
275
276 1
        return radio_silence, formation, params
277
278 1
    def _parse_target(self, params):
279 1
        target_id, target_route_point = params[:2]
280
281 1
        self.point.update({
282
            'target_id': target_id,
283
            'target_route_point': int(target_route_point),
284
        })
285
286 1
        if self.point['type'] is RoutePointTypes.normal:
287 1
            self.point['type'] = RoutePointTypes.air_attack
288
289 1
        self.point_class = FlightRouteAttackPoint
290
291 1
    def clean(self):
292 1
        self._finalize_current_point()
293 1
        return {self.output_key: self.data}
294
295 1
    def _finalize_current_point(self):
296 1
        if self.point:
297 1
            point_class = getattr(self, 'point_class') or FlightRoutePoint
298 1
            self.data.append(point_class(**self.point))
299 1
            self.point = None
300
            self.point_class = None
301