Completed
Push — master ( 79c35a...3e3ee6 )
by Marek
16s queued 14s
created

ige.ospace.IPlayer.IPlayer.scrapShipDesign()   C

Complexity

Conditions 9

Size

Total Lines 29
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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