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

osci.dialog.ProblemsDlg.ProblemsDlg.hide()   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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