Passed
Pull Request — master (#291)
by Marek
07:23
created

ige.ospace.Rules.Techs.init()   F

Complexity

Conditions 38

Size

Total Lines 173
Code Lines 116

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 38
eloc 116
nop 1
dl 0
loc 173
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like ige.ospace.Rules.Techs.init() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Outer Space.
5
#
6
#  Outer Space is free software; you can redistribute it and/or modify
7
#  it under the terms of the GNU General Public License as published by
8
#  the Free Software Foundation; either version 2 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Outer Space is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  GNU General Public License for more details.
15
#
16
#  You should have received a copy of the GNU General Public License
17
#  along with Outer Space; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
import sys, copy
22
from ige import log
23
from ige.ospace import Const
24
import ige.ospace.Rules
25
import hashlib, os.path
26
import cPickle as pickle
27
import types
28
from ige.ospace import TechHandlers
29
from xml.sax.handler import ContentHandler
30
from ige.IDataHolder import IDataHolder
31
import xml.sax
32
import copy
33
34
def noop(*args, **kwargs):
35
    return 1
36
37
attrs = {
38
    'id': 0,
39
    # types of technologies
40
    'isDiscovery': 0,
41
    'isStructure': 0,
42
    'isProject': 0,
43
    'isShipEquip': 0,
44
    'isShipHull': 0,
45
    'isMine': 0,
46
    'isStarting': 0,
47
    "subtype": "techSubtype",
48
    "level": 0,
49
    "maxImprovement": 5,  # Rules.techMaxImprovement,
50
    "isMilitary": 0,
51
    "globalDisabled": 0,  # to disallow some projects in global queues
52
    # dialog info
53
    'shortname': '',  # for TechInfoDlg tech linkages
54
    # construction costs & conditions
55
    'buildProd': 0,
56
    'buildTurns': 1,
57
    'buildSRes': {'resource': 'amount'},
58
    # operation costs
59
    'operBio': 0,
60
    'operEn': 0,
61
    "operWorkers": 0,
62
    # production
63
    'prodBio': 0  # bio
64
    'prodEn': 0  # energy
65
    'prodProd': 0  # production
66
    'prodSci': 0  # science
67
    'prodEnv': 0  # enviromental effect
68
    'prodPop': 0  # produce population
69
    'prodBioMod': [0.0, 0.0, 0.0, 0.0]  # tuple of (plBio, plMin, plEn, default)
70
    'prodEnMod': [0.0, 0.0, 0.0, 0.0]  # dtto
71
    'prodProdMod': [0.0, 0.0, 0.0, 0.0]  # dtto
72
    'prodSciMod': [0.0, 0.0, 0.0, 1.0]  # ditto; default is 1.0 for legacy support
73
    # env
74
    'solarMod': 0  # -: cloak solar radiation / +: create thermal radiation
75
    # storage
76
    'storBio': 0,
77
    'storProd': 0,
78
    'storEn': 0,
79
    'storPop': 0,
80
    # morale affecting
81
    'revoltThr': 0,
82
    'moraleTrgt': 0,
83
    'govPwr': 0,
84
    # military
85
    'scannerPwr': 0,
86
    "structWeapons": [0],
87
    "planetShield": 0  # planetary shield; when structure built, shield = 0; shield will regenerate at 2% per turn until equal to this value. Structures do not add shield strength; strongest shield = planet shield
88
    "systemAtt": 0,
89
    "systemDef": 0,
90
    "refuelMax": 0,
91
    "refuelInc": 0,
92
    "repairShip": 0.0,
93
    "upgradeShip": 0,
94
    "trainShipInc": 0.0  # how many exp/turn
95
    "trainShipMax": 0  # exp. cap (in base exps), not affected by techEff
96
    "fleetSpeedBoost": 0.0  # speed boost for stargates
97
    # misc
98
    "unpackPop": 0,
99
    'envDmg': 0,
100
    'maxHP': 0,
101
    "fullInfo": 0  # if True, show full tech info even player not own tech
102
    # ship equipment
103
    'equipType': ''  # identifier of subtype seq_mod's equipment type; see maxEquipType in Rules/__init__
104
    "addMP": 0  # for extra MP to be added to ship equipment
105
    'combatClass': 0,
106
    'combatAtt': 0  # not cumulative for equipment; cumulative for hull, drives, cockpits, etc
107
    'combatDef': 0  # not cumulative for equipment; cumulative for hull, drives, cockpits, etc
108
    "missileDef": 0  # not cumulative for equipment; cumulative for hull, drives, cockpits, etc
109
    "combatAttPerc": 1.0  # multiplier of ATT; min of 100%; not cumulative
110
    "combatDefPerc": 1.0  # multiplier of DEF; min of 100%; not cumulative
111
    "missileDefPerc": 1.0  # multiplier of missile DEF; min of 100%; not cumulative
112
    'unpackStruct': '',
113
    'deployHandlerID': ''  # technology ID of tech to find deployHandlerFunction & deployHandlerValidator (this can be the deployable device OR a project)
114
    'deployHandlerFunction': noop  # function name of TechHandler
115
    'deployHandlerValidator': noop  # function name of TechHandler Validator
116
    'signature': 0  # **** NOT cumulative (change effective 0.5.63)
117
    'signatureCloak': 1.0  # max of 1.0 is effective; not cumulative
118
    'signatureDecloak': 1.0  # min of 1.0 is effective; not cumulative
119
    "minSignature": 0,
120
    "slots": 0,
121
    "weight": 0,
122
    "maxWeight": 0,
123
    "engPwr": 0,
124
    "engStlPwr": 0,
125
    "shieldPerc": 0.0  # how many percent of maxHP have shields
126
    "minHull": 0,
127
    "maxHull": 10  # just make this higher than the largest hull so we know it doesn't break anything
128
    "maxInstallations": 0,
129
    "shieldRechargeFix": 0  # fixed amount of HP/turn to recharge
130
    "shieldRechargePerc": 0.0  # how many percent of shieldHP/turn is recharged
131
    "hardShield": 0.0  # shield penetrating weapons will penetrate at 100%; use as 1-hardShield for penentration level (hardShield percent = %damage absorbed by shield)
132
    "autoRepairFix": 0  # fixed amount of HP/turn to repair
133
    "autoRepairPerc": 0.0  # how many percent of maxHP/turn is repaired
134
    "damageAbsorb": 0  # amount of damage absorbed by the hull (not shield!); max sum is 5 damage (set in Rules)
135
    # weapons
136
    'weaponDmgMin': 0,
137
    'weaponDmgMax': 0,
138
    'weaponAtt': 0,
139
    'weaponClass': 0,
140
    "weaponROF": 0.0,
141
    "weaponIgnoreShield": 0,
142
    "weaponIsMissile": 0,
143
    "weaponGoodForFlak": 1,
144
    # mines
145
    'mineclass':0  # tech id of the mine; usually level 99 tech - structure in the system with the highest tech id will always deploy; others will be ignored (per player)
146
    'minenum':0  # number of mines this control structure supports; if another structure built more mines, mines will not self destruct
147
    'minerate':0  # number of turns between mine deployments; note that system will deploy mines on: turn%minerate==0
148
    # research
149
    'researchRequires': ['technology'],
150
    'researchEnables': ['technology'],
151
    'researchDisables': ['technology'],
152
    'researchReqSRes': ['resource'],
153
    "researchMod": "expr",
154
    'researchTurns': 1,
155
    "researchRaces": "BCH",
156
    # misc
157
    "data": "none",
158
    "recheckWhenTechLost": 0,
159
    "deprecated": 0  # this tech is no longer active
160
    # before build handler
161
    'validateConstrHandler': noop,
162
    # after build handler
163
    'finishConstrHandler': noop,
164
    # after research handler
165
    'finishResearchHandler': noop,
166
    # names
167
    'name': u'Unspecified',
168
    # textual description
169
    'textPreRsrch': u'Not specified',
170
    'textDescr': u'Not specified',
171
    'textFlavor': u'Not specified',
172
}
173
174
# class representing technologies
175
class Technology:
176
177
    def __init__(self, id, symbol, reg):
178
        self.id = id
179
        self.symbol = symbol
180
        if id in reg:
181
            raise KeyError("%s is already registered" % id)
182
        reg[id] = self
183
184
    def set(self, key, value):
185
        if attrs.has_key(key):
186
            attrType = type(attrs[key])
187
            if attrType == types.IntType:
188
                value = int(value)
189
            elif attrType == types.FloatType:
190
                value = float(value)
191
            elif attrType == types.UnicodeType:
192
                pass
193
            elif attrType == types.StringType:
194
                value = str(value)
195
            elif attrType == types.FunctionType:
196
                value = getattr(TechHandlers, value)
197
            elif attrType == types.ListType:
198
                itemType = type(attrs[key][0])
199
                if itemType == types.IntType:
200
                    convertFunc = int
201
                elif itemType == types.StringType:
202
                    convertFunc = str
203
                elif itemType == types.FloatType:
204
                    convertFunc = float
205
                else:
206
                    raise 'Unsupported attribute type %s' % repr(itemType)
207
                result = []
208
                for item in value.split(','):
209
                    if item:
210
                        result.append(convertFunc(item))
211
                value = result
212
            elif attrType == types.DictType:
213
                # format is key:value,key2:value2
214
                dict_key, dict_value = copy.copy(attrs[key]).popitem()
215
                keyType = type(dict_key)
216
                if keyType == types.IntType:
217
                    convertFuncKey = int
218
                elif keyType == types.StringType:
219
                    convertFuncKey = str
220
                elif keyType == types.FloatType:
221
                    convertFuncKey = float
222
                else:
223
                    raise 'Unsupported attribute type %s' % repr(keyType)
224
                valueType = type(dict_value)
225
                if valueType == types.IntType:
226
                    convertFuncValue = int
227
                elif valueType == types.StringType:
228
                    convertFuncValue = str
229
                elif valueType == types.FloatType:
230
                    convertFuncValue = float
231
                else:
232
                    raise 'Unsupported attribute type %s' % repr(valueType)
233
                # let's parse!
234
                result = {}
235
                for pair in value.split(','):
236
                    pair_key, pair_value = pair.split(':')
237
                    pair_key = convertFuncKey(pair_key)
238
                    pair_value = convertFuncValue(pair_value)
239
                    result[pair_key] = pair_value
240
                value = result
241
            else:
242
                raise 'Unsupported attribute type %s' % repr(attrType)
243
            setattr(self, key, value)
244
        else:
245
            raise AttributeError('Cannot create %s - unsupported attribute.' % key)
246
247
    def __getattr__(self, attr):
248
        if attrs.has_key(attr):
249
            # optimalization
250
            setattr(self, attr, attrs[attr])
251
            return attrs[attr]
252
        else:
253
            raise AttributeError('No attribute %s' % attr)
254
255
    def isDefault(self, attr):
256
        if hasattr(self, attr):
257
            return getattr(self, attr) == attrs[attr]
258
        else:
259
            return 1
260
261
    def __repr__(self):
262
        result = '(Technology '
263
        for key, value in self.__dict__.items():
264
            result += '%s : %s, ' % (repr(key), repr(value))
265
        result += ')'
266
        return result
267
268
# holder for all technologies
269
techs = {}
270
271
# parse TechTree.xml and create all tech objects
272
class TechTreeContentHandler(ContentHandler):
273
    def startDocument(self):
274
        self.state = 1
275
        self.text = ''
276
277
    def endDocument(self):
278
        if self.state != 1:
279
            raise 'Wrong TechTree specification'
280
281
    def startElement(self, name, attrs):
282
        if self.state == 1 and name == 'techtree':
283
            self.state = 2
284
        elif self.state == 2 and name == 'technology':
285
            log.debug('Tech %s [%s]' % (attrs['name'], attrs['id']))
286
            self.state = 3
287
            self.tech = Technology(int(attrs['id']), attrs['symbol'], techs)
288
            setattr(Tech, attrs['symbol'], int(attrs['id']))
289
            self.tech.set('name', attrs['name'])
290
        elif self.state == 3 and name == 'structure':
291
            self.tech.set('isStructure', 1)
292
            for key in attrs.keys():
293
                self.tech.set(key, attrs[key])
294
            realBuildProd = int(self.tech.buildProd * ige.ospace.Rules.structDefaultCpCosts)
295
            self.tech.set('buildProd', realBuildProd)
296
        elif self.state == 3 and name == 'discovery':
297
            self.tech.set('isDiscovery', 1)
298
            for key in attrs.keys():
299
                self.tech.set(key, attrs[key])
300
        elif self.state == 3 and name == 'notdiscovery':
301
            self.tech.set('isDiscovery', 0)
302
            for key in attrs.keys():
303
                self.tech.set(key, attrs[key])
304
        elif self.state == 3 and name == 'starting':
305
            self.tech.set('isStarting', 1)
306
            for key in attrs.keys():
307
                self.tech.set(key, attrs[key])
308
        elif self.state == 3 and name == 'notstarting':
309
            self.tech.set('isStarting', 0)
310
            for key in attrs.keys():
311
                self.tech.set(key, attrs[key])
312
        elif self.state == 3 and name == 'shipequip':
313
            self.tech.set('isShipEquip', 1)
314
            for key in attrs.keys():
315
                self.tech.set(key, attrs[key])
316
        elif self.state == 3 and name == 'project':
317
            self.tech.set('isProject', 1)
318
            for key in attrs.keys():
319
                self.tech.set(key, attrs[key])
320
        elif self.state == 3 and name == 'shiphull':
321
            self.tech.set('isShipHull', 1)
322
            for key in attrs.keys():
323
                self.tech.set(key, attrs[key])
324
        elif self.state == 3 and name == 'mine':
325
            self.tech.set('isMine', 1)
326
            for key in attrs.keys():
327
                self.tech.set(key, attrs[key])
328
        elif self.state == 3 and name == 'data':
329
            for key in attrs.keys():
330
                self.tech.set(key, attrs[key])
331
        elif self.state == 3 and name == 'preresearch':
332
            self.state = 4
333
            self.text = ''
334
        elif self.state == 3 and name == 'description':
335
            self.state = 4
336
            self.text = ''
337
        elif self.state == 3 and name == 'flavor':
338
            self.state = 4
339
            self.text = ''
340
        else:
341
            raise 'Unsupported tag %s' % str(name)
342
343
    def endElement(self, name):
344
        if self.state == 2 and name == 'techtree':
345
            self.state = 1
346
        elif self.state == 3 and name == 'technology':
347
            self.state = 2
348
        elif self.state == 4 and name == 'preresearch':
349
            self.tech.textPreRsrch = self.text
350
            self.state = 3
351
        elif self.state == 4 and name == 'description':
352
            self.tech.textDescr = self.text
353
            self.state = 3
354
        elif self.state == 4 and name == 'flavor':
355
            self.tech.textFlavor = self.text
356
            self.state = 3
357
358
    def characters(self, text):
359
        self.text += text
360
361
Tech = IDataHolder()
362
363
## init is wrapping all code that needs to be performed before module
364
# is ready. That is checking if techs changed, if not loading pickled
365
# data, othervise rebuild database from source data
366
# We should be able to do server-provided techs in the future (per galaxy
367
# rulesets)
368
369
370
def init(configDir):
371
    global Tech, techs
372
    ## check, if anything has been changed
373
374
    def chsumDir(chsum, dirname, names):
375
        names.sort()
376
        for filename in names:
377
            if os.path.splitext(filename)[1] in ('.xml', '.py'):
378
                log.debug('Checking file', filename)
379
                fh = open(os.path.join(dirname, filename), 'rb')
380
                chsum.update(fh.read())
381
                fh.close()
382
383
    # compute checksum
384
    moduleFile = sys.modules['ige.ospace.Rules'].__file__
385
    forceLoad = 0
386
387
    # regular module
388
    moduleDirectory = os.path.dirname(moduleFile)
389
    chsum = hashlib.sha1()
390
    os.path.walk(moduleDirectory, chsumDir, chsum)
391
392
    # read old checksum
393
    try:
394
        fh = open(os.path.join(configDir, 'checksum'), 'rb')
395
        oldChsum = fh.read()
396
        fh.close()
397
    except IOError:
398
        oldChsum = ''
399
400
    # compare
401
    if forceLoad or chsum.hexdigest() == oldChsum:
402
        # load old definitions
403
        log.message('Loading stored specifications from', configDir)
404
        techs = pickle.load(open(os.path.join(configDir, 'techs.spf'), 'rb'))
405
        Tech = pickle.load(open(os.path.join(configDir, 'Tech.spf'), 'rb'))
406
407
        log.message("There is %d technologies" % len(techs))
408
409
        # clean up 'type' in lists
410
        for key in attrs.keys():
411
            if type(attrs[key]) == types.ListType and len(attrs[key]) == 1:
412
                log.debug("Cleaning up", key)
413
                attrs[key] = []
414
415
    else:
416
        # create new ones
417
        ## load technologies definitions
418
419
        def processDir(arg, dirname, names):
420
            log.message('Loading XML files from', dirname)
421
            names.sort()
422
            for filename in names:
423
                if os.path.splitext(filename)[1] == '.xml':
424
                    log.message('Parsing XML file', filename)
425
                    xml.sax.parse(os.path.join(dirname, filename), TechTreeContentHandler())
426
427
        # collect xml files
428
        os.path.walk(moduleDirectory, processDir, None)
429
430
        # clean up 'type' in lists
431
        for key in attrs.keys():
432
            if type(attrs[key]) == types.ListType and len(attrs[key]) == 1:
433
                log.debug("Cleaning up", key)
434
                attrs[key] = []
435
            elif type(attrs[key]) == types.DictType and len(attrs[key]) == 1:
436
                log.debug("Cleaning up", key)
437
                attrs[key] = {}
438
439
440
        # link tech tree using researchRequires fields
441
        # construct researchEnables fields
442
        log.message('Converting symbolic fields...')
443
        for techID in techs.keys():
444
            tech = techs[techID]
445
            # convert symbolic names to numbers
446
            techIDs = []
447
            for techSymName in tech.researchRequires:
448
                symName, improvement = techSymName.split('-')
449
                techIDs.append((getattr(Tech, symName), int(improvement)))
450
            tech.researchRequires = techIDs
451
            techIDs = {1: [], 2:[], 3:[], 4:[], 5:[], 6:[]}
452
            for techSymName in tech.researchEnables:
453
                improvement, symName = techSymName.split('-')
454
                techIDs[int(improvement)].append(getattr(Tech, symName))
455
            tech.researchEnables = techIDs
456
            techIDs = []
457
            for techSymName in tech.researchDisables:
458
                techIDs.append(getattr(Tech, techSymName))
459
            tech.researchDisables = techIDs
460
            techIDs = []
461
            if tech.unpackStruct:
462
                tech.unpackStruct = getattr(Tech, tech.unpackStruct)
463
            else:
464
                tech.unpackStruct = 0
465
466
            # strat. resources
467
            # this has to be here to prevent import deadlock in Rules
468
            from ige.ospace.Rules import stratResAmountBig
469
            from ige.ospace.Rules import stratResAmountSmall
470
471
            stratRes = []
472
            for sr in tech.researchReqSRes:
473
                stratRes.append(getattr(Const, sr))
474
            tech.researchReqSRes = stratRes
475
            # build requirements with quantities
476
            stratRes = {}
477
            for resource in tech.buildSRes:
478
                resource_id = getattr(Const, resource)
479
                # prescription contains constants for big and small amounts
480
                # possibly within expression
481
                resource_amount = eval(tech.buildSRes[resource])
482
                stratRes[resource_id] = resource_amount
483
            tech.buildSRes = stratRes
484
485
            # evaluate researchMod
486
            if tech.researchMod == "expr":
487
                tech.researchMod = 1.0
488
            else:
489
                tech.researchMod = eval(tech.researchMod)
490
491
        # link
492
        log.message('Linking tech tree...')
493
        for techID in techs.keys():
494
            tech = techs[techID]
495
            for tmpTechID, improvement in tech.researchRequires:
496
                if techID not in techs[tmpTechID].researchEnables[improvement]:
497
                    techs[tmpTechID].researchEnables[improvement].append(techID)
498
            for improvement in tech.researchEnables.keys():
499
                for tmpTechID in tech.researchEnables[improvement]:
500
                    if (techID, improvement) not in techs[tmpTechID].researchRequires:
501
                        techs[tmpTechID].researchRequires.append((techID, improvement))
502
503
        changed = 1
504
        while changed:
505
            changed = 0
506
            log.debug("Tech disable iteration")
507
            for techID in techs:
508
                tech = techs[techID]
509
                for tech2ID in tech.researchDisables:
510
                    tech2 = techs[tech2ID]
511
                    if techID not in tech2.researchDisables and techID != tech2ID:
512
                        tech2.researchDisables.append(techID)
513
                        changed = 1
514
                        log.debug("Adding", tech2ID, "DISABLES", techID, ", NOW", tech2.researchDisables)
515
                    for tech3ID in tech2.researchDisables:
516
                        tech3 = techs[tech3ID]
517
                        if tech3ID not in tech.researchDisables and tech3ID != techID:
518
                            tech.researchDisables.append(tech3ID)
519
                            changed = 1
520
                            log.debug("Adding", techID, "DISABLES", tech3ID, "NOW", tech.researchDisables)
521
522
        # save new specification
523
        log.message('Saving specification...')
524
        pickle.dump(techs, open(os.path.join(configDir, 'techs.spf'), 'wb'), 1)
525
        pickle.dump(Tech, open(os.path.join(configDir, 'Tech.spf'), 'wb'), 1)
526
        fh = open(os.path.join(configDir, 'checksum'), 'wb')
527
        fh.write(chsum.hexdigest())
528
        fh.close()
529
530
        log.message("There is %d technologies" % len(techs))
531