ige.ospace.IPlayer.IPlayer.getScannerMap()   F
last analyzed

Complexity

Conditions 23

Size

Total Lines 56
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 45
nop 3
dl 0
loc 56
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.IPlayer.IPlayer.getScannerMap() 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 math
22
import re
23
import time
24
25
import ige
26
import Rules
27
import Utils
28
import ShipUtils
29
30
from ige import log
31
from ige.IObject import IObject, public
32
from ige.IDataHolder import IDataHolder
33
import Const
34
35
class IPlayer(IObject):
36
37
    typeID = Const.T_PLAYER
38
    resignTo = Const.T_AIPLAYER
39
    forums = {"INBOX": 56, "OUTBOX": 56, "EVENTS": 4}
40
41
    def init(self, obj):
42
        IObject.init(self, obj)
43
        #
44
        obj.login = u''
45
        obj.name = u''
46
        obj.fullName = u''
47
        #
48
        obj.buoys = {}
49
        obj.alliedBuoys = {}
50
        obj.planets = []
51
        obj.fleets = []
52
        obj.techs = {} # techs and their sublevel
53
        obj.obsoleteTechs = set()
54
        obj.rsrchQueue = []
55
        obj.sciPoints = 0
56
        obj.effSciPoints = 0
57
        obj.techLevel = 1
58
        obj.shipDesigns = {}
59
        obj.race = "H" # race Bionic, Human, Cyborg
60
        # bonuses
61
        obj.prodEff = 1.0
62
        obj.sciEff = 1.0
63
        #
64
        obj.govPwr = 0
65
        obj.govPwrCtrlRange = 1
66
        # fleet support
67
        obj.fleetUpgradePool = 0.0
68
        obj.fleetUpgradeInProgress = 0
69
        # production
70
        obj.prodQueues = [[],[],[],[],[]]
71
        obj.prodIncreasePool = 0.0
72
        # diplomacy
73
        obj.diplomacyRels = {}
74
        obj.defaultRelation = Rules.defaultRelation
75
        obj.voteFor = Const.OID_NONE
76
        obj.governorOf = Const.OID_NONE
77
        obj.governors = []
78
        # obj.alliance = Const.OID_NONE # not utilized, but can be in DB somewhere
79
        obj.imperator = 0
80
        # combat
81
        # anti-small, anti-medium, anti-large, shield generator
82
        obj.planetWeapons = [None, None, None, None, None]
83
        #
84
        obj.staticMap = {}
85
        obj.dynamicMap = {}
86
        obj.galaxy = None
87
        obj.validSystems = []
88
        #
89
        obj.stats = IDataHolder()
90
        obj.stats.type = Const.T_STATS
91
        obj.timeEnabled = 0
92
        obj.stratRes = {}
93
        obj.lastLogin = 0.0
94
        #
95
        obj.shipRedirections = {}
96
        obj.buoys = {}
97
        #
98
        obj.clientStats = {}
99
100
    def update(self, tran, obj):
101
        # refresh technologies
102
        self.setStartingTechnologies(obj)
103
        # clean up obsoleted technologies
104
        obsoleted = set(obj.techs.keys()).difference(Rules.techs.keys())
105
        for techID in obsoleted:
106
            log.debug("Deleting obsoleted technology", obj.oid, techID)
107
            del obj.techs[techID]
108
        # update all designs
109
        for designID in obj.shipDesigns:
110
            old = obj.shipDesigns[designID]
111
            new = ShipUtils.makeShipMinSpec(obj, old.name, old.hullID,
112
                old.eqIDs, old.improvements, raiseExs = False)
113
            new.built = old.built
114
            if hasattr(old, "upgradeTo"):
115
                new.upgradeTo = old.upgradeTo
116
            obj.shipDesigns[designID] = new
117
        # check all diplomacyRels
118
        for partyID in obj.diplomacyRels.keys():
119
            party = tran.db.get(partyID, None)
120
            if not party or party.type not in Const.PLAYER_TYPES:
121
                log.debug("Deleting party", obj.oid, partyID)
122
                del obj.diplomacyRels[partyID]
123
        # delete obj with low scan pwr
124
        # check type of the objects in the map
125
        for objID in obj.staticMap.keys():
126
            obj.staticMap[objID] = min(obj.staticMap[objID], Rules.maxScanPwr)
127
            if obj.staticMap[objID] < Rules.level1InfoScanPwr:
128
                del obj.staticMap[objID]
129
            if objID not in tran.db or tran.db[objID].type not in (Const.T_SYSTEM, Const.T_WORMHOLE):
130
                log.debug("Deleting non system %d from static map of player %d" % (objID, obj.oid))
131
                del obj.staticMap[objID]
132
        for objID in obj.dynamicMap.keys():
133
            if obj.dynamicMap[objID] < Rules.level1InfoScanPwr:
134
                del obj.dynamicMap[objID]
135
            if objID not in tran.db or tran.db[objID].type != Const.T_FLEET:
136
                log.debug("Deleting obj %d from dynamic map of player %d" % (objID, objID))
137
                del obj.dynamicMap[objID]
138
        # check if all planets are planets
139
        for objID in obj.planets[:]:
140
            try:
141
                if objID not in tran.db:
142
                    log.debug("Planet does not exists - removing", obj.oid, objID)
143
                    obj.planets.remove(objID)
144
                if tran.db[objID].type != Const.T_PLANET:
145
                    log.debug("Planet is not a planet - removing", obj.oid, objID)
146
                    obj.planets.remove(objID)
147
            except:
148
                log.warning("There is a problem when processing planet - removing", obj.oid, objID)
149
                obj.planets.remove(objID)
150
        # check if systems in buoys are systems
151
        for objID in obj.buoys.keys():
152
            try:
153
                if objID not in tran.db:
154
                    log.debug("System for buoy does not exists - removing", obj.oid, objID)
155
                    del obj.buoys[objID]
156
                if tran.db[objID].type not in (Const.T_SYSTEM, Const.T_WORMHOLE):
157
                    log.debug("System for buoy is not a system - removing", obj.oid, objID)
158
                    del obj.buoys[objID]
159
            except:
160
                log.warning("There is a problem when processing system for buoy - removing", obj.oid, objID)
161
                del obj.buoys[objID]
162
        # check if fleets are fleets
163
        for objID in obj.fleets[:]:
164
            try:
165
                if objID not in tran.db:
166
                    log.debug("Fleet does not exists - removing", obj.oid, objID)
167
                    obj.fleets.remove(objID)
168
                if tran.db[objID].type != Const.T_FLEET:
169
                    log.debug("Fleet is not a fleet - removing", obj.oid, objID)
170
                    obj.fleets.remove(objID)
171
            except:
172
                log.warning("There is a problem when processing planet - removing", obj.oid, objID)
173
        # check accessible technologies
174
        wip = 1
175
        while wip:
176
            wip = 0
177
            for techID in obj.techs.keys():
178
                if techID not in Rules.techs:
179
                    wip = 1
180
                    log.debug("Deleting nonexistent tech", techID, "player", obj.oid)
181
                    del obj.techs[techID]
182
                    continue
183
                tech = Rules.techs[techID]
184
                # check tech level
185
                if tech.level > obj.techLevel:
186
                    wip = 1
187
                    log.debug("Deleting tech", techID, "player", obj.oid)
188
                    if techID in obj.techs: del obj.techs[techID]
189
        for rTask in obj.rsrchQueue[:]:
190
            if rTask.techID not in Rules.techs:
191
                log.debug("Deleting res task for nonexistent tech", rTask.techID, "player", obj.oid)
192
                obj.rsrchQueue.remove(rTask)
193
                continue
194
            tech = Rules.techs[rTask.techID]
195
            if tech.level == 99:
196
                log.debug("Deleting res task", rTask.techID, "player", obj.oid)
197
                obj.rsrchQueue.remove(rTask)
198
        # check if player is in the universe
199
        universe = tran.db[Const.OID_UNIVERSE]
200
        if obj.oid not in universe.players and obj.oid not in (Const.OID_NATURE, Const.OID_ADMIN):
201
            log.debug(obj.oid, "Adding player to the universe")
202
            universe.players.append(obj.oid)
203
        # check if player is a leader
204
        if not obj.galaxy:
205
            log.debug(obj.oid, obj.name, "IS NOT IN ANY GALAXY")
206
        else:
207
            galaxy = tran.db[obj.galaxy]
208
            if galaxy.imperator != obj.oid and obj.imperator > 0:
209
                log.debug(obj.oid, "Removing imperator/leader bonus")
210
                obj.imperator = 0
211
        ## NON VALIDATING CODE (DERIVED ATTRS AND SO ON)
212
        # get best technologies for planet weapons
213
        bestScores = [0, 0, 0, 0]
214
        obj.planetWeapons = [None, None, None, None, None]
215
        for techID in obj.techs:
216
            tech = Rules.techs[techID]
217
            if tech.isShipEquip and tech.weaponDmgMin > 0 and not tech.buildSRes\
218
                and tech.weaponGoodForFlak:
219
                # compute score
220
                weaponEff = Rules.techImprEff[obj.techs.get(techID, Rules.techBaseImprovement)]
221
                score = (tech.weaponDmgMin + tech.weaponDmgMax) / 2.0 * \
222
                    tech.weaponROF * (tech.weaponAtt + 10.0)/10 * weaponEff
223
                if score > bestScores[tech.weaponClass]:
224
                    obj.planetWeapons[tech.weaponClass] = techID
225
                    bestScores[tech.weaponClass] = score
226
        #@log.debug(obj.oid, "Planet weapons", obj.planetWeapons)
227
        # update all ship designs
228
        for designID in obj.shipDesigns:
229
            old = obj.shipDesigns[designID]
230
            new = ShipUtils.makeShipMinSpec(obj, old.name, old.hullID,
231
                old.eqIDs, old.improvements, raiseExs = False)
232
            new.built = old.built
233
            new.upgradeTo = old.upgradeTo
234
            obj.shipDesigns[designID] = new
235
236
    @staticmethod
237
    def setStartingPlanet(tran, playerID, planet):
238
        planet.plSlots = max(planet.plSlots, 9)
239
        planet.plMaxSlots = max(planet.plMaxSlots, 9)
240
        planet.plDiameter = max(planet.plDiameter, 9000)
241
        planet.slots = [
242
            Utils.newStructure(tran, Rules.Tech.PWRPLANTNUK1, playerID, Const.STRUCT_STATUS_ON, Rules.structNewPlayerHpRatio),
243
            Utils.newStructure(tran, Rules.Tech.FARM1, playerID, Const.STRUCT_STATUS_ON, Rules.structNewPlayerHpRatio),
244
            Utils.newStructure(tran, Rules.Tech.FARM1, playerID, Const.STRUCT_STATUS_ON, Rules.structNewPlayerHpRatio),
245
            Utils.newStructure(tran, Rules.Tech.ANCFACTORY, playerID, Const.STRUCT_STATUS_ON, Rules.structNewPlayerHpRatio),
246
            Utils.newStructure(tran, Rules.Tech.ANCFACTORY, playerID, Const.STRUCT_STATUS_ON, Rules.structNewPlayerHpRatio),
247
            Utils.newStructure(tran, Rules.Tech.ANCRESLAB, playerID, Const.STRUCT_STATUS_ON, Rules.structNewPlayerHpRatio),
248
            Utils.newStructure(tran, Rules.Tech.REPAIR1, playerID, Const.STRUCT_STATUS_ON, Rules.structNewPlayerHpRatio),
249
        ]
250
        planet.storPop = Rules.startingPopulation
251
        planet.storBio = Rules.startingBio
252
        planet.storEn = Rules.startingEn
253
        planet.scannerPwr = Rules.startingScannerPwr
254
        planet.morale = Rules.maxMorale
255
256
    @staticmethod
257
    def setStartingTechnologies(obj):
258
        for techID, tech in Rules.techs.iteritems():
259
            if tech.isStarting and techID not in obj.techs:
260
                obj.techs[techID] = (Rules.techBaseImprovement + tech.maxImprovement) / 2
261
262
    @staticmethod
263
    def setStartingShipDesigns(obj):
264
        obj.shipDesigns[1] = ShipUtils.makeShipMinSpec(obj, 'Scout', Rules.Tech.SMALLHULL0,
265
                {Rules.Tech.SCOCKPIT0:1, Rules.Tech.SCANNERMOD0:1, Rules.Tech.FTLENG0:3}, [])
266
        obj.shipDesigns[2] = ShipUtils.makeShipMinSpec(obj, 'Fighter', Rules.Tech.SMALLHULL0,
267
                {Rules.Tech.SCOCKPIT0:1, Rules.Tech.CANNON0:2, Rules.Tech.FTLENG0:3}, [])
268
        obj.shipDesigns[3] = ShipUtils.makeShipMinSpec(obj, 'Bomber', Rules.Tech.SMALLHULL0,
269
                {Rules.Tech.SCOCKPIT0:1, Rules.Tech.CONBOMB0:1, Rules.Tech.FTLENG0:3}, [])
270
        obj.shipDesigns[4] = ShipUtils.makeShipMinSpec(obj, 'Patrol Corvette', Rules.Tech.SMALLHULL0,
271
                {Rules.Tech.SCOCKPIT0:1, Rules.Tech.CANNON0:3, Rules.Tech.FTLENG0:1, Rules.Tech.STLENG1:2}, [])
272
        obj.shipDesigns[5] = ShipUtils.makeShipMinSpec(obj, 'Colony Ship', Rules.Tech.MEDIUMHULL0,
273
                {Rules.Tech.SCOCKPIT0:1, Rules.Tech.COLONYMOD0:1, Rules.Tech.FTLENG0:5}, [])
274
275
    @staticmethod
276
    def setStartingFleet(tran, playerID, system):
277
        # add small fleet
278
        log.debug('Creating fleet')
279
        fleet = tran.gameMngr.cmdPool[Const.T_FLEET].new(Const.T_FLEET)
280
        tran.db.create(fleet)
281
        log.debug('Creating fleet - created', fleet.oid)
282
        tran.gameMngr.cmdPool[Const.T_FLEET].create(tran, fleet, system, playerID)
283
        log.debug('Creating fleet - addShips')
284
        # for IDs, see setStartingShipDesigns
285
        tran.gameMngr.cmdPool[Const.T_FLEET].addNewShip(tran, fleet, 1)
286
        tran.gameMngr.cmdPool[Const.T_FLEET].addNewShip(tran, fleet, 1)
287
        tran.gameMngr.cmdPool[Const.T_FLEET].addNewShip(tran, fleet, 2)
288
        tran.gameMngr.cmdPool[Const.T_FLEET].addNewShip(tran, fleet, 2)
289
        tran.gameMngr.cmdPool[Const.T_FLEET].addNewShip(tran, fleet, 5)
290
291
    @public(Const.AL_FULL)
292
    def startGlobalConstruction(self, tran, player, techID, quantity, isShip, reportFinished, queue):
293
        if len(player.prodQueues) <= queue:
294
            raise ige.GameException('Invalid queue.')
295
        if len(player.prodQueues[queue]) > Rules.maxProdQueueLen:
296
            raise ige.GameException('Queue is full.')
297
        if quantity < 1:
298
            raise ige.GameException("Quantity must be greater than 0")
299
        if techID not in player.techs and isShip == 0:
300
            raise ige.GameException('You do not own this kind of technology.')
301
        if techID not in player.shipDesigns and isShip == 1:
302
            raise ige.GameException('You do not own this ship design.')
303
        if isShip:
304
            tech = player.shipDesigns[techID]
305
            if tech.upgradeTo:
306
                raise ige.GameException("You cannot build obsolete ship design.")
307
        else:
308
            tech = Rules.techs[techID]
309
            if tech.isStructure or not tech.isProject:
310
                raise ige.GameException('You cannot construct this technology.')
311
            elif tech.globalDisabled:
312
                raise ige.GameException('You cannot construct targeted project.')
313
        neededSR = {}
314
        for sr in tech.buildSRes:
315
            nSR = neededSR.get(sr, 0) + tech.buildSRes[sr] * quantity
316
            if player.stratRes.get(sr, 0) < nSR:
317
                raise ige.GameException("You do not own required strategic resource(s)")
318
            neededSR[sr] = nSR
319
        # consume strategic resources
320
        for sr in neededSR:
321
            player.stratRes[sr] -= neededSR[sr]
322
        # start construction
323
        item = IDataHolder()
324
        item.techID = techID
325
        item.quantity = int(quantity)
326
        item.changePerc = 0
327
        item.isShip = bool(isShip)
328
        item.reportFin = bool(reportFinished)
329
        item.type = Const.T_TASK
330
        player.prodQueues[queue].append(item)
331
        return player.prodQueues[queue], player.stratRes
332
333
    @public(Const.AL_FULL)
334
    def changeGlobalConstruction(self, tran, player, queue, index, quantity):
335
        if index < 0 or index >= len(player.prodQueues[queue]):
336
            raise ige.GameException("No such item in the construction queue.")
337
338
        if quantity < 1:
339
            raise ige.GameException("Quantity must be greater than 0")
340
341
        item = player.prodQueues[queue][index]
342
        if item.isShip:
343
            tech = player.shipDesigns[item.techID]
344
        else:
345
            tech = Rules.techs[item.techID]
346
347
        quantityChange = quantity - player.prodQueues[queue][index].quantity
348
349
        neededSR = {}
350
        for sr in tech.buildSRes:
351
            nSR = neededSR.get(sr, 0) + tech.buildSRes[sr] * quantityChange
352
            if player.stratRes.get(sr, 0) < nSR:
353
                raise ige.GameException("You do not own required strategic resource(s)")
354
            neededSR[sr] = nSR
355
        # consume strategic resources
356
        for sr in neededSR:
357
            player.stratRes[sr] += (-1 * neededSR[sr])
358
359
        player.prodQueues[queue][index].quantity = quantity
360
        player.prodQueues[queue][index].const = tech.buildProd * quantity
361
        return player.prodQueues[queue], player.stratRes
362
363
    @public(Const.AL_FULL)
364
    def abortGlobalConstruction(self, tran, player, queue, index):
365
        if len(player.prodQueues) <= queue or queue < 0:
366
            raise ige.GameException('Invalid queue.')
367
        if len(player.prodQueues[queue]) <= index or index < 0:
368
            raise ige.GameException('Invalid task.')
369
        item = player.prodQueues[queue][index]
370
        # return strategic resources
371
        #is ship
372
        if item.techID < 1000:
373
            tech = player.shipDesigns[item.techID]
374
        else:
375
            tech = Rules.techs[item.techID]
376
        for sr in tech.buildSRes:
377
            player.stratRes[sr] += item.quantity * tech.buildSRes[sr]
378
        player.prodQueues[queue].pop(index)
379
        return player.prodQueues[queue], player.stratRes
380
381
    @public(Const.AL_FULL)
382
    def moveGlobalConstrItem(self, tran, player, queue, index, rel):
383
        if index >= len(player.prodQueues[queue]):
384
            raise ige.GameException('No such item in the construction queue.')
385
        if index + rel < 0 or index + rel >= len(player.prodQueues[queue]):
386
            raise ige.GameException('Cannot move.')
387
        item = player.prodQueues[queue][index]
388
        del player.prodQueues[queue][index]
389
        player.prodQueues[queue].insert(index + rel, item)
390
        return player.prodQueues[queue]
391
392
    def getReferences(self, tran, obj):
393
        return obj.fleets
394
395
    def loggedIn(self, tran, obj):
396
        obj.lastLogin = time.time()
397
398
    @public(Const.AL_OWNER)
399
    def resign(self, tran, obj):
400
        """Remove player from the game. Give remaining planets, ... to the REBELS"""
401
        # cannot resign when time is stopped
402
        # TODO smarted conditions (like cannot resign twice a week or so)
403
        galaxy = tran.db[obj.galaxy]
404
        if galaxy.scenario == Const.SCENARIO_SINGLE:
405
            raise ige.GameException('You cannot resign current game - it is single player game.')
406
        if not obj.timeEnabled:
407
            raise ige.GameException('You cannot resign current game - time is stopped.')
408
        log.debug("Resigning player", obj.oid)
409
        # morph player to AI
410
        obj.type = self.resignTo
411
        self.cmd(obj).upgrade(tran, obj)
412
        self.cmd(obj).update(tran, obj)
413
        # reregister
414
        tran.gameMngr.removePlayer(obj.oid)
415
        self.cmd(obj).register(tran, obj, obj.galaxy)
416
417
    @public(Const.AL_ADMIN)
418
    def delete(self, tran, obj):
419
        # check whether it is AI or normal player
420
        log.debug("Deleting player", obj.oid)
421
        # delete relations
422
        for playerID in tran.db[Const.OID_UNIVERSE].players:
423
            player = tran.db[playerID]
424
            self.cmd(player).deleteDiplomacyWith(tran, player, obj.oid)
425
        # delete fleets
426
        for fleetID in obj.fleets:
427
            fleet = tran.db[fleetID]
428
            self.cmd(fleet).disbandFleet(tran, fleet)
429
        try:
430
            tran.gameMngr.removePlayer(obj.oid)
431
        except Exception:
432
            log.warning("Cannot remove player")
433
434
    @public(Const.AL_ADMIN)
435
    def giveUp(self, tran, obj, playerID):
436
        """Remove player from the game. Give remaining planets, ... to the specified player"""
437
        # cannot resign when time is stopped
438
        # TODO smarted conditions (like cannot resign twice a week or so)
439
        if not obj.timeEnabled:
440
            raise ige.GameException('You cannot resign current game - time is stopped.')
441
        player = tran.db[playerID]
442
        # give planets
443
        for planetID in obj.planets[:]: # needs a copy - changeOwner modifies this
444
            planet = tran.db[planetID]
445
            self.cmd(planet).changeOwner(tran, planet, playerID, force = 1)
446
        # give fleets
447
        for fleetID in obj.fleets[:]:
448
            fleet = tran.db[fleetID]
449
            fleet.owner = playerID
450
            player.fleets.append(fleetID)
451
        # remove player
452
        tran.gameMngr.removePlayer(obj.oid)
453
        try:
454
            tran.db[Const.OID_UNIVERSE].players.remove(obj.oid)
455
        except ValueError:
456
            pass
457
458
    @public(Const.AL_OWNER)
459
    def addShipDesign(self, tran, obj, name, hullID, eqIDs):
460
        """Add ship design to the database of designs."""
461
        # normalize design
462
        name = name.strip()
463
        # check technologies
464
        if hullID not in obj.techs:
465
            raise ige.GameException("You do not posses this hull type.")
466
        for techID in eqIDs:
467
            if techID not in obj.techs:
468
                raise ige.GameException("You do not posses technology(ies) to construct this ship.")
469
        # create spec (throws exception for invad ones)
470
        spec = ShipUtils.makeShipMinSpec(obj, name, hullID, eqIDs, [])
471
        # check number of designs
472
        if len(obj.shipDesigns) > Rules.shipMaxDesigns:
473
            raise ige.GameException("No space to store design.")
474
        # check name of designs
475
        for designID in obj.shipDesigns:
476
            if obj.shipDesigns[designID].name == name:
477
                raise ige.GameException("Design name is already used.")
478
        if re.match("^\s*$",name):
479
            raise ige.GameException("Design name must not be entirely whitespace.")
480
        # find free design id
481
        index = 1
482
        ids = obj.shipDesigns.keys()
483
        while 1:
484
            if index not in ids:
485
                break
486
            index += 1
487
        # add design
488
        obj.shipDesigns[index] = spec
489
        return obj.shipDesigns, index
490
491
    @public(Const.AL_OWNER)
492
    def addBuoy(self, tran, obj, systemID, text, type):
493
        """Add new buoy to player buoys."""
494
        # delete buoy
495
        if not text:
496
            if systemID in obj.buoys:
497
                del obj.buoys[systemID]
498
                return obj.buoys
499
            else:
500
                raise ige.GameException("Buoy at specified system does not exist.")
501
502
        if type not in (Const.BUOY_PRIVATE, Const.BUOY_TO_ALLY, Const.BUOY_TO_SCANNERSHARE):
503
            raise ige.GameException("Wrong bouy type.")
504
505
        # edit buoy
506
        if systemID in obj.buoys:
507
            obj.buoys[systemID] = (text, type)
508
            return obj.buoys
509
510
        if len(obj.buoys) >= 30:
511
            raise ige.GameException("You cannot add more than 30 buoys.")
512
513
        if tran.db[systemID].type not in (Const.T_SYSTEM, Const.T_WORMHOLE):
514
            raise ige.GameException("You can add buoy only to system.")
515
516
        # new buoy
517
        if len(text) > 0:
518
            obj.buoys[systemID] = (text, type)
519
520
        return obj.buoys
521
522
    @public(Const.AL_OWNER)
523
    def scrapShipDesign(self, tran, obj, designID):
524
        """Remove ship design from the database of designs and remove all
525
            active ships using this design."""
526
        # check design ID
527
        if designID not in obj.shipDesigns:
528
            raise ige.GameException("No such design.")
529
        # delete ships
530
        for fleetID in obj.fleets[:]: # make copy, fleet can be deleted
531
            fleet = tran.db[fleetID]
532
            self.cmd(fleet).deleteDesign(tran, fleet, designID)
533
        # delete tasks
534
        for planetID in obj.planets:
535
            planet = tran.db[planetID]
536
            self.cmd(planet).deleteDesign(tran, planet, designID)
537
        # delete from global queues
538
        for queueID in xrange(len(obj.prodQueues)):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
539
            queue = obj.prodQueues[queueID][:]
540
            for taskID in xrange(len(queue)):
541
                if obj.prodQueues[queueID][taskID].techID == designID:
542
                    self.cmd(obj).abortGlobalConstruction(tran, obj, queueID, taskID)
543
        # clear upgradeTo
544
        for tmpDesignID in obj.shipDesigns:
545
            spec = obj.shipDesigns[tmpDesignID]
546
            if spec.upgradeTo == designID:
547
                spec.upgradeTo = 0
548
        # delete design
549
        del obj.shipDesigns[designID]
550
        return obj.shipDesigns, obj.fleets, obj.stratRes, obj.prodQueues
551
552
    def getShipDesign(self,tran,obj,designID):
553
        if designID not in obj.shipDesigns:
554
            raise ige.GameException("No such design.")
555
        return obj.shipDesigns[designID]
556
557
    @public(Const.AL_OWNER)
558
    def upgradeShipDesign(self, tran, obj, oldDesignID, newDesignID):
559
        # check designs ID
560
        if oldDesignID not in obj.shipDesigns:
561
            raise ige.GameException("No such design.")
562
        if newDesignID not in obj.shipDesigns:
563
            raise ige.GameException("No such design.")
564
        if oldDesignID == newDesignID:
565
            raise ige.GameException("Designs are the same.")
566
        oldSpec = obj.shipDesigns[oldDesignID]
567
        newSpec = obj.shipDesigns[newDesignID]
568
        if oldSpec.upgradeTo:
569
            raise ige.GameException("Old design has already been made obsolete.")
570
        if newSpec.upgradeTo:
571
            raise ige.GameException("New design has already been made obsolete.")
572
        if oldSpec.combatClass != newSpec.combatClass:
573
            raise ige.GameException("Designs must be of the same combat class.")
574
        # set old design as upgradable
575
        oldSpec.upgradeTo = newDesignID
576
        # if something is upgraded to oldDesign change it to new design
577
        for desID in obj.shipDesigns:
578
            if obj.shipDesigns[desID].upgradeTo == oldDesignID:
579
                obj.shipDesigns[desID].upgradeTo = newDesignID
580
        # compute strat res difference
581
        stratRes = {}
582
        for sr in oldSpec.buildSRes:
583
            stratRes[sr] = stratRes.get(sr, 0) - oldSpec.buildSRes[sr]
584
        for sr in newSpec.buildSRes:
585
            stratRes[sr] = stratRes.get(sr, 0) + newSpec.buildSRes[sr]
586
            if stratRes[sr] == 0:
587
                del stratRes[sr]
588
        log.debug("upgradeShipDesign", obj.oid, stratRes)
589
        # modify tasks
590
        tasksUpgraded = False
591
        if not stratRes:
592
            log.debug("upgradeShipDesign - upgrading tasks")
593
            for planetID in obj.planets:
594
                planet = tran.db[planetID]
595
                self.cmd(planet).changeShipDesign(tran, planet, oldDesignID, newDesignID)
596
            # upgrade global queue as well
597
            for queue in obj.prodQueues:
598
                for task in queue:
599
                    if task.techID == oldDesignID:
600
                        task.techID = newDesignID
601
            tasksUpgraded = True
602
        else:
603
            log.debug("upgradeShipDesing - NOT upgrading tasks")
604
        return obj.shipDesigns, obj.stratRes, tasksUpgraded, obj.prodQueues
605
606
    @public(Const.AL_OWNER)
607
    def cancelUpgradeShipDesign(self, tran, obj, designID):
608
        # check designs ID
609
        if designID not in obj.shipDesigns:
610
            raise ige.GameException("No such design.")
611
        obj.shipDesigns[designID].upgradeTo = Const.OID_NONE
612
        return obj.shipDesigns
613
614
    @public(Const.AL_FULL)
615
    def startResearch(self, tran, obj, techID, improveToMax = 0):
616
        if len(obj.rsrchQueue) > Rules.maxRsrchQueueLen:
617
            ige.GameException('Queue is full.')
618
        tech = Rules.techs[techID]
619
        # player has to be a right race
620
        if obj.race not in tech.researchRaces:
621
            raise ige.GameException("Your race cannot research this technology.")
622
        # item cannot be researched twice
623
        for tmpTech in obj.rsrchQueue:
624
            if tmpTech.techID == techID:
625
                raise ige.GameException('Technology is already sheduled for research.')
626
        # disabled?
627
        for tmpTechID in obj.techs:
628
            if techID in Rules.techs[tmpTechID].researchDisables:
629
                raise ige.GameException("Previous research has disabled this technology.")
630
        # check requirements
631
        for tmpTechID, improvement in tech.researchRequires:
632
            if tmpTechID not in obj.techs or obj.techs[tmpTechID] < improvement:
633
                raise ige.GameException('You cannot research this technology yet.')
634
        improvement = obj.techs.get(techID, Rules.techBaseImprovement - 1) + 1
635
        if improvement > Rules.techMaxImprovement or improvement > tech.maxImprovement:
636
            raise ige.GameException('You cannot improve this technology further.')
637
        if tech.level > obj.techLevel:
638
            raise ige.GameException("Your technological level is insufficient.")
639
        # check strategic resources
640
        if improvement == 1:
641
            for stratRes in tech.researchReqSRes:
642
                if obj.stratRes.get(stratRes, 0) < 1:
643
                    raise ige.GameException("Required strategy resource missing.")
644
        item = IDataHolder()
645
        item.techID = techID
646
        item.improvement = improvement
647
        item.currSci = 0
648
        item.changeSci = 0
649
        item.improveToMax = improveToMax
650
        item.type = Const.T_RESTASK
651
        obj.rsrchQueue.append(item)
652
        return obj.rsrchQueue
653
654
    @public(Const.AL_FULL)
655
    def abortResearch(self, tran, obj, index):
656
        if index >= len(obj.rsrchQueue) or index < 0:
657
            ige.GameException('No such item in queue.')
658
        del obj.rsrchQueue[index]
659
        return obj.rsrchQueue
660
661
    @public(Const.AL_FULL)
662
    def editResearch(self, tran, obj, index, improveToMax = 0):
663
        if index >= len(obj.rsrchQueue) or index < 0:
664
            ige.GameException('No such item in queue.')
665
        obj.rsrchQueue[index].improveToMax = improveToMax
666
        return obj.rsrchQueue
667
668
    @public(Const.AL_FULL)
669
    def moveResearch(self, tran, obj, index, rel):
670
        if index >= len(obj.rsrchQueue):
671
            raise ige.GameException('No such item in the researcg queue.')
672
        if index + rel < 0 or index + rel >= len(obj.rsrchQueue):
673
            raise ige.GameException('Cannot move.')
674
        item = obj.rsrchQueue[index]
675
        del obj.rsrchQueue[index]
676
        obj.rsrchQueue.insert(index + rel, item)
677
        return obj.rsrchQueue
678
679
    @public(Const.AL_FULL)
680
    def redirectShips(self, tran, obj, sourceSystemID, targetSystemID):
681
        # check sourceSystemID
682
        ok = 0
683
        if sourceSystemID == targetSystemID:
684
            targetSystemID = Const.OID_NONE
685
        for planetID in tran.db[sourceSystemID].planets:
686
            if tran.db[planetID].owner == obj.oid:
687
                ok = 1
688
        if not ok:
689
            raise ige.GameException("You must own planet in the source system")
690
        # check targetSystemID
691
        if targetSystemID != Const.OID_NONE and 0: # TODO: switch on
692
            ok = 0
693
            for planetID in tran.db[targetSystemID].planets:
694
                if tran.db[planetID].owner == obj.oid:
695
                    ok = 1
696
            if not ok:
697
                raise ige.GameException("You must own planet in the target system")
698
        # fine - record it
699
        log.debug(obj.oid, "Adding redirection", sourceSystemID, targetSystemID)
700
        if targetSystemID:
701
            obj.shipRedirections[sourceSystemID] = targetSystemID
702
        else:
703
            try:
704
                del obj.shipRedirections[sourceSystemID]
705
            except KeyError:
706
                pass
707
        return obj.shipRedirections
708
709
    @public(Const.AL_NONE)
710
    def getPublicInfo(self, tran, obj):
711
        result = IObject.getPublicInfo(self, tran, obj)
712
        result.type = obj.type
713
        result.name = obj.name
714
        return result
715
716
    @public(Const.AL_OWNER)
717
    def changePactCond(self, tran, obj, playerID, pactID, state, conditions):
718
        log.debug("changePactCond", obj.oid, playerID, pactID)
719
        # must have a contact
720
        if playerID not in obj.diplomacyRels:
721
            raise ige.GameException('No contact with this player.')
722
        player = tran.db[playerID]
723
        # must be a player
724
        if player.type not in Const.PLAYER_TYPES:
725
            raise ige.GameException('Pacts can be offered to players only.')
726
        # check pactID
727
        pact = Rules.pactDescrs.get(pactID, None)
728
        if not pact:
729
            raise ige.GameException('No such pact type.')
730
        # check state
731
        if state not in (Const.PACT_OFF, Const.PACT_INACTIVE, Const.PACT_ACTIVE):
732
            raise ige.GameException("Wrong pact state")
733
        # check conditions
734
        for tmpPactID in conditions:
735
            pact = Rules.pactDescrs.get(tmpPactID, None)
736
            if not pact:
737
                raise ige.GameException('No such pact type.')
738
        # record pact
739
        dipl = self.cmd(obj).getDiplomacyWith(tran, obj, playerID)
740
        dipl.pacts[pactID] = [state]
741
        dipl.pacts[pactID].extend(conditions)
742
        # if state if Const.PACT_OFF, disable state on partner's side
743
        if state == Const.PACT_OFF:
744
            partner = tran.db[playerID]
745
            dipl = self.cmd(partner).getDiplomacyWith(tran, partner, obj.oid)
746
            if pactID in dipl.pacts:
747
                dipl.pacts[pactID][0] = Const.PACT_OFF
748
            else:
749
                dipl.pacts[pactID] = [Const.PACT_OFF]
750
        return obj.diplomacyRels
751
752
    def getDiplomacyWith(self, tran, obj, playerID):
753
        if obj.governorOf:
754
            # player is a governor
755
            leader = tran.db[obj.governorOf]
756
            return self.cmd(leader).getDiplomacyWith(tran, leader, objID)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable objID does not seem to be defined.
Loading history...
757
        # player is independent
758
        dipl = obj.diplomacyRels.get(playerID, None)
759 View Code Duplication
        if not dipl:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
760
            # make default
761
            dipl = IDataHolder()
762
            dipl.type = Const.T_DIPLREL
763
            dipl.pacts = {
764
                Const.PACT_ALLOW_CIVILIAN_SHIPS: [Const.PACT_ACTIVE, Const.PACT_ALLOW_CIVILIAN_SHIPS]
765
            }
766
            dipl.relation = obj.defaultRelation
767
            dipl.relChng = 0
768
            dipl.lastContact = tran.db[Const.OID_UNIVERSE].turn
769
            dipl.contactType = Const.CONTACT_NONE
770
            dipl.stats = None
771
            if playerID != obj.oid:
772
                obj.diplomacyRels[playerID] = dipl
773
            else:
774
                log.debug("getDiplomacyWith myself", obj.oid)
775
        return dipl
776
777
    @public(Const.AL_OWNER)
778
    def getPartyDiplomacyRels(self, tran, obj, partyID):
779
        if partyID not in obj.diplomacyRels:
780
            return None, None
781
        if obj.diplomacyRels[partyID].contactType == Const.CONTACT_NONE:
782
            return obj.diplomacyRels[partyID], None
783
        party = tran.db[partyID]
784
        return obj.diplomacyRels[partyID], party.diplomacyRels.get(obj.oid, None)
785
786
    def isPactActive(self, tran, obj, partnerID, pactID):
787
        #@log.debug("isPactActive", obj.oid, partnerID, pactID)
788
        if partnerID not in obj.diplomacyRels:
789
            return 0
790
        partner = tran.db[partnerID]
791
        partnerDipl = partner.diplomacyRels.get(obj.oid, None)
792
        if not partnerDipl:
793
            return 0
794
        return partnerDipl.pacts.get(pactID, [Const.PACT_OFF])[0] == Const.PACT_ACTIVE
795
796
    def deleteDiplomacyWith(self, tran, obj, playerID):
797
        if playerID in obj.diplomacyRels:
798
            del obj.diplomacyRels[playerID]
799
800
    @public(Const.AL_FULL)
801
    def getRelationTo(self, tran, obj, objID):
802
        if objID == Const.OID_NONE:
803
            return Const.REL_UNDEF
804
        if obj.oid == objID:
805
            return Const.REL_UNITY
806
        if obj.governorOf:
807
            leader = tran.db[obj.governorOf]
808
            return self.cmd(leader).getRelationTo(tran, leader, objID)
809
        dipl = obj.diplomacyRels.get(objID, None)
810
        if dipl:
811
            return dipl.relation
812
        else:
813
            return obj.defaultRelation
814
815
    @public(Const.AL_OWNER)
816
    def setVoteFor(self, tran, obj, playerID):
817
        if playerID not in obj.diplomacyRels and playerID != obj.oid and playerID != Const.OID_NONE:
818
            raise ige.GameException("No contact with this commander.")
819
        # check type
820
        if playerID != Const.OID_NONE:
821
            player = tran.db[playerID]
822
            if player.type != Const.T_PLAYER:
823
                raise ige.GameException("You cannot vote for this player.")
824
        # set
825
        obj.voteFor = playerID
826
        return obj.voteFor
827
828
    @public(Const.AL_ADMIN)
829
    def processDIPLPhase(self, tran, obj, data):
830
        if not obj.timeEnabled:
831
            return
832
        turn = tran.db[Const.OID_UNIVERSE].turn
833
        # record changes from valid pacts
834
        for partyID in obj.diplomacyRels:
835
            dipl = obj.diplomacyRels[partyID]
836
            # check contact
837
            if dipl.contactType == Const.CONTACT_NONE:
838
                #@log.debug("Skipping contact", obj.oid, partyID)
839
                continue
840
            # base change of relation
841
            dipl.relChng += Rules.baseRelationChange
842
            # process pacts
843
            for pactID in dipl.pacts:
844
                #@log.debug("Processing pact", obj.oid, partyID, pactID, dipl.pacts[pactID])
845
                if dipl.pacts[pactID][0] != Const.PACT_ACTIVE:
846
                    continue
847
                pactSpec = Rules.pactDescrs[pactID]
848
                if dipl.relation < pactSpec.validityInterval[0] or \
849
                    dipl.relation > pactSpec.validityInterval[1] or \
850
                    dipl.relChng < Rules.relLostWhenAttacked / 2:
851
                    # skip this non active pact, mark it as off
852
                    # mark all pact off when attacked
853
                    dipl.pacts[pactID][0] = Const.PACT_OFF
854
                    # TODO report it
855
                    continue
856
                # pact is valid
857
                if dipl.relation < pactSpec.targetRel:
858
                    #@log.debug("Affecting relation", pactSpec.relChng)
859
                    dipl.relChng += min(pactSpec.targetRel - dipl.relation, pactSpec.relChng)
860
        # apply relation changes
861
        for partyID in obj.diplomacyRels:
862
            dipl = obj.diplomacyRels[partyID]
863
            dipl.relation += dipl.relChng
864
            dipl.relation = min(dipl.relation, Const.REL_ALLY_HI)
865
            dipl.relation = max(dipl.relation, Const.REL_ENEMY_LO)
866
            #@log.debug('IPlayer', 'Final relation', obj.oid, partyID, dipl.relation, dipl.relChng)
867
868
    @public(Const.AL_OWNER)
869
    def getScannerMap(self, tran, obj):
870
        scanLevels = {}
871
        # full map for the admin
872
        if obj.oid == Const.OID_ADMIN:
873
            universe = tran.db[Const.OID_UNIVERSE]
874
            for galaxyID in universe.galaxies:
875
                galaxy = tran.db[galaxyID]
876
                for systemID in galaxy.systems:
877
                    system = tran.db[systemID]
878
                    obj.staticMap[systemID] = 111111
879
                    for planetID in system.planets:
880
                        obj.staticMap[planetID] = 111111
881
        # adding systems with buoys
882
        for objID in obj.buoys:
883
            scanLevels[objID] = Rules.level1InfoScanPwr
884
        # fixing system scan level for mine fields
885
        systems = {}
886
        for planetID in obj.planets:
887
            systems[tran.db[planetID].compOf] = None
888
        for systemID in systems.keys():
889
            scanLevels[systemID] = Rules.partnerScanPwr
890
        # player's map
891
        for objID in obj.staticMap:
892
            scanLevels[objID] = max(scanLevels.get(objID, 0), obj.staticMap[objID])
893
        for objID in obj.dynamicMap:
894
            scanLevels[objID] = max(scanLevels.get(objID, 0), obj.dynamicMap[objID])
895
        # parties' map
896
        for partnerID in obj.diplomacyRels:
897
            if self.cmd(obj).isPactActive(tran, obj, partnerID, Const.PACT_SHARE_SCANNER):
898
                # load partner's map
899
                partner = tran.db[partnerID]
900
                for objID in partner.staticMap:
901
                    scanLevels[objID] = max(scanLevels.get(objID, 0), partner.staticMap[objID])
902
                for objID in partner.dynamicMap:
903
                    scanLevels[objID] = max(scanLevels.get(objID, 0), partner.dynamicMap[objID])
904
                # partner's fleets and planets
905
                for objID in partner.fleets:
906
                    scanLevels[objID] = Rules.partnerScanPwr
907
                for objID in partner.planets:
908
                    scanLevels[objID] = Rules.partnerScanPwr
909
910
        # create map
911
        map = dict()
912
        for objID, level in scanLevels.iteritems():
913
            tmpObj = tran.db.get(objID, None)
914
            if not tmpObj:
915
                continue
916
            # add movement validation data
917
            if tmpObj.type in (Const.T_SYSTEM,Const.T_WORMHOLE) and objID not in obj.validSystems:
918
                obj.validSystems.append(objID)
919
            for info in self.cmd(tmpObj).getScanInfos(tran, tmpObj, level, obj):
920
                if (info.oid not in map) or (info.scanPwr > map[info.oid].scanPwr):
921
                    map[info.oid] = info
922
923
        return map
924
925
    #@public(Const.AL_OWNER)
926
    def mergeScannerMap(self, tran, obj, map):
927
        #@log.debug(obj.oid, "Merging scanner map")
928
        contacts = {}
929
        for object, level in map.iteritems():
930
            objID = object.oid
931
            if object.type in (Const.T_SYSTEM, Const.T_WORMHOLE):
932
                obj.staticMap[objID] = max(obj.staticMap.get(objID, 0), level)
933
                contacts.update(object.scannerPwrs)
934
            elif object.type == Const.T_FLEET:
935
                obj.dynamicMap[objID] = max(obj.dynamicMap.get(objID, 0), level)
936
                contacts[object.owner] = None
937
            else:
938
                raise ige.GameException("Unsupported type %d" % object.type)
939
        if obj.oid in contacts:
940
            del contacts[obj.oid]
941
        if Const.OID_NONE in contacts:
942
            del contacts[Const.OID_NONE]
943
        for partyID in contacts:
944
            # add to player's contacts
945
            dipl = self.cmd(obj).getDiplomacyWith(tran, obj, partyID)
946
            dipl.contactType = max(dipl.contactType, Const.CONTACT_DYNAMIC)
947
            dipl.lastContact = tran.db[Const.OID_UNIVERSE].turn
948
            # add to detected owner's contacts
949
            owner = tran.db[partyID]
950
            assert owner.type in Const.PLAYER_TYPES
951
            dipl = self.cmd(obj).getDiplomacyWith(tran, owner, obj.oid)
952
            dipl.contactType = max(dipl.contactType, Const.CONTACT_DYNAMIC)
953
            dipl.lastContact = tran.db[Const.OID_UNIVERSE].turn
954
955
    @public(Const.AL_ADMIN)
956
    def processRSRCHPhase(self, tran, obj, data):
957
        if not obj.timeEnabled:
958
            return
959
        # sci pts from allies
960
        pts = obj.sciPoints
961
        for partnerID in obj.diplomacyRels:
962
            if self.cmd(obj).isPactActive(tran, obj, partnerID, Const.PACT_MINOR_SCI_COOP):
963
                partner = tran.db[partnerID]
964
                pactSpec = Rules.pactDescrs[Const.PACT_MINOR_SCI_COOP]
965
                pts += min(
966
                    int(partner.sciPoints * pactSpec.effectivity),
967
                    int(obj.sciPoints * pactSpec.effectivity),
968
                )
969
            if self.cmd(obj).isPactActive(tran, obj, partnerID, Const.PACT_MAJOR_SCI_COOP):
970
                partner = tran.db[partnerID]
971
                pactSpec = Rules.pactDescrs[Const.PACT_MAJOR_SCI_COOP]
972
                pts += min(
973
                    int(partner.sciPoints * pactSpec.effectivity),
974
                    int(obj.sciPoints * pactSpec.effectivity),
975
                )
976
        # compute effective sci pts
977
        obj.effSciPoints = epts = pts - int(obj.stats.storPop * Rules.sciPtsPerCitizen[obj.techLevel])
978
        index = 0
979
        while epts > 0 and obj.rsrchQueue and index < len(obj.rsrchQueue):
980
            item = obj.rsrchQueue[index]
981
            tech = Rules.techs[item.techID]
982
            # check requirements
983
            canResearch = 1
984
            # player has to be a right race
985
            if obj.race not in tech.researchRaces:
986
                canResearch = 0
987
            for stratRes in tech.researchReqSRes:
988
                if obj.stratRes.get(stratRes, 0) < 1 and item.improvement == 1:
989
                    Utils.sendMessage(tran, obj, Const.MSG_MISSING_STRATRES, Const.OID_NONE, stratRes)
990
                    canResearch = 0
991
                    break
992
            for tmpTechID in obj.techs:
993
                if item.techID in Rules.techs[tmpTechID].researchDisables:
994
                    canResearch = 0
995
                    Utils.sendMessage(tran, obj, Const.MSG_DELETED_RESEARCH, Const.OID_NONE, item.techID)
996
                    del obj.rsrchQueue[index]
997
                    index -= 1
998
                    break
999
            if tech.level > obj.techLevel:
1000
                canResearch = 0
1001
                Utils.sendMessage(tran, obj, Const.MSG_DELETED_RESEARCH, Const.OID_NONE, item.techID)
1002
                del obj.rsrchQueue[index]
1003
                index -= 1
1004
            if not canResearch:
1005
                index += 1
1006
                continue
1007
            researchSci = Utils.getTechRCost(obj, item.techID)
1008
            wantSci = min(epts, researchSci - item.currSci,
1009
                researchSci / tech.researchTurns)
1010
            item.currSci += wantSci
1011
            item.changeSci = wantSci
1012
            epts -= wantSci
1013
            if item.currSci >= researchSci:
1014
                del obj.rsrchQueue[index]
1015
                obj.techs[item.techID] = item.improvement
1016
                # call finish handler
1017
                tech = Rules.techs[item.techID]
1018
                tech.finishResearchHandler(tran, obj, tech)
1019
                Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_RESEARCH, Const.OID_NONE, item.techID)
1020
                # update derived attributes of player
1021
                self.cmd(obj).update(tran, obj)
1022
                # repeat research if required by player
1023
                if item.improveToMax == 1 and item.improvement < Rules.techMaxImprovement:
1024
                    # reinsert the item on the top of the queue
1025
                    self.cmd(obj).startResearch(tran, obj, item.techID, improveToMax = 1)
1026
                    idx = len(obj.rsrchQueue) - 1
1027
                    self.cmd(obj).moveResearch(tran, obj, idx, - idx)
1028
        if epts > 0 and 0: # TODO: remove me
1029
            Utils.sendMessage(tran, obj, Const.MSG_WASTED_SCIPTS, Const.OID_NONE, epts)
1030
            return
1031
        # oops we have negative epts (AI does not regress)
1032
        isAI = obj.type in Const.AI_PLAYER_TYPES
1033
        while epts < 0 and not isAI:
1034
            log.debug("Not enough RP", epts, obj.oid)
1035
            if obj.rsrchQueue:
1036
                item = obj.rsrchQueue[0]
1037
                if item.currSci > 0:
1038
                    wantSci = min(item.currSci, - epts)
1039
                    item.currSci -= wantSci
1040
                    item.changeSci = - wantSci
1041
                    epts += wantSci
1042
                if item.currSci == 0:
1043
                    # remove item from the queue - TODO send message to player
1044
                    del obj.rsrchQueue[0]
1045
                # at this point, epts can be zero
1046
                if epts == 0:
1047
                    log.debug("RP deficit satisfied", obj.oid)
1048
                    break
1049
                # try next project
1050
                if obj.rsrchQueue:
1051
                    continue
1052
            # oops we must find technology to degrade
1053
            avail = obj.techs.keys()
1054
            # do not degrade technologies, which enables others
1055
            for techID in obj.techs:
1056
                tech = Rules.techs[techID]
1057
                for tmpTechID, impr in tech.researchRequires:
1058
                    if tmpTechID in avail:
1059
                        avail.remove(tmpTechID)
1060
            log.debug("Techs avialable for degradation", avail)
1061
            if not avail:
1062
                # no technology...
1063
                break
1064
            # from hight to low IDs
1065
            avail.sort()
1066
            avail.reverse()
1067
            degraded = 0
1068
            for level in range(obj.techLevel, 0, -1):
1069
                for techID in avail:
1070
                    tech = Rules.techs[techID]
1071
                    # check level
1072
                    if tech.level != level:
1073
                        continue
1074
                    # do not touch starting technologies
1075
                    if tech.isStarting and obj.techs[techID] <= 3:
1076
                        continue
1077
                    # ok we have one to degrade
1078
                    item = IDataHolder()
1079
                    item.techID = techID
1080
                    item.improvement = obj.techs[techID]
1081
                    item.currSci = Utils.getTechRCost(obj, techID, obj.techs[techID])
1082
                    item.changeSci = 0
1083
                    item.improveToMax = 0
1084
                    item.type = Const.T_RESTASK
1085
                    obj.rsrchQueue.append(item)
1086
                    # degrade tech
1087
                    if obj.techs[techID] == 1:
1088
                        # TODO send message
1089
                        del obj.techs[techID]
1090
                    else:
1091
                        # TODO send message
1092
                        obj.techs[techID] -= 1
1093
                    if tech.recheckWhenTechLost:
1094
                        # reset some attributes
1095
                        plLevel = obj.techLevel
1096
                        obj.techLevel = 1
1097
                        # recheck remaining techs
1098
                        for level in range(1, plLevel + 1):
1099
                            for techID in obj.techs:
1100
                                tech = Rules.techs[techID]
1101
                                if tech.level != level:
1102
                                    continue
1103
                                # call finish handler again
1104
                                tech.finishResearchHandler(tran, obj, tech)
1105
                    degraded = 1
1106
                    break
1107
                if degraded: break
1108
1109
        return
1110
1111
    @public(Const.AL_ADMIN)
1112
    def processACTIONPhase(self, tran, obj, data):
1113
        return NotImplementedError()
1114
1115
    @public(Const.AL_ADMIN)
1116
    def processINITPhase(self, tran, obj, data):
1117
        if not obj.timeEnabled:
1118
            return
1119
        # reset stats
1120
        obj.stats.storPop = 0
1121
        obj.stats.prodProd = 0
1122
        obj.stats.effProdProd = 0
1123
        obj.stats.prodSci = 0
1124
        obj.stats.effProdSci = 0
1125
        obj.stats.slots = 0
1126
        obj.stats.structs = 0
1127
        obj.stats.planets = 0
1128
        obj.stats.fleetPwr = 0
1129
        obj.stats.fleetSupportProd = 0
1130
        obj.govPwr = Rules.baseGovPwr
1131
        # remove old messages
1132
        self.cmd(obj).deleteOldMsgs(tran, obj)
1133
        # clear fleet upgrade flag
1134
        obj.fleetUpgradeInProgress = 0
1135
        # clear production pool
1136
        obj.prodIncreasePool = 0
1137
        # clear map
1138
        obj.dynamicMap.clear()
1139
        # set empty population distribution
1140
        obj.tmpPopDistr = {}
1141
        # do not process other cmds if time disabled
1142
        # clear contacts and delete too old rels
1143
        turn = tran.db[Const.OID_UNIVERSE].turn
1144
        for objID in obj.diplomacyRels.keys():
1145
            dipl = obj.diplomacyRels[objID]
1146
            # reset contact type
1147
            obj.diplomacyRels[objID].contactType = Const.CONTACT_NONE
1148
            # delete old contacts
1149
            if dipl.lastContact + Rules.contactTimeout < turn:
1150
                del obj.diplomacyRels[objID]
1151
                continue
1152
        # lower scan powers in static map
1153
        for objID in obj.staticMap:
1154
            level = obj.staticMap[objID]
1155
            if level > Rules.level3InfoScanPwr:
1156
                obj.staticMap[objID] = max(
1157
                    Rules.level3InfoScanPwr,
1158
                    int(level * Rules.mapForgetScanPwr),
1159
                )
1160
                #@log.debug(obj.oid, "player static map fix", objID, level - obj.staticMap[objID])
1161
        # clear relations change indicator
1162
        for partyID in obj.diplomacyRels:
1163
            obj.diplomacyRels[partyID].relChng = 0
1164
        # reset science points
1165
        obj.sciPoints = 0
1166
1167
    @public(Const.AL_ADMIN)
1168
    def processFINALPhase(self, tran, obj, data):
1169
        if not obj.timeEnabled:
1170
            return
1171
        #try/except so that entire final process doesn't break on error in sub-phase
1172
        try:
1173
            self.cmd(obj).processRSRCHPhase(tran, obj, data)
1174
        except:
1175
            log.warning('Cannot execute FINAL/RSRCH on %d' % (obj.oid))
1176
        try:
1177
            self.cmd(obj).processDIPLPhase(tran, obj, data)
1178
        except:
1179
            log.warning('Cannot execute FINAL/DIPL on %d' % (obj.oid))
1180
        # efficiency
1181
        obj.prodEff = 1.0
1182
        obj.sciEff = 1.0
1183
        if obj.imperator == 1:
1184
            log.debug(obj.oid, "Leader bonus")
1185
            obj.prodEff += Rules.galLeaderBonus
1186
            obj.sciEff += Rules.galLeaderBonus
1187
        elif obj.imperator >= 2:
1188
            log.debug(obj.oid, "Imperator bonus")
1189
            obj.prodEff += Rules.galImperatorBonus
1190
            obj.sciEff += Rules.galImperatorBonus
1191
        #@log.debug("Fleet upgrade pool", obj.oid, obj.fleetUpgradePool, obj.fleetUpgradeInProgress)
1192
        # compute some stats
1193
        # TODO remove, RAW SCI PTS represented now obj.stats.prodSci = obj.effSciPoints
1194
        obj.stats.planets = len(obj.planets)
1195
        # fleet support
1196
        #@log.debug("Fleet support", obj.oid, obj.stats.fleetSupportProd, obj.stats.prodProd)
1197
        if obj.stats.fleetSupportProd > 0 and obj.stats.prodProd > 0:
1198
            # TODO 0.1 shall be dependend on the race / government type
1199
            obj.prodEff += min(0.1 - float(obj.stats.fleetSupportProd + obj.fleetUpgradePool * Rules.operProdRatio) / obj.stats.prodProd, 0.0)
1200
        # delete non active player
1201
        if obj.lastLogin + Rules.playerTimeout < time.time():
1202
            log.message("Resigning inactive player", obj.name, obj.oid)
1203
            # TODO send a message?
1204
            self.cmd(obj).resign(tran, obj)
1205
        # delete nonactive newbie player
1206
        if obj.lastLogin + Rules.novicePlayerTimeout < time.time() \
1207
            and len(obj.planets) < 4:
1208
            log.message("Resigning inactive novice player", obj.name, obj.oid)
1209
            # TODO send a message?
1210
            self.cmd(obj).resign(tran, obj)
1211
        # acquire government power
1212
        if obj.planets:
1213
            planet = tran.db[obj.planets[0]]
1214
            for slot in planet.slots:
1215
                tech = Rules.techs[slot[Const.STRUCT_IDX_TECHID]]
1216
                if tech.govPwr > 0 and slot[Const.STRUCT_IDX_STATUS] & Const.STRUCT_STATUS_ON:
1217
                    eff = Utils.getTechEff(tran, slot[Const.STRUCT_IDX_TECHID], obj.oid)
1218
                    obj.govPwr = max(int(tech.govPwr * eff * (slot[Const.STRUCT_IDX_OPSTATUS] / 100.0)), obj.govPwr)
1219
        # compute government controll range
1220
        if not hasattr(obj,"tmpPopDistr"): #when player is force-resigned, tmpPopDistr is unset. This is easiest fix.
1221
            obj.tmpPopDistr = {}
1222
        ranges = obj.tmpPopDistr.keys()
1223
        ranges.sort()
1224
        sum = 0
1225
        range = 1
1226
        for range in ranges:
1227
            sum += obj.tmpPopDistr[range]
1228
            if sum > obj.govPwr:
1229
                break
1230
        obj.govPwrCtrlRange = max(1, range)
1231
        if sum < obj.govPwr and sum > 0:
1232
            #@log.debug(obj.oid, "GovPwr compensation", obj.govPwrCtrlRange, obj.govPwr, sum)
1233
            obj.govPwrCtrlRange = int(obj.govPwrCtrlRange * obj.govPwr / float(sum))
1234
        #@log.debug(obj.oid, "GovPwr control range", obj.govPwrCtrlRange)
1235
        # compute prodBonus and sciBonus
1236
        sum = 0
1237
        for range in ranges:
1238
            sum += obj.tmpPopDistr[range]
1239
        if sum < obj.govPwr and sum > 0:
1240
            ratio = float(obj.govPwr - sum) / obj.govPwr
1241
            #@log.debug(obj.oid, "SMALL EMPIRE BONUS", ratio, "govPwr", obj.govPwr, "sum", sum)
1242
            # TODO let user to define how much to invest into prod and to sci
1243
            obj.prodEff += ratio / 2
1244
            obj.sciEff += ratio / 2
1245
        del obj.tmpPopDistr # delete temporary attribute
1246
        # increase prod eff from pacts
1247
        # CPs from allies
1248
        sum = 0
1249
        for partnerID in obj.diplomacyRels:
1250
            if self.cmd(obj).isPactActive(tran, obj, partnerID, Const.PACT_MINOR_CP_COOP):
1251
                partner = tran.db[partnerID]
1252
                pactSpec = Rules.pactDescrs[Const.PACT_MINOR_CP_COOP]
1253
                sum += min(
1254
                    partner.stats.prodProd * pactSpec.effectivity,
1255
                    obj.stats.prodProd * pactSpec.effectivity,
1256
                )
1257
            if self.cmd(obj).isPactActive(tran, obj, partnerID, Const.PACT_MAJOR_CP_COOP):
1258
                partner = tran.db[partnerID]
1259
                pactSpec = Rules.pactDescrs[Const.PACT_MAJOR_CP_COOP]
1260
                sum += min(
1261
                    partner.stats.prodProd * pactSpec.effectivity,
1262
                    obj.stats.prodProd * pactSpec.effectivity,
1263
                )
1264
        # apply production increase pool
1265
        obj.prodIncreasePool += sum
1266
        if obj.stats.prodProd > 0:
1267
            ratio = (Rules.unusedProdMod * obj.prodIncreasePool) / obj.stats.prodProd
1268
            real = min(ratio, math.sqrt(ratio))
1269
            #@log.debug(obj.oid, "Increase production by", ratio, "real", real)
1270
            obj.prodEff += real
1271
        # clean up prodEff if prodEff < 0 (prevent abuse)
1272
        if obj.prodEff < 0:
1273
            obj.prodEff = 0.0
1274
        # clean up ship redirections
1275
        systems = {}
1276
        for planetID in obj.planets:
1277
            systems[tran.db[planetID].compOf] = None
1278
        for systemID in obj.shipRedirections.keys():
1279
            if systemID not in systems:
1280
                del obj.shipRedirections[systemID]
1281
1282
        # delete allied bouys
1283
        obj.alliedBuoys = {}
1284
1285
        # find all allies
1286
        for partnerID in obj.diplomacyRels.keys():
1287
            dipl = obj.diplomacyRels[partnerID]
1288
            getAllyBuoys = False
1289
            getScannerBuoys = False
1290
            if dipl.relation >= Const.REL_ALLY_LO:
1291
                getAllyBuoys = True
1292
            if self.isPactActive(tran, obj, partnerID, Const.PACT_SHARE_SCANNER):
1293
                getScannerBuoys = True
1294
            if (getAllyBuoys or getScannerBuoys):
1295
                partner = tran.db[partnerID]
1296
                if hasattr(partner, "buoys"):
1297
                    for systemID in partner.buoys.keys():
1298
                        toAllyBuoy = Const.BUOY_NONE
1299
                        if getAllyBuoys and partner.buoys[systemID][1] == Const.BUOY_TO_ALLY:
1300
                            toAllyBuoy = (partner.buoys[systemID][0], Const.BUOY_FROM_ALLY, partner.name)
1301
                        elif getScannerBuoys and partner.buoys[systemID][1] == Const.BUOY_TO_SCANNERSHARE:
1302
                            toAllyBuoy = (partner.buoys[systemID][0], Const.BUOY_FROM_ALLY, partner.name)
1303
                        if toAllyBuoy != Const.BUOY_NONE:
1304
                            if systemID in obj.alliedBuoys:
1305
                                obj.alliedBuoys[systemID].append(toAllyBuoy)
1306
                            else:
1307
                                obj.alliedBuoys[systemID] = [toAllyBuoy]
1308
        return None
1309
1310
    ## messaging
1311
    def canSendMsg(self, tran, obj, oid, forum):
1312
        if forum == "INBOX":
1313
            sender = tran.db[oid]
1314
            return oid == Const.OID_ADMIN or (oid in obj.diplomacyRels) or \
1315
                (obj.oid in sender.diplomacyRels)
1316
        if forum == "OUTBOX":
1317
            return obj.oid == oid
1318
        return 0
1319
1320
    @public(Const.AL_OWNER)
1321
    def cleanUpMsgs(self, tran, obj):
1322
        # get messages
1323
        msgs = self.cmd(obj).getMsgs(tran, obj)
1324
        # build list of events
1325
        delete = []
1326
        for msg in msgs:
1327
            if msg["forum"] == "EVENTS":
1328
                delete.append(msg["id"])
1329
        # delete
1330
        self.cmd(obj).deleteMsgs(tran, obj, delete)
1331
        return 1
1332
1333
    @public(Const.AL_OWNER)
1334
    def setResolution(self, tran, obj, x, y):
1335
        if not hasattr(obj,'clientStats'):
1336
            obj.clientStats = {}
1337
        obj.clientStats['x'] = x;
1338
        obj.clientStats['y'] = y;
1339
1340
    def getResolution(self, obj):
1341
        if not hasattr(obj,'clientStats'):
1342
            obj.clientStats = {}
1343
        if 'x' in obj.clientStats and 'y' in obj.clientStats:
1344
            return ("%s,%s" % (obj.clientStats['x'],obj.clientStats['y']))
1345
        else:
1346
            return "0,0"
1347
1348
    @public(Const.AL_FULL)
1349
    def addObsoleteTechs(self, tran, player, techID):
1350
        # add tech
1351
        temp = set([techID])
1352
        player.obsoleteTechs = player.obsoleteTechs | temp
1353
        return player.obsoleteTechs
1354
1355
    @public(Const.AL_FULL)
1356
    def delObsoleteTechs(self, tran, player, techID):
1357
        # del tech
1358
        temp = set([techID])
1359
        player.obsoleteTechs = player.obsoleteTechs - temp
1360
        return player.obsoleteTechs
1361
1362