ige.ospace.ISystem   F
last analyzed

Complexity

Total Complexity 234

Size/Duplication

Total Lines 889
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 640
dl 0
loc 889
rs 1.96
c 0
b 0
f 0
wmc 234

25 Methods

Rating   Name   Duplication   Size   Complexity  
A ISystem.init() 0 18 1
F ISystem.update() 0 42 20
A ISystem.processPRODPhase() 0 11 4
A ISystem.getObjectsInSpace() 0 9 3
C ISystem.getScanInfos() 0 40 11
F ISystem.processACTIONPhase() 0 83 19
A ISystem.getReferences() 0 2 1
A ISystem.processINITPhase() 0 5 1
A ISystem._expandExistingMinefield() 0 12 5
A ISystem.getSystemMineLauncher() 0 11 5
A ISystem.clearMines() 0 6 2
A ISystem.loadDOMNode() 0 20 5
A ISystem.removeMine() 0 16 5
F ISystem.processBATTLEPhase() 0 382 100
D ISystem.rename() 0 41 13
A ISystem.deployMines() 0 15 4
A ISystem.sortPlanets() 0 7 3
B ISystem.getSystemCombatBonuses() 0 13 6
A ISystem.getMines() 0 8 2
A ISystem.createPlanet() 0 6 1
A ISystem.getSystemMineSource() 0 10 5
A ISystem.processFINALPhase() 0 13 5
B ISystem.addMine() 0 27 6
A ISystem.getAllMines() 0 10 3
A ISystem.fireMine() 0 19 4

How to fix   Complexity   

Complexity

Complex classes like ige.ospace.ISystem 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
import random
21
import copy
22
23
from xml.dom.minidom import Node
24
25
import ige
26
import Const
27
import Rules
28
import Utils
29
30
from ige import log
31
from ige.IObject import IObject, public
32
from ige.IDataHolder import IDataHolder
33
34
class ISystem(IObject):
35
36
    typeID = Const.T_SYSTEM
37
38
    def init(self, obj):
39
        IObject.init(self, obj)
40
        #
41
        obj.x = 0.0
42
        obj.y = 0.0
43
        obj.planets = []
44
        obj.fleets = []
45
        obj.closeFleets = []
46
        obj.starClass = u'---' # Star clasification
47
        obj.signature = 100
48
        # renaming
49
        obj.lastNameChng = 0
50
        # combat
51
        obj.combatCounter = 0
52
        # system wide data
53
        obj.scannerPwrs = {}
54
        # mine field
55
        obj.minefield = {}  # for every owner (key) list of minefield (triplet) exists (mine_id, amount, last_deployed)
56
57
    def update(self, tran, obj):
58
        # check existence of all planets
59
        if 0:
60
            for planetID in obj.planets:
61
                if not tran.db.has_key(planetID):
62
                    log.debug("CONSISTENCY - planet %d from system %d does not exists" % (planetID, obj.oid))
63
                elif tran.db[planetID].type != Const.T_PLANET:
64
                    log.debug("CONSISTENCY - planet %d from system %d is not a Const.T_PLANET" % (planetID, obj.oid))
65
        # check that all .fleet are in .closeFleets
66
        for fleetID in obj.fleets:
67
            if fleetID not in obj.closeFleets:
68
                log.debug("CONSISTENCY - fleet %d is in .fleet but not in .closeFleets - adding" % fleetID)
69
                obj.closeFleets.append(fleetID)
70
        # check existence of all fleets
71
        for fleetID in obj.closeFleets:
72
            if not tran.db.has_key(fleetID):
73
                log.debug("CONSISTENCY - fleet %d from system %d does not exists" % (fleetID, obj.oid))
74
            elif tran.db[fleetID].type != Const.T_FLEET:
75
                log.debug("CONSISTENCY - fleet %d from system %d is not a Const.T_FLEET" % (fleetID, obj.oid))
76
        # delete nonexistent fleets
77
        index = 0
78
        while index < len(obj.closeFleets) and obj.closeFleets:
79
            fleet = tran.db.get(obj.closeFleets[index], None)
80
            if fleet == None:
81
                log.debug("CONSISTENCY - fleet %d does not exists" % obj.closeFleets[index])
82
                fleetID = obj.closeFleets[index]
83
                obj.closeFleets.remove(fleetID)
84
                obj.fleets.remove(fleetID)
85
            else:
86
                index += 1
87
        # check compOf
88
        if not tran.db.has_key(obj.compOf) or tran.db[obj.compOf].type != Const.T_GALAXY:
89
            log.debug("CONSISTENCY invalid compOf for system", obj.oid)
90
        # rebuild closeFleets attribute
91
        old = obj.closeFleets
92
        obj.closeFleets = []
93
        for fleetID in old:
94
            fleet = tran.db.get(fleetID, None)
95
            if fleet and fleet.closeSystem == obj.oid and fleetID not in obj.closeFleets:
96
                obj.closeFleets.append(fleetID)
97
        if old != obj.closeFleets:
98
            log.debug("System close fleets fixed", obj.oid, old, obj.closeFleets)
99
100
    def getReferences(self, tran, obj):
101
        return obj.planets
102
103
    def getScanInfos(self, tran, obj, scanPwr, player):
104
        result = IDataHolder()
105
        results = [result]
106
        if scanPwr >= Rules.level1InfoScanPwr:
107
            result._type = Const.T_SCAN
108
            result.scanPwr = scanPwr
109
            result.oid = obj.oid
110
            result.x = obj.x
111
            result.y = obj.y
112
            if hasattr(obj, 'destinationOid'):
113
                result.destinationOid = obj.destinationOid
114
            result.signature = obj.signature
115
            result.type = obj.type
116
            result.compOf = obj.compOf
117
            result.starClass = obj.starClass
118
        if scanPwr >= Rules.level2InfoScanPwr:
119
            result.name = obj.name
120
            result.combatCounter = obj.combatCounter
121
        if scanPwr >= Rules.level3InfoScanPwr:
122
            result.planets = obj.planets
123
            result.owner = obj.owner
124
            for planetID in obj.planets:
125
                planet = tran.db[planetID]
126
                if planet.owner == player: ####### This was player.owner, which made no sense. Hope this change doesn't break something
127
                    continue
128
                newPwr = scanPwr * planet.signature / obj.signature
129
                results.extend(self.cmd(planet).getScanInfos(tran, planet, newPwr, player))
130
        if scanPwr >= Rules.level4InfoScanPwr:
131
            result.fleets = obj.fleets
132
            for fleetID in obj.fleets:
133
                fleet = tran.db[fleetID]
134
                if fleet.owner == player:
135
                    continue
136
                newPwr = scanPwr * fleet.signature / obj.signature
137
                results.extend(self.cmd(fleet).getScanInfos(tran, fleet, newPwr, player))
138
139
            result.minefield = self.getMines(obj, player.oid)
140
            ownsMines = 1 if result.minefield else 0
141
            result.hasmines = min(2, len(self.getAllMines(obj))) - ownsMines
142
        return results
143
144
    @public(Const.AL_ADMIN)
145
    def processINITPhase(self, tran, obj, data):
146
        obj.scannerPwrs = {}
147
148
        return obj.planets
149
150
    @public(Const.AL_ADMIN)
151
    def processPRODPhase(self, tran, obj, data):
152
        # mine deployment
153
        owners = []
154
        for planetID in obj.planets:
155
            planet = tran.db[planetID]
156
            if planet.owner not in owners + [Const.OID_NONE]:
157
                owners.append(planet.owner)
158
        for owner_id in owners:
159
            self.deployMines(tran, obj, owner_id)
160
        return obj.planets
161
162
    @public(Const.AL_ADMIN)
163
    def processACTIONPhase(self, tran, obj, data):
164
        # distribute resources
165
        planets = {}
166
        # group planets by owner
167
        for planetID in obj.planets:
168
            planet = tran.db[planetID]
169
            if planet.owner != Const.OID_NONE:
170
                tmp = planets.get(planet.owner, [])
171
                tmp.append(planet)
172
                planets[planet.owner] = tmp
173
        # group planets if owners are allied
174
        # TODO
175
        # process each group
176
        for owner in planets.keys():
177
            # skip alone planets
178
            if len(planets[owner]) < 2:
179
                continue
180
            # process each resource
181
            for resName in ('Bio', 'En'):
182
                donors = []
183
                donees = []
184
                minRes = 'min%s' % resName
185
                maxRes = 'max%s' % resName
186
                storRes = 'stor%s' % resName
187
                donorsSum = 0
188
                doneesSum = 0
189
                # put planets into donors/donees
190
                for planet in planets[owner]:
191
                    if getattr(planet, storRes) > getattr(planet, minRes):
192
                        donors.append(planet)
193
                        donorsSum += getattr(planet, storRes) - getattr(planet, minRes)
194
                    elif getattr(planet, storRes) < getattr(planet, minRes):
195
                        donees.append(planet)
196
                        doneesSum += getattr(planet, minRes) - getattr(planet, storRes)
197
                #@log.debug('ISystem', obj.oid, 'Donors / donees for %s' % resName, donorsSum, doneesSum)
198
                # there are requests for donation and there is somebody able to donate
199
                if doneesSum > 0 and donorsSum > 0:
200
                    #@log.debug('ISystem', 'Redistributin %s for' % resName, owner)
201
                    # give
202
                    balance = 0
203
                    tmpRatio = min(float(doneesSum) / donorsSum, 1.0)
204
                    for planet in donees:
205
                        diff = getattr(planet, minRes) - getattr(planet, storRes)
206
                        amount = int(float(diff) / doneesSum * donorsSum * tmpRatio)
207
                        #@log.debug('ISystem', 'Give res', planet.oid, amount)
208
                        balance -= amount
209
                        setattr(planet, storRes, getattr(planet, storRes) + amount)
210
                    # take
211
                    assert donorsSum + balance >= 0
212
                    lastPlanet = None
213
                    tmpRatio = min(float(donorsSum) / doneesSum, 1.0)
214
                    for planet in donors:
215
                        diff = getattr(planet, storRes) - getattr(planet, minRes)
216
                        amount = int(float(diff) / donorsSum * doneesSum * tmpRatio)
217
                        balance += amount
218
                        #@log.debug('ISystem', 'Take res', planet.oid, amount)
219
                        setattr(planet, storRes, getattr(planet, storRes) - amount)
220
                        lastPlanet = planet
221
                    # fix rounding error
222
                    setattr(lastPlanet, storRes, getattr(lastPlanet, storRes) + balance)
223
                    #@log.debug('ISystem', 'Rounding error', balance)
224
                # try to move additional resources to the other planets
225
                for planet in planets[owner]:
226
                    if getattr(planet, storRes) > getattr(planet, maxRes):
227
                        excess = getattr(planet, storRes) - getattr(planet, maxRes)
228
                        #@log.debug('ISystem', 'Trying to move excess rsrcs from', planet.oid, excess)
229
                        for planet2 in planets[owner]:
230
                            if planet == planet2:
231
                                continue
232
                            if getattr(planet2, storRes) < getattr(planet2, maxRes):
233
                                space = getattr(planet2, maxRes) - getattr(planet2, storRes)
234
                                amount = min(space, excess)
235
                                #@log.debug('ISystem', 'Moved to', planet2.oid, amount)
236
                                setattr(planet2, storRes, getattr(planet2, storRes) + amount)
237
                                excess -= amount
238
                                if excess == 0:
239
                                    break
240
                        #@log.debug('ISystem', 'Cannot move excess rsrcs on', planet.oid, excess)
241
                        setattr(planet, storRes, getattr(planet, maxRes) + excess)
242
        # process planets and fleets
243
        #@log.debug("System close fleets", obj.oid, obj.closeFleets)
244
        return obj.planets[:] + obj.closeFleets[:]
245
246
    @public(Const.AL_ADMIN)
247
    def getObjectsInSpace(self, tran, obj):
248
        inSpace = obj.closeFleets[:]
249
        for fleetID in obj.fleets:
250
            try:
251
                inSpace.remove(fleetID)
252
            except ValueError:
253
                log.warning(obj.oid, "Cannot remove fleet from closeFleets", fleetID, obj.fleets, obj.closeFleets)
254
        return inSpace
255
256
    @public(Const.AL_ADMIN)
257
    def processBATTLEPhase(self, tran, obj, data):
258
        system = obj
259
        #@log.debug('ISystem', 'BATTLE - system', obj.oid)
260
        # we are processing fleets, planets, ...
261
        objects = obj.planets[:] + obj.fleets[:]
262
        # shuffle them to prevent predetermined one-sided battles (temporary hack)
263
        random.shuffle(objects)
264
        # store owners of objects
265
        # find enemies and allies
266
        attack = {}
267
        allies = {}
268
        owners = {}
269
        ownerIDs = {}
270
        systemAtt = {}
271
        systemDef = {}
272
        hasMine = {}
273
        isOwnedObject = 0
274
        for objID in objects:
275
            attack[objID] = []
276
            allies[objID] = []
277
            owner = tran.db[objID].owner
278
            owners[objID] = owner
279
            ownerIDs[owner] = owner
280
            if owner != Const.OID_NONE:
281
                isOwnedObject = 1
282
        for owner in ownerIDs:
283
            tempAtt, tempDef = self.getSystemCombatBonuses(tran, system, owner)
284
            systemAtt[owner] = tempAtt
285
            systemDef[owner] = tempDef
286
            hasMine[owner] = self.getSystemMineSource(tran, system, owner)
287
        if not isOwnedObject:
288
            #@log.debug('ISystem', 'No combat')
289
            # reset combat counters
290
            system.combatCounter = 0
291
            return
292
        # first - direct ones
293
        index = 1
294
        for obj1ID in objects:
295
            obj1 = tran.db[obj1ID]
296
            if obj1.owner == Const.OID_NONE:
297
                index += 1
298
                continue
299
            commander = tran.db[obj1.owner]
300
            # relationships
301
            #for obj2ID in objects[index:]:
302
            for obj2ID in objects:
303
                obj2 = tran.db[obj2ID]
304
                if obj2.owner == Const.OID_NONE or obj1 is obj2:
305
                    continue
306
                if obj1.owner == obj2.owner:
307
                    allies[obj1ID].append(obj2ID)
308
                    allies[obj2ID].append(obj1ID)
309
                    continue
310
                # planet and military object
311
                elif obj1.type == Const.T_PLANET and obj2.isMilitary and \
312
                    not self.cmd(commander).isPactActive(tran, commander, obj2.owner, Const.PACT_ALLOW_MILITARY_SHIPS):
313
                    #@log.debug("ISystem pl - mil", obj1ID, obj2ID)
314
                    if obj2ID not in attack[obj1ID]:
315
                        attack[obj1ID].append(obj2ID)
316
                    if obj1ID not in attack[obj2ID]:
317
                        attack[obj2ID].append(obj1ID)
318
                # planet and civilian object
319
                elif obj1.type == Const.T_PLANET and not obj2.isMilitary and \
320
                    not self.cmd(commander).isPactActive(tran, commander, obj2.owner, Const.PACT_ALLOW_CIVILIAN_SHIPS):
321
                    #@log.debug("ISystem pl - civ", obj1ID, obj2ID)
322
                    if obj2ID not in attack[obj1ID]:
323
                        attack[obj1ID].append(obj2ID)
324
                    if obj1ID not in attack[obj2ID]:
325
                        attack[obj2ID].append(obj1ID)
326
                # military and military object
327
                elif obj1.isMilitary and obj2.isMilitary and \
328
                    not self.cmd(commander).isPactActive(tran, commander, obj2.owner, Const.PACT_ALLOW_MILITARY_SHIPS):
329
                    #@log.debug("ISystem mil - mil", obj1ID, obj2ID)
330
                    if obj2ID not in attack[obj1ID]:
331
                        attack[obj1ID].append(obj2ID)
332
                    if obj1ID not in attack[obj2ID]:
333
                        attack[obj2ID].append(obj1ID)
334
                # military and civilian object
335
                elif obj1.isMilitary and not obj2.isMilitary and \
336
                    not self.cmd(commander).isPactActive(tran, commander, obj2.owner, Const.PACT_ALLOW_CIVILIAN_SHIPS):
337
                    #@log.debug("ISystem mil - civ", obj1ID, obj2ID)
338
                    if obj2ID not in attack[obj1ID]:
339
                        attack[obj1ID].append(obj2ID)
340
                    if obj1ID not in attack[obj2ID]:
341
                        attack[obj2ID].append(obj1ID)
342
                # planet and fleet
343
                #elif obj1.type == Const.T_PLANET and obj2.type == Const.T_FLEET and \
344
                #    self.cmd(commander).isPactActive(tran, commander, obj2.owner, PACT_MUTUAL_DEFENCE):
345
                #    allies[obj1ID].append(obj2ID)
346
                #    allies[obj2ID].append(obj1ID)
347
                # fleet and fleet
348
                #elif obj1.type == Const.T_FLEET and obj2.type == Const.T_FLEET and \
349
                #    self.cmd(commander).isPactActive(tran, commander, obj2.owner, PACT_MUTUAL_OFFENCE):
350
                #    allies[obj1ID].append(obj2ID)
351
                #    allies[obj2ID].append(obj1ID)
352
            index += 1
353
        #@log.debug('ISystem', 'Targets:', targets)
354
        #@log.debug('ISystem', 'Allies:', allies)
355
        # find indirect a/e
356
        #for objID in objects:
357
        #    iTargets = []
358
        #    iAllies = []
359
        #    # find indirect a/e
360
        #    todo = allies[objID][:]
361
        #    while todo:
362
        #        id = todo.pop(0)
363
        #        iTargets.extend(targets[id])
364
        #        for tmpID in allies[id]:
365
        #            if tmpID not in iAllies:
366
        #                todo.append(tmpID)
367
        #                iAllies.append(tmpID)
368
        #    # remove allies from targets
369
        #    for id in iAllies:
370
        #        if id in iTargets:
371
        #            iTargets.remove(id)
372
        #    # IMPORTATNT preffer NOT to fire at possible allies
373
        #    # add my targets
374
        #    #for id in targets[objID]:
375
        #    #    if id not in iTargets:
376
        #    #        iTargets.append(id)
377
        #    # that's all folks
378
        #    for id in iTargets:
379
        #        if objID not in attack[id]:
380
        #            attack[id].append(objID)
381
        #        if id not in attack[objID]:
382
        #            attack[objID].append(id)
383
        # NOT VALID: objects with action ACTION_ATTACK will attack only their targets
384
        # check, if there are any targets
385
        isCombat = 0
386
        for objID in objects:
387
            if attack[objID]:
388
                isCombat = 1
389
                break #end loop
390
        if not isCombat:
391
            #@log.debug('ISystem', 'No combat')
392
            # reset combat counters
393
            system.combatCounter = 0
394
            for fleetID in system.fleets:
395
                tran.db[fleetID].combatCounter = 0
396
            return
397
        # increase combat counters
398
        system.combatCounter += 1
399
        for fleetID in system.fleets:
400
            tran.db[fleetID].combatCounter += 1
401
        # debug
402
        log.debug('ISystem', 'Final attacks in system %d:' % system.oid, attack)
403
        # mines detonate before battle
404
        shots = {}
405
        targets = {}
406
        firing = {}
407
        damageCaused = {}
408
        killsCaused = {}
409
        damageTaken = {}
410
        shipsLost = {}
411
        minesTriggered = {}
412
        fleetOwners = {}
413
        isCombat = False
414
        isMineCombat = False
415
        for owner in ownerIDs:
416
            if owner not in hasMine: # no planets
417
                continue
418
            if not hasMine[owner]: # no planet with control structure
419
                continue
420
            controlPlanetID = hasMine[owner][0]  # there is list returned, all planets have same effect
421
            if len(self.getMines(system, owner)) == 0:
422
                continue # no mines, something broke
423
            if len(attack[controlPlanetID]) == 0:
424
                continue # no targets
425
            isMineFired = True
426
            mineTargets = copy.copy(attack[controlPlanetID])
427
            while isMineFired:
428
                while len(mineTargets) > 0:
429
                    targetID = random.choice(mineTargets) # select random target
430
                    targetobj = tran.db.get(targetID, None)
431
                    try:
432
                        if targetobj.type == Const.T_FLEET:
433
                            fleetOwners[targetID] = targetobj.owner
434
                            break # target found
435
                        mineTargets.remove(targetID)  # remove an object type that a mine can't hit from the temporary targets list
436
                    except:
437
                        mineTargets.remove(targetID)  # remove a dead fleet from the temporary targets list
438
439
                if len(mineTargets) == 0:
440
                    break # no fleet targets for mines
441
                temp, temp, firing[targetID] = self.cmd(targetobj).getPreCombatData(tran, targetobj)  # fix firing for "surrender to" section
0 ignored issues
show
introduced by
The variable targetobj does not seem to be defined for all execution paths.
Loading history...
442
                damage, att, ignoreshield, mineID = self.cmd(obj).fireMine(system, owner)
443
                if not damage: # no more mines
444
                    isMineFired = False
445
                    break
446
                log.debug('ISystem', 'Mine Shooting (damage, att, ignore shield):', damage, att, ignoreshield)
447
                isMineCombat = True
448
                minesTriggered[mineID] = minesTriggered.get(mineID, 0) + 1
449
                # Process Combat
450
                # for now we assume only one ship can be destroyed by one mine
451
                dmg, destroyed = self.cmd(targetobj).applyMine(tran, targetobj, att, damage, ignoreshield)
452
                #log.debug('ISystem-Mines', 'Actual Damage Done:',dmg)
453
                if dmg > 0:
454
                    damageTaken[targetID] = damageTaken.get(targetID, 0) + dmg
0 ignored issues
show
introduced by
The variable targetID does not seem to be defined for all execution paths.
Loading history...
455
                    shipsLost[targetID] = shipsLost.get(targetID, 0) + destroyed
456
                    killsCaused[mineID] = killsCaused.get(mineID, 0) + destroyed
457
                if dmg > 0:
458
                    damageCaused[mineID] = damageCaused.get(mineID, 0) + dmg
459
            # send messages about mine effects to the owner of the minefield
460
            # collect hit players
461
            players = {}
462
            for triggerID in firing.keys():
463
                players[owners[triggerID]] = None
464
            controllerPlanet = tran.db.get(controlPlanetID, None)
465
            damageCausedSum = 0
466
            killsCausedSum = 0
467
            for mineID in damageCaused.keys():
468
                damageCausedSum = damageCausedSum + damageCaused.get(mineID, 0)
469
                killsCausedSum = killsCausedSum + killsCaused.get(mineID, 0)
470
            Utils.sendMessage(tran, controllerPlanet, Const.MSG_MINES_OWNER_RESULTS, system.oid, (players.keys(),(damageCaused, killsCaused, minesTriggered),damageCausedSum,killsCausedSum))
471
        # send messages to the players whose fleets got hit by minefields
472
        for targetID in damageTaken.keys():
473
            targetFleet = tran.db.get(targetID, None)
474
            if targetFleet:
475
                Utils.sendMessage(tran, targetFleet, Const.MSG_MINES_FLEET_RESULTS, system.oid, (damageTaken[targetID], shipsLost[targetID]))
476
            else:
477
                targetFleet = IDataHolder()
478
                targetFleet.oid = fleetOwners[targetID]
479
                Utils.sendMessage(tran, targetFleet, Const.MSG_MINES_FLEET_RESULTS, system.oid, (damageTaken[targetID], shipsLost[targetID]))
480
                Utils.sendMessage(tran, targetFleet, Const.MSG_DESTROYED_FLEET, system.oid, ())
481
        damageCaused = {}
482
        killsCaused = {}
483
        damageTaken = {}
484
        shipsLost = {}
485
        # now to battle
486
        for objID in objects:
487
            obj = tran.db.get(objID, None)
488
            # get shots from object, should be sorted by weaponClass
489
            # shots = [ shot, ...], shot = (combatAtt, weaponID)
490
            # get target classes and numbers
491
            # (class1, class2, class3, class4)
492
            # cls0 == fighters, cls1 == midships, cls2 == capital ships, cls3 == planet installations
493
            #@log.debug(objID, obj.name, "getting pre combat data")
494
            if obj: # source already destroyed; ignore
495
                shots[objID], targets[objID], firing[objID] = self.cmd(obj).getPreCombatData(tran, obj)
496
                if firing[objID]:
497
                    isCombat = True
498
        if not isCombat and not isMineCombat:
499
            # no shots has been fired
500
            #@log.debug('ISystem', 'No combat')
501
            # reset combat counters
502
            system.combatCounter = 0
503
            for fleetID in system.fleets:
504
                tran.db[fleetID].combatCounter = 0
505
            return
506
        #@log.debug("Shots:", shots)
507
        #@log.debug("Targets", targets)
508
        if isCombat:
509
            for shotIdx in (3, 2, 1, 0):
510
                for objID in objects:
511
                    # obj CAN be deleted at this point
512
                    obj = tran.db.get(objID, None)
513
                    if obj == None:
514
                        continue # source already destroyed; move to next source
515
                    # if object is fleet, then it's signature is max
516
                    if obj and obj.type == Const.T_FLEET:
517
                        obj.signature = Rules.maxSignature
518
                    # target preselection
519
                    totalClass = [0, 0, 0, 0]
520
                    total = 0
521
                    for targetID in attack[objID]:
522
                        totalClass[0] += targets[targetID][0]
523
                        totalClass[1] += targets[targetID][1]
524
                        totalClass[2] += targets[targetID][2]
525
                        totalClass[3] += targets[targetID][3]
526
                    total = totalClass[0] + totalClass[1] + totalClass[2] + totalClass[3]
527
                    # process shots
528
                    for combatAtt, weaponID in shots[objID][shotIdx]:
529
                        weapon = Rules.techs[weaponID]
530
                        weaponClass = weapon.weaponClass
531
                        if total == 0:
532
                            # there are no targets
533
                            break
534
                        #@log.debug('ISystem', 'Processing shot', objID, weapon.name, weaponClass)
535
                        # process from weaponClass up
536
                        # never shoot on smaller ships than weaponClass
537
                        applied = 0
538
                        for tmpWpnClass in xrange(weaponClass, 4):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
539
                            #@log.debug('ISystem', 'Trying target class', tmpWpnClass, totalClass[tmpWpnClass])
540
                            # select target
541
                            if totalClass[tmpWpnClass]:
542
                                target = Utils.rand(0, totalClass[tmpWpnClass])
543
                                #@log.debug('ISystem', 'Target rnd num', target, totalClass[tmpWpnClass])
544
                                for targetID in attack[objID]:
545
                                    if target < targets[targetID][tmpWpnClass]:
546
                                        #@log.debug(objID, 'attacks', targetID, tmpWpnClass)
547
                                        # targetID can be deleted at this point
548
                                        anObj = tran.db.get(targetID, None)
549
                                        if anObj:
550
                                            dmg, destroyed, destroyedClass = self.cmd(anObj).applyShot(tran, anObj, systemDef[owners[targetID]], combatAtt + systemAtt[owners[objID]], weaponID, tmpWpnClass, target)
551
                                            #@log.debug("ISystem result", dmg, destroyed, destroyedClass, tmpWpnClass)
552
                                            #@print objID, 'dmg, destroyed', dmg, destroyed
553
                                            damageTaken[targetID] = damageTaken.get(targetID, 0) + dmg
554
                                            if destroyed > 0:
555
                                                shipsLost[targetID] = shipsLost.get(targetID, 0) + destroyed
556
                                                total -= destroyed
557
                                                totalClass[destroyedClass] -= destroyed
558
                                            if dmg > 0 and obj:
559
                                                obj.combatExp += dmg
560
                                                damageCaused[objID] = damageCaused.get(objID, 0) + dmg
561
                                            applied = 1
562
                                        else:
563
                                            continue # target already destroyed, move to next target
564
                                        break
565
                                    else:
566
                                        #@log.debug('ISystem', 'Lovering target by', targets[targetID][tmpWpnClass])
567
                                        target -= targets[targetID][tmpWpnClass]
568
                            if applied:
569
                                break
570
        # send messages and modify diplomacy relations
571
        # distribute experience pts
572
        for objID in objects:
573
            obj = tran.db.get(objID, None)
574
            if obj:
575
                self.cmd(obj).distributeExp(tran, obj)
576
            if attack[objID]:
577
                source = obj or tran.db[owners[objID]]
578
                # collect players
579
                players = {}
580
                for attackerID in attack[objID]:
581
                    players[owners[attackerID]] = None
582
                d1 = damageTaken.get(objID,0)
583
                d2 = damageCaused.get(objID,0)
584
                l = shipsLost.get(objID, 0)
585
                if d1 or d2 or l:
586
                    # send only if damage is taken/caused
587
                    Utils.sendMessage(tran, source, Const.MSG_COMBAT_RESULTS, system.oid, (d1, d2, l, players.keys()))
588
                if not obj:
589
                    # report DESTROYED status
590
                    Utils.sendMessage(tran, source, Const.MSG_DESTROYED_FLEET, system.oid, ())
591
                # modify diplomacy relations
592
                objOwner = tran.db[owners[objID]]
593
                for attackerID in attack[objID]:
594
                    attOwner = tran.db.get(owners[attackerID], None)
595
                    # owner of the fleet
596
                    rel = self.cmd(objOwner).getDiplomacyWith(tran, objOwner, attOwner.oid)
597
                    rel.relChng = Rules.relLostWhenAttacked
598
                    # attacker
599
                    rel = self.cmd(attOwner).getDiplomacyWith(tran, attOwner, objOwner.oid)
600
                    rel.rechChng = Rules.relLostWhenAttacked
601
        # check if object surrenders
602
        for objID in objects:
603
            # object surrender IFF it and its allies had target and was not able
604
            # to fire at it, planet is not counted as ally in this case
605
            obj = tran.db.get(objID, None)
606
            if firing[objID] and obj:
607
                continue
608
            surrenderTo = []
609
            for attID in attack[objID]:
610
                if firing[attID] and tran.db.has_key(attID):
611
                    surrenderTo.append(tran.db[attID].owner)
612
            for allyID in allies[objID]:
613
                if not tran.db.has_key(allyID):
614
                    continue
615
                ally = tran.db[allyID]
616
                if firing[allyID] and ally.type != Const.T_PLANET:
617
                    surrenderTo = []
618
                    break
619
            if surrenderTo:
620
                index = Utils.rand(0, len(surrenderTo))
621
                if obj:
622
                    if self.cmd(obj).surrenderTo(tran, obj, surrenderTo[index]):
623
                        winner = tran.db[surrenderTo[index]]
624
                        source = tran.db.get(owners[objID], None)
625
                        log.debug('ISystem', 'BATTLE - surrender', objID, surrenderTo[index], surrenderTo)
626
                        if source:
627
                            Utils.sendMessage(tran, source, Const.MSG_COMBAT_LOST, system.oid, winner.oid)
628
                            Utils.sendMessage(tran, winner, Const.MSG_COMBAT_WON, system.oid, source.oid)
629
                        else:
630
                            Utils.sendMessage(tran, winner, Const.MSG_COMBAT_WON, system.oid, obj.oid)
631
                else:
632
                    winner = tran.db[surrenderTo[index]]
633
                    source = tran.db[owners[objID]]
634
                    log.debug('ISystem', 'BATTLE - surrender', objID, surrenderTo[index], surrenderTo)
635
                    Utils.sendMessage(tran, source, Const.MSG_COMBAT_LOST, system.oid, winner.oid)
636
                    Utils.sendMessage(tran, winner, Const.MSG_COMBAT_WON, system.oid, source.oid)
637
        return
638
639
    @public(Const.AL_ADMIN)
640
    def processFINALPhase(self, tran, obj, data):
641
        # TODO find new starting points
642
        # clean up mines if system ownership was lost
643
        owners = []
644
        for planetID in obj.planets:
645
            planet = tran.db[planetID]
646
            if planet.owner not in owners:
647
                owners.append(planet.owner)
648
        for ownerid in obj.minefield.keys():
649
            if ownerid not in owners:
650
                self.clearMines(obj, ownerid)
651
        return obj.planets[:] + obj.closeFleets[:]
652
653
    def sortPlanets(self, tran, obj, data):
654
        obj.planets.sort(key=lambda planetID: tran.db[planetID].plEn, reverse = True)
655
        orbit = 1
656
        for planetID in obj.planets:
657
            planet = tran.db[planetID]
658
            planet.orbit = orbit
659
            orbit += 1
660
661
    @public(Const.AL_NONE)
662
    def rename(self, tran, obj, newName, nType):
663
        newName = newName.strip()
664
        # you have to own all planets
665
        # TODO: Throw another cmdr exc AFTER you have no planet
666
        haveOne = 0
667
        anotherComm = 0
668
        for planetID in obj.planets:
669
            planet = tran.db[planetID]
670
            if planet.owner != tran.session.cid and planet.owner != Const.OID_NONE:
671
                anotherComm = 1
672
            if planet.owner == tran.session.cid:
673
                haveOne = 1
674
        if not haveOne:
675
            raise ige.GameException('You cannot change name of this system - you have no planet in this system.')
676
        if anotherComm:
677
            raise ige.GameException('You cannot change name of this system - another commander in system.')
678
        # check validity of name
679
        if not Utils.isCorrectName(newName):
680
            raise ige.GameException('Invalid name. Only characters, digits, space, dot and dash permitted, max. length is 30 characters.')
681
        # check if there is other system with this name
682
        galaxy = tran.db[obj.compOf]
683
        for systemID in galaxy.systems:
684
            if tran.db[systemID].name == newName and systemID != obj.oid:
685
                raise ige.GameException('This name is already used.')
686
        # TODO you have to own this system longer than previous owner
687
        # one change per 1 day allowed
688
        turn = tran.db[Const.OID_UNIVERSE].turn
689
        if obj.lastNameChng + Rules.turnsPerDay <= turn:
690
            # rename system
691
            obj.name = newName
692
            # rename planets
693
            newNames = [obj.name]
694
            for planetID in obj.planets:
695
                planet = tran.db[planetID]
696
                planet.name = Utils.getPlanetName(obj.name, nType, planet.orbit - 1)
697
                newNames.append(planet.name)
698
            obj.lastNameChng = turn
699
        else:
700
            raise ige.GameException('You cannot change name of this system - name has been changed recently (try it one day later).')
701
        return newNames
702
703
    def createPlanet(self, tran, obj):
704
        planet = self.new(Const.T_PLANET)
705
        planet.compOf = obj.oid
706
        oid = tran.db.create(planet)
707
        obj.planets.append(oid)
708
        return oid
709
710
    @public(Const.AL_ADMIN)
711
    def deployMines(self, tran, obj, owner_id):
712
        """ Go through all mine control structures and attempt to add mines.
713
714
        """
715
        for tech, struct in self.getSystemMineLauncher(tran, obj, owner_id):
716
            if not struct[Const.STRUCT_IDX_STATUS] & Const.STRUCT_STATUS_ON:
717
                # structure is offline, reset timer
718
                self.addMine(tran, obj, owner_id, tech.mineclass, 0)
719
                continue
720
            efficiency = struct[Const.STRUCT_IDX_HP] / float(tech.maxHP)
721
            minerate = int(tech.minerate / efficiency)
722
            minenum = int(tech.minenum * efficiency)
723
            if self.addMine(tran, obj, owner_id, tech.mineclass, minenum, minerate):
724
                log.debug('ISystem', 'Mine deployed for owner %d in system %d' % (owner_id, obj.oid))
725
726
    def _expandExistingMinefield(self, minefields, index, current_turn, max_amount, mine_rate):
727
        mine_id, amount, deploy_turn = minefields[index]
728
        if max_amount is not None and amount >= max_amount:
729
            # cannot add more, thus update deploy turn to current one
730
            # (so replenish starts after that amount of turns if needed)
731
            minefields[index] = (mine_id, amount, current_turn)
732
            return False
733
        if mine_rate and (current_turn - deploy_turn) < mine_rate:
734
            # need more time to deploy new mine
735
            return False
736
        minefields[index] = (mine_id, amount + 1, current_turn)
737
        return True
738
739
    @public(Const.AL_ADMIN)
740
    def addMine(self, tran, obj, owner_id, mine_tech_id, max_amount=None, mine_rate=None):
741
        """ Increment amount within particular minefield of particular player.
742
        Set current turn as a date of latest deployment.
743
744
        Returns True if mine was added.
745
746
        """
747
        current_turn = tran.db[obj.compOf].galaxyTurn
748
        if owner_id in obj.minefield:
749
            index = -1
750
            for mine_id, amount, deploy_turn in obj.minefield[owner_id]:
751
                index += 1
752
                if mine_id != mine_tech_id:
753
                    continue
754
                return self._expandExistingMinefield(obj.minefield[owner_id], index, current_turn, max_amount, mine_rate)
755
            # owner has some minefield, but not this type
756
            obj.minefield[owner_id].append((mine_tech_id, 1, current_turn))
757
            return True
758
        else:
759
            # this will be owner's first minefield in this system
760
            if mine_rate == 1 or mine_rate is None:
761
                obj.minefield[owner_id] = [(mine_tech_id, 1, current_turn)]
762
                return True
763
            else:
764
                obj.minefield[owner_id] = [(mine_tech_id, 0, current_turn - 1)]
765
                return False
766
767
768
    @public(Const.AL_ADMIN)
769
    def removeMine(self, obj, owner_id, mine_tech_id):
770
        """ Decrement amount within particular minefield of particular player.
771
        It's not possible to clean up records - it's expected to stay there till
772
        the end of game like that.
773
774
        """
775
        if owner_id in obj.minefield:
776
            index = -1
777
            for mine_id, amount, deploy_turn in obj.minefield[owner_id]:
778
                index += 1
779
                if mine_id != mine_tech_id or amount == 0:
780
                    continue
781
                amount -= 1
782
                obj.minefield[owner_id][index] = (mine_id, amount, deploy_turn)
783
                break
784
785
    def getMines(self, obj, owner_id):
786
        """ Return list of tuples representing each minefield.
787
788
        """
789
        try:
790
            return [mines for mines in obj.minefield[owner_id] if mines[Const.MINE_IDX_AMOUNT]]
791
        except KeyError:
792
            return []
793
794
    def getAllMines(self, obj):
795
        """ Return list of tuples representing each minefield.
796
797
        """
798
        minefields = {}
799
        for owner_id in obj.minefield:
800
            mines = self.getMines(obj, owner_id)
801
            if mines:
802
                minefields[owner_id] = mines
803
        return minefields
804
805
    def clearMines(self, obj, owner_id):
806
        """ Remove all minefields of given owner from the system.
807
808
        """
809
        if owner_id in obj.minefield:
810
            del obj.minefield[owner_id]
811
812
    @public(Const.AL_ADMIN)
813
    def fireMine(self, obj, owner_id): # shoot the mine
814
        if owner_id not in obj.minefield:
815
            return False, False, False, False
816
817
        mine_ids = []
818
        amounts = []
819
        for mine_id, amount, deploy_turn in obj.minefield[owner_id]:
820
            mine_ids.append(mine_id)
821
            amounts.append(amount)
822
        if not sum(amounts):
823
            return False, False, False, False
824
        mine = Utils.weightedRandom(mine_ids, amounts) # select random mine to detonate
825
        self.removeMine(obj, owner_id, mine)
826
        tech = Rules.techs[mine]
827
        damage = random.randrange(tech.weaponDmgMin, tech.weaponDmgMax)
828
        attack = tech.weaponAtt
829
        ignore_shield = tech.weaponIgnoreShield
830
        return damage, attack, ignore_shield, mine
831
832
    def getSystemMineLauncher(self, tran, obj, playerID):
833
        launchers = []
834
        for planetID in obj.planets:
835
            planet = tran.db[planetID]
836
            if planet.owner == playerID:
837
                for struct in planet.slots:
838
                    tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
839
                    if tech.mineclass:
840
                        launchtech = tech
841
                        launchers.append((launchtech, struct))
842
        return launchers
843
844
    def getSystemMineSource(self, tran, obj, playerID):
845
        sources = []
846
        for planetID in obj.planets:
847
            planet = tran.db[planetID]
848
            if planet.owner == playerID:
849
                for struct in planet.slots:
850
                    tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
851
                    if tech.mineclass:
852
                        sources.append(planetID)
853
        return sources
854
855
    def getSystemCombatBonuses(self, tran, obj, playerID):
856
        systemAtt = 0;
857
        systemDef = 0;
858
        for planetID in obj.planets:
859
            planet = tran.db[planetID]
860
            if planet.owner == playerID:
861
                for struct in planet.slots:
862
                    tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
863
                    techEff = Utils.getTechEff(tran, struct[Const.STRUCT_IDX_TECHID], planet.owner)
864
                    if tech.systemAtt > 0 or tech.systemDef > 0:
865
                        systemAtt = max(systemAtt, tech.systemAtt * techEff)
866
                        systemDef = max(systemDef, tech.systemDef * techEff)
867
        return (systemAtt, systemDef)
868
869
    def loadDOMNode(self, tran, obj, xoff, yoff, node):
870
        obj.x = float(node.getAttribute('x')) + xoff
871
        obj.y = float(node.getAttribute('y')) + yoff
872
        orbit = 1
873
        nType = Utils.getPlanetNamesType()
874
        for elem in node.childNodes:
875
            if elem.nodeType == Node.ELEMENT_NODE:
876
                name = elem.tagName
877
                if name == 'properties':
878
                    self.loadDOMAttrs(obj, elem)
879
                elif name == 'planet':
880
                    # create planet
881
                    planet = tran.db[self.createPlanet(tran, obj)]
882
                    self.cmd(planet).loadDOMNode(tran, planet, obj.x, obj.y, orbit, elem)
883
                    # planet.name = u'%s %s' % (obj.name, '-ABCDEFGHIJKLMNOPQRSTUVWXYZ'[orbit])
884
                    planet.name = Utils.getPlanetName(obj.name, nType, orbit - 1)
885
                    orbit += 1
886
                else:
887
                    raise ige.GameException('Unknown element %s' % name)
888
        return Const.SUCC
889