Passed
Pull Request — master (#291)
by Marek
02:00
created

osci.dialog.ProblemsDlg.ProblemsDlg.createUI()   B

Complexity

Conditions 1

Size

Total Lines 51
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 40
nop 1
dl 0
loc 51
rs 8.92
c 0
b 0
f 0

How to fix   Long Method   

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:

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 pygameui as ui
22
import re
23
from osci import gdata, client, res
24
import ige.ospace.Const as Const
25
from ige.ospace import Utils, Rules
26
import string, math, copy
27
28
class ProblemsDlg:
29
    """Displays 'Problem locator' dialog.
30
31
    """
32
    def __init__(self, app):
33
        self.app = app
34
        self.createUI()
35
36
    def display(self):
37
        self.show()
38
        self.win.show()
39
        # register for updates
40
        if self not in gdata.updateDlgs:
41
            gdata.updateDlgs.append(self)
42
43
    def hide(self):
44
        self.win.setStatus(_("Ready."))
45
        self.win.hide()
46
        # unregister updates
47
        if self in gdata.updateDlgs:
48
            gdata.updateDlgs.remove(self)
49
50
    def update(self):
51
        self.show()
52
53
    class Problems:
54
        def __init__(self, win):
55
            self.items = []
56
            self.checkboxes = {gdata.CRI : win.vCritical.checked,
57
                               gdata.MAJ : win.vMajor.checked,
58
                               gdata.MIN : win.vMinor.checked,
59
                               gdata.INFO: win.vInfo.checked}
60
61
        def append(self, severity, item):
62
            if self.checkboxes[severity]:
63
                item.foreground = gdata.sevColors[severity]
64
                self.items.append(item)
65
66
    def _addProblemsStructStatus(self, problems, struct, planet):
67
        player = client.getPlayer()
68
        status = struct[Const.STRUCT_IDX_STATUS]
69
        tech = client.getFullTechInfo(struct[Const.STRUCT_IDX_TECHID])
70
71
        if hasattr(player, 'techs'):
72
            techEff = Rules.techImprEff[player.techs.get(struct[Const.STRUCT_IDX_TECHID], Rules.techBaseImprovement)]
73
        else:
74
            techEff = Rules.techImprEff[Rules.techBaseImprovement]
75
76
        HPturn = max(1, int(0.02 * tech.maxHP * techEff))
77
        turnsToDestroy = math.ceil(struct[Const.STRUCT_IDX_HP] / HPturn)
78
79
        if turnsToDestroy < Rules.turnsPerDay * 2:
80
            severity = gdata.MAJ
81
            if turnsToDestroy < Rules.turnsPerDay:
82
                severity = gdata.CRI
83
        else:
84
            severity = gdata.MIN
85
86
        if not status & Const.STRUCT_STATUS_ON:
87
            # structure is off
88
            problems.append(severity, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
89
                    vDescription = _('Structure (%s) is off and will be destroyed in %s turns.') % (tech.name, res.formatTime(turnsToDestroy))))
90
91
        if status & Const.STRUCT_STATUS_DETER:
92
            problems.append(gdata.MAJ, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
93
                vDescription = _('Structure (%s) is deteriorating.') % (tech.name,)))
94
        if status & Const.STRUCT_STATUS_NOBIO:
95
            problems.append(gdata.INFO, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
96
                vDescription = _('Structure (%s) has insufficient supply of biomatter.') % (tech.name,)))
97
        if status & Const.STRUCT_STATUS_NOEN:
98
            problems.append(gdata.INFO, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
99
                vDescription = _('Structure (%s) has insufficient supply of energy.') % (tech.name,)))
100
        if status & Const.STRUCT_STATUS_NOPOP:
101
            problems.append(gdata.INFO, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
102
                vDescription = _('Structure (%s) has insufficient supply of workers.') % (tech.name,)))
103
        if status & Const.STRUCT_STATUS_REPAIRING:
104
            problems.append(gdata.INFO, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
105
                vDescription = _('Structure (%s) is repairing.') % (tech.name,)))
106
107
    def _addProblemsFleets(self, problems, fleet):
108
        player = client.getPlayer()
109
        energyReserve = fleet.storEn  * 100 / fleet.maxEn
110
        system = None
111
112
        maxRefuelMax = 0
113
        if hasattr(fleet, 'orbiting') and fleet.orbiting:
114
            system = client.get(fleet.orbiting, noUpdate = 1)
115
            for planetID in system.planets:
116
                planet = client.get(planetID, noUpdate = 1)
117
                if hasattr(planet, 'owner') and hasattr(planet, 'refuelMax'):
118
                    if planet.owner == player.oid:
119
                        maxRefuelMax = max(maxRefuelMax, planet.refuelMax)
120
                    elif player.diplomacyRels.has_key(planet.owner):
121
                        dipl = client.getDiplomacyWith(planet.owner)
122
                        if dipl.pacts.has_key(Const.PACT_ALLOW_TANKING) and dipl.pacts[Const.PACT_ALLOW_TANKING][0] == Const.PACT_ACTIVE:
123
                            maxRefuelMax = max(maxRefuelMax, planet.refuelMax)
124
        else:
125
            # we do not report fleets in flight
126
            return
127
128
        systemName = res.getUnknownName()
129
        if system and hasattr(system, "name"):
130
            systemName = system.name
131
132
        # is fleet named?
133
        if hasattr(fleet,'customname') and fleet.customname:
134
            name = fleet.customname
135
        else:
136
            name = getattr(fleet, "name", None)
137
138
        note = _(' and is NOT refueling')
139
        if maxRefuelMax:
140
            severity = gdata.INFO
141
            if maxRefuelMax <= energyReserve:
142
                note = _(' and CAN refuel, but reached planet maximum refuel amount')
143
            else:
144
                note = _(' and IS refueling')
145
        elif energyReserve < 25:
146
            severity = gdata.CRI
147
        elif energyReserve < 50:
148
            severity = gdata.MAJ
149
        elif energyReserve == 100:
150
            problems.append(gdata.INFO, ui.Item(systemName, tOID = fleet.oid, tType = Const.T_FLEET,
151
                        vDescription = _('Fleet "%s" has full fuel tanks.') % (name)))
152
            return
153
        else:
154
            severity = gdata.INFO
155
        problems.append(severity, ui.Item(systemName, tOID = fleet.oid, tType = Const.T_FLEET,
156
                    vDescription = _('Fleet "%s" is low on fuel [%d %%]%s.') % (name, energyReserve, note)))
157
158
    def _addProblemsMaterial(self, problems, system, mat, totalMat, material):
159
        if mat >= 0:
160
            return
161
        surplusTurns = totalMat / (-mat)
162
        if surplusTurns < Rules.turnsPerDay * 7:
163
            severity = gdata.MAJ
164
        elif surplusTurns < Rules.turnsPerDay * 2:
165
            severity = gdata.CRI
166
        else:
167
            severity = gdata.MIN
168
169
        if totalMat > 0:
170
            note = _(' surplus %d (%s)' % (totalMat, res.formatTime(surplusTurns)))
171
        else:
172
            note = _(' with no surplus left!')
173
        problems.append(severity, ui.Item(system.name, tOID = system.oid, tType = Const.T_SYSTEM,
174
                     vDescription = _('%s decreasing - last turn change %d, %s.') % (material, mat, note)))
175
176
    def _addProblemsResearch(self, problems):
177
        player = client.getPlayer()
178
        totalEtc = 0
179
        # compute length of research queue
180
        for task in player.rsrchQueue:
181
            tech = client.getTechInfo(task.techID)
182
            fulltech = client.getFullTechInfo(task.techID)
183
            researchSci = Utils.getTechRCost(player, task.techID, task.improvement)
184
            maxImprovement = min(Rules.techMaxImprovement,fulltech.maxImprovement)
185
            maxImpTotalSci = 0
186
            if task.improveToMax and task.improvement < maxImprovement:
187
                for impr in range(task.improvement+1,maxImprovement+1):
188
                    maxImpTotalSci += Utils.getTechRCost(player, task.techID, impr)
189
            if task.changeSci > 0:
190
                value = float(researchSci - task.currSci) / max(task.changeSci, player.effSciPoints)
191
                totalEtc += int(value + 1)
192
                if player.effSciPoints != 0:
193
                    totalEtc += float(maxImpTotalSci) / player.effSciPoints
194
            elif task.changeSci < 0:
195
                totalEtc -= float(task.currSci) / min(task.changeSci, player.effSciPoints)
196
            elif player.effSciPoints > 0:
197
                value = float(researchSci) / player.effSciPoints
198
                totalEtc += int(value + 1)
199
                totalEtc += float(maxImpTotalSci) / player.effSciPoints
200
            else:
201
                totalEtc = 99999
202
                break
203
204
        # check empty research queue
205
        if totalEtc == 0 and len(player.rsrchQueue) == 0 and player.effSciPoints > 0:
206
            problems.append(gdata.CRI, ui.Item(_('Research'), tType = Const.T_TECHNOLOGY,
207
                vDescription = _('Research queue is empty.')))
208
        # check short reseach queue
209
        elif totalEtc < Rules.turnsPerDay * 2:
210
            severity = gdata.MIN
211
            if totalEtc < Rules.turnsPerDay:
212
                severity = gdata.MAJ
213
            problems.append(severity, ui.Item(_('Research'), tType = Const.T_TECHNOLOGY,
214
                        vDescription = _('Research queue ends in %s turns, %d item(s) on list.') % (res.formatTime(totalEtc), len(player.rsrchQueue))))
215
216
    def _addProblemsGlobalQueues(self, problems):
217
        player = client.getPlayer()
218
        ## go through all planets to understand the state of global queues
219
        # holder for (number , eff production) of planets set to each queue
220
        globalQueueStats=[(0,0), (0,0), (0,0), (0,0), (0,0)]
221
        queConstValues = [0, 0, 0, 0, 0]
222
        queEtc = [0, 0, 0, 0, 0]
223
224
        for planetID in player.planets:
225
            planet = client.get(planetID, noUpdate = 1)
226
            globalQueueStats[planet.globalQueue] = tuple([sum(x) for x in zip(globalQueueStats[planet.globalQueue], (1, planet.effProdProd))])
227
228
        # evaluate depletion rate of the global queue
229
        for queue in range(5):
230
            quePlanets, queEffProd = globalQueueStats[queue]
231
            for task in player.prodQueues[queue]:
232
                if task.isShip:
233
                    tech = player.shipDesigns[task.techID]
234
                else:
235
                    tech = client.getFullTechInfo(task.techID)
236
                queConstValues[queue] += task.quantity * tech.buildProd
237
            if queEffProd > 0:
238
                queEtc[queue] = math.ceil(float(queConstValues[queue])/queEffProd)
239
            else:
240
                queEtc[queue] = 99999
241
242
        # creation of items with global queue problems
243
        for queue in range(1, 5):
244
            queName = res.globalQueueName(queue)
245
            quePlanets = globalQueueStats[queue][0]
246
            # check empty global production queue with at least one planet [so its relevant]
247
            if queConstValues[queue] == 0 and  quePlanets > 0:
248
                problems.append(gdata.CRI, ui.Item(_('Global queue ' + queName), tType = Const.T_QUEUE,
249
                    vDescription = _('Global production queue {0} used by {1} planet(s) is empty.'.format(queName, quePlanets))))
250
            # check end of global production queue
251
            elif queEtc[queue] < Rules.turnsPerDay * 2:
252
                severity = gdata.MIN
253
                if queEtc[queue] < Rules.turnsPerDay:
254
                    severity = gdata.MAJ
255
                problems.append(severity, ui.Item(_('Global queue ' + queName), tType = Const.T_QUEUE,
256
                    vDescription = _('Global production queue {0} used by {1} planet(s) runs out in {2} turns.'.format(queName, quePlanets, res.formatTime(queEtc[queue])))))
257
        return queEtc[0] # time of depletion of the default queue will be reused later
258
259
    def _addProblemsSlots(self, problems, system):
260
        player = client.getPlayer()
261
        playerPlanets = set(system.planets) & set(player.planets)
262
        freeSlots = {}
263
        for planetID in system.planets:
264
            planet = client.get(planetID, noUpdate = 1)
265
            freeSlots[planetID] = planet.plSlots - len(planet.slots)
266
        buildingInfo = {}
267
268
        for planetID in playerPlanets:
269
            planet = client.get(planetID, noUpdate = 1)
270
            if planet.prodQueue and planet.effProdProd > 0:
271
                for task in planet.prodQueue:
272
                    if task.isShip:
273
                        continue
274
                    tech = client.getFullTechInfo(task.techID)
275
                    if tech.isStructure and hasattr(task, "demolishStruct") and task.demolishStruct == 0:
276
                        freeSlots[planetID] -= task.quantity
277
                        # information about source and target of constructing
278
                        if buildingInfo.has_key((planetID, task.targetID)):
279
                            buildingInfo[(planetID, task.targetID)] += task.quantity
280
                        else:
281
                            buildingInfo[(planetID, task.targetID)] = task.quantity
282
                    elif tech.isProject and tech.id == 3802:
283
                        # we are constructing Habitable Surface Expansion
284
                        # so after construction we will have new slot on the planet
285
                        freeSlots[task.targetID] += 1
286
287
        for planetID, free in freeSlots.items():
288
            if free < 0:
289
                # not enough space, report for every planet that builds on this one
290
                planet = client.get(planetID, noUpdate = 1)
291
                for (sourceID, targetID), quantity in buildingInfo.items():
292
                    if planetID == targetID:
293
                        source = client.get(sourceID, noUpdate = 1)
294
                        problems.append(gdata.MAJ, ui.Item(source.name, tOID = sourceID, tType = Const.T_PLANET,
295
                            vDescription = _('There is no space for all constructed buildings on %s.') % (planet.name)))
296
297
    def _addProblemsDefaultQueue(self, problems, planet, defaultQueueEtc):
298
        player = client.getPlayer()
299
        if not planet.effProdProd > 0:
300
            return
301
        planetEtc = 0
302
        # compute length of production queue
303
        for task in planet.prodQueue:
304
            if task.isShip:
305
                tech = player.shipDesigns[task.techID]
306
            else:
307
                tech = client.getFullTechInfo(task.techID)
308
            modifier = Rules.buildOnAnotherPlanetMod if task.targetID != planet.oid else 1
309
            planetEtc += math.ceil(float(task.quantity * tech.buildProd * modifier - task.currProd) / planet.effProdProd)
310
311
        etc = planetEtc + defaultQueueEtc
312
        # check empty production queue
313
        if not etc:
314
            problems.append(gdata.CRI, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
315
                vDescription = _('Production queue is empty.')))
316
            return
317
        if etc < Rules.turnsPerDay:
318
            severity = gdata.MAJ
319
        elif etc < Rules.turnsPerDay * 2:
320
            severity = gdata.MIN
321
        else:
322
            severity = gdata.INFO
323
324
        problems.append(severity, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
325
            vDescription = _('Production queue may end in {0} turns ({1} directly in planet queue).'.format(res.formatTime(etc), res.formatTime(planetEtc)))))
326
327
    def show(self):
328
        critical = self.win.vCritical.checked
329
        major = self.win.vMajor.checked
330
        minor = self.win.vMinor.checked
331
        info = self.win.vInfo.checked
332
333
        disp = 1
334
335
        player = client.getPlayer()
336
        problems = self.Problems(self.win)
337
        systems = set([])
338
        for planetID in player.planets:
339
            planet = client.get(planetID, noUpdate = 1)
340
            systems.add(planet.compOf)
341
342
        defaultQueueEtc = self._addProblemsGlobalQueues(problems)
343
344
345
        for systemID in systems:
346
            system = client.get(systemID, noUpdate = 1)
347
            bio = 0
348
            totalBio = 0
349
            en = 0
350
            totalEn = 0
351
            # holds modified planets
352
            planetCopies = {}
353
354
            for planetID in system.planets:
355
                planet = client.get(planetID, noUpdate = 1)
356
                # copy of planet to change plSlots count
357
                if hasattr(planet, 'owner') and planet.owner == player.oid:
358
                    # add planet to the global queue stats
359
                    # compute bio and en for system
360
                    bio += planet.changeBio
361
                    totalBio += max(0, planet.storBio - planet.minBio)
362
                    en  += planet.changeEn
363
                    totalEn += max(0, planet.storEn - planet.minEn)
364
                    # the planet needs to have global queue 0 - the default one - to have its queue reported
365
                    if self.win.vPlanets.checked:
366
                        if not planet.globalQueue:
367
                            self._addProblemsDefaultQueue(problems, planet, defaultQueueEtc)
368
                        for struct in planet.slots:
369
                            self._addProblemsStructStatus(problems, struct, planet)
370
371
            # free slots within the system
372
            self._addProblemsSlots(problems, system)
373
            # check bio for system
374
            if self.win.vSystems.checked:
375
                self._addProblemsMaterial(problems, system, bio, totalBio, 'Bio')
376
                self._addProblemsMaterial(problems, system, en, totalEn, 'En')
377
378
        # check fleets
379
        if self.win.vFleets.checked:
380
            for fleetID in player.fleets:
381
                fleet = client.get(fleetID, noUpdate = 1)
382
                self._addProblemsFleets(problems, fleet)
383
384
        # check research queue
385
        if self.win.vResearch.checked:
386
            self._addProblemsResearch(problems)
387
            
388
389
        self.win.vProblems.items = problems.items
390
        self.win.vProblems.itemsChanged()
391
392
    def onClose(self, widget, action, data):
393
        self.hide()
394
395
    def onShowSource(self, widget, action, data):
396
        item = self.win.vProblems.selection[0]
397
        if item.tType == Const.T_FLEET:
398
            object = client.get(item.tOID, noUpdate = 1)
399
            # center on map
400
            if hasattr(object, "x"):
401
                gdata.mainGameDlg.win.vStarMap.highlightPos = (object.x, object.y)
402
                gdata.mainGameDlg.win.vStarMap.setPos(object.x, object.y)
403
                self.hide()
404
                return
405
        elif item.tType in (Const.T_SYSTEM, Const.T_PLANET):
406
            if item.tOID != Const.OID_NONE:
407
                gdata.mainGameDlg.onSelectMapObj(None, None, item.tOID)
408
                return
409
        elif item.tType == Const.T_TECHNOLOGY:
410
            gdata.mainGameDlg.researchDlg.display()
411
            return
412
        elif item.tType == Const.T_QUEUE:
413
            gdata.mainGameDlg.globalQueuesDlg.display()
414
        self.win.setStatus(_("Cannot show location."))
415
416
    def onShowLocation(self, widget, action, data):
417
        item = self.win.vProblems.selection[0]
418
        if item.tType in (Const.T_SYSTEM, Const.T_PLANET, Const.T_FLEET):
419
            object = client.get(item.tOID, noUpdate = 1)
420
            # center on map
421
            if hasattr(object, "x"):
422
                gdata.mainGameDlg.win.vStarMap.highlightPos = (object.x, object.y)
423
                gdata.mainGameDlg.win.vStarMap.setPos(object.x, object.y)
424
                self.hide()
425
                return
426
        self.win.setStatus(_("Cannot show location."))
427
428
    def onToggleCondition(self, widget, action, data):
429
        self.update()
430
431
    def createUI(self):
432
        screenWidth, screenHeight = gdata.scrnSize
433
        # size of dialog in layout metrics (for SimpleGridLM)
434
        cols = 40
435
        rows = 29
436
        # dialog width and height in pixels
437
        isSmallWin = screenHeight == 600 and screenWidth == 800
438
        width = cols * 20 + 4 * (not isSmallWin)
439
        height = rows * 20 + 4 * (not isSmallWin)
440
        #creating dialog window
441
        self.win = ui.Window(self.app,
442
            modal = 1,
443
            escKeyClose = 1,
444
            movable = 0,
445
            title = _("Problems Locator"),
446
            titleOnly = isSmallWin,
447
            #rect = ui.Rect((screenWidth - width) / 2, ((screenHeight - height) / 2) * (not isSmallWin), width, height),
448
            rect = ui.Rect((screenWidth - 800 - 4 * (not isSmallWin)) / 2, (screenHeight - 600 - 4 * (not isSmallWin)) / 2, width, height),
449
            layoutManager = ui.SimpleGridLM(),
450
        )
451
        self.win.subscribeAction('*', self)
452
        # first row is window title
453
        rows -= 1
454
455
        ui.Listbox(self.win, layout = (0, 0, cols, rows - 2), id = 'vProblems',
456
            columns = [(_('Location'), 'text', 10, ui.ALIGN_W),
457
            (_('Problem description'), 'vDescription', 30, ui.ALIGN_W)],
458
            columnLabels = 1, action='onShowSource', rmbAction='onShowLocation')
459
460
        btnWidth = 4
461
        ui.Check(self.win, layout = (btnWidth * 0, rows - 2, btnWidth, 1), id = 'vSystems',
462
            text = _('Systems'), action = 'onToggleCondition', checked = 1)
463
        ui.Check(self.win, layout = (btnWidth * 1, rows - 2, btnWidth, 1), id = 'vPlanets',
464
            text = _('Planets'), action = 'onToggleCondition', checked = 1)
465
        ui.Check(self.win, layout = (btnWidth * 2, rows - 2, btnWidth, 1), id = 'vFleets',
466
            text = _('Fleets'), action = 'onToggleCondition', checked = 1)
467
        ui.Check(self.win, layout = (btnWidth * 3, rows - 2, btnWidth, 1), id = 'vResearch',
468
            text = _('Research'), action = 'onToggleCondition', checked = 1)
469
470
        ui.Check(self.win, layout = (btnWidth * 6, rows - 2, btnWidth, 1), id = 'vCritical',
471
            text = _('Critical'), action = 'onToggleCondition', checked = 1)
472
        ui.Check(self.win, layout = (btnWidth * 7, rows - 2, btnWidth, 1), id = 'vMajor',
473
            text = _('Major'), action = 'onToggleCondition', checked = 1)
474
        ui.Check(self.win, layout = (btnWidth * 8, rows - 2, btnWidth, 1), id = 'vMinor',
475
            text = _('Minor'), action = 'onToggleCondition', checked = 1)
476
        ui.Check(self.win, layout = (btnWidth * 9, rows - 2, btnWidth, 1), id = 'vInfo',
477
            text = _('Info'), action = 'onToggleCondition', checked = 0)
478
479
        # dialog bottom line
480
        ui.Title(self.win, layout = (0, rows - 1, cols - 5, 1))
481
        ui.TitleButton(self.win, layout = (cols - 5, rows - 1, 5, 1), text = _("Close"), action = 'onClose')
482