Completed
Push — master ( 76aa34...acc4d7 )
by Oleksandr
07:51 queued 06:41
created

FlightSectionParser.clean()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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