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